logo

Babylon.js Market

CarDrive

vehicular · vehicle-combat

CarDrive

Listens toKeyboardInput
1
Unlock

Install with the CLI:

bjs download CarDrive

CarDrive

Throttle, brake and steering turned into a heading plus lateral slip, so the thing you're driving feels like a car.

What it does

A walking character's velocity is its input vector: press left, go left. CarDrive breaks that link. The throttle sets a speed along the nose, the wheel turns the nose, and the velocity follows the nose only as fast as the tyres can drag it round. CarDriveComponent is that handling profile — eight numbers plus its key bindings — and CarDriveSystem runs one entity per frame: held keys become throttle / brake / steer, the mesh's live world position comes back off the adapter, stepCarDrive returns the next position and heading, and the System writes both to the mesh. Everything the car does happens on the XZ plane; Y belongs to whatever put the car on the road.

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.

Fastest path

bjs download scene CarDrive

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or 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 CarDrive
    bjs download CarDrive

    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
{
  "Input": {
    "components": {
      "KeyboardInput": {}
    }
  },
  "Car": {
    "tags": ["player"],
    "components": {
      "MeshPrimitive": {
        "primitive": "box",
        "width": 1.8,
        "height": 1,
        "depth": 4,
        "position": [0, 0.5, 0]
      },
      "CarDrive": {
        "maxSpeed": 26,
        "acceleration": 14,
        "braking": 28,
        "reverseSpeed": 8,
        "steerRate": 2.6,
        "steerSpeedFalloff": 0.55,
        "grip": 0.85,
        "drag": 0.8
      }
    }
  }
}

KeyboardInput is what publishes keyboard.keydown / keyboard.keyup. Without it in the scene, the car has a handling profile and no driver.

The three things that make it a car

Steering scales with speed. Yaw rate is steer · steerRate · s · (1 − steerSpeedFalloff·|s|), where s is speed over maxSpeed. Two consequences fall out of the same expression. At rest s is 0, so a parked car turns nothing however hard you saw at the wheel. And because the yaw rate grows more slowly than the speed does, the radius opens out:

TypeScript
turnRadius(DEFAULT_CAR_DRIVE_PARAMS, 1, 5)   // → 11.2 — tight
turnRadius(DEFAULT_CAR_DRIVE_PARAMS, 1, 20)  // → 17.3 — same lock, half again as wide

s is signed, so reverse comes free: back up with the wheel right and the nose swings left.

Momentum doesn't turn with the car. Each frame the world velocity is split against the new heading into a forward part and a sideways part. Turning the car changes which direction counts as "forward", not where the car is actually going — that mismatch is the slide, and slip on the returned state is exactly how big it is.

Grip decides how long the slide lasts. grip is the share of sideways speed scrubbed off every 0.1s. At 0 nothing scrubs it: the car yaws freely and keeps travelling on its old line, which is an ice rink. At 1 the sideways part is zeroed every frame, so velocity is always along the nose and there is no slide at all. The interesting settings are between: 0.35 hangs the tail out and catches it a beat later, 0.85 is a road car.

Props

  • maxSpeed (number, default 26) — top forward speed in world units/sec.
  • acceleration (number, default 14) — throttle acceleration, u/s². Reverse builds at the same rate.
  • braking (number, default 28) — brake deceleration while rolling forward, u/s².
  • reverseSpeed (number, default 8) — top speed in reverse, u/s.
  • steerRate (number, default 2.6) — yaw rate at full lock before the speed terms scale it, rad/s.
  • steerSpeedFalloff (number, default 0.55) — how far the steering washes out with speed, 0..1. At 0 the turn radius is the same at every speed.
  • grip (number, default 0.85) — sideways speed scrubbed per 0.1s, 0..1.
  • drag (number, default 0.8) — coasting decay, applied as exp(-drag·dt) and only with both pedals up.
  • driftThreshold (number, default 3) — sideways speed, u/s, that counts as a drift for the events below.
  • heading (number, default 0) — initial yaw in radians; 0 faces +Z. The System owns it after the first tick.
  • keyboardControlled (boolean, default true) — set false on AI cars so the player's keys don't drive the whole grid.
  • throttleKeys / brakeKeys / steerLeftKeys / steerRightKeys (string[]) — KeyboardEvent.code lists, defaulting to WASD plus the arrows.

Events

  • listens for keyboard.keydown / keyboard.keyup (published by KeyboardInput) to track held keys.
  • listens for carDrive.controls.set{ entityId, throttle?, brake?, steer? } — the way an AI or a chase director drives a rival. It only sticks on a car with keyboardControlled: false; otherwise the next frame's key read overwrites it.
  • emits carDrive.drift.started / carDrive.drift.ended{ entityId, slip } — when sideways speed crosses driftThreshold. Edge-triggered, so skid audio and tyre smoke get one event each way rather than sixty a second.
  • emits carDrive.reversing.changed{ entityId, reversing } — for reversing lights and a rear-facing camera.

Dependencies

  • MeshPrimitive — the body being driven. The System reads its live world position through the adapter and writes position + yaw back.
  • KeyboardInput — the bridge that publishes the key events. Only needed for cars that read the keyboard.

Notes

  • stepCarDrive(state, input, params, dt) returns a new state and never touches the one it was handed, so the whole handling model is testable without a world, a renderer or a frame loop. CarDriveComponent is structurally a CarDriveParams, which is why the panel's slider edits land on the very next tick.
  • The System re-reads the mesh's position from the adapter every frame rather than trusting MeshPrimitiveComponent.position. Nothing writes that array back from physics, so a car pushed by a body — or teleported by a respawn — would otherwise be dragged back to where the drive law last thought it was.
  • Drag only applies with both pedals up. That keeps maxSpeed an honest top speed instead of wherever thrust happens to balance friction, and makes drag mean one readable thing: how long a coast lasts.
  • Cornering costs speed. Grip scrubs the sideways component without converting it into forward motion, so hanging the tail out is slow — which is how it should feel.
  • Braking stops at zero rather than sailing through it. Keep the brake held on a stopped car and the next frame starts reversing, capped at reverseSpeed.
  • No hotkey on the tuning panel: WASD is the driving control and the spare digits are taken. Open it from the panel index.

More like this

KeyboardInput
MeshPrimitive
BoatMode
BoatWater
ContactDamage
FlightIntent
Flipper
FreighterChain
Jump
KeyboardMover
Movement
PinballBuilderInput
PlayerFlightInput
PlayerInput
PlayerInputBridge
Plunger
PokerInput
RoadSpawner
RoadTreadmill
ShipFlight
SmokeScreen
SurfacePatch
TrafficConvoy
Transform6DOF
Velocity6DOF
WeaponsVan
WorldOriginAnchor

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search