0Pricing
Learn Rust Coding · 课时

碰撞和游戏状态

添加规则和获胜条件。

碰撞和游戏状态 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

AABB 碰撞基础

最简单的碰撞检测是轴对齐包围盒(AABB)重叠检测。当两个矩形在 x 轴和 y 轴上都发生重叠时,它们就会碰撞。

对于精灵来说,这种方法速度快,在使用完整物理引擎之前,已经足以满足许多二维游戏的需求。

fn overlaps(a_pos: Vec2, a_size: Vec2, b_pos: Vec2, b_size: Vec2) -> bool {
    (a_pos.x - b_pos.x).abs() < (a_size.x + b_size.x) * 0.5
        && (a_pos.y - b_pos.y).abs() < (a_size.y + b_size.y) * 0.5
}

Collider 组件

请为每个实体附加一个 Collider 组件,其中存储实体的半尺寸或完整尺寸。系统随后读取变换和碰撞体来检测重叠。

将尺寸保存在独立组件中,可以让不同实体共用同一个碰撞系统。

#[derive(Component)]
struct Collider {
    size: Vec2,
}

在系统中检测碰撞

要将玩家与多个障碍物进行比较,请查询一次玩家,然后遍历障碍物。使用两个独立的查询,可以避免两次借用同一份数据。

发生重叠时,您可以通过 Commands 或修改组件来做出反应,例如造成伤害、反弹或销毁实体。

fn detect(
    player: Query<(&Transform, &Collider), With<Player>>,
    walls: Query<(&Transform, &Collider), With<Wall>>,
) {
    let (pt, pc) = player.single().unwrap();
    for (wt, wc) in &walls {
        if overlaps(pt.translation.truncate(), pc.size, wt.translation.truncate(), wc.size) {
            info!("hit a wall");
        }
    }
}

使用事件做出反应

与其直接处理后果,不如发送自定义的 Event。一个系统检测碰撞并发出事件,另一个系统读取事件并应用效果。

这样可以将检测与反应解耦,并让每个系统保持简洁。

#[derive(Event)]
struct CollisionEvent { entity: Entity }

fn emit(mut writer: EventWriter<CollisionEvent>, e: Entity) {
    writer.send(CollisionEvent { entity: e });
}

读取事件

读取系统使用 EventReader 提取排队的事件,并逐个处理。事件会保留两个帧周期,因此任何读取系统都能看到它们。

这种发布—订阅流程具有良好的扩展性:可以拥有多个生产者和多个消费者,而无需紧密耦合。

fn on_hit(mut reader: EventReader<CollisionEvent>, mut commands: Commands) {
    for ev in reader.read() {
        commands.entity(ev.entity).despawn();
    }
}

更新分数

玩家收集拾取物时,请使用 ResMut 修改全局的 Score 资源。

由于资源具有唯一性,任何系统都可以读取或更新分数,而不必手动传递它。

fn collect(mut score: ResMut<Score>, mut reader: EventReader<PickupEvent>) {
    for _ in reader.read() {
        score.0 += 10;
    }
}

状态:整体概览

游戏会在不同模式之间切换——菜单、进行中、暂停和游戏结束。Bevy 使用通过 init_state 注册的 States 枚举来表示这些模式。

同一时间只有一个状态处于活动状态,并且可以限制系统,使其只在特定状态下运行。

#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum GameState {
    #[default]
    Menu,
    Playing,
    GameOver,
}

注册状态

请使用 init_state 将状态添加到应用中。随后,Bevy 会跟踪当前值,并提供 State 和 NextState 资源。

当系统需要知道当前模式时,请使用 Res<State<GameState>> 读取当前状态。

App::new()
    .add_plugins(DefaultPlugins)
    .init_state::<GameState>()
    .run();

在指定状态下运行系统

请使用 run_if 条件和 in_state 将系统限制在某个状态下运行。只有处于 Playing 状态时,移动和碰撞系统才会运行。

暂停功能就是这样实现的:离开 Playing 状态即可停止游戏系统,它们不会再运行。

app.add_systems(
    Update,
    (drive, detect).run_if(in_state(GameState::Playing)),
);

转换状态

要更改状态,请在任意系统中设置 NextState。Bevy 会在下一帧边界应用这次转换。

请使用 OnEnter 和 OnExit 调度,在进入状态时生成用户界面,或在离开状态时进行清理。

fn lose(mut next: ResMut<NextState<GameState>>, health: Res<Health>) {
    if health.0 == 0 {
        next.set(GameState::GameOver);
    }
}

退出时清理

离开 Playing 状态时,请销毁游戏实体,以便下一轮能够从干净的状态开始。请为它们添加标记,然后销毁所有带有该标记的实体。

请在 OnExit(GameState::Playing) 中注册清理逻辑,以确保每次状态转换只运行一次。

fn cleanup(mut commands: Commands, q: Query<Entity, With<GameEntity>>) {
    for e in &q {
        commands.entity(e).despawn();
    }
}
// app.add_systems(OnExit(GameState::Playing), cleanup);

快速检查

请思考如何干净地暂停游戏。

回顾:碰撞与游戏状态

AABB 检测结合 Collider 组件可以检测重叠;事件将检测与反应解耦,Score 资源则用于跟踪进度。借助包含 run_if、NextState 和 OnEnter/OnExit 的 States 系统,可以组织菜单、游戏进行和游戏结束状态。

现在您已经掌握了完整流程:生成、控制、碰撞和状态转换。请利用这些部分构建一个完整的 Bevy 游戏。

常见问题解答

「碰撞和游戏状态」课时是免费的吗?

是的 — 「碰撞和游戏状态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「碰撞和游戏状态」这节课中我会学到什么?

添加规则和获胜条件。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「碰撞和游戏状态」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. ECS 思维方式
  2. 创建并移动实体
  3. 处理输入和时间
  4. 碰撞和游戏状态
← 返回 Learn Rust Coding