logo

Babylon.js Market

Parts

shooter

Parts

1
Unlock

Install with the CLI:

bjs download Parts

Parts

Reads a ship as a list of destructible boxes instead of one HP bar — shoot a wing off and it flies on; shoot the cockpit and it's gone.

What it does

PartsComponent is pure data: a per-part hitbox and damage list for one ship in a 6-DOF dogfighter. Instead of a single HP number, a ship is defs — a list of axis-aligned boxes in ship-LOCAL space, each with a center (cx/cy/cz), half-extents (hx/hy/hz), its own maxHp, and a critical flag. Two parallel arrays carry the live state: hp[] (current HP per box) and destroyed[] (whether that box has been shed), both index-aligned to defs and both serializable, so a half-wrecked ship round-trips cleanly.

The behaviour lives in the siblings, not here. A bullet's impact system transforms the round's swept segment into the ship's local frame using the Transform6DOF basis, then AABB-tests it against these boxes. On a hit it decrements hp[i]; when a box reaches 0 it sets destroyed[i] and emits wingman.part.destroyed. Dropping a non-critical box (wing, engine, cargo container) sheds that part and the ship keeps flying; dropping a critical box (body, cockpit) routes the whole ship through the shared destroyShip kill path. Two aggregate helpers, totalHp() and totalMaxHp(), sum the boxes for the HUD. Because it owns no System, Parts ships as data only — the registry resolves PartsComponent by name and the impact systems do the work.

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

    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
{
  "Raider": {
    "tags": ["enemy", "ship"],
    "components": {
      "Transform6DOF": { "x": 0, "y": 12, "z": 220 },
      "Parts": {
        "defs": [
          { "name": "body",      "cx": 0,    "cy": 0,   "cz": 0,   "hx": 1.2, "hy": 1.0, "hz": 3.0, "maxHp": 60, "critical": true,  "meshSuffixes": ["Fuselage"] },
          { "name": "cockpit",   "cx": 0,    "cy": 0.8, "cz": 2.0, "hx": 0.7, "hy": 0.7, "hz": 1.0, "maxHp": 30, "critical": true,  "meshSuffixes": ["Canopy"] },
          { "name": "leftWing",  "cx": -2.4, "cy": 0,   "cz": 0,   "hx": 1.8, "hy": 0.2, "hz": 1.2, "maxHp": 20, "critical": false, "meshSuffixes": ["WingL"] },
          { "name": "rightWing", "cx": 2.4,  "cy": 0,   "cz": 0,   "hx": 1.8, "hy": 0.2, "hz": 1.2, "maxHp": 20, "critical": false, "meshSuffixes": ["WingR"] }
        ]
      }
    }
  }
}

Omit hp and destroyed and they derive from defs — every box at full HP, nothing shed.

Props

  • defs (PartDef[], default []) — the ship's hitbox layout, one box per part. Empty means no hitboxes: the component still constructs (the framework contract battery builds it with no args), the ship just can't be hit.
  • hp (number[], optional) — current HP per box, index-aligned to defs. Omitted, each entry starts at its box's maxHp. Pass it (with destroyed) to restore a saved ship mid-fight.
  • destroyed (boolean[], optional) — which boxes have been shed, index-aligned to defs. Omitted, all false.

Each entry in defs is a PartDef:

  • name (ShipPartName) — one of body, cockpit, leftWing, rightWing, engines, container1, container2, container3 (the three containers are the freighter's cargo slots).
  • cx / cy / cz (number) — box center in ship-LOCAL space.
  • hx / hy / hz (number) — box half-extents in ship-LOCAL space.
  • maxHp (number) — starting HP for this box; also the aggregate totalMaxHp() sums.
  • critical (boolean) — true means dropping this box to 0 kills the whole ship; false means it only sheds that part.
  • meshSuffixes (string[]) — renderer mesh-name suffixes that visually belong to this part, carried on the destroy event so the renderer can detach the right meshes.

Two read helpers back the HUD: totalHp() sums max(0, hp[i]) across live boxes, totalMaxHp() sums every box's maxHp. serialize() returns { defs, hp, destroyed }.

Events

  • Emits wingman.part.destroyed (PartsEvents.DESTROYED) the frame a box's hp reaches 0 — payload { entityId, part, x, y, z, meshSuffixes }, where part is the ShipPartName, x/y/z is the box center projected back into world space, and meshSuffixes names the meshes to detach. Parts declares the event constant, but the emit fires from the sibling impact systems (the bullet/missile impact pass on a non-critical kill) and again from SpaceShooterHealth's destroyShip, which re-emits it for every still-attached part as it blows a ship apart.
  • Listens for nothing. Parts owns no System and subscribes to no bus event. Everything that mutates it reaches in through world.getEntitiesWithComponent(PartsComponent), not the bus.

Dependencies

meta.json declares none — PartsComponent constructs with no args and holds no reference to another component, so nothing is a hard dependency. What actually reads it to make it do anything:

  • Transform6DOF — supplies the ship's position and basis vectors. The impact systems use them to fold a bullet's swept segment into the ship's local frame before AABB-testing, and to project a shed box's local center back to world space for the event payload. A Parts ship with no Transform6DOF can't be hit.
  • Bullet + Missile — their impact systems are the code that reads defs, decrements hp, sets destroyed, and emits wingman.part.destroyed. Without one of them in the world, Parts is inert data.
  • SpaceShooterHealth — owns destroyShip, the shared kill path a critical box triggers; it also re-emits wingman.part.destroyed per remaining part while it sheds debris and removes the ship.

Notes

  • Adding PartsComponent alone changes nothing. It ships without a System — the ship stays un-hittable until a Bullet or Missile impact system is in the world to read and mutate it.
  • defs are ship-LOCAL and axis-aligned. The boxes are axis-aligned in the ship's own frame, then rotated by Transform6DOF at test time. Author extents around a nose-forward ship; don't pre-rotate them into world space.
  • critical decides shed-vs-kill. A non-critical box (wing / engine / container) drops to 0, emits the event, and the ship flies on. A critical box (body / cockpit) drops to 0 and the whole ship dies through destroyShip. Mark exactly the boxes that should be fatal.
  • hp[] / destroyed[] are positional. They index into defs, so serialize() round-trips a mid-fight ship — but reorder or resize defs and any previously saved hp / destroyed arrays no longer line up.

More like this

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
ShipLoadout
ShooterCamera
SkeletonAnimator
SpaceShooterBullet
SpaceShooterHealth
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