bevy-ecs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Bevy ECS

Bevy ECS

Structure a Bevy game in Rust around the Entity Component System: the
App
and plugins, components and resources, systems with queries, scheduling, and frame-rate-independent updates. Pins Bevy 0.16+ (code targets 0.16; Bevy's API shifts each minor release — match your
Cargo.toml
).
基于实体组件系统(ECS)使用Rust构建Bevy游戏:涵盖
App
与插件、组件与资源、带查询的系统、调度以及帧率无关的更新。本文针对**Bevy 0.16+**版本(代码以0.16为目标;Bevy的API在每个小版本都会变更——请与你的
Cargo.toml
版本匹配)。

When to use

使用场景

  • Use when wiring a Bevy
    App
    , defining
    Component
    /
    Resource
    types, writing systems that query entities, ordering/filtering systems, or fixing borrow-conflict panics and frame-dependent movement.
  • Use when
    Cargo.toml
    depends on
    bevy
    and code calls
    App::new()
    ,
    add_systems
    ,
    Query
    , or
    Commands
    .
When not to use: this is the ECS core. Deep rendering, custom shaders/ pipelines, UI layout, and audio are separate concerns. For engine-agnostic AI or procedural algorithms, pair with
game-ai
/
procedural-gen
.
  • 适用于配置Bevy
    App
    、定义
    Component
    /
    Resource
    类型、编写查询实体的系统、排序/过滤系统,或解决借用冲突 panic 以及帧率相关移动问题的场景。
  • Cargo.toml
    依赖
    bevy
    且代码调用
    App::new()
    add_systems
    Query
    Commands
    时使用。
不适用于: 本文聚焦ECS核心功能。深度渲染、自定义着色器/管线、UI布局和音频属于独立领域。对于引擎无关的AI或 procedural 算法,请搭配
game-ai
/
procedural-gen
使用。

Core workflow

核心工作流程

  1. Pin the version. Bevy's API changes every minor release. Set
    bevy = "0.16"
    (or your target) in
    Cargo.toml
    and treat the matching docs as truth. Enable
    dynamic_linking
    in dev for faster iterative builds.
  2. Build the
    App
    .
    App::new().add_plugins(DefaultPlugins)
    gives windowing, input, rendering, time, etc. Register systems into schedules:
    Startup
    (once) and
    Update
    (every frame).
  3. Model data as components, globals as resources.
    #[derive(Component)]
    for per-entity data;
    #[derive(Resource)]
    for one-of-a-kind data (score, settings, the
    Time
    clock).
  4. Write systems as plain functions. Parameters declare data access:
    Query<...>
    for entities,
    Res<T>
    /
    ResMut<T>
    for resources,
    Commands
    for deferred spawn/despawn. Systems run in parallel when their accesses don't conflict.
  5. Drive motion by
    time.delta_secs()
    so speed is frame-rate independent.
  6. Order only what must be ordered with
    .chain()
    or explicit constraints; gate systems with
    run_if
    . Group related setup into
    Plugin
    s. Build with
    cargo run
    and read the panics — Bevy reports conflicting queries at startup.
  1. 锁定版本:Bevy的API每个小版本都会变化。在
    Cargo.toml
    中设置
    bevy = "0.16"
    (或你的目标版本),并以对应版本的文档为准。在开发环境中启用
    dynamic_linking
    以加快迭代构建速度。
  2. 构建
    App
    App::new().add_plugins(DefaultPlugins)
    提供窗口、输入、渲染、时间等功能。将系统注册到调度中:
    Startup
    (仅运行一次)和
    Update
    (每帧运行)。
  3. 将数据建模为组件,全局数据建模为资源
    #[derive(Component)]
    用于每个实体的数据;
    #[derive(Resource)]
    用于唯一数据(分数、设置、
    Time
    时钟)。
  4. 将系统编写为普通函数:参数声明数据访问权限:
    Query<...>
    用于实体,
    Res<T>
    /
    ResMut<T>
    用于资源,
    Commands
    用于延迟生成/销毁实体。当系统的访问权限不冲突时,它们会并行运行。
  5. 通过
    time.delta_secs()
    驱动运动
    ,确保速度与帧率无关。
  6. 仅对必须排序的内容排序,使用
    .chain()
    或显式约束;用
    run_if
    控制系统执行。将相关设置分组到
    Plugin
    中。使用
    cargo run
    构建并查看panic信息——Bevy会在启动时报告冲突的查询。

Patterns

模式示例

1. Cargo.toml + minimal App

1. Cargo.toml + 最小化App

toml
undefined
toml
undefined

Cargo.toml — pin the version; the API differs across minor releases.

Cargo.toml — 锁定版本;不同小版本的API存在差异。

[dependencies] bevy = "0.16"
[dependencies] bevy = "0.16"

Dev-only: faster recompiles. (Add the matching dynamic linking setup per the book.)

仅开发环境:更快的重新编译速度。(根据官方文档添加对应的动态链接设置。)

bevy = { version = "0.16", features = ["dynamic_linking"] }

bevy = { version = "0.16", features = ["dynamic_linking"] }


```rust
// main.rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)            // window, input, render, time, ...
        .add_systems(Startup, setup)            // runs once at startup
        .add_systems(Update, move_players)      // runs every frame
        .run();
}

```rust
// main.rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)            // 窗口、输入、渲染、时间等功能
        .add_systems(Startup, setup)            // 启动时仅运行一次
        .add_systems(Update, move_players)      // 每帧运行
        .run();
}

2. Components, resources, and spawning

2. 组件、资源与实体生成

rust
#[derive(Component)]
struct Player;

#[derive(Component)]
struct Velocity(Vec2);

#[derive(Resource)]
struct Score(u32);

fn setup(mut commands: Commands) {
    commands.insert_resource(Score(0));

    // Camera2d is a component with required components (bundles removed in 0.16);
    // spawning it pulls in Transform, Camera, etc. automatically.
    commands.spawn(Camera2d);

    // Spawn an entity as a tuple of components.
    commands.spawn((
        Player,
        Velocity(Vec2::new(150.0, 0.0)),
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));
}
rust
#[derive(Component)]
struct Player;

#[derive(Component)]
struct Velocity(Vec2);

#[derive(Resource)]
struct Score(u32);

fn setup(mut commands: Commands) {
    commands.insert_resource(Score(0));

    // Camera2d是包含必要组件的类型(0.16版本已移除bundle);
    // 生成它会自动引入Transform、Camera等组件。
    commands.spawn(Camera2d);

    // 以组件元组形式生成实体。
    commands.spawn((
        Player,
        Velocity(Vec2::new(150.0, 0.0)),
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));
}

3. A system with a query + the Time resource

3. 包含查询与Time资源的系统

rust
// Iterate every entity that has BOTH Velocity and Transform; mutate Transform.
fn move_players(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {
    for (velocity, mut transform) in &mut query {
        // delta_secs() is f32 seconds (renamed from delta_seconds() in 0.16).
        transform.translation += velocity.0.extend(0.0) * time.delta_secs();
    }
}
rust
// 遍历所有同时拥有Velocity和Transform组件的实体;修改Transform。
fn move_players(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {
    for (velocity, mut transform) in &mut query {
        // delta_secs()是浮点数类型的秒数(0.16版本中从delta_seconds()重命名而来)。
        transform.translation += velocity.0.extend(0.0) * time.delta_secs();
    }
}

4. Query filters (With / Without / Changed)

4. 查询过滤器(With / Without / Changed)

rust
// Only entities tagged Player (the Player component itself isn't read).
fn aim_player(mut q: Query<&mut Transform, With<Player>>) { /* ... */ }

// Disjoint two mutable Transform queries so they don't conflict at runtime.
fn separate(
    mut players: Query<&mut Transform, With<Player>>,
    mut enemies: Query<&mut Transform, Without<Player>>,
) { /* ... */ }

// React only when Health changed since last run (change detection).
fn on_health_change(q: Query<&Health, Changed<Health>>) {
    for health in &q { /* update the HUD, etc. */ }
}
rust
// 仅筛选标记有Player的实体(不会读取Player组件本身)。
fn aim_player(mut q: Query<&mut Transform, With<Player>>) { /* ... */ }

// 使两个可变Transform查询互斥,避免运行时冲突。
fn separate(
    mut players: Query<&mut Transform, With<Player>>,
    mut enemies: Query<&mut Transform, Without<Player>>,
) { /* ... */ }

// 仅在Health自上次运行后发生变化时响应(变更检测)。
fn on_health_change(q: Query<&Health, Changed<Health>>) {
    for health in &q { /* 更新HUD等操作 */ }
}

5. Resources: read and write

5. 资源:读取与写入

rust
fn add_points(mut score: ResMut<Score>) {
    score.0 += 10;                 // ResMut = write access
}

fn show_score(score: Res<Score>) {
    info!("score: {}", score.0);   // Res = read access
}
rust
fn add_points(mut score: ResMut<Score>) {
    score.0 += 10;                 // ResMut = 写入权限
}

fn show_score(score: Res<Score>) {
    info!("score: {}", score.0);   // Res = 读取权限
}

6. Ordering, run conditions, and plugins

6. 排序、运行条件与插件

rust
fn main() {
    App::new()
        .add_plugins((DefaultPlugins, GameplayPlugin))
        // .chain() forces order: damage resolves before death is checked.
        .add_systems(Update, (apply_damage, check_deaths).chain())
        // run_if gates a system on a condition each frame.
        .add_systems(Update, spawn_wave.run_if(wave_timer_finished))
        .run();
}

struct GameplayPlugin;
impl Plugin for GameplayPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(Score(0))
           .add_systems(Startup, setup)
           .add_systems(Update, (move_players, add_points));
    }
}
rust
fn main() {
    App::new()
        .add_plugins((DefaultPlugins, GameplayPlugin))
        // .chain()强制顺序:伤害结算先于死亡检查。
        .add_systems(Update, (apply_damage, check_deaths).chain())
        // run_if根据每帧的条件控制系统是否执行。
        .add_systems(Update, spawn_wave.run_if(wave_timer_finished))
        .run();
}

struct GameplayPlugin;
impl Plugin for GameplayPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(Score(0))
           .add_systems(Startup, setup)
           .add_systems(Update, (move_players, add_points));
    }
}

Pitfalls

常见陷阱

  • delta_seconds()
    not found
    → it was renamed to
    time.delta_secs()
    (and
    elapsed_secs()
    ) in 0.16. Using the old name fails to compile.
  • Movement speed scales with frame rate → multiply per-frame changes by
    time.delta_secs()
    . Never assume a fixed frame time.
  • Panic: "conflicting accesses" / "&mut T and &mut T" → two
    Query
    s in one system both write the same component, or one reads while another writes overlapping entities. Make them disjoint with
    With
    /
    Without
    , or use
    ParamSet
    .
  • Camera2dBundle
    /
    SpriteBundle
    not found
    → bundles were deprecated in 0.15 and removed in 0.16. Spawn the components directly (
    Camera2d
    ,
    Sprite
    ,
    Transform
    ); required components fill in the rest.
  • "trait
    Component
    is not implemented"
    → you forgot
    #[derive(Component)]
    (or
    #[derive(Resource)]
    for a resource).
  • Spawned entity not visible to a later query in the same frame
    Commands
    are deferred and applied at the next sync point. Read the entity in a subsequent system, not the one that spawned it.
  • System order assumed but not enforced → systems run in parallel by default. If
    B
    must follow
    A
    , add
    (A, B).chain()
    or an explicit ordering constraint.
  • Copy-pasting older Bevy snippets → APIs shift between minor versions (e.g. the buffered-event API was reworked after 0.16). Verify against the docs for your pinned version; don't mix versions.
  • 找不到
    delta_seconds()
    → 该方法在0.16版本中重命名为
    time.delta_secs()
    (以及
    elapsed_secs()
    )。使用旧名称会导致编译失败。
  • 移动速度随帧率变化 → 将每帧的变化量乘以
    time.delta_secs()
    。永远不要假设固定帧率。
  • Panic:"conflicting accesses" / "&mut T and &mut T" → 同一系统中的两个
    Query
    都写入同一组件,或一个读取、另一个写入重叠实体。使用
    With
    /
    Without
    使它们互斥,或使用
    ParamSet
  • 找不到
    Camera2dBundle
    /
    SpriteBundle
    → Bundle在0.15版本中被弃用,0.16版本中被移除。 直接生成组件(
    Camera2d
    Sprite
    Transform
    );必要组件会自动补充。
  • "trait
    Component
    is not implemented"
    → 你忘记添加
    #[derive(Component)]
    (对于资源则是
    #[derive(Resource)]
    )。
  • 生成的实体在同一帧后续查询中不可见
    Commands
    是延迟执行的,会在下一个同步点应用。在后续系统中读取实体,而非生成它的系统。
  • 假设系统顺序但未强制执行 → 默认情况下系统并行运行。如果
    B
    必须在
    A
    之后执行,添加
    (A, B).chain()
    或显式顺序约束。
  • 复制粘贴旧版Bevy代码片段 → 小版本之间API会变更(例如缓冲事件API在0.16之后被重新设计)。请根据你锁定的版本验证文档;不要混合不同版本。

References

参考资料

  • For schedules and
    SystemSet
    ordering,
    States
    /
    OnEnter
    /
    OnExit
    , change detection,
    Commands
    lifecycle and sync points,
    ParamSet
    for conflicting queries, and a version note on the events/observers API, read
    references/queries-and-scheduling.md
    .
  • 关于调度与
    SystemSet
    排序、
    States
    /
    OnEnter
    /
    OnExit
    、变更检测、
    Commands
    生命周期与同步点、解决查询冲突的
    ParamSet
    ,以及事件/观察者API的版本说明,请阅读
    references/queries-and-scheduling.md

Related skills

相关技能

  • game-ai
    — FSMs/behavior trees/steering as portable concepts to implement in ECS.
  • procedural-gen
    — noise/RNG/generation algorithms to drive from systems.
  • pygame-core
    /
    love2d-core
    — lighter-weight engines for smaller projects.
  • game-ai
    — 可在ECS中实现的可移植概念,如有限状态机/行为树/转向算法。
  • procedural-gen
    — 可由系统驱动的噪声/随机数生成/生成算法。
  • pygame-core
    /
    love2d-core
    — 适用于小型项目的轻量级引擎。