Skip to content

The platformer

A creator who says “make it a platformer” gets a player who jumps and a camera that frames the jump. Today nothing lifts an actor off the ground, no key or tap is bound to a jump, and the cameras are the follow orbit and the fly camera. This page is the source of truth for what the engine adds, and the layers at the end build it in order.

What exists already, and stays: the Player’s capsule and the character controller under her, the World’s gravity, the movement keys and the on-screen stick, the Tap control in @game/ui that was drawn for a jump, and the fixed box collider an Entity can stand on. A platformer is these plus a jump, two cameras and three clips.

A press of Space, or of a Tap on a phone, jumps her: she leaves the ground at the speed that reaches jumpHeight under the World’s gravity, and the same gravity brings her down. The step reads the jump as one field of its input, so a test, the harness and a room replay it as they replay the stick.

  • The input is a press, not a held key. The keys are level-triggered: a held key walks her every step. A jump is edge-triggered: Space joins the bound keys, its keydown latches one request, and the frame takes the request once. A held Space jumps once, as Unity’s and Godot’s templates do, and unlike Roblox, where a held Space keeps hopping. On a phone the Tap fires the same request on its press; the engine’s input store carries the call beside steer, so a game mounts <Tap label="Jump"> beside its Joystick and binds nothing else. A focused button owns Space, as the existing key handling says.
  • The step carries it. StepInput gains jump, so the room (@daniel-zarinski/room) sends and replays it with the intent, and an agent jumps her headless through stepWorld with no browser. A frame that runs no step keeps the request for the next one; a frame that runs two hands it to the first.
  • The lift is a height, not a speed. jumpHeight is metres, 1.2 by default, and the launch speed is derived from it and the World’s gravity each jump, so a game that sets gravity keeps its jump height. Roblox states its jump as Humanoid.JumpHeight, 7.2 studs, and Unity’s Starter Assets as JumpHeight, 1.2 m; Unreal states JumpZVelocity, 700 cm/s, and Godot’s template JUMP_VELOCITY, 4.5 m/s, which are speeds that change height when gravity does. A height is what a creator asks for. Under the default gravity the 1.2 m jump leaves the ground at 4.85 m/s and lands 0.99 s later. A platformer that wants a snappier arc raises gravity: Unity’s template runs its 1.2 m jump under 15 m/s².
  • The fall is the one that exists. The controller’s move already carries a vertical speed, negative while she falls. A jump sets it positive once, and the step’s gravity pull brings it back through zero. The standing skip, which leaves a grounded actor with no horizontal move where she stands, lets a jump through: a standing jump is a move. Rapier’s snap-to-ground skips a move that goes up, so the first rising step leaves the ground rather than being pulled back to it; the jump layer’s test proves it.
  • No coyote time and no jump buffer in the first layer. Unity’s, Unreal’s and Godot’s templates ship neither. The plain jump lands first and gets felt on a phone; if a press at a platform’s edge reads as dropped, a later layer adds the two windows, 0.1 s each as most 2D platformers keep them.
  • Full air control. The stick steers her in the air as on the ground, at the same speed, as Roblox and Godot’s template do; Unreal’s AirControl of 0.35 is a knob a game asks for. A turn on the spot never starts in the air.
  • One jump. No double jump and no shorter jump on an early release; Unreal’s JumpMaxCount and JumpMaxHoldTime are follow-ups when a game asks for them.
  • What the step keeps. A Jump trait on the hero holds whether the last step ended grounded and how long she has been in the air. The physics writes both after it moves her, as it writes her Steer, so the pose and a game read them off the entity: Roblox’s HumanoidStateType, Unity’s isGrounded, Godot’s is_on_floor(). jumpHeight lives on her Movement trait with the rest of her movement settings.

The map’s edge is a wall, and nothing walks off the map. The walls reach 1.4 m above the highest ground today, sized for a walker who never leaves it; a 1.2 m jump with a 0.6 m autostep on top reaches 1.8 m and would stand on the wall. The walls grow to the map’s size in height, so the highest jump a game sets still meets a wall, and the map keeps its promise that a game needs no check for a fall. A wall is one cuboid whatever its height. Roblox, Unity, Unreal and Godot have no map edge; each destroys or respawns an actor below a kill height, Roblox’s FallenPartsDestroyHeight and Unreal’s KillZ. The engine needs none, because there is nowhere to fall to.

A platformer frames the jump from the side or from behind her. Each is a component of its own over the one rig, as the fly camera is, so a scene mounts one camera and the loop, the save and the devtools read whichever it is. The follow orbit stays the default and the one a 3D platformer in the style of Mario 64 already has, with the player turning the camera.

<SideCamera /> looks at her from a fixed direction at a fixed distance: an orbit whose azimuth, tilt and distance are locked, that the loop aims at her each frame as it aims the follow orbit, with no drag and no wheel. Her left and right on the stick stay across the screen, because the heading the steer is read in never turns; at the default azimuth they are the world’s x. The default looks along negative z from 9 m out, tilted 10 degrees down, so a player walking toward positive x walks right across the screen; azimuth, polar and distance are its props. Depth is not locked: the stick’s forward walks her into the screen, as a 2.5D game allows, and a lane that holds her at one z is a follow-up when a game asks. The eye does not settle in front of a trunk: the side view keeps its distance, as Unreal’s side-scroller turns its spring arm’s collision test off.

The first build aims hard, as the follow orbit does. A damped follow and a dead zone that lets her jump without the camera chasing every jump are the knobs that lift it: Godot’s Camera2D has drag_margin, Unity’s Cinemachine framing transposer a dead zone, and Unreal’s spring arm a camera lag, which its side-scroller template, a spring arm with the rotation fixed, leaves off. Roblox has no side view; a game scripts one with CameraType.Scriptable.

<ChaseCamera /> stays behind her: its azimuth eases toward her facing at turnRate, the short way round, while its tilt and distance stay where the player’s wheel and drag left them, within the orbit’s bounds. A drag wins: the swing holds while a control is on the orbit and for a second after it lets go. Roblox’s Follow camera type and Unreal’s spring arm with the pawn’s yaw inherited are the same camera; Unity’s Cinemachine and Godot’s SpringArm3D leave the turn to the player.

Both of them read the stick against the camera every frame, and so does this engine, and a camera that turns toward her while the stick is read in camera space spirals: the stick 3 degrees off straight spun the view 183 degrees in two seconds, measured on three-mmorpg#416. The chase camera closes the loop by latching the heading. While the chase camera is mounted, the heading the steer is read in is the camera’s azimuth on the frame the push began, and it stays until the stick lets go or turns by more than 30 degrees, when it is read again. A held forward walks her straight along the direction it began in while the camera comes round behind her, and comes to rest there; a new push reads the camera where it now stands. This is input latching, the shape a 3D platformer with a camera that follows on its own runs under, and one of the three shapes the phone recentre issue names as the only ones that can work. The latch is the chase camera’s alone: the follow orbit never turns on its own, and a drag under a held key turns her walk live, which is the desktop feel as it stands.

A platform is an entity with a fixed box collider and a mesh, the way the example game’s ball is a dynamic sphere, and the engine adds no platform component. Her controller already lands on a fixed body and autosteps onto one within step, so a platform stands higher than 0.6 m or a walker walks up it. Roblox’s platform is a Part with Anchored on; Unity’s a box collider on a static mesh; Unreal’s a static mesh actor; Godot’s a StaticBody3D. The layer that builds the jump tests the landing on a box before anything is drawn. A walker that blocks her is ground too: she lands on a monster or another player, and a jump from its top is one jump height above it, as the Player page says.

The one map, meadow, is enough to show them: its centre is a level disc 20 m in radius with an 8 m clearing that nothing stands in, and a side view along x runs across it. A platformer map with a flat lane and hills to jump between is a game’s own registerMap, not the engine’s second map. The moving platform is its own issue: she stands on a kinematic body at its height, but the standing skip leaves her where a platform moves out from under her.

jump.vrma, run-jump.vrma and land.vrma fit the engine’s bodies: every clip in packages/assets/animations/ carries the same 52-bone VRM humanoid from the same exporter, and the four that play today, idle, run, turn-left and turn-right, are retargeted onto each body by the same call the three new ones take. Read out of the files:

Clip Length Hips travel
jump 1.67 s none, held at 1.10 m
run-jump 1.00 s none, held at 0.94 m
land 1.04 s 1.69 m down to 0.67 m, a fall

jump and run-jump move nothing but the limbs, so the capsule’s own arc carries her and the clip reads as the jump. land carries the fall in its hips: played as it is on a body the physics has already brought down, it drops her a second time. The layer that plays it drops the hips translation track from that clip, or plays it from the frame the feet touch.

The pose gains three states. Jump plays while she is in the air from a standing start and RunJump from a running one, chosen on the frame she leaves the ground and held until she lands, however long the air time; a clip longer than the arc is cut by the landing. Land plays after she touches down with the stick idle, for the length of its clip, as Unreal’s JumpEnd state leaves on the clip’s remaining time; a stick held on landing goes straight to the run, as Roblox’s Landed state gives way to walking and as Unity’s template, which ships no landing clip, does. The blend between them is the fade the other clips share. Roblox, Unity, Unreal and Godot all cut a jump into a take-off, a fall loop and a landing; the engine has no fall loop, and adds one when a jump runs long enough to need it.

Each layer is a sub-issue of the design issue, in the order it runs:

  1. The jump: the input, the lift and the fall, the Jump trait and the taller walls.
  2. The jump clips: three pose states and the land hips.
  3. The side camera.
  4. The chase camera, with the latched heading.
  5. A platformer in the example game: platforms in the meadow’s clearing, the side camera, and the Tap beside the stick on a phone.