Coding Quests
The Scroll Library
Tutorials

Godot 4 Top-Down Movement Tutorial (CharacterBody2D)

June 16, 20265 min read

Top-down movement is the first thing most people build, and for good reason. It's the foundation under any RPG, twin-stick shooter, or roguelike. Get it feeling right and the rest of your game has something solid to stand on.

In Godot 4 it's about fifteen lines of code. Here's the whole thing, done properly.

Use CharacterBody2D

For a player you control directly, CharacterBody2D is the node you want. It gives you a velocity property and a move_and_slide() method that handles sliding along walls for you, without the unpredictable physics of a RigidBody2D.

Set up your scene like this:

  • CharacterBody2D (attach the script here)
    • Sprite2D (the look)
    • CollisionShape2D (the hitbox, give it a shape)

Set up the input map

Don't hardcode keys. Go to Project Settings, then Input Map, and add four actions: move_up, move_down, move_left, move_right. Bind each to the arrow keys and WASD. This means you can rebind controls later, support a gamepad, and read input cleanly. It's five minutes that saves you a rewrite.

The movement code

GDScript
extends CharacterBody2D
@export var speed: float = 250.0
func _physics_process(_delta: float) -> void:
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = direction * speed
move_and_slide()

That's a fully working top-down character. Press play and move around.

A few things worth understanding here. Input.get_vector() reads all four actions and hands you a single Vector2 pointing the way the player is pushing. It also normalizes the result, which quietly fixes a bug almost every beginner hits: without it, moving diagonally is faster than moving straight, because you're adding two full-speed inputs together.

Notice this runs in _physics_process, not _process. Anything that moves a physics body belongs in _physics_process, which runs at a fixed rate and keeps collisions stable.

Flip the sprite and play a walk animation

Movement looks dead without a little feedback. If you're using an AnimatedSprite2D, swap between idle and walk based on whether the player is moving:

GDScript
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
func _physics_process(_delta: float) -> void:
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = direction * speed
move_and_slide()
if direction.x != 0:
sprite.flip_h = direction.x < 0
if direction == Vector2.ZERO:
sprite.play("idle")
else:
sprite.play("walk")

Add controller and mobile support

Because you used the input map, a gamepad already works if you bind the stick to your move actions. For phones, you'll want an on-screen joystick. Godot 4.7 ships one built in, and it drops straight into this same code by feeding its output into velocity. I wrote that up here: Godot 4.7 VirtualJoystick tutorial.

A common gotcha

If your character moves but slides right through walls, check two things: the wall needs its own collision (a StaticBody2D with a CollisionShape2D), and your player needs a CollisionShape2D with an actual shape assigned, not an empty one. An empty collision shape is the usual culprit when nothing collides.

Build the real thing

This is the exact foundation our 2D Top-Down Movement quest starts from, and the quest is free. It takes this base and layers on the polish: smooth acceleration, animation states, and the structure you need before you start adding combat and enemies. Movement is the floor everything else gets built on, so it's worth building once and building right.

FAQ

Should I use CharacterBody2D or RigidBody2D for a player?

CharacterBody2D for anything you control directly. It gives you precise, predictable movement and handles wall sliding through move_and_slide(). RigidBody2D is for objects you want the physics engine to push around, like crates and debris, not the player.

Why does my character move faster diagonally?

You're adding two inputs together without normalizing. Input.get_vector() fixes this automatically by capping the length of the direction vector at 1. If you build the vector by hand, call .normalized() on it before multiplying by speed.

Why use _physics_process instead of _process for movement?

_physics_process runs at a fixed timestep, which keeps collision detection consistent regardless of frame rate. Any code that moves a physics body and calls move_and_slide() belongs there. Use _process for visual-only things that don't touch physics.

godottutorial2dmovement

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.