# Bevy Game Loop Waves

Bevy ECS skill for encounter waves.

- Repository: https://github.com/thedivergentai/Game-Dev-Agentic-Skills (917 stars)
- Category: Engineering
- License: LGPL-3.0
- Last updated: 2026-10-16
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add thedivergentai/Game-Dev-Agentic-Skills --skill bevy-game-loop-waves -g -a claude-code -y`
Codex: `npx skills add thedivergentai/Game-Dev-Agentic-Skills --skill bevy-game-loop-waves -g -a codex -y`
Cursor: `npx skills add thedivergentai/Game-Dev-Agentic-Skills --skill bevy-game-loop-waves -g -a cursor -y`
Gemini CLI: `npx skills add thedivergentai/Game-Dev-Agentic-Skills --skill bevy-game-loop-waves -g -a gemini-cli -y`

## Description

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

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# 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

```toml
[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.

```rust
#[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`.

The rest: https://github.com/thedivergentai/Game-Dev-Agentic-Skills/blob/HEAD/skills/bevy-game-loop-waves/SKILL.md
