Your First Platformer Character in Godot
Build a character that runs and jumps, then add the small tricks that make platformers feel good: variable jump height, acceleration and coyote time.

Quest objectives
- Set up a player scene with CharacterBody2D
- Move and jump with velocity and move_and_slide()
- Make the jump respond to how long the button is held
- Forgive late jumps with coyote time
A platformer lives or dies by how its character feels. The good news: you need surprisingly little code to get there. In this tutorial you build a character from scratch and then tune it with three small tricks that almost every good platformer uses.
Set up the scenes
Your character is its own scene, so you can drop it into any level later.
Create a new scene with a CharacterBody2D as its root and rename it to
Player.Add a Sprite2D child and drag
icon.svgfrom the FileSystem dock onto its Texture property.Add a CollisionShape2D child, give it a new RectangleShape2D and size it to match the sprite.
Save the scene as
player.tscn.
Now create a second scene for the level. Use a Node2D as the root and add a StaticBody2D with a wide CollisionShape2D as the floor. Drag player.tscn into the level above the floor, save it as level.tscn and set it as the main scene.
Run and jump
Select the Player node and attach a new script called player.gd:
extends CharacterBody2D
@export var speed := 220.0
@export var jump_velocity := -380.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = jump_velocity
var direction := Input.get_axis("ui_left", "ui_right")
velocity.x = direction * speed
move_and_slide()Three things happen in every physics tick:
In the air, gravity pulls on
velocity. Multiplying bydeltamakes this independent of the frame rate.Input.get_axis()returns a value from -1 to 1 for the left and right keys. Godot's built-inui_left,ui_rightandui_acceptactions already map to the arrow keys and Space, so there is nothing to configure yet.move_and_slide()moves the body by itsvelocity, resolves collisions and keepsis_on_floor()up to date. Its order matters: it belongs at the end.
In Godot, a negative Y points up, which is why jump_velocity is negative. Press F5 and try it: the character runs and jumps. It also feels stiff, because it starts and stops instantly and every jump is the same height. Let's fix that.
Add weight with acceleration
Instead of setting the speed directly, move towards it a little on every tick with move_toward():
velocity.x = move_toward(velocity.x, direction * speed, acceleration * delta)With an acceleration of 1600 the character reaches full speed in under a sixth of a second. That is short enough to feel responsive and long enough to feel like it has weight. The @export keyword shows the value in the Inspector, so you can tune it while the game is running.
Variable jump height
Players expect a short tap to give a short hop. When the button is released while the character is still rising, cut the upward velocity:
if Input.is_action_just_released("ui_accept") and velocity.y < 0.0:
velocity.y *= 0.5Coyote time
Named after a certain cartoon coyote, this trick lets the player jump for a few moments after walking off a ledge. Players never notice it, but they do notice when it's missing: to them, the game simply ate their button press.
Instead of checking is_on_floor() when jumping, keep a timer that is refilled on the ground and runs down in the air. Here is the finished script with all three tricks:
extends CharacterBody2D
@export var speed := 220.0
@export var acceleration := 1600.0
@export var jump_velocity := -380.0
@export var coyote_time := 0.1
var coyote_left := 0.0
func _physics_process(delta: float) -> void:
if is_on_floor():
coyote_left = coyote_time
else:
coyote_left -= delta
velocity += get_gravity() * delta
if Input.is_action_just_pressed("ui_accept") and coyote_left > 0.0:
velocity.y = jump_velocity
coyote_left = 0.0
if Input.is_action_just_released("ui_accept") and velocity.y < 0.0:
velocity.y *= 0.5
var direction := Input.get_axis("ui_left", "ui_right")
velocity.x = move_toward(velocity.x, direction * speed, acceleration * delta)
move_and_slide()Where to go next
You now have a character that feels good to control. From here, try adding a Camera2D as a child of the Player, or replace the floor with a TileMapLayer to build a real level.