logo

Babylon.js Market

ShipLoadout

shooter

ShipLoadout

1
Unlock

Install with the CLI:

bjs download ShipLoadout

ShipLoadout

Scene JSON can't import a TypeScript array — so a ship writes ShipLoadout: { loadout: 'spaceplane' } and gets its whole hitbox table minted for it on attach.

What it does

ShipLoadoutComponent is pure setup data: a loadout name (spaceplane, kilrathi, freighter-head, or freighter-car) and a player flag. That's all a scene declares. The names map to four canonical PartDef[] tables baked into ShipLoadout.core.ts — local-space AABBs, per-part HP, critical flags, and the mesh suffixes each hitbox binds to (the Wingman hitboxes, verbatim from the flight game's data/shipParts.ts).

ShipLoadoutSystem (priority 95, query [ShipLoadoutComponent]) does the resolution. The first time it sees a loadout-carrying ship — via onInitialize for scene-time entities, onEntityAdded for live spawns — it calls resolveLoadout(name, player) to get the table, then attaches two components the scene never wrote:

  • PartsComponent({ defs }) — the per-part hitboxes.
  • HealthComponent({ hp, maxHp }) sized to parts.totalMaxHp() — the aggregate HUD bar.

This is the exact imperative attach the original createGame did by hand, moved into a System so the wiring is data-declared and unit-testable. Attach is one-shot: the first line of attachLoadout returns early if the entity already carries a PartsComponent, so a re-attach — or a save/load that already restored Parts — never doubles up. onUpdate does nothing.

The four tables differ in shape. spaceplane and kilrathi are 5-part fighters (cockpit, both wings, engines, body) with two critical parts each. freighter-head is the supply-train locomotive: three modules (bridge, engine block, chassis), all critical, so any one at zero HP explodes the head. freighter-car is a single non-critical cargo container — drop its HP and the whole car is shed, leaving a gap in the chain.

The player flag runs the base table through playerParts, which triples every part's maxHp. The lone player is getting shot at by six enemies at once and needs the buffer; enemies leave it off.

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

    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
{
  "PlayerShip": {
    "tags": ["player", "ship"],
    "components": {
      "Transform6DOF": { "x": 0, "y": 0, "z": 0 },
      "ShipLoadout": { "loadout": "spaceplane", "player": true }
    }
  },
  "EnemyFighter": {
    "tags": ["enemy", "ship"],
    "components": {
      "Transform6DOF": { "x": 30, "y": 4, "z": 180 },
      "ShipLoadout": { "loadout": "kilrathi" }
    }
  }
}

Neither ship declares Parts or HealthShipLoadoutSystem mints both on attach. The player's spaceplane comes in at 3× per-part HP; the enemy Kilrathi at base.

Props

  • loadout (ShipLoadoutName, default "spaceplane") — which named hitbox/HP table to resolve. One of "spaceplane", "kilrathi", "freighter-head", "freighter-car". An unrecognized name resolves to an empty table (zero parts, 0 HP).
  • player (boolean, default false) — apply the ×3 per-part HP buff via playerParts. Off for enemies, on for the player ship.

Events

  • Emits nothing. The System's entire job is the one-shot attach; it never touches the bus.
  • Listens for nothing. Ships are gathered by the system query ([ShipLoadoutComponent]) and driven off the onInitialize / onEntityAdded lifecycle hooks, not events.

The Health and Parts components it mints own their own kill/hit/debris events downstream — ShipLoadout only wires them up.

Dependencies

  • Parts — the per-part hitbox component ShipLoadoutSystem constructs from the resolved PartDef[]. ShipLoadout.core.ts type-imports PartDef from it, and every table's meshSuffixes are Parts' hitbox-to-mesh bindings.
  • Health — the aggregate hull bar, minted with hp and maxHp both set to PartsComponent.totalMaxHp().

Both are attached by the System, not declared in the scene. The ship still needs its own renderable body carrying the child meshes named by each part's meshSuffixes (e.g. -cockpit, -lwing, -engineBlock) for the hitboxes to bind to.

Notes

  • Attach is one-shot and Parts-guarded. attachLoadout bails if the entity already has a PartsComponent. Editing ShipLoadout.loadout at runtime (the tune panel does this) won't re-mint an already-kitted ship — it's a design/setup knob, not a live hull swap.
  • The panel's re-mint gap: ShipLoadout.panel.tsx writes the loadout name and player flag straight onto the component, but because Parts is already attached, the change only takes effect on the next ship spawned with that component — not the one on screen.
  • Player buff aliasing: resolveLoadout returns the shared frozen table when player is false, and a fresh mapped array only when the buff is on. Don't mutate a resolved table in place; the un-buffed path hands back the module-level constant.
  • Freighter criticality differs by table. Every freighter-head part is critical: true (lose one, lose the head); the freighter-car's single container is non-critical (it sheds instead). This lives in the tables, not in ShipLoadout's logic.

More like this

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

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search