logo

Babylon.js Market

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, ported from the game's enemyWaves data), 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 wingman.waypoint.reached to release the wave bucketed to that gate (consuming the bucket so a back-and-forth pass can't re-spawn), wingman.enemy.killed to track each wave's alive set and advance clearedWaves (emitting wingman.wave.cleared, then wingman.waves.allCleared), wingman.track.completed to win, and wingman.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.

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 wingman.wave.spawned (WaveDirectorEvents.WAVE_SPAWNED) — { index, count }, when a wave is spawned.
  • Emits wingman.wave.cleared (WaveDirectorEvents.WAVE_CLEARED) — { index }, when a spawned wave's last enemy dies.
  • Emits wingman.waves.allCleared (WaveDirectorEvents.ALL_CLEARED) — {}, once every spawned wave is cleared and none remain pending.
  • Emits wingman.mission.complete (WaveDirectorEvents.MISSION_COMPLETE) — {}, when the escort track is completed.
  • Emits wingman.mission.failed (WaveDirectorEvents.MISSION_FAILED) — {}, when the escort freighter is destroyed.
  • Listens for wingman.waypoint.reached (WaypointEvents.REACHED) — releases the wave(s) bucketed to that gate index.
  • Listens for wingman.track.completed (WaypointEvents.COMPLETED) — wins the mission.
  • Listens for wingman.enemy.killed (SpaceShooterHealthEvents.ENEMY_KILLED) — { targetId }; removes that enemy from its wave's alive set.
  • Listens for wingman.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 wingman.waypoint.reached: flying back and forth through the same gate won't re-trigger its surge.
  • wingman.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
Animation
ArcCamera
Asteroid
BallPossession
BallPursuit
BallReset
Bullet
CameraFollow
ChaseCamera6DOF
ChaseCameraTarget
DirectionalLight
EnemySpawner
EnvironmentTexture
FreighterCar
FreighterChain
FreighterHead
Goal
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
Lifetime
LineOfSight
Mesh
MeshPrimitive
Missile
MissileLauncher
MissionDirector
MoneyField
Movement
Obstacle
ObstacleField
OilBlob
Physics
PlayerFlightInput
PlayerInput
PlayerWalkAnimator
PokerAI
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerAITilt
PokerBetting
PokerHandEval
PokerMultiplayer
PokerTableDirector
RespawnTimer
Score
Shadow
ShooterCamera
SkeletonAnimator
SoccerDirector
SpaceDust
SpaceShooterBullet
SpaceShooterScore
TurnPacer
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