Coding Quests
The Scroll Library
Tutorials

Godot 4 Platformer Movement: Jump, Gravity, and Coyote Time

June 16, 20266 min read

Platformer movement lives or dies on feel. The difference between a jump that feels great and one that feels stiff isn't the engine, it's a handful of small touches most tutorials skip. Let's build movement that feels good, not just movement that works.

The basic setup

Use a CharacterBody2D with a Sprite2D and a CollisionShape2D, same as any 2D character. Add input actions for move_left, move_right, and jump in Project Settings, Input Map. Then the bones of it:

GDScript
extends CharacterBody2D
@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
func _physics_process(delta: float) -> void:
# Gravity pulls you down every frame you're in the air
if not is_on_floor():
velocity += get_gravity() * delta
# Jump
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# Left and right
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()

That's a working platformer character. get_gravity() pulls the project's gravity setting, so you tune it in one place. is_on_floor() only works because of move_and_slide(), which figures out what you're standing on. And note jump_velocity is negative, because in 2D, up is negative Y.

This works. But it doesn't feel good yet. Here's why.

Coyote time

Real players press jump a few frames after walking off a ledge, then get annoyed when nothing happens. Coyote time gives them a tiny grace window where a jump still counts even though they've technically left the floor. Named after a certain cartoon coyote who hangs in the air a moment too long.

GDScript
@export var coyote_time: float = 0.1
var coyote_timer: float = 0.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
coyote_timer -= delta
else:
coyote_timer = coyote_time
if Input.is_action_just_pressed("jump") and coyote_timer > 0.0:
velocity.y = jump_velocity
coyote_timer = 0.0
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()

One hundred milliseconds is invisible to the player but makes the controls feel forgiving instead of punishing.

Jump buffering

The flip side: players hit jump just before they land. A jump buffer remembers that press for a few frames and fires it the instant they touch ground:

GDScript
@export var jump_buffer_time: float = 0.1
var jump_buffer_timer: float = 0.0
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("jump"):
jump_buffer_timer = jump_buffer_time
else:
jump_buffer_timer -= delta
# ... gravity and coyote logic ...
if jump_buffer_timer > 0.0 and coyote_timer > 0.0:
velocity.y = jump_velocity
jump_buffer_timer = 0.0
coyote_timer = 0.0

Coyote time and jump buffering together are the single biggest upgrade you can make to a platformer's feel, and they're maybe ten lines.

A snappier fall

Floaty jumps feel bad. A cheap fix is to fall faster than you rise. Multiply gravity when the player is moving downward:

GDScript
if velocity.y > 0:
velocity += get_gravity() * delta * 1.5

Small number, big difference. The character snaps back to the ground instead of drifting.

Where movement becomes a game

Tight movement is the price of entry for a platformer, but it's just the start. The moment you add running, dashing, wall jumps, and attacks, you'll feel the code turn into a mess of booleans fighting each other. That's the problem a state machine solves, and it's worth reading before your player script gets out of hand.

If you want a structured path that builds movement fundamentals the right way, the free 2D Top-Down Movement quest covers the same CharacterBody2D base these platformer mechanics sit on.

FAQ

How do I add gravity to a CharacterBody2D in Godot 4?

Call get_gravity() and add it to velocity each frame while the body is in the air: velocity += get_gravity() * delta inside an if not is_on_floor() check. get_gravity() reads the project's default gravity, so you tune it in Project Settings.

What is coyote time and why do I need it?

Coyote time is a short grace period after walking off a ledge where a jump still registers. Players naturally press jump a few frames late, and without this window the controls feel unfair. A tenth of a second is enough to feel right without looking like a bug.

Why is my jump velocity negative?

In Godot's 2D coordinate system, the Y axis points down, so negative Y is up. To launch the character upward you set velocity.y to a negative number like -400. A positive value would push them into the floor.

godottutorial2dplatformer

Reading is the map. The quest is the territory.

Build this for real in the 2D Top-Down Movement Quest. It's free, no card needed.

Start the Quest
Written by Coding Quests

We teach Godot 4 by making you build complete systems: inventories, save systems, souls-like controllers, enemy AI. The scrolls are free. The quests are where it sticks.