logo

Babylon.js Market

MissionDirector

shooter

MissionDirector

1
Unlock

Install with the CLI:

bjs download MissionDirector

MissionDirector

The win/loss brain for a wave-based escort mission — one record that counts kills, drains lives, watches the escort target, and latches the run to won or lost the instant it's decided.

What it does

MissionDirectorComponent is a single serializable record on the world/director entity: kills, lives, objectiveComplete, a latched status ('active''won' / 'lost'), and an optional killQuota. MissionDirectorSystem is the listener those mission events were always missing — it subscribes to four events on the bus and turns each into a state transition:

  • SpaceShooterHealthEvents.ENEMY_KILLEDkills += 1. If killQuota > 0 and kills meets it, the run is won.
  • SpaceShooterHealthEvents.PLAYER_KILLEDlives -= 1 (floored at 0). The run is lost when the last life goes.
  • SpaceShooterHealthEvents.FREIGHTER_KILLED — instant loss regardless of lives left; the escort target is gone.
  • WaypointEvents.COMPLETED — sets objectiveComplete and wins the run; the escort reached the final gate.

Every transition rebroadcasts the whole match state as mission.stateChanged. The deciding transition emits mission.won or mission.lost exactly once, then latches status — once the run is decided the System ignores every later event (getActive() returns null for anything but 'active'). There's no per-frame work; the System reacts to the bus and nothing else. It's a world singleton: the System takes the first MissionDirectorComponent it finds, so keep exactly one in the scene.

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 MissionDirector
    bjs download MissionDirector

    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
{
  "MissionControl": {
    "tags": ["director"],
    "components": {
      "MissionDirector": { "lives": 3, "killQuota": 0 }
    }
  }
}

No tag is required — the System finds the record by its component, not a tag — so drop MissionDirector on whatever entity owns your run state. killQuota: 0 is the default escort mission: it's won by completing the waypoint track, not by a kill count.

Props

  • lives (number, default 3) — player deaths allowed before the run is lost. Each PLAYER_KILLED drains one; the run ends when it reaches 0.
  • killQuota (number, default 0) — kills that win the run outright. 0 disables the kill-count win, leaving an escort-only mission won by finishing the track.
  • kills (number, default 0) — hostiles downed so far. Normally starts at 0; it's serialized so a save/load mid-run keeps the count.
  • objectiveComplete (boolean, default false) — true once the escort track has been completed. Set by WaypointEvents.COMPLETED.
  • status ('active' | 'won' | 'lost', default 'active') — the run state. Latches to a terminal value once decided and is never reopened.

Events

  • Emits mission.stateChanged (MissionDirectorEvents.STATE_CHANGED) on every mutation — a kill counted, a life lost, the objective completed, and the deciding transition too. Payload is the flat MissionSnapshot: { status, kills, lives, objectiveComplete, killQuota }. Wire a HUD to this.
  • Emits mission.won (MissionDirectorEvents.WON) exactly once when the run is won — by hitting killQuota or completing the track. Same MissionSnapshot payload, with status === 'won'.
  • Emits mission.lost (MissionDirectorEvents.LOST) exactly once when the run is lost — the last life gone or the freighter destroyed. Same MissionSnapshot payload, with status === 'lost'.
  • Listens for wingman.enemy.killed (SpaceShooterHealthEvents.ENEMY_KILLED) → counts a kill; wingman.player.killed (SpaceShooterHealthEvents.PLAYER_KILLED) → drains a life; wingman.freighter.killed (SpaceShooterHealthEvents.FREIGHTER_KILLED) → instant loss; wingman.track.completed (WaypointEvents.COMPLETED) → win. The System reads only these four; the payloads themselves are ignored — each event is a signal, not data.

Dependencies

  • Health — the source of the three combat signals the director consumes (ENEMY_KILLED, PLAYER_KILLED, FREIGHTER_KILLED). Without a system emitting them, kills and lives never move.
  • Waypoint — emits WaypointEvents.COMPLETED when the escort reaches the final gate, the event that wins the default escort mission.

The director never touches these components directly — it's a bus listener, so Health and Waypoint don't need to sit on the same entity (or any entity the director can see). They only have to be in the world, emitting. If nothing emits WaypointEvents.COMPLETED and killQuota is left at 0, the run has no win condition.

Notes

  • One record per scene. The System takes the first MissionDirectorComponent it finds (getPrimary()); a second one is dead weight. Put it on a single director entity.
  • The terminal events fire after the final stateChanged. The winning/losing transition emits mission.stateChanged first (already carrying status: 'won'/'lost'), then mission.won/mission.lost. A HUD bound only to stateChanged already sees the outcome; the dedicated events are for one-shot end-screen / music triggers.
  • The freighter loss ignores lives. FREIGHTER_KILLED loses immediately even with lives to spare — the escort target is the mission, not the player's survival.
  • Latching is one-way. Once status leaves 'active', every later event is dropped and no further events emit. To run again, replace the component (a fresh one defaults back to 'active'), don't try to reset the old one mid-flight.
  • Every field round-trips. serialize() returns all five numbers/flags, so saving and reloading preserves a run in progress — kills counted, lives spent, objective state, and the latched status all restore.

More like this

SpaceShooterHealth
Waypoint
AiPilot
Animation
ArcCamera
Asteroid
BallReset
Bullet
CameraFollow
DirectionalLight
Enemy
EnemySpawner
EnvironmentTexture
FreighterCar
FreighterChain
FreighterHead
GameConfig
Goal
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
Lifetime
LineOfSight
MachineGun
Mesh
MeshPrimitive
Missile
MissileLauncher
MoneyField
Movement
Obstacle
ObstacleField
OilBlob
Parts
Physics
PlayerInput
PlayerWalkAnimator
PokerBetting
PokerHandEval
PokerMultiplayer
PokerTableDirector
RespawnTimer
Score
Shadow
ShipLoadout
ShooterCamera
SkeletonAnimator
SoccerDirector
SpaceShooterBullet
SpaceShooterScore
TurnPacer
TwinStickShooter
WaveDirector
WaypointTrack

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search