logo
CarCollision

vehicle · vehicle-combat

CarCollision

1
Unlock

Install with the CLI:

bjs download CarCollision

CarCollision

Cars that hit like cars, and a world they can crash into.

What it does

Every frame, after CarDrive has moved everyone, CarCollision finds each pair of cars that overlap and each car touching the world, and works out the shove each one gets. It moves nothing itself. Each car's fix goes back to CarDrive as one carDrive.nudge (position, velocity and a yaw kick), and the damage goes out as health.damage.

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

    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
{
  "Car": {
    "components": {
      "CarCollision": {
        "mass": 1.6,
        "halfWidth": 0.95,
        "halfLength": 2.05,
        "armor": 0.15,
        "ramDamage": 1.6
      }
    }
  }
}

The car also needs CarDrive and a MeshPrimitive.

Oriented boxes

A car is about twice as long as it is wide. As a circle, two cars in side-by-side lanes would touch, and a tap on a rear corner would push the whole car sideways. As boxes:

  1. Separating axes find the overlap and the contact normal.
  2. The corner that dug in is the contact point.
  3. One push along the normal (the bounce) and one along the surface (the scrape, capped by friction) are solved for the speed of the two contact points, which includes how fast each car is turning.

Corner hits

The turn a hit gives is the push times its lever arm, the distance from the car's middle to the contact point:

TypeScript
  // Perpendicular of the lever arm, in the yaw convention above.
  const raPx = c.pz - a.z, raPz = -(c.px - a.x);
  // ...
  out.dwA = -(raPx * Jx + raPz * Jz) * invIA;

Hit a car dead centre and the arm is zero, so it only slides. Hit the rear corner of a 4.1 m car and the arm is about 2 m, so the same push turns it. spinGain then scales that turn for arcade feel.

Damage from a knock

TypeScript
export function impactDamage(dv: number, threshold: number, perUnit: number): number {
  return dv > threshold ? (dv - threshold) * perUnit : 0;
}

// With the defaults (a worked call, not from the source):
impactDamage(10, 4, 2.2) // → 13.2
impactDamage(3, 4, 2.2)  // → 0

Against another car, that is scaled by this car's armor and the other car's ramDamage. A tree or a wall hurts 1.35 times as much, because it does not give.

damageTaken scales every hit from anything on the road: rams, gunfire, bombs and craters. AutoRunnerProjectile and RoadHazard read it through damageTo() in CarCollision.defs. A car that drives into a river is lost whatever it is set to.

Spin-outs

A hard hit, or carCollision.spin from an oil slick, sets the car spinning and takes its grip away for a moment. It slides on its old line while it turns, and its own grip comes back as the spin dies down. Set recoverDamping low and the nose keeps swinging after the slide, fighting the steering.

A grip change from anything else (a tuning panel, a refit) is kept as the car's own grip. A pooled car that leaves play mid-slide gets its grip back first, so it never comes back stuck with the slide grip.

The world

With a HighwayRoad and RoadsideScenery in the scene, roadside props are solid and a bridge's rails are walls. Grass drags the car down and jolts it. The river drowns it. fieldEdge is a hedge at the far edge of the fields, so no car drives off the generated ground into empty fog.

A kinematic car (a trailer on its hitch) pushes and is never pushed back. A car tagged ghost is ignored, like a car inside a truck or between lives.

Settings

FieldDefaultWhat it does
halfWidth0Half the body's width, metres. 0 uses half the MeshPrimitive's width.
halfLength0Half its length. 0 uses half the MeshPrimitive's depth.
mass1.4How heavy the car is. A heavy car shoves a light one further.
restitution0.3Bounce between two cars, 0 to 1. The two cars' values are averaged.
friction0.35Scrape grip between two cars, 0 to 1. Also averaged.
spinGain1.6Multiplies the yaw a hit gives this car.
maxSpin7Top yaw rate, rad/s.
spinDecay2.4How fast a spin dies while the car slides, per second. Higher is a shorter slide.
slideGrip0.3Share of the car's grip left while it slides, 0 to 1.
gripBackAt1.8Spin (rad/s) below which the tyres bite again.
recoverDamping9How fast the leftover spin dies once the tyres bite, per second.
damageThreshold4Knock (m/s) the car shrugs off with no damage.
damagePerKnock2.2Damage per m/s of knock past the threshold.
armor0Share of crash damage this car ignores, 0 to 1.
ramDamage1Multiplies the damage this car deals when it rams.
damageTaken1Multiplies every hit the car takes. 0.5 is half, 0 is invincible (it still drowns).
offroadtrueThe car feels the road: grass, river, bridge rails and the field edge. Off, it ignores all four.
offroadDrag0.9How fast the grass slows the car, per second.
scenerytrueIt can hit trees, poles and houses.
fieldEdge45Metres past the paved edge where a hedge stops the car. 0 is none.
kinematicfalseMoved by something else. It pushes other cars, takes no push back, and takes no damage.

Events

EventPayloadDirection
carCollision.hit{ a, b, x, z, closing, dvA, dvB }emits
carCollision.crashed{ entityId, kind, x, z, closing }emits
carCollision.spinout.started{ entityId }emits
carCollision.spinout.ended{ entityId }emits
carCollision.surface.changed{ entityId, surface }, surface is road, shoulder, verge, rail or wateremits
carCollision.drowned{ entityId, x, z }emits
carDrive.nudge{ entityId, dx, dz, dvx, dvz, dheading }emits
health.damage{ entityId, damage }emits
carCollision.spin{ entityId, spin, seconds }listens

closing is in m/s. A touch under 2 m/s sends nothing, and one car sends at most one crash every 0.35 s.

Needs

  • CarDrive moves every car. This reads its heading, speed and grip, and sends fixes back through carDrive.nudge.
  • MeshPrimitive gives each car's position, and its size when the half sizes are 0.
  • HighwayRoad says what is under each car, where the bridge rails are, and where the river runs.
  • RoadsideScenery lists the props beside the road.

More like this

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search