WireRail
A wire track that picks up the pinball, carries it over the table and lets it go somewhere else.
What it does
Real tables call these habitrails: two steel wires that carry the ball from the top of a ramp back down to a flipper. The path never changes, so the game does not have to simulate it. The rail slides the ball along at a steady speed and lets go at the end.
You give it a path: the points the ball's centre passes through, first to last. On its first frame it strings two thin wires along each piece, a little under and either side of that line. They are only for show, with no physics, because the ball never touches them.
When the ball comes within entryRadius of the first point, the rail takes it. The ball's physics body stops simulating and follows its mesh. Each frame the rail moves the ball speed × dt further along the path. At the last point it hands the ball back to the physics engine, rolling on along the last piece at exitSpeed. It says wireRail.entered and wireRail.exited.
Steady speed means the same distance each second. A path of a 3-long straight and a 4-long piece round a corner is 7 long. At half way, 3.5, the ball is half a unit past the corner, not at it. Step through "piece 1, then piece 2" instead and the ball would crawl along long straights and race round the short pieces of a curve.
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:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add WireRail
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
The first point sits just past the top of the ramp, at the ball's centre height there: the ramp's rise 2.4 plus the ball's radius 0.42. The last point is on the playfield. The ball needs the ball tag.
Props
path([x, y, z][], default[]). The ball's centre line. Needs two points or more.speed(number, default22). Riding speed, units per second.exitSpeed(number, default8). Speed the ball leaves the last point at.entryRadius(number, default1). How close the ball must come to the first point.gauge(number, default0.55). Gap between the two wires.wireDiameter(number, default0.09). Wire thickness.ballRadius(number, default0.42). Sets how far under the ball's centre the wires hang.color([r, g, b], default[0.85, 0.87, 0.92]). Wire colour.
Events
Emits:
wireRail.entered,{ entityId, ballId }. The rail picked the ball up.wireRail.exited,{ entityId, ballId }. The rail let it go.
Dependencies
MeshPrimitive. The wires are thin cylinders, and the ball's mesh is what the rail moves.Physics. Its plugin'ssetBodyDrivenByMeshhands the ball between the rail and the engine.
Notes
- After letting go, a rail ignores the ball for 0.6 seconds so it does not pick it straight back up.
- Two rails that start at the same point never both take the ball.
- The wires are child entities named
<id>_w<piece>land<id>_w<piece>r. Remove the rail and they go too.







































