Rollover
Lane switches that light up.
What it does
The top lanes are a row of switches the ball rolls over on its way down. Each one lights when the ball crosses it. Light them all and the row pays out and goes dark.
A rollover has no physics body, so the ball rolls straight over. When the ball crosses a lane:
- It pays
points, lit or not. - If the lane was dark, it lights: the mesh glows
litColor, and the group's pattern goes out onrollover.changed. - If that was the last dark lane, it says
rollover.complete, paysgroupPoints, and the whole group goes dark again.
Lanes fire once per pass. The ball has to leave a lane before it can score there again.
Lane change
The flipper buttons slide the lit lanes sideways. With three lanes lit as L L _, the right flipper makes it _ L L, and again makes it L _ L. The light that falls off one end comes round the other, so pressing a button never costs the player a lane. A good player keeps moving the dark lane under the ball.
Lanes are ordered left to right by their x position. The keys are read from keyboard.keydown on the bus, which Flipper sends.
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 Rollover
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.
Add Lane_2 and Lane_3 the same way in the same group, and put short PinballWall guides between them.
Props
group(string, default"lanes"). Lanes with the same name light and complete together.points(number, default250). Paid each time the ball crosses.groupPoints(number, default2500). Paid when every lane in the group is lit.ownerEntity(string, default"Player"). Who gets the points.hitRadius(number, default0.7). Distance on the table (x, z) from the lane's centre that counts as crossing.litColor([r, g, b], default[1, 0.75, 0.2]). Glow when lit. Dark lanes go back to their own material glow.rotateLeftKey,rotateRightKey(key codes, defaults"ShiftLeft"and"ShiftRight"). Slide the lit lanes. The first lane in a group decides the keys for the group.
Events
Emits:
rollover.changed,{ group, index, lit }.indexis the lane crossed, or-1for a lane change or the group going dark.litis every lane, left to right.rollover.complete,{ group, count }.score.add,{ ownerEntity, points }.
Listens to:
keyboard.keydown,{ code }. Lane change.
Dependencies
MeshPrimitive on the same entity. Score to count the points. The ball needs the ball tag.
The lane maths
- The pattern logic is in
Rollover.core.ts:lightLaneandrotateLit. - A slide that changes nothing (all dark, or all lit) says nothing.

































