logo
Unlock

Install with the CLI:

bjs download SupplyTruck

SupplyTruck

The weapons truck: drive into the back, pick a refit, roll back out.

What it does

supplyTruck.dispatch brings a tractor and its trailer onto the slow lane, arriveAhead metres in front of the player. The tractor is an ordinary car with an AutoRunnerDriver in the truck role. The trailer is dragged behind it.

Drive into the back of the trailer, roughly lined up. Full throttle is fine; coming in sideways is a crash. The car turns ghost, rolls to the middle of the trailer over loadSeconds and goes out of sight. While it rides inside, the HUD offers up to three refits:

  • missiles: +3
  • oil slicks: +5
  • smoke screens: +5
  • guns: one level up
  • armour: a full repair

A weapon you are full of is never offered. Armour only shows up below 95% health, unless there is nothing else to offer. Pick one with the chooseKeys, or the first offer is fitted after menuSeconds. Then the car rolls out of the tail a little slower than the truck, and the truck speeds off and goes back to its pools.

If the player never docks, the truck waits patience seconds, or gives up at once if the player drives 70 m past it or drops 160 m behind.

supplyTruck.deliver uses the same truck for a new life: the new car rides inside for deliverSeconds and is let out behind it.

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

    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
{
  "Game": {
    "components": {
      "SupplyTruck": {
        "player": "Player",
        "cabPool": "truckCab",
        "trailerPool": "truckTrailer"
      }
    }
  },
  "TruckCabBlueprint": {
    "tags": ["car", "truck"],
    "components": {
      "Pool": { "name": "truckCab", "size": 1 },
      "MeshPrimitive": {
        "primitive": "box",
        "width": 2.4,
        "height": 2.8,
        "depth": 7
      },
      "CarDrive": { "keyboardControlled": false, "maxSpeed": 64 },
      "CarCollision": { "mass": 9, "halfWidth": 1.2, "halfLength": 3.5 },
      "AutoRunnerDriver": { "role": "truck", "cruiseSpeed": 30 }
    }
  },
  "TruckTrailerBlueprint": {
    "tags": ["car", "truck", "trailer"],
    "components": {
      "Pool": { "name": "truckTrailer", "size": 1 },
      "MeshPrimitive": {
        "primitive": "box",
        "width": 2.4,
        "height": 2.6,
        "depth": 6.4
      },
      "CarDrive": {
        "maxSpeed": 80,
        "acceleration": 0,
        "braking": 0,
        "steerRate": 0,
        "keyboardControlled": false
      },
      "CarCollision": {
        "mass": 8,
        "halfWidth": 1.2,
        "halfLength": 3.2,
        "kinematic": true
      }
    }
  }
}

Give the trailer "kinematic": true on CarCollision, so a crash pushes other cars off it without fighting the hitch.

The trailer swings wide

The trailer is never driven. Each frame its nose sits on the tractor's hitch and its heading eases toward the tractor's:

TypeScript
  const L = Math.max(1, halfLength * 2);
  const heading = trailerHeading + (Math.abs(speed) / L) * Math.sin(d) * dt;
  const h = hitchPoint(cab, hitchBack);
  return { x: h.x - Math.sin(heading) * halfLength, z: h.z - Math.cos(heading) * halfLength, heading };

d is the angle between tractor and trailer. Speed times dt is the distance the truck moved, so the trailer turns by distance, not by time. For each trailer length of road, the angle shrinks by about two thirds, at any speed. A 6.4 m trailer at 30 m/s covers its own length in about 0.2 seconds. Through a bend the trailer lags the tractor and cuts inside its line, which is why a real semi swings wide.

The dock gate

Docking is a box behind the trailer's doors. The player's nose has to be in it, lined up, and closing:

TypeScript
export const DEFAULT_DOCK_GATE: DockGate = {
  // The trailer's half width (1.2) plus half a car: a car that overlaps the
  // doors by half its width is taken in. Any less and it clips the corner.
  halfWidth: 1.7,
  // Deep enough that a car closing at 30 m/s is seen inside it for a few frames
  // before its nose reaches the doors and CarCollision bounces it off.
  behind: 3,
  inside: 1.2,
  maxAngle: 0.55,
  minClosing: -4,
  // Full throttle (52 m/s) on a 30 m/s truck is 22: that has to dock.
  maxClosing: 30,
};

maxAngle is about 31 degrees. A player who aims at the back of the truck gets in. One who comes in at 45 degrees rams the trailer instead.

Settings

FieldDefaultWhat it does
player"Player"The car it serves
cabPool"truckCab"Pool the tractor comes from
trailerPool"truckTrailer"Pool the trailer comes from
cruiseSpeed30The truck's pace on the road, m/s
arriveAhead75How far ahead of the player it turns up, metres
hitchBack0.4Hitch distance, metres. The hitch sits half the tractor's length + hitchBack − 0.6 m behind its centre.
loadSeconds0.6Seconds the car takes to roll from the doors to the middle of the trailer
menuSeconds6Seconds to pick a refit before the first one is fitted
deliverSeconds1.4Seconds a new car rides inside before it rolls out
patience30Seconds the truck waits on the road before it leaves
chooseKeys[["Digit1", "Numpad1"], ["Digit2", "Numpad2"], ["Digit3", "Numpad3"]]Keys for refits 1, 2 and 3

Events

EventPayloadDirection
supplyTruck.arrived{ truckId }emits
supplyTruck.loading{ entityId, truckId }emits
supplyTruck.docked{ entityId, truckId, offers, seconds }emits: the HUD shows offers
supplyTruck.upgraded{ entityId, offer }emits
supplyTruck.released{ entityId, truckId }emits
supplyTruck.delivered{ entityId, truckId }emits: the director hands the car back
supplyTruck.left{ truckId }emits
autoRunnerWeapons.grant{ entityId, weapon, amount }emits
autoRunnerWeapons.enabled.set{ entityId, enabled }emits: holds fire while aboard
health.heal{ entityId, amount }emits
mesh.visible.set{ entityId, visible }emits
carDrive.nudge{ entityId, dx, dz, dvx, dvz, dheading }emits: steers the trailer and the car inside
supplyTruck.dispatch{}listens
supplyTruck.deliver{ entityId }listens
supplyTruck.choose{ index }listens
keyboard.keydown{ code }listens

Needs

  • CarDrive moves the tractor, the trailer and the car inside.
  • CarCollision gives the sizes, and its ghost tag lets the car pass through the doors.
  • MeshPrimitive gives positions.
  • HighwayRoad puts the truck in the slow lane and off any bridge.
  • AutoRunnerDriver drives the tractor.
  • AutoRunnerWeapons is read to pick offers, and takes the refit.
  • Health is read for the armour offer, and takes the repair.
  • KeyboardInput turns DOM key presses into keyboard.keydown on the bus, which is how the refit keys arrive.

More like this

AutoRunnerDirector
AutoRunnerDriver
AutoRunnerHUD
AutoRunnerWeapons
CarCollision
CarDrive
Health
HighwayRoad
KeyboardInput
Mesh
MeshPrimitive
RoadAudio
AiDriver
AutoRunnerBomber
AutoRunnerProjectile
BoatMode
BoatWater
CaptureZone
CarPaint
ContactDamage
ContactImpulse
DayNightCycle
Headlights
HighwaySpawner
MoneyField
RoadCamera
RoadFX
RoadHazard
RoadSpawner
RoadTreadmill
RoadsideScenery
SmokeScreen
SurfacePatch
TrafficConvoy
WeaponsVan

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search