logo
Unlock

Install with the CLI:

bjs download SpaceGolfDirector

SpaceGolfDirector

Runs a game of Space Golf: lays each level out of pools, keeps the tally, and moves on when a level is won or lost.

What it does

The director sits on the world entity and is the game. Every other Space Golf component does one job, and none of them knows a game is on.

Levels are pure data. To lay one out the director takes every piece from its pool and sets it up, then releases them all when the level ends, so nothing is created or destroyed in play and no level has code of its own.

Each start counts down 3, 2, 1 while the player sets up, then the chute opens. A rock is in play from launch until the plant takes it, a mass ends it, it leaves bounds, or it flies longer than maxFlightSeconds because it found an orbit. Meeting quota clears the level, with a bonus. Reaching maxMisses fails it, and a tie goes to the clear.

A failed level runs again with the player's black holes where they left them, because the retry is about adjusting the shot, not rebuilding it. A restart, a jump to another level, and each new level start from the level's own layout.

Clicking empty space adds a black hole there, up to the level's maxBlackHoles. Past the limit the director emits spacegolf.blackhole.refused, since a click that does nothing reads as broken.

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

    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"],
    "components": {
      "KeyboardInput": {},
      "Score": {},
      "SpaceGolfDirector": {
        "bounds": [36, 16, 16],
        "levels": [
          {
            "name": "First Bend",
            "hint": "Drag the black hole below the stream",
            "quota": 3,
            "maxMisses": 6,
            "maxBlackHoles": 3,
            "previewSeconds": 8,
            "chute": { "position": [-30, 0, -8], "direction": [1, 0, 0] },
            "plant": { "position": [27, 0, 7] },
            "blackHoles": [{ "position": [-2, 0, -14], "mass": 450 }],
            "solution": [{ "position": [2, 0, 6], "mass": 525 }]
          }
        ]
      }
    }
  }
}

The real scene carries twenty levels, plus a pool blueprint for every kind of piece a level can place.

Props

  • levels (array, default []) — the course, in order.
  • startLevel (number, default 0) — the first level, counted from 0.
  • bounds ([x, y, z], default [34, 16, 16]) — half-extents of the field round the origin.
  • maxFlightSeconds (number, default 16) — a rock still flying after this is lost.
  • pointsPerRock, clearBonus, missBonus (numbers, defaults 100, 500, 100): paid for each delivery, for clearing a level, and for each miss left unused.
  • countdownSeconds (number, default 3) — the count before the chute opens. 0 opens it on the next frame.
  • outroSeconds (number, default 2.8) — the pause after a level ends.
  • owner (string, default "player") — the score bucket the points go to.
  • cadenceScales (number[], default [0.75, 1, 1.5, 2, 3]). Notches for the time between rocks, as multiples of each level's own interval, sorted fastest first. The notch in use carries from level to level.
  • cadence (number, default 1) — the notch play starts on.
  • wormholeColors ([r, g, b][], default [[0.4, 0.8, 1], [1, 0.45, 0.85], [0.6, 1, 0.45]]). Given to a level's wormhole pairs in turn.
  • pools (object) — pool names by piece, merged over the defaults. Each defaults to its key in lower case, so blackHole is "blackhole".
  • Keys, as key codes: restartKey (KeyR), addKey (KeyB, adds a hole at the centre), removeKey (Delete, removes the hovered hole; Backspace works too), prevLevelKey and nextLevelKey (BracketLeft, BracketRight), slowerKey and fasterKey (Minus, Equal).

Each level is an object:

  • name, hint (strings) — the title, and one optional line under it.
  • quota (number) — rocks to deliver.
  • maxMisses (number) — the number of misses that fails the level.
  • maxBlackHoles (number) — black holes allowed out at once.
  • previewSeconds (number, optional) — seconds of flight the aim line shows.
  • chute — { position, direction, speed?, interval?, spread? }. Anything left out keeps the blueprint's value.
  • plant — { position }.
  • blackHoles — [{ position, mass? }], where the level's holes start.
  • asteroids — [{ position, radius?, mass? }]. The radius also scales the rock's look.
  • ships — [{ center, radius, speed, phase?, mass? }], each flying a flat circle at its centre's height.
  • whiteHoles — [{ position, strength?, radius? }]. Strength is the push in black-hole mass; radius is the core a rock crashes into.
  • wormholes — [{ a, b, radius?, color? }]: two mouths that are one place.
  • solution — [{ position, mass? }], the holes SOLVE puts in place of the player's, at most maxBlackHoles. A level cleared that way pays its rocks but no level bonus.

Events

Emits:

  • spacegolf.level.started — { index, total, name, quota, maxMisses, maxBlackHoles, hint, solvable }.
  • spacegolf.countdown.changed — { seconds }: 3, 2, 1, then 0 at GO.
  • spacegolf.progress.changed — { delivered, quota, missed, maxMisses, blackHoles, maxBlackHoles }.
  • spacegolf.rock.delivered — { entityId, points, x, y, z }.
  • spacegolf.rock.lost — { entityId, reason, x, y, z }, with reason one of crashed, swallowed, escaped, expired.
  • spacegolf.level.cleared — { index, name, bonus, assisted }.
  • spacegolf.level.failed — { index, name }.
  • spacegolf.game.completed — the last level was cleared.
  • spacegolf.blackhole.refused — { max }.
  • spacegolf.cadence.changed — { scale, interval, slowest, fastest }.
  • spacegolf.level.solved — { index, holes }.
  • score.add — { ownerEntity, points } for each rock and each level bonus.
  • score.resetRequest — at the first start after the game is completed.
  • massdriver.active.set — the chute on at GO, off when the level ends.
  • spaceshooter.explosion — where a rock crashed; a swallow makes none.

Listens:

  • pool.ready: play waits for the pools.
  • massdriver.launched, capturezone.captured, gravity.body.contact: the tally.
  • pointerdrag.empty.clicked, pointerdrag.context, pointerdrag.hover.changed: add, remove, and the hovered hole for the remove key.
  • keyboard.keydown: the keys above.
  • spacegolf.blackhole.add, spacegolf.blackhole.remove, spacegolf.level.restart, spacegolf.level.goto, spacegolf.cadence.adjust, spacegolf.level.solve: the HUD's buttons. Goto takes { index } or { step }; cadence takes { steps }, +1 slower.

Dependencies

The scene also needs CaptureZone on the plant, GravityBody on the rocks and PointerDrag on the holes, which the director hears only through events.

Notes

  • A delivery pays pointsPerRock; the plant's own CaptureZone points is ignored.
  • Play waits for the rock, blackhole, chute and plant pools, and any other pool the first level uses. A missing or misnamed one fails quietly: the game never starts, or a later level's pieces are left out.
  • Restart is ignored during a cleared level's outro, so a won level cannot pay its bonus twice.
  • SOLVE works during the countdown and in play. A failed retry keeps the solution's holes, and the lost bonus with them.

More like this

CaptureZone
GravityBody
GravityMass
KeyboardInput
MassDriver
MeshPrimitive
OrbitMover
PointerDrag
Score
SpaceGolfHUD
SpaceRock
SpaceShooterFX
TrajectoryPreview
WhiteHole
Wormhole
AxisViewCamera
BallPossession
BallReset
BlackHole
Enemy
GameConfig
Goal
GravityLens
MissionDirector
MotionTrail
P2PNetwork
PokerBetting
PokerHandEval
PokerMultiplayer
PokerTableDirector
RespawnTimer
RoundReset
Scoreboard
SoccerDirector
Spin
TurnPacer
WaveDirector

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search