logo
HighwayRoad

vehicle · vehicle-combat

HighwayRoad

Listens toDayNightCycle
1
Unlock

Install with the CLI:

bjs download HighwayRoad

HighwayRoad

An endless highway grown from one seed number, so each run gets the same bends.

What it does

HighwayRoadComponent holds the seed and the shape of the road. HighwayRoad.core turns them into a plan: a centre line, a lane count per 380 m block, rivers with bridges, and towns. Every question about the road is a function of the seed and the distance, so it has one answer everywhere. The meshes, the trees, the AI cars and the crash tests all ask the same plan.

HighwayRoadSystem builds meshes only for the stretch around the followed car. It keeps a ring of 30 m chunk slots, two behind and eight ahead. When the car moves on, the slot that fell behind is rebuilt as the next chunk ahead.

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

    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
{
  "Road": {
    "components": {
      "HighwayRoad": {
        "seed": 1983,
        "follow": "Player",
        "laneWidth": 3.7,
        "startLanes": 3,
        "bridgeChance": 0.32
      }
    }
  }
}

Call registerHighwayRoadExtension(adapter) from adapter/register.ts at bootstrap for the full look. Without it, each layer is drawn in one flat colour, and the look settings (the asphalt maps, paintGlow, the water) do nothing. The road plan works the same either way.

Road space

A car thinks in "how far along" (s) and "how far across" (u), not in world X and Z. The plan converts both ways. Here is the default road at the start line, where it has three lanes:

TypeScript
const plan = createRoadPlan({});
plan.laneOffset(0, 0);    // -3.7  lane 0, on the −X side
plan.laneOffset(0, 2);    //  3.7
plan.surfaceAt(6.5, 10);  // 'shoulder'
plan.surfaceAt(7.5, 10);  // 'verge'
plan.project(x, z);       // { s, u }, and plan.toWorld(s, u) goes back

Other components get the plan with findRoad(world) from HighwayRoad.defs. They never import the drawing System.

The centre line

The bends are not stored anywhere. The centre line is three sine waves added up, each with a phase picked from the seed:

TypeScript
    const w =
      0.74 * Math.sin(k1 * z + ph1) + 0.3 * Math.sin(k2 * z + ph2) + 0.07 * Math.sin(k3 * z + ph3);
    return p.curveAmplitude * c * w;

The longest wave is curveWavelength long. The other two are shorter, so the road wanders instead of repeating. c is how curvy the current block is. It eases from one block to the next. It is also zero for the first half of straightStart and fades in after, so a run never opens on a bend.

What a chunk holds

  • Asphalt: the lanes and lighter shoulders, tinted per vertex so the tarmac is never one flat sheet, and a dark curb down each edge.
  • Paint: a solid yellow line on the +X edge, a solid white one on the −X edge, and dashed white lane lines.
  • Terrain: a gravel lip, a ditch, then rolling grass coloured by noise. It sinks to mud and river bed at a crossing.
  • Water and structure (bridge parapets and piers) appear only where the chunk crosses a river. The water is close to a mirror, so it shows the scene's sky.

Next-door chunks share their edge row exactly, so there is no seam. At night the paint glows brighter so the lines still show.

Settings

FieldDefaultWhat it does
seed1983Same seed, same road, forever.
laneWidth3.7Width of one lane, metres.
shoulderWidth1.6Paved shoulder outside the edge line, each side.
minLanes2Fewest lanes a block can have.
maxLanes4Most lanes a block can have.
startLanes3Lanes on the opening stretch.
blockLength380One block has one lane count, one curviness and at most one river.
taperLength80Metres a change in lane count is spread over, at the start of a block.
curveAmplitude26Largest sideways swing of the centre line, metres.
curveWavelength560Length of the main S-bend, metres. Shorter is twistier.
straightStart300No bends for the first half of this, full bends by one and a half times it.
bridgeChance0.32Chance a block (from the third on) has a river and a bridge.
riverWidthMin34Narrowest river, metres.
riverWidthMax64Widest river, metres.
townChance0.22Chance a block with no river is a town: houses and street lamps, not forest.
chunkLength30Length of one mesh chunk, metres.
sampleStep1.5Distance between the rows a chunk is built from.
terrainWidth110How far the ground reaches past the shoulder, each side.
railHeight0.85Height of the bridge parapets.
waterY-1.1River surface height. The road deck is at 0.
riverbedY-3.6River bed height.
dashLength3Length of a painted lane dash, metres.
dashGap6Gap after each dash, metres.
lineWidth0.16Width of every painted line.
hillHeight3.2Height of the rolling hills away from the road.
follow"Player"Entity whose position decides which stretch is built.
chunksBehind2Chunks kept behind the followed car.
chunksAhead8Chunks kept ahead of it.
warnDistance160Fire highwayRoad.bridge.ahead this far before a bridge. 0 = never.
asphaltTexture"/vehiclecombat/textures/road/albedo.webp"Asphalt colour map. Empty = flat grey.
asphaltNormal"/vehiclecombat/textures/road/normal.webp"Asphalt normal map.
asphaltRoughness"/vehiclecombat/textures/road/roughness.webp"Asphalt roughness map (white = matte).
asphaltAO"/vehiclecombat/textures/road/ao.webp"Asphalt shadow-in-the-cracks (AO) map.
paintGlow0.18How much the painted lines glow, 0 to 1. Brighter at night.
waterColor[0.06, 0.2, 0.26]River colour.
waterAlpha0.86River see-through, 0 clear to 1 solid.

Events

EventPayloadDirection
highwayRoad.ready{ entityId, chunkLength }emits once, when the first chunks are built
highwayRoad.block.entered{ entityId, index, lanes, town, river }emits when the followed car enters a new block
highwayRoad.bridge.ahead{ entityId, distance, width }emits once per river, warnDistance before the parapets
dayNight.changed{ entityId, hour, darkness, phase }listens, reads darkness to set the paint glow

Needs

  • MeshPrimitive: where the followed car is right now.

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