Moving Platforms
When followPlatform is enabled (the default), a character standing on a
moving or kinematic body inherits that body’s motion — you ride elevators,
rafts, and rotating discs without sliding off, and you jump with the platform’s
velocity.
How platform-follow works
Section titled “How platform-follow works”Each tick, if the ground body under the character is kinematic or dynamic, the controller:
- Computes the platform’s velocity at the contact point (linear + angular).
- Expresses the character’s velocity relative to that surface — this is what
relativeVelocity/moveSpeedreport. - Drives the relative planar velocity toward zero using
platformGripFactor. - On a rotating platform, folds the platform’s spin into the character’s facing
(exposed as
turnOnYQuat) so you turn with the disc.
controller.isOnPlatform (and snapshot().isOnPlatform) is true whenever this
coupling is active.
Grip: sliding vs sticking
Section titled “Grip: sliding vs sticking”Two separate knobs control friction because static and moving ground want different feels:
| Option | Applies to | Default | Effect |
|---|---|---|---|
slideGripFactor |
Static ground | 0.5 |
Soft slide-to-stop when you release the stick. |
platformGripFactor |
Moving / kinematic ground | 1 |
How strongly planar velocity matches the surface each tick. 1 = fully match (no slide); lower = slippery. |
Because platformGripFactor couples you to the surface velocity, it’s also what
carries you off the edge of a rolling platform — stronger grip means both
“no sliding on the mover” and “a stronger roll-off from a spinning cylinder”.
Kinematic platforms
Section titled “Kinematic platforms”Kinematic bodies are the simplest movers: you drive them, physics doesn’t. Create
one on a layer the character can stand on, and move it toward a target each tick
with Body.moveKinematic(...) (see the jolt-ts docs for the exact signature):
const platform = world.createBody({ type: "kinematic", shape: Shape.box({ halfExtents: [4, 0.2, 4] }), position: [0, 0.2, 0], layer: "moving",});
function frame(t: number, dt: number) { const target = [Math.sin(t) * 6, 0.2, 0]; platform.moveKinematic(target, [0, 0, 0, 1], dt); // velocity-based motion
controller.setMovement(input); controller.step(dt); world.step(dt);}Dynamic platforms and counter-impulses
Section titled “Dynamic platforms and counter-impulses”On dynamic ground — a physics raft, a see-saw, a stack of crates — the character should push back, or it would appear weightless. The controller applies Newton’s-third-law reactions to the ground body, each individually toggleable:
| Option | Default | Reaction applied to dynamic ground |
|---|---|---|
applyCounterMass |
true |
The character’s weight presses down (a raft sinks under you). |
applyCounterJumpImp |
true |
Jumping off pushes the ground down. |
counterJumpImpFactor |
1 |
Scales the counter-jump impulse. |
applyCounterMoveImp |
true |
Walking pushes the ground backward. |
counterMoveImpFactor |
1 |
Scales the counter-move impulse. |
Mass ratio fall-off
Section titled “Mass ratio fall-off”How hard the character pushes back is scaled by the mass ratio between the
ground body and the character, remapped through
massRatioFallOffCurveData — a curve. This keeps behavior
believable across scales: a light plank reacts strongly to your weight, while a
massive barge barely notices you.
const controller = new CharacterController({ world, massRatioFallOffCurveData: { points: [ { x: 0, y: 0, r_out: 0 }, { x: 0.5, y: 0, r_in: 0, r_out: 0 }, { x: 1, y: 1, r_in: 0 }, ], },});If you replace the curve at runtime, call
controller.refreshMassRatioFallOffCurve() to rebake it.