Skills

Bevy Game Loop Waves

Bevy ECS skill for encounter waves.

Install

npx skills add thedivergentai/Game-Dev-Agentic-Skills --skill bevy-game-loop-waves -g -a claude-code -y
Category
Engineering
Updated
19d ago
License
LGPL-3.0
Context cost
Mediumabout 1,700 tokens
Runs scripts
No, instructions only

What it does

Bevy ECS skill for encounter waves. Keywords: Bevy, waves, spawn, encounter, ECS.

Bevy wave loop

When to use

Load this when enemies arrive in waves and the wave index advances after a clear.

ECS shape

  • Components: a wave index and a remaining-count you define.
  • Resources: run-wide tables live in a resource (bevy-data-resources).
  • Systems: mutate those components on Update or the fixed timestep. Use .chain() when damage must land before death.
  • Messages: one-shot facts (hit, pickup, quest advance) go through bevy-events-messaging. Wave advance below is a resource, not a new message type.

Examples

[dependencies]
bevy = "0.20"

The wave index and the spawn budget are one resource. Each enemy is an entity. Spawning and clearing share WaveDirector, so those systems are chained.

#[derive(Resource)]
struct WaveDirector {
    index: u32,
    budget: u32,
    intermission: f32,
}

#[derive(Component)]
struct Enemy;

fn boot(mut commands: Commands) {
    commands.insert_resource(WaveDirector {
        index: 1,
        budget: 4,
        intermission: 0.0,
    });
}

fn spawn_budget(mut commands: Commands, mut wave: ResMut<WaveDirector>) {
    if wave.intermission > 0.0 || wave.budget == 0 {
        return;
    }
    for _ in 0..wave.budget {
        commands.spawn(Enemy);
    }
    wave.budget = 0;
}

fn advance_on_clear(
    enemies: Query<(), With<Enemy>>,
    mut wave: ResMut<WaveDirector>,
    time: Res<Time>,
) {
    if !enemies.is_empty() {
        wave.intermission = 0.0;
        return;
    }
    wave.intermission += time.delta_secs();
    if wave.intermission < 2.0 {
        return;
    }
    wave.index += 1;
    wave.budget = 3 + wave.index;
    wave.intermission = 0.0;
}

fn install(app: &mut App) {
    app.add_systems(Startup, boot)
        .add_systems(Update, (spawn_budget, advance_on_clear).chain());
}

A fixed step is the right place to tick remaining time on a live enemy. The clear system above stays on Update and only reads Enemy.

Read the whole SKILL.md on GitHub

More in Engineering