logo
WaveDirector

shooter · space-combat-sim

WaveDirector

2
Unlock

Install with the CLI:

bjs download WaveDirector

WaveDirector

The singleton mission-wave director for a 6-DOF escort shooter — it spawns enemy waves from a pooled fleet, tracks which are cleared, and latches the win/lose outcome.

What it does

WaveDirectorComponent is a singleton you drop on the world / director entity: it carries the wave schedule (start waves plus waypoint-gated surges), the enemy pool size, and serializable progress (spawnedWaves, clearedWaves, missionState). WaveDirectorSystem registers a pre-allocated enemy World pool once (never per-spawn createEntity) — each slot is kitted with the full dogfight stack (Transform6DOF/Velocity6DOF/FlightIntent/ShipFlight/AiPilot/Enemy/ShipLoadout('kilrathi')/MachineGun/Renderable) and reset per spawn. On its first update it reads the optional GameConfig: test mode drops one hand-test target dead ahead, otherwise it resolves the level schedule, fires every start wave immediately, and buckets the waypoint waves by gate index. It listens for spaceshooter.waypoint.reached to release the wave bucketed to that gate (consuming the bucket so a back-and-forth pass can't re-spawn), spaceshooter.enemy.killed to track each wave's alive set and advance clearedWaves (emitting spaceshooter.wave.cleared, then spaceshooter.waves.allCleared), spaceshooter.track.completed to win, and spaceshooter.freighter.killed to lose — giving the combat and objective events real listeners.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene WaveDirector

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add WaveDirector
    bjs download WaveDirector

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "World": {
    "tags": ["world", "director"],
    "components": {
      "KeyboardInput": {},
      "GameConfig": {},
      "WaveDirector": { "poolSize": 8 }
    }
  }
}

Props

  • waves (EnemyWave[], default DEFAULT_WAVES) — the schedule: each entry has a trigger ({ kind: 'start' } or { kind: 'waypoint', index }), a count, an optional variant ('spaceplane' | 'kilrathi' | 'mixed'), an optional targetTag ('player' | 'freighter'), and optional explicit positions[]. A GameConfig.level in WAVE_LEVELS overrides this.
  • poolSize (number, default 32) — size of the pre-allocated enemy pool; set it to the peak concurrent enemy count across all waves so recycling never steals a live ship.
  • spawnedWaves (number, default 0) — progress: how many waves have been fired so far. Save/load round-trips it.
  • clearedWaves (number, default 0) — progress: how many waves have had every enemy killed.
  • missionState ('playing' | 'won' | 'lost', default 'playing') — the win/lose latch; once won/lost it doesn't flip again.

Events

  • Emits spaceshooter.wave.spawned (WaveDirectorEvents.WAVE_SPAWNED) — { index, count }, when a wave is spawned.
  • Emits spaceshooter.wave.cleared (WaveDirectorEvents.WAVE_CLEARED) — { index }, when a spawned wave's last enemy dies.
  • Emits spaceshooter.waves.allCleared (WaveDirectorEvents.ALL_CLEARED) — {}, once every spawned wave is cleared and none remain pending.
  • Emits spaceshooter.mission.complete (WaveDirectorEvents.MISSION_COMPLETE) — {}, when the escort track is completed.
  • Emits spaceshooter.mission.failed (WaveDirectorEvents.MISSION_FAILED) — {}, when the escort freighter is destroyed.
  • Listens for spaceshooter.waypoint.reached (WaypointEvents.REACHED) — releases the wave(s) bucketed to that gate index.
  • Listens for spaceshooter.track.completed (WaypointEvents.COMPLETED) — wins the mission.
  • Listens for spaceshooter.enemy.killed (SpaceShooterHealthEvents.ENEMY_KILLED) — { targetId }; removes that enemy from its wave's alive set.
  • Listens for spaceshooter.freighter.killed (SpaceShooterHealthEvents.FREIGHTER_KILLED) — loses the mission.

Dependencies

The pooled enemy kit — Transform6DOF, Velocity6DOF, FlightIntent, ShipFlight, AiPilot, Enemy, ShipLoadout, MachineGun, Renderable, plus Parts + Health (minted by ShipLoadoutSystem on attach) — must have their Systems in the world, since the director acquires ships carrying them. Waypoint supplies the reached/completed events that gate surges and win the mission; Health supplies the enemy.killed/freighter.killed events that clear waves and lose it. GameConfig is optional (test mode / level selection).

Notes

  • It's a singleton: exactly one entity should carry WaveDirector, and it belongs on the world / director entity alongside the other global components — it operates on that entity, not on the enemies it spawns.
  • Enemies are pooled, not created per spawnpoolSize is peak-concurrent, not total-over-the-level; a slot released on death is parked and re-acquired for the next wave, so the GC/GPU never churn.
  • With no live freighter-head in the scene, a targetTag: 'freighter' wave falls back to orbiting the player, then origin — so the default schedule still spawns and dogfights even without an escort.
  • Waypoint gates are consumed on spaceshooter.waypoint.reached: flying back and forth through the same gate won't re-trigger its surge.
  • spaceshooter.waves.allCleared fires once, and only after at least one wave has spawned and every spawned wave is cleared with nothing left pending in the waypoint buckets.
  • missionState is a one-way latch: the first of track.completed / freighter.killed wins/loses and later ones are ignored, so the outcome can't be overwritten.

More like this

AiPilot
Enemy
FlightIntent
GameConfig
MachineGun
Parts
Renderable
ShipFlight
ShipLoadout
SpaceShooterHealth
Transform6DOF
Velocity6DOF
Waypoint
AIGoalSeek
AIKick
AIZone
AiDriver
Animation
ArcCamera
Asteroid
AsteroidField
BallPossession
BallPursuit
BallReset
Bullet
CameraFollow
ChaseCamera6DOF
ChaseCameraTarget
DirectionalLight
EnemySpawner
EnvironmentTexture
FreighterCar
FreighterChain
FreighterHead
Goal
Hand
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
LensFlare
Lifetime
LineOfSight
Mesh
MeshPrimitive
Missile
MissileLauncher
MissionDirector
MoneyField
MouseInput
Movement
NebulaSky
Obstacle
ObstacleField
OilBlob
P2PNetwork
Physics
PlayerFlightInput
PlayerInput
PlayerWalkAnimator
PokerAI
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerAITilt
PokerBetting
PokerHandEval
PokerMultiplayer
PokerTableDirector
RespawnTimer
RoundReset
Score
Shadow
ShipDamageFX
ShooterCamera
SkeletonAnimator
SoccerDirector
SpaceDust
SpaceShooterBullet
SpaceShooterFX
SpaceShooterHUD
SpaceShooterScore
TrafficConvoy
TurnPacer
TwinStickEnemy
TwinStickShooter
WaypointTrack
WorldOriginAnchor

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search