logo
GravityBody

physics · physics-puzzle

GravityBody

2
Unlock

Install with the CLI:

bjs download GravityBody

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:

TypeScript
planSteps(0, 1 / 60); // { steps: 2, carry: 0 }      a 60 fps frame
planSteps(0, 0.005);  // { steps: 0, carry: 0.005 }  too short, so wait
planSteps(0, 10);     // { steps: 12, carry: 0 }     a hidden tab: the backlog is dropped

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:

  1. goes through any Wormhole mouth it is inside;
  2. is tested against every mass surface, and stops if it touches one;
  3. is tested against every CaptureZone that 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:

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

    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
{
  "Rock": {
    "tags": ["rock"],
    "components": {
      "MeshPrimitive": { "primitive": "sphere", "diameter": 1, "position": [-28, 0, -8] },
      "GravityBody": { "velocity": [11, 0, 0], "radius": 0.42, "maxSpeed": 45 },
      "GravityMass": { "mass": 0.3 }
    }
  },
  "Hole": {
    "components": {
      "MeshPrimitive": { "primitive": "sphere", "diameter": 2, "position": [2, 0, 6] },
      "GravityMass": { "mass": 525, "radius": 1, "absorbs": true, "softening": 0.3 }
    }
  },
  "Plant": {
    "components": {
      "MeshPrimitive": { "primitive": "torus", "diameter": 4.6, "thickness": 0.5, "position": [27, 0, 7] },
      "CaptureZone": { "radius": 2.5, "acceptTag": "rock", "points": 100 }
    }
  }
}

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, default 0.3) — the body's own size when it is tested against mass surfaces.
  • maxSpeed (number, default 60) — 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, default true) — test for contact with mass surfaces at all. Off, the body flies through them but still takes wormholes and zones.
  • releaseOnContact (boolean, default true) — 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 }, with swallowed true when the mass absorbs. With releaseOnContact on, 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 bodyRadius and maxSpeed must match the body's radius and maxSpeed (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.

More like this

BlackHole
CaptureZone
GravityMass
MeshPrimitive
MotionTrail
SpaceGolfDirector
Wormhole
AiDriver
AxisViewCamera
BoatMode
BoatWater
Bumper
CarDrive
ContactDamage
ContactImpulse
FreighterChain
GravityLens
Jump
KeyboardMover
MassDriver
MouseHole
Movement
Obstacle
OrbitMover
Physics
PointerDrag
ShipFlight
SpaceGolfHUD
SpaceRock
Spin
Spinner
SurfacePatch
TrafficConvoy
TrajectoryPreview
Transform6DOF
Velocity6DOF
WhiteHole
WorldOriginAnchor

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search