GravityBody
Moves bodies through the pull of every GravityMass: n-body gravity with no physics engine.
What it does
One GravityBodySystem runs gravity for the whole world. Each frame it reads every mass, then spends the frame's time in fixed steps of 1/120 s and carries what is left to the next frame:
The step size never follows the frame. The aim line (TrajectoryPreview) flies its ghost with the same step function at the same step size, so in a still field a rock follows its line to the last bit. Step by frame time instead and the two agree only to a few percent, and a few percent of a close pass round a black hole is the gap between a slingshot and a crash.
Each step sets the velocity first, then moves by the new velocity (semi-implicit Euler). It costs the same as plain Euler, but an orbit does not gain energy lap after lap, so a rock that slingshots round a hole leaves at the speed it came in. Plain Euler drifts out by more than half the radius in ten laps.
After every step, each body in turn:
- goes through any Wormhole mouth it is inside;
- is tested against every mass surface, and stops if it touches one;
- is tested against every
CaptureZonethat takes it by tag. A body that has just come into one stops for the rest of the frame, so the zone finds it where it arrived, not a step past the far edge or inside a hole parked on the near one.
The aim line makes the same three checks at the same point in its step, so the rock ends where its line ends, at any frame rate. A body that starts a frame already inside a zone is not coming in, and flies on.
Bodies that are also masses pull on each other as they go, and never on themselves.
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 GravityBody
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.
With the hole where SpaceGolf's first level wants it, the rock curves into the plant about 4.6 s after launch. In the game the rocks come from a Pool instead, and MassDriver writes velocity at each launch.
Props
velocity([x, y, z], default[0, 0, 0]) — units per second.radius(number, default0.3) — the body's own size when it is tested against mass surfaces.maxSpeed(number, default60) — speed cap in units/s, 0 for none. A body that grazes a heavy mass gets a huge kick in one step, and the cap stops it crossing the map in a frame.collides(boolean, defaulttrue) — test for contact with mass surfaces at all. Off, the body flies through them but still takes wormholes and zones.releaseOnContact(boolean, defaulttrue) — on contact, take the body out of play, which parks a pooled one. Off, it stops dead and stays in play, still pulling if it is a mass, until a game system removes it.
Events
gravity.body.jumped— a body went through a Wormhole. Payload{ bodyId, fromId, toId, x, y, z, vx, vy, vz }: the two mouths' entity ids, where it came out, and its velocity, unchanged by the pass.gravity.body.contact— a body touched a mass surface. Payload{ bodyId, massId, swallowed, x, y, z, vx, vy, vz }, withswallowedtrue when the mass absorbs. WithreleaseOnContacton, the body is already out of play when this fires.
Both fire after the step loop, every jump before any contact, so a listener can move or remove entities safely. GravityBody listens to nothing.
Dependencies
MeshPrimitive— holds the position, and the System writes the mesh.GravityMass— the pull it flies through.Wormhole— the mouths it carries bodies through.CaptureZone— the zones it stops arriving bodies in.
Notes
- Give the aim line the rock's numbers. Its
bodyRadiusandmaxSpeedmust match the body'sradiusandmaxSpeed(0.42 and 45 in SpaceGolf), or the line and the rock part ways. - Priority 50 puts it after launchers (MassDriver, 60), so a rock fired this frame moves this frame, and before the capture zones (45).
- Below 10 fps the 12-step cap drops time, so a slow machine plays the flight in slow motion. The rock still lands on its line.






































