Gravity and jumping
Model gravity as a constant downward acceleration, implement a jump impulse, and improve player feel with coyote time and jump buffering.
- Model gravity as a constant downward acceleration added each frame
- Implement a jump as an upward velocity impulse
- Explain coyote time and jump buffering as UX improvements
Gravity is the single most important force in a platformer. Getting it right makes the game feel tight and responsive. Getting it wrong — even slightly — makes everything feel floaty, sticky, or unfair.
Gravity as acceleration
In the real world, gravity accelerates objects downward at 9.8 m/s². In a game, the unit is pixels per second per second, and the exact value is chosen for feel rather than realism. A typical pygame platformer uses something in the range of 800–1200 px/s².
The implementation is simple: every frame, add a downward velocity delta:
vel.y += GRAVITY * dt
pos.y += vel.y * dtThe ball starts falling slowly, then faster, then faster still — exactly the
parabolic arc players expect. The critical detail is that you also need a
ground check: when the player's feet touch a platform, zero vel.y and
stop moving downward.
if feet_on_ground:
vel.y = 0Without that reset the player sinks through the floor as accumulated velocity continues driving them downward.
Jump as an impulse
A jump is not a continuous force — it is an impulse: a one-time change to
velocity. When the player presses the jump key, set vel.y to a large
negative value (negative because pygame's y-axis points downward):
if jump_key_pressed and on_ground:
vel.y = -JUMP_SPEEDFrom that point on, gravity decelerates the upward motion, brings it to a
momentary stop at the apex, then accelerates the player back down. The jump arc
is entirely determined by the ratio of JUMP_SPEED to GRAVITY.
A common guideline: the player should feel the peak of the jump is "under
their control". This usually means JUMP_SPEED / GRAVITY is in the range
0.3–0.5 seconds of air time before the apex. Tweak both constants together to
preserve the arc height while changing the snappiness.
Coyote time
Coyote time is a small grace period that lets the player jump for a few frames after walking off a ledge. Without it, a player who walks past the edge by a single pixel is denied a jump that feels — to them — like it should have worked.
The implementation: maintain a coyote_frames counter. While the player is on
the ground, reset it to a small value (e.g. 6). Each frame the player is in the
air, decrement it. A jump is allowed if coyote_frames > 0, not just if
on_ground.
Players never consciously notice coyote time when it is present. They do notice when it is absent.
Jump buffering
Jump buffering is the mirror of coyote time: when the player presses jump slightly before landing, the press is buffered and fires as soon as the feet touch ground. Without buffering, pressing jump a frame too early results in nothing happening, which feels unresponsive.
Implementation: maintain a jump_buffer counter. When the jump key is pressed,
set it to a small value (e.g. 8). Decrement it each frame. On the frame the
player lands, check if jump_buffer > 0 and fire the jump immediately.
Together, coyote time and jump buffering make the jump feel generous and fair — a major factor in whether a platformer is enjoyable to play.
Where to go next
Next: gravity in code — adding all of this to a running pygame player class, including ground collision and the coyote time counter.
Velocity and acceleration
Implement Euler integration so objects accelerate smoothly and move at the same speed on any machine — frame-rate independence in practice.
Gravity in code
Add gravity, a jump impulse, ground collision, and coyote time to a pygame player — the complete platformer physics foundation.