Add gravity, ground contact, jumping and variable jump height to a canvas platformer.
Why this matters
Jumping is the signature verb of a whole genre, and "game feel" lives in a handful of numbers: how hard gravity pulls, how fast the jump starts, what happens when the player lets go of the button. You do not need a physics engine to get a great jump. You need three ideas (velocity, acceleration, ground contact) and the discipline to tune them as design values, not magic numbers.
Position, velocity, acceleration
Each frame:
- acceleration changes velocity,
- velocity changes position.
Gravity is a constant downward acceleration. With y growing downwards on a canvas, gravity is positive:
const GRAVITY = 1800; // pixels per second per second
function applyPhysics(dt) {
player.vy += GRAVITY * dt; // velocity changes by acceleration
player.y += player.vy * dt; // position changes by velocity
}
Updating velocity first and then position with the new velocity is called semi-implicit Euler integration. It is simple and stable enough for most games. Because both lines multiply by dt, the jump is roughly the same height at 30 fps and 144 fps (here about 90 px vs 98 px; a fixed timestep makes it identical on every machine).
Landing on the ground
Without a floor the player falls forever. The simplest floor is a single line:
const GROUND_Y = 180;
if (player.y + player.h >= GROUND_Y) {
player.y = GROUND_Y - player.h; // snap on top of the ground
player.vy = 0; // stop falling
player.onGround = true;
} else {
player.onGround = false;
}
Two things matter. Snap the player to the surface so they never sink in. Zero the velocity, or gravity keeps building speed while they stand still and the next step off a ledge becomes a sudden plunge. For platforms, apply the same idea with the AABB test from the last lesson: if the player is falling (vy > 0) and overlaps a platform, put them on top of it.
The jump
A jump is an instant upward velocity, applied only when standing:
const JUMP_SPEED = 600;
function jump() {
if (!player.onGround) return; // no infinite air jumps
player.vy = -JUMP_SPEED; // negative = up on a canvas
player.onGround = false;
}
Gravity then slows the rise, stops it at the peak and pulls the player back down. Physics gives you the shape for free: the peak height is JUMP_SPEED² / (2 × GRAVITY). With 600 and 1800 that is 100 pixels, and the time to the peak is 600 / 1800 = 0.33 seconds. Designers often work backwards: choose the height and time to peak you want, then solve for gravity and jump speed. The GDC talk "Building a Better Jump" walks through this method.
Variable jump height
In many platformers, tapping jump gives a hop and holding it gives a full jump. A simple way to get that: when the player releases the button while still rising fast, cut the upward speed.
const JUMP_CUT = 0.5;
function releaseJump() {
const minRise = -JUMP_SPEED * JUMP_CUT;
if (player.vy < minRise) player.vy = minRise; // still rising fast: slow down
}
If the player is already falling or rising slowly, nothing changes, so a late release does not cause a strange mid-air stop.
Feel tricks worth knowing
- Coyote time: allow a jump for about 0.1 s after walking off a ledge. Players perceive this as fair, not as cheating.
- Jump buffering: remember a jump press made just before landing and perform it on landing.
- Higher gravity when falling: many games multiply gravity by 1.5 to 2 on the way down for a snappier arc.
- Terminal velocity: cap
vyso long falls stay readable.
Each of these is a few lines once the basic state (vy, onGround) exists. Add them one at a time and playtest each.
Treat numbers as design data
Put tuning values in one place, name them, and show them on screen or in a debug panel while you play:
const TUNING = { GRAVITY: 1800, JUMP_SPEED: 600, JUMP_CUT: 0.5, MAX_FALL: 900 };
A designer who can change jump height in five seconds will try twenty variations; one who must hunt through code will try two.
Common mistakes
- Gravity with the wrong sign. On a canvas, down is positive.
- Not resetting
vyon landing, so speed keeps building while standing. - Jumping without checking
onGround, which gives infinite jumps. - Forgetting
dtin either line, which makes the jump depend on frame rate. - Setting
onGroundonly to true, never back to false when walking off a ledge.
Quick recap
- Acceleration changes velocity; velocity changes position, both times
dt. - Snap to the ground, zero
vy, trackonGround. - Jump = set
vyto a negative speed, only from the ground. - Releasing early cuts upward speed for variable height.
- Name your tuning values and tweak them while playing.
Try it
Open the Do tab to implement applyPhysics, jump and releaseJump for a canvas platformer.