Input Buffering and Coyote Time in 2D - A Godot 4 and Unity-Friendly Timing Primer
If your jump button "does nothing" when players press it at the edge of a ledge, your game probably feels harder than intended. Not because of level design, but because timing tolerance is too strict.
Two small mechanics solve most of this:
- Coyote time lets the player jump a few milliseconds after leaving ground.
- Input buffering stores a jump press for a short window before landing so it still executes.
Together they make 2D controls feel responsive without making gameplay easier in a cheap way.
This guide covers both patterns with engine-agnostic logic you can drop into Godot 4 or Unity workflows.

Why input buffering and coyote time matter
Players do not press buttons on the same frame your game expects. They press:
- Slightly before touching ground
- Slightly after stepping off an edge
- During high visual intensity where reaction timing varies
Without tolerance windows, your game misreads intention as failure.
Most polished platformers use tiny timing forgiveness windows. You do not notice them directly, but you definitely feel them.
Quick definitions with practical defaults
Coyote time
A grace window after losing grounded state.
- Typical range: 80 to 140 ms
- Good starting point: 100 ms
Input buffering
A queue window that remembers jump input before jump becomes legal.
- Typical range: 80 to 150 ms
- Good starting point: 120 ms
Use these as baseline values, then adjust by game pace:
- Fast precision platformer: slightly shorter windows
- Heavier action platformer: slightly longer windows
Core logic model you can reuse in any engine
Track two timers:
time_since_groundedtime_since_jump_pressed
Jump should execute when:
time_since_grounded <= coyote_windowtime_since_jump_pressed <= buffer_window- and jump is otherwise allowed (not in hard lockout)
On successful jump:
- consume buffered input
- reset relevant timers
That is the whole system. The rest is clean state handling and debugging.
Godot 4 implementation pattern
In Godot, most teams run this inside _physics_process:
- Increment timers every physics tick.
- Reset
time_since_groundedwhenis_on_floor()is true. - Reset
time_since_jump_pressedwhen jump input is pressed. - Evaluate jump conditions in one place.
Example pattern:
const COYOTE_TIME := 0.10
const JUMP_BUFFER := 0.12
var time_since_grounded := 999.0
var time_since_jump_pressed := 999.0
func _physics_process(delta: float) -> void:
time_since_grounded += delta
time_since_jump_pressed += delta
if is_on_floor():
time_since_grounded = 0.0
if Input.is_action_just_pressed("jump"):
time_since_jump_pressed = 0.0
var can_coyote_jump := time_since_grounded <= COYOTE_TIME
var has_buffered_jump := time_since_jump_pressed <= JUMP_BUFFER
if can_coyote_jump and has_buffered_jump:
velocity.y = -jump_force
time_since_jump_pressed = 999.0
If your movement stack also supports wall-jump or dash cancel, apply explicit priority order so jump buffering does not trigger the wrong move.
Unity implementation pattern
In Unity, you can implement the same timer model in Update (input/timers) and execute physics-sensitive jump in FixedUpdate or a consolidated movement step.
Example structure:
public float coyoteTime = 0.10f;
public float jumpBuffer = 0.12f;
private float timeSinceGrounded = 999f;
private float timeSinceJumpPressed = 999f;
void Update()
{
timeSinceGrounded += Time.deltaTime;
timeSinceJumpPressed += Time.deltaTime;
if (IsGrounded())
timeSinceGrounded = 0f;
if (Input.GetButtonDown("Jump"))
timeSinceJumpPressed = 0f;
if (timeSinceGrounded <= coyoteTime && timeSinceJumpPressed <= jumpBuffer)
{
DoJump();
timeSinceJumpPressed = 999f;
}
}
If you use the new Input System, keep input event capture and timer reset deterministic across frame rate changes.
Tuning checklist for better jump feel
Start with defaults and test in three scenarios:
- Edge test - press jump right after leaving ledge
- Landing test - press jump just before touching floor
- Stress test - heavy scene with unstable frame pacing
Then adjust in this order:
- Tune coyote time first
- Tune buffer second
- Recheck jump arc and gravity last
Do not tune five movement variables at once. You will lose cause-and-effect quickly.
Common mistakes to avoid
Mistake - Buffer input never consumed
Result: accidental double jumps or delayed jumps
Fix: clear buffered jump immediately after a successful jump.
Mistake - Ground check updates late
Result: coyote logic feels inconsistent by frame rate
Fix: unify where grounded state is evaluated and where timers are consumed.
Mistake - One window is much longer than the other
Result: controls feel sticky or overly floaty
Fix: keep windows in a similar band first, then branch intentionally.
Mistake - Testing only on one frame rate
Result: good feel in editor, bad feel on exported build
Fix: run 30/60/120 FPS checks before locking values.
Debugging tips that save hours
- Print timer values around jump events for one controlled test map.
- Visualize grounded state and buffered input as on-screen debug text.
- Record 10 failed jump attempts and classify whether each failure is intended or input-read timing.
A simple debug overlay often reveals that "bad jump feel" is actually a ground detection bug, not a coyote/buffer setting issue.
If you are validating combat and movement timing together, pair this with our deterministic combat timing chapter in the Godot guide.
Practical value ranges by game style
- Tight precision platformer: coyote 70-100 ms, buffer 70-110 ms
- Action platformer: coyote 90-140 ms, buffer 100-150 ms
- Casual mobile platformer: coyote 110-170 ms, buffer 120-180 ms
Use ranges, not magic numbers copied from another project.
FAQ
Is coyote time cheating?
No. It maps player intention to game response and removes unintentional frame-perfect failures.
Should input buffering apply to attacks too?
Usually yes for action games, but with shorter windows and clear priority rules.
Can I use frame counts instead of seconds?
You can, but seconds are easier to tune across different frame rates.
Should I disable buffering in advanced difficulty modes?
Usually no. Difficulty should come from design and decision pressure, not inconsistent input feel.
Responsive controls are one of the highest leverage upgrades in 2D gameplay. Add coyote time, add input buffering, validate across frame rates, and your jump feel improves immediately for both new and experienced players. If this primer helped, bookmark it and share it with your gameplay programmer or technical designer before your next movement polish pass.
Thumbnail: Hulkamania! (Dribbble).