关于 bevy-ecs
bevy-ecs 是面向 Rust 游戏开发者的 Bevy ECS 技能,讲解 App、插件、组件、资源、系统查询与调度。示例针对 Bevy 0.20,并强调版本匹配、帧率无关更新和查询冲突处理。
主要功能
- 01搭建 App 与插件
- 02定义组件和资源
- 03编写系统与实体查询
- 04配置系统调度和运行条件
- 05处理查询冲突与延迟命令
适用场景
- 01为 Bevy 游戏组织 ECS 逻辑
- 02实现按帧更新的实体移动
- 03安排有先后依赖的系统
- 04排查组件访问冲突或版本 API 错误
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill bevy-ecs安装后在 Claude Code 中输入/bevy-ecs确认已加载。
附带文件references/queries-and-scheduling.md
详细介绍
Bevy ECS
围绕实体组件系统组织 Rust 中的 Bevy 游戏:包括 App 和插件、组件与资源、带查询的系统、调度,以及与帧率无关的更新。新示例面向 Bevy 0.20。如果项目已固定到其他版本,请保持该版本,并使用对应的迁移指南。
适用场景
- 用于接入 Bevy
App、定义Component/Resource类型、编写查询实体的系统、为系统排序/筛选,或修复借用冲突导致的 panic 和依赖帧率的移动问题。 - 用于
Cargo.toml依赖bevy,且代码调用App::new()、add_systems、Query或Commands的场景。
**不适用场景:**这是 ECS 核心内容。深入的渲染、自定义着色器/管线、UI 布局和音频属于其他领域。对于与引擎无关的 AI 或程序化算法,可搭配使用 game-ai / procedural-gen。
核心工作流
- **检测并固定版本。**先查看
Cargo.toml和Cargo.lock。新项目使用bevy = "0.20";绝不要悄然将现有项目迁移到其他 Bevy 次要版本。以对应的文档和迁移指南为准。 - 构建
App。App::new().add_plugins(DefaultPlugins)会提供窗口、输入、渲染、时间等功能。将系统注册到调度中:Startup(运行一次)和Update(每帧运行)。 - **将数据建模为组件,将全局数据建模为资源。**使用
#[derive(Component)]定义每个实体的数据;使用#[derive(Resource)]定义唯一数据(分数、设置、Time时钟)。从 0.19 起,Resource扩展自Component,且每个资源都存储在自己的实体上,因此不要同时派生两者。 - **将系统编写为普通函数。**参数声明数据访问方式:使用
Query<...>查询实体,使用Res<T>/ResMut<T>访问资源,使用Commands延迟生成/销毁实体。只要访问不存在冲突,系统就会并行运行。 - 使用
time.delta_secs()驱动移动,确保移动速度与帧率无关。 - 只对必须排序的系统排序,使用
.chain()或显式约束;使用run_if控制系统运行条件。将相关的初始化工作归入Plugin。使用cargo run构建并查看 panic 信息——Bevy 会在启动时报告查询冲突。
模式
1. Cargo.toml + 最小化 App
# Cargo.toml — pin the version; the API differs across minor releases.
[dependencies]
bevy = "0.20"
// 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();
}
2. 组件、资源与实体生成
#[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),
));
}
3. 使用查询和 Time 资源的系统
// 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();
}
}
4. 查询筛选器(With / Without / Changed)
// 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. */ }
}
5. 资源:读取和写入
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
}
6. 排序、运行条件和插件
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));
}
}
常见问题
- 找不到
delta_seconds()→ 在 0.16 中,它已重命名为time.delta_secs()(以及elapsed_secs())。使用旧名称会导致编译失败。 - 移动速度随帧率变化 → 将每帧的变化量乘以
time.delta_secs()。切勿假设帧时间固定。 - Panic
error[B0001]:"... accesses component(s) Transform in a way that conflicts with a previous system parameter" → 同一系统中的两个Query都写入同一个组件,或者一个读取组件、另一个写入组件,且它们涉及的实体有重叠。使用With/Without让它们处理互不重叠的实体,或使用ParamSet。 - 找不到
Camera2dBundle/SpriteBundle→ 这些 Bundle 在 0.15 中已弃用,并在 0.16 中移除。 直接生成组件(Camera2d、Sprite、Transform);必需组件会自动补齐其余部分。 - “trait
Componentis not implemented” → 忘记添加#[derive(Component)](如果是资源,则添加#[derive(Resource)])。 - 同一帧中生成的实体无法被后续查询看到 →
Commands会延迟执行,并在下一个同步点应用。请在后续系统中读取该实体,而不是在生成它的系统中读取。 - 假设系统顺序已确定,但实际上并未强制执行 → 默认情况下,系统会并行运行。如果
B必须在A之后运行,请添加(A, B).chain()或明确的顺序约束。 若要查找仅因偶然而生效的顺序依赖,可在测试中使用ScheduleBuildSettings::shuffle_seed打乱调度(0.20,需启用debug功能;参见参考资料)。 - 同时派生
Resource和Component后出现“conflicting implementations of traitComponent” → 从 0.19 起,Resource扩展了Component;只派生Resource即可。 - “Despawn everything”导致 Panic,或
Res<T>旁边的Query<EntityMut>因error[B0002]导致 Panic → 从 0.19 起,每个资源都存在于带有IsResource标记的实体上,因此Query<Entity>/EntityRef/EntityMut也会匹配这些实体。请查询你自己的标记组件,或添加Without<IsResource>(use bevy::ecs::resource::IsResource)。 On<Add, Player>因“struct takes 1 generic argument but 2 generic arguments were supplied”而失败 → 0.20 将组件移入了生命周期事件中:应写为On<Add<Player>>(Insert<T>、Discard<T>、Remove<T>、Despawn<T>也一样)。- “切换”到当前状态时,
OnExit/OnEnter再次运行 →NextState::set总会运行状态转换调度。使用set_if_different跳过状态未变化的转换(该方法在 0.20 中从set_if_neq重命名而来;在ResMut<NextState<_>>上调用set_if_neq会匹配到无关的变更检测方法,并因“can't compareNextState”而失败)。 Name::new(text.as_str())报“textdoes not live long enough” → 从 0.20 起,Name接受&'static str字面量或拥有所有权的String,而不接受借用的&str;请传入Name::new(text.clone())或Name::new(format!("Enemy {id}"))。- 升级到 0.20 后,重叠的精灵绘制顺序发生变化 → 精灵现在通过
Mesh2d后端渲染,而相同z值下的绘制顺序从未得到保证。请为可能重叠的精灵设置不同的Transformz 值。 - 复制粘贴较旧版本的 Bevy 代码片段 → API 会随次版本变化。缓冲事件系统已变为消息系统:
EventReader/EventWriter/add_event均已不存在,请改用#[derive(Message)]、MessageReader/MessageWriter和app.add_message::<T>()。请对照你固定使用的版本对应的文档和迁移指南进行核实;不要混用版本。
参考资料
- 关于调度和
SystemSet顺序(包括 0.20 弱排序和调度打乱)、States/OnEnter/OnExit和状态范围内的实体销毁、变更检测、Commands生命周期与同步点、ParamSet和资源实体冲突、iter_many结果,以及 0.20 语法中的消息/观察者,请阅读references/queries-and-scheduling.md。
相关技能
game-ai— 可移植的 FSM/行为树/转向概念,可在 ECS 中实现。procedural-gen— 可由系统驱动的噪声/RNG/生成算法。pygame-core/love2d-core— 更轻量的引擎,适用于较小型项目。
评论
支持 Markdown