Why CharacterBody2D for Player Movement
In Godot 4, CharacterBody2D is designed for characters you control directly (player, enemies). It provides a built-in velocity property and helper movement methods that work well with collision shapes and the physics engine. The key idea is: you compute a desired velocity each physics frame, then let the engine move the body while resolving collisions.
For a beginner-friendly platformer, the most common pattern is:
- Read horizontal input.
- Apply gravity when not on the floor.
- Set jump velocity when jump is pressed and you are allowed to jump.
- Call
move_and_slide()to move and slide along floors/walls/slopes.
Scene Setup: Player with Collision
1) Create the Player node structure
Create a new scene for the player and use this node tree:
Player (CharacterBody2D)\n Sprite2D\n CollisionShape2DAssign your player texture to Sprite2D.
2) Add a CollisionShape2D that matches the sprite
Select CollisionShape2D and create a shape in the Inspector (commonly CapsuleShape2D for characters, or RectangleShape2D for simple sprites). Resize it so it covers the visible body of the character.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
- Keep the shape slightly inside the sprite edges to avoid snagging on corners.
- For a capsule, align it so the rounded bottom sits where the feet are.
- Avoid very thin shapes; they can behave poorly on slopes and edges.
Scene Setup: Platforms, Walls, and Slopes
Static platforms and walls
Create a separate scene (or build directly in your level) using:
Platform (StaticBody2D)\n Sprite2D (optional)\n CollisionShape2DUse StaticBody2D for level geometry that does not move. Add a CollisionShape2D (often rectangles) for floors and walls.
Slopes
For slopes, use a CollisionPolygon2D (or a polygon shape in a CollisionShape2D) to create an angled surface. Slopes work best when the collision is a clean, simple polygon (avoid jagged edges).
Implementing Movement with velocity, Gravity, and move_and_slide
Core movement script (beginner-friendly)
Attach a script to Player (CharacterBody2D). This version focuses on reliable basics: left/right movement, gravity, and jumping.
extends CharacterBody2D
@export var speed: float = 220.0
@export var jump_velocity: float = -420.0
@export var gravity: float = 1200.0
func _physics_process(delta: float) -> void:
# 1) Horizontal input
var axis := Input.get_axis("move_left", "move_right")
velocity.x = axis * speed
# 2) Gravity
if not is_on_floor():
velocity.y += gravity * delta
else:
# Optional: keep a small downward force so you stay grounded on slopes
# velocity.y = 0.0
pass
# 3) Jump
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# 4) Move with collision response
move_and_slide()Notes:
jump_velocityis negative because in 2D, up is usually negative Y.move_and_slide()uses the currentvelocityand automatically slides along surfaces instead of stopping abruptly.is_on_floor()becomes true when the body is contacting a surface considered a floor (based on the floor angle settings).
Step-by-step: making it work in your project
Create input actions in Project Settings → Input Map:
move_left(A, Left Arrow)move_right(D, Right Arrow)jump(Space)
Place the Player above a platform in your level scene.
Run the scene and verify: you fall due to gravity, land on the platform, move left/right, and jump.
Understanding Collisions: Layers and Masks
Godot uses collision layers and collision masks to decide what collides with what.
- Layer: what the object is (which layers it belongs to).
- Mask: what the object checks against (which layers it wants to collide with).
Recommended beginner setup
| Object | Collision Layer | Collision Mask |
|---|---|---|
| Player (CharacterBody2D) | Layer 1: Player | Layer 2: World |
| Platforms/Walls (StaticBody2D) | Layer 2: World | (Often none needed for static bodies) |
In practice, you can leave the platform mask on, but it’s useful to learn the separation early. The important part: the player mask must include the world layer, or you will fall through.
Testing Interactions: Floors, Walls, and Slopes
Floors
When the player lands, is_on_floor() should become true. If it never becomes true, check:
- The player mask includes the platform layer.
- The platform has a collision shape.
- The player collision shape is not disabled and is sized correctly.
Walls
With move_and_slide(), running into a wall should stop horizontal movement naturally. If you need wall checks later, is_on_wall() can tell you when you are touching a wall.
Slopes
Slopes should allow the character to walk up/down while staying grounded. If you see jittering or the character “bouncing”:
- Simplify the slope collision polygon.
- Ensure the player collision shape is not too boxy (capsules often behave better).
- Try keeping a small downward velocity when grounded (some games do this to reduce tiny separations on slopes).
Collision Shape Fit: Practical Checklist
Checklist: shapes fit sprites
- The player collision shape covers the body, not empty transparent pixels.
- The bottom of the shape matches the “feet” position.
- The shape is not wider than the sprite unless you want earlier wall contact.
- Platforms have collision shapes that match their visible surface.
- Slopes use clean polygons without tiny zig-zags.
Checklist: collision settings
- Player layer is set (for example, Layer 1).
- Player mask includes the world layer (for example, Layer 2).
- Platforms are on the world layer.
- No accidental disabled collision shapes (
CollisionShape2D.disabledshould be false). - Transforms are correct: collision shapes are positioned where the sprites are (watch for offset children).
Checklist: visible collision debugging
Use Godot’s debug drawing to see collision shapes while the game runs:
- Run the game, then enable Debug → Visible Collision Shapes.
- Confirm the player and platforms overlap exactly where you expect.
- If the player appears to collide “in the air,” the collision shape is likely offset or too large.
Optional Refinements: Coyote Time and Jump Buffering
These are small quality-of-life features that make jumping feel more forgiving. They are optional; you can add them after the base movement works.
Coyote time (jump shortly after leaving a ledge)
Coyote time gives the player a brief window to jump after walking off an edge. Implementation idea: keep a timer that resets when grounded and counts down when airborne.
extends CharacterBody2D
@export var speed: float = 220.0
@export var jump_velocity: float = -420.0
@export var gravity: float = 1200.0
@export var coyote_time: float = 0.10
var coyote_timer: float = 0.0
func _physics_process(delta: float) -> void:
var axis := Input.get_axis("move_left", "move_right")
velocity.x = axis * speed
if is_on_floor():
coyote_timer = coyote_time
else:
coyote_timer = max(0.0, coyote_timer - delta)
velocity.y += gravity * delta
if Input.is_action_just_pressed("jump") and coyote_timer > 0.0:
velocity.y = jump_velocity
coyote_timer = 0.0
move_and_slide()Jump buffering (press jump slightly early)
Jump buffering stores a jump press for a short time. If the player presses jump just before landing, the jump triggers on the first frame they are allowed to jump.
extends CharacterBody2D
@export var speed: float = 220.0
@export var jump_velocity: float = -420.0
@export var gravity: float = 1200.0
@export var jump_buffer_time: float = 0.12
var jump_buffer_timer: float = 0.0
func _physics_process(delta: float) -> void:
var axis := Input.get_axis("move_left", "move_right")
velocity.x = axis * speed
# Buffer jump input
if Input.is_action_just_pressed("jump"):
jump_buffer_timer = jump_buffer_time
else:
jump_buffer_timer = max(0.0, jump_buffer_timer - delta)
# Gravity
if not is_on_floor():
velocity.y += gravity * delta
# Consume buffered jump when landing/allowed
if jump_buffer_timer > 0.0 and is_on_floor():
velocity.y = jump_velocity
jump_buffer_timer = 0.0
move_and_slide()Combining both (common platformer feel)
You can combine coyote time and jump buffering by keeping both timers and allowing a jump when (buffered jump exists) AND (on floor OR coyote timer active). If you do this, keep the logic readable and test carefully on edges and slopes.
Common Problems and Quick Fixes
Player falls through platforms
- Player mask does not include the platform layer.
- Platform has no collision shape or the shape is too small.
- Player collision shape is missing/disabled.
Player gets stuck on corners
- Use a capsule collision shape for the player.
- Shrink the collision shape slightly so it doesn’t catch on tile edges.
- Ensure platform collision is not made of many tiny shapes with gaps.
Jitter on slopes
- Simplify slope collision geometry.
- Check that the player collision shape is smooth (capsule) and not too wide.
- Try a small constant downward velocity when grounded if needed for stability.