Quick answer: Store the time of the most recent jump press. On each physics tick, if the player is grounded and the press is within 100ms, consume it. Combine with coyote time for full forgiveness.

Players press jump 1 frame before they land. The press is lost; the character doesn’t jump on contact. Frustrating. Pro platformers buffer presses for a short window.

The Pattern

extends CharacterBody2D

const JUMP_BUFFER = 0.12   # 120 ms
const COYOTE_TIME = 0.10   # 100 ms

var jump_buffer_timer: float = 0.0
var coyote_timer: float = 0.0

func _physics_process(delta):
    if Input.is_action_just_pressed("jump"):
        jump_buffer_timer = JUMP_BUFFER

    if is_on_floor():
        coyote_timer = COYOTE_TIME
    else:
        coyote_timer = max(0, coyote_timer - delta)

    jump_buffer_timer = max(0, jump_buffer_timer - delta)

    if jump_buffer_timer > 0 and coyote_timer > 0:
        velocity.y = JUMP_VELOCITY
        jump_buffer_timer = 0
        coyote_timer = 0

    move_and_slide()

The press is buffered. Each tick: if the player is (or was recently) grounded and a recent press is queued, jump. Both timers consume on jump.

Why It Feels So Much Better

Player presses jump 100ms before landing: with buffer, the input takes effect on the landing frame instead of being lost. Player walks off a ledge and presses jump 50ms later: with coyote time, the jump still executes.

Both windows operate on perception thresholds. Players almost never consciously notice the buffering — they just experience “the game does what I meant.”

Tuning

Verifying

Test the pattern: hold jump while character is falling; should jump on landing. Step off a ledge then press jump; should still jump within the coyote window. Both feel responsive without exposing the underlying mechanic.

“Forgiving controls aren’t cheating. They’re respecting the human nervous system’s timing variance.”

Add input buffer day one to any platformer. Cost: 20 lines. Win: significantly tighter feel.