PinballGate
A one-way gate.
What it does
A real one-way gate is a flap on a hinge. Push it from one side and it swings up out of the way. Push from the other and it hits a stop.
A physics engine has no solid that only stops things one way. But a solid can come and go. So the gate is solid while the ball is past it, and gone while the ball is before it. The ball goes through, and cannot come back.
PinballGate builds the flap as a child entity, <id>_flap, along from to to. Each frame it works out which side of the gate the ball is on:
- More than a ball radius past the gate: the flap gets a static body, and it says
pinballGate.passed. - More than a ball radius before it: the body goes.
- In between, the ball is in the gate, and nothing changes. A body that appears there would be inside the ball, and the engine would shove the ball out sideways.
While the ball goes through, the flap swings up so you can see it. The side is only checked while the ball is level with the gate, so a ball elsewhere on the table never flips it.
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 PinballGate
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.
This is the top of a shooter lane. The ball goes up the lane (-Z) onto the playfield and can never roll back down into it.
Props
from,to([x, z], defaults[-1, 0]and[1, 0]). The two ends of the gate.passDir([x, z], default[0, -1]). The way through. It only needs to point to the right side of the gate.height(number, default0.9). Flap height.thickness(number, default0.1).ballRadius(number, default0.42). How far past the gate the ball must be before it shuts.color([r, g, b], default[0.9, 0.9, 0.95]).
Events
Emits:
pinballGate.passed,{ entityId, ballId }. The ball went through the allowed way.
Dependencies
MeshPrimitive and Physics for the flap. The ball needs the ball tag. It watches the first ball only.
The side test
- The side test is in
PinballGate.core.ts:gateSidegives the signed distance, andgateSoliddecides. - A ball that starts past the gate shuts it on the first frame and counts as a pass.







































