logo

Babylon.js Market

BoatWater

vehicular · vehicle-combat

BoatWater

1
Unlock

Install with the CLI:

bjs download BoatWater

BoatWater

A stretch of water: where the road stops being a road, and how high its surface sits.

What it does

BoatWater is a footprint and a height, and nothing else. Put it on an entity with a MeshPrimitive and that entity is a body of water: a box for a river or a canal, a disc for a lake. BoatModeSystem collects every one of these each frame, asks whether a vehicle's centre is inside any of them, and on the frame the answer changes it swaps that vehicle to boat handling and floats its hull at surfaceY.

Splitting the water out from BoatMode is what lets one river serve every amphibious vehicle in the scene, and lets one vehicle cross several bodies of water without knowing any of them exist. The vehicle declares what it becomes; the water declares where. Neither names the other.

There is no BoatWaterSystem. This component is read, never run — which is why it has no update cost at all beyond being looked at.

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 BoatWater

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

    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
{
  "River": {
    "components": {
      "MeshPrimitive": {
        "primitive": "box",
        "width": 18,
        "height": 0.1,
        "depth": 120,
        "position": [0, 0, 0],
        "color": [0.1, 0.3, 0.5]
      },
      "BoatWater": {
        "shape": "box",
        "halfWidth": 9,
        "halfDepth": 60,
        "surfaceY": 0
      }
    }
  }
}

The MeshPrimitive is what you see and where the water is; the BoatWater numbers are what the system measures against. Keep them in agreement — a mesh 18 units wide wants halfWidth: 9 — or the water will look wider or narrower than it behaves. They are deliberately separate so a shallow decorative apron can extend past the part that actually floats a car.

The footprint is centred on the mesh, not on the origin

The centre of the water is wherever its mesh currently is, read from the renderer every frame. So a river that is itself moving — scrolled past a parked car by RoadTreadmill, or carried by a moving platform — is measured where it actually is this frame, not where the scene JSON first put it. That is why a component this simple still has to be collected every frame rather than cached at load.

Both shapes' numbers are always present

A disc still carries halfWidth / halfDepth, and a box still carries radius. Only the fields belonging to the current shape are read. Keeping the other set intact means you can flip shape live — from a panel, or from a director — and get the footprint you had before, instead of a collapsed one.

Props

  • shape ("box" | "disc", default "box") — a box is a river or a channel, a disc is a lake or a pond.
  • halfWidth / halfDepth (number, defaults 10 / 40) — box half-extents on X and Z. The default is deliberately long and narrow, because the common case is a river crossing the road.
  • radius (number, default 20) — disc radius. Ignored unless shape is "disc".
  • surfaceY (number, default 0) — height of the water surface. A hull floats draft below this. Negative is normal: the road sits at 0 and the riverbed below it.

Events

  • emits nothing, listens for nothing. The water is inert data; BoatMode is what emits boatMode.entered / boatMode.exited when a vehicle crosses this footprint.

Dependencies

  • MeshPrimitive — required. It supplies the water's centre (and the surface you can see). A BoatWater on an entity with no mesh is skipped entirely rather than treated as being at the origin.
  • BoatMode — not a dependency, but nothing observes this component without one. Water with no amphibious vehicle in the scene is simply never asked about.

Notes

  • The class lives in BoatMode.ts — the two are one idea — and is re-exported here. This directory exists because every resolver in the pipeline is dir-per-name: the scaffold registry globs <Name>/<Name>.ts, and bjs download scene resolves against component directories. Without it a scene could name BoatWater and nothing could load it, so the river would silently never launch a boat.
  • Because it is the same class and not a parallel copy, getEntitiesWithComponent(BoatWaterComponent) matches whichever import path a scene came through. A duplicate class here would typecheck and never match — a unit test pins the identity for that reason.
  • Several water bodies can overlap. The first one found containing the vehicle wins, so a lake sitting inside a wider river behaves as one surface rather than fighting over the hull.

More like this

MeshPrimitive
BoatMode
CarDrive
ContactDamage
RoadSpawner
RoadTreadmill
Skybox
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