0Pricing
Learn Rust Coding · Lesson

Collisions and Game State

Add rules and win conditions.

Collisions and Game State is a free Learn Rust Coding lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

AABB Collision Basics

The simplest collision test is axis-aligned bounding box (AABB) overlap. Two rectangles collide when they overlap on both the x and y axes.

For sprites this is fast and good enough for many 2D games before reaching for a full physics engine.

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
}

A Collider Component

Attach a Collider component holding each entity's half-extents or full size. Systems then read transform plus collider to test overlaps.

Keeping size in its own component lets different entities share the same collision system.

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

Detecting Collisions in a System

To compare a player against many obstacles, query the player once and iterate obstacles. Two separate queries avoid borrowing the same data twice.

On overlap you react — damage, bounce, or despawn — through Commands or by mutating components.

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");
        }
    }
}

Reacting with Events

Rather than handling consequences inline, send a custom Event. One system detects collisions and emits events; another reads and applies effects.

This decouples detection from reaction and keeps each system small.

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

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

Reading Events

A reader system pulls queued events with an EventReader and acts on each. Events live for two frames, so any reader sees them.

This publish-subscribe flow scales: many producers and many consumers without tight coupling.

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

Updating the Score

When the player collects a pickup, mutate the global Score resource with ResMut.

Because resources are unique, any system can read or update the score without passing it around manually.

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

States: The Big Picture

Games move between modes — menu, playing, paused, game over. Bevy models these with a States enum registered via init_state.

Only one state is active at a time, and systems can be gated to run in specific states.

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

Registering State

Add the state to the app with init_state. Bevy then tracks the current value and exposes State and NextState resources.

Read the current state with Res<State<GameState>> when a system needs to know the mode.

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

Running Systems in a State

Gate systems to a state with the run_if condition in_state. Movement and collision only run while Playing.

This is how pausing works: stop the gameplay systems by leaving the Playing state, and they simply don't run.

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

Transitioning States

To change state, set NextState from any system. Bevy applies the transition at the next frame boundary.

Use OnEnter and OnExit schedules to spawn UI when entering a state or clean up when leaving it.

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

Cleanup on Exit

When leaving Playing, despawn gameplay entities so the next round starts clean. Tag them with a marker and despawn everything carrying it.

Register the cleanup in OnExit(GameState::Playing) so it runs exactly once per transition.

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);

Quick Check

Consider how to pause gameplay cleanly.

Recap: Collisions and Game State

AABB tests plus a Collider component detect overlaps; events decouple detection from reaction, and a Score resource tracks progress. The States system, with run_if, NextState, and OnEnter/OnExit, structures menus, play, and game-over.

You now have the full loop: spawn, control, collide, and transition. Build a complete Bevy game from these pieces.

Frequently asked questions

Is the “Collisions and Game State” lesson free?

Yes — the full text of “Collisions and Game State” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.

What will I learn in “Collisions and Game State”?

Add rules and win conditions. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Learn Rust Coding?

No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Collisions and Game State” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Learn Rust Coding lesson?

Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The ECS Mindset
  2. Spawning and Moving Entities
  3. Handling Input and Time
  4. Collisions and Game State
← Back to Learn Rust Coding