Free Ebook cover Godot 4 for Beginners: Build a Small 2D Game from Scratch

Godot 4 for Beginners: Build a Small 2D Game from Scratch

New course

10 pages

D Movement and Physics with CharacterBody2D and Collision Shapes

Capítulo 4

Estimated reading time: 7 minutes

+ Exercise

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  CollisionShape2D

Assign 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 App

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  CollisionShape2D

Use 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_velocity is negative because in 2D, up is usually negative Y.
  • move_and_slide() uses the current velocity and 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

  1. Create input actions in Project Settings → Input Map:

    • move_left (A, Left Arrow)
    • move_right (D, Right Arrow)
    • jump (Space)
  2. Place the Player above a platform in your level scene.

  3. 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

ObjectCollision LayerCollision Mask
Player (CharacterBody2D)Layer 1: PlayerLayer 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.disabled should 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.

Now answer the exercise about the content:

In a beginner-friendly CharacterBody2D movement setup, what is the main purpose of calling move_and_slide() each physics frame?

You are right! Congratulations, now go to the next page

You missed! Try again.

move_and_slide() uses the current velocity to move the character while handling collision response, allowing it to slide along floors, walls, and slopes instead of stopping abruptly.

Next chapter

Signals and Events: Clean Communication Between Nodes

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.