Phaser AE

Terrain Rider

Free packv3.0.0-33DLogic

Something that sits on the ground and moves over it, ridden smoothly: a snowboard, a kart, a rolling ball, a walking character or a hovering drone. Give it a height function and it hands back ride height, pitch, roll and whether it is in the air, without the jitter a single height sample gives.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

Terrain Rider: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

rider3d

function rider3d(): Pack<{ rider: Rider3dApi }>

The rider-3d pack: bodies that ride a height field without jitter.

It owns the VERTICAL — ride height, orientation, and the grounded-versus-airborne state. The GAME still owns HORIZONTAL motion, because that genuinely varies: a board carves, a ball rolls, a kart grips. Each frame the game moves x and z its own way, then calls step(dt, x, z, yaw) and reads back y, pitch, roll, grounded, landed and airTime.

Headless and pure — it holds no objects and draws nothing.

Functions

buildFootprint

function buildFootprint( contact: ContactShape, length: number, width: number, radius: number, ringTaps: number, ): FootTap[]

Build the contact footprint for a shape (exported for the tests). A PLANK/point is a symmetric plus of fore×side taps; a SPHERE is the centre + two rings sampling the ball's contact disc (averaged).

footprintTaps

function footprintTaps(L: number): number[]

Fore-aft sample offsets across a footprint of length L (a symmetric 5-tap line, or a single centre tap when L = 0). Exported for the tests.

Classes

TerrainRider

class TerrainRider

Rides a body on a height-field surface without jitter — owns ride height, slope orientation and the grounded/airborne state. The game moves x/z its own way, then calls step(dt, x, z, yaw) and reads the outputs back.

  • x: number — Horizontal position (the game writes these via step).
  • z: number — Horizontal Z position, in world units (the game writes it via step).
  • yaw: number — Heading the footprint is sampled along (radians).
  • y: number — The smoothed ride height — set this on your mesh.
  • vy: number — Vertical velocity (managed in the air; 0 while grounded).
  • grounded: boolean — True while the rider is on the surface, false while airborne.
  • airTime: number — Seconds since takeoff (frozen at flight length after landing — read it on the landed frame to scale a stomp/impact).
  • landed: boolean — True ONLY on the frame the rider touched down.
  • pitch: number — Surface-following orientation for the body (radians). Add your own lean (carve, steer) on top.
  • roll: number — Surface-following roll for the body, in radians — the cross-slope bank.
  • slope: number — The fitted along-heading slope (dY/dForward), smoothed.
  • groundY: number — This frame's raw support height and last frame's — the delta is the height traded for speed if you run an energy model (v² += 2g·Δh).
  • lastGroundY: number — Last frame's raw support height, in world units — pair with groundY.
  • constructor(opts: TerrainRiderOptions)
  • sample(x: number, z: number, yaw: number): { support: number; slopeFore: number; slopeSide: number } — Where the body rests on the terrain, and the surface slope under it. SPHERE: the average ground over the ball's contact disc (its centre and two rings), plus radius — averaging smooths the terrain's quad kinks, so a ball rolls over them and bridges notches without the hard support-switching a footprint max() gives. PLANK: a plane at the fitted slope, laid on the highest contact — bridges dips, pivots crests, banks to the slope. Pure (tests drive it).
  • reset(x: number, z: number, yaw = 0): void — Place the rider on the ground at (x, z), at rest — call before the first step (and on respawn).
  • launch(vy: number): void — Pop into the air with an initial upward velocity (a jump).
  • step(dt: number, x: number, z: number, yaw: number, speed?: number): void — Advance one frame. The GAME has already moved the rider horizontally — pass the new (x, z, yaw). speed (horizontal, world u/s) tunes the suspension stiffness; omit it and it's derived from the move. Sets y, pitch, roll, grounded, landed, airTime, groundY/lastGroundY.

Types

ContactShape

type ContactShape = 'point' | 'plank' | 'sphere'

How the body meets the ground. point = a single sample (a marker, a foot). sphere = a ball: the average ground under its contact disc (rolls over quad kinks and small notches without the hard support-switching a footprint max() gives — the right model for a round thing). plank = a flat footprint (board/kart/box/GLB) that rests on its highest contact and, if tilt, banks to the slope. Auto-picked: radius → sphere, length → plank, else point.

Rider3dApi

interface Rider3dApi

game.rider.

  • add(opts: TerrainRiderOptions): TerrainRider — Make a rider over a surface. groundAt is the one required input.
  • readonly all: readonly TerrainRider[] — Every rider made, so a game can step them together.
  • clear(): void — Forget them all.

TerrainRiderOptions

interface TerrainRiderOptions

Options for the TerrainRider constructor — groundAt is the one required input.

  • groundAt: (x: number, z: number) => number — Surface height at world (x, z) — the ONE required input. Ride terrain with (x, z) => terrain.heightAt(x, z) (or the fresh field, or a seabed…). The rider adds the body's own thickness (radius / the plank plane), so groundAt returns the bare surface. Keep it smooth (the fresh field, not the carved surface, if the rider carves — else it rides its own cut).
  • contact?: ContactShape — Contact shape (default auto from radius/length — see ContactShape).
  • radius?: number — SPHERE radius — a ball/wheel. The centre rides the spherical envelope, so it bridges notches narrower than R and never jitters on inclines.
  • ringTaps?: number — Sphere envelope resolution — samples on a ring (default 12). More = smoother over rough ground, costs more groundAt calls.
  • length?: number — Fore-aft footprint length (PLANK; default 0 = point). A board/kart/box BRIDGES dips and PIVOTS crests over this length — the biggest anti-jitter win for a flat-bottomed body.
  • width?: number — Lateral footprint width (PLANK; default 0). Set it to bank to the cross-slope (a wide vehicle).
  • tilt?: boolean — Bank/pitch the body to the surface slope (default: true for a plank, false for sphere/point). Set false for a box that stays level, resting on its highest corner.
  • settle?: number — How far the contact settles INTO the surface (default 0). A board compacts the snow it rides; the trench opens up behind it.
  • gravity?: number — Air gravity, world units/s² (default 30). Ignored while glued.
  • detach?: number — Detach into the air when the surface drops more than this below the rider — a lip throws you (default 0.9). Infinity = GLUED (never airborne; e.g. a ball stuck to a seabed).
  • followK?: number — Suspension stiffness = followK + speed·followKGain, capped at followKMax. Soft enough to filter terrain-quad steps, stiff enough not to float off a fast descent — but the cap matters: uncapped it goes near-rigid at speed and transmits every support switch raw. Defaults 25 / 0.5 / 55. Set followK = Infinity for a hard follow.
  • followKGain?: number — Extra suspension stiffness per unit of speed. Default 0.5.
  • followKMax?: number — The cap on the speed-scaled suspension stiffness. Default 55.
  • slopeK?: number — Orientation smoothing: the fitted slope eases at slopeK, then the pitch/roll ease toward it at orientK (defaults 14 / 10).
  • orientK?: number — Ease rate of pitch/roll toward the fitted slope, per second. Default 10.
  • pitchGain?: number — Surface pitch/roll = atan(slope) × gain (defaults 0.9).
  • rollGain?: number — Roll = atan(cross-slope) × this gain. Default 0.9.
  • maxFall?: number — Terminal fall speed (default 200).