2D Movement with CharacterBody2D

Every body needs at least one CollisionShape2D or CollisionPolygon2D child.

20.1 Body types

Your code (velocity + move_and_slide()) Players, NPCs, enemies Physics engine (forces, impulses) Crates, balls, debris, ragdolls

Doesn't move (or moved by code without physics response) Animation/code, pushes other bodies Detects overlaps; no collision response Triggers, pickups, hitboxes

Every body needs at least one CollisionShape2D or CollisionPolygon2D child.

20.2 Top-down movement

extends CharacterBody2D

@export var speed := 300.0
@export var acceleration := 1500.0
@export var friction := 1200.0

func _physics_process(delta: float) -> void:
	var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
	if input != Vector2.ZERO:
		velocity = velocity.move_toward(input * speed, acceleration * delta)
	else:
		velocity = velocity.move_toward(Vector2.ZERO, friction * delta)
	move_and_slide()
	if velocity.length() > 10.0:
		rotation = velocity.angle()

20.3 Platformer movement

extends CharacterBody2D

@export var speed := 220.0
@export var jump_velocity := -420.0
@export var coyote_time := 0.1
@export var jump_buffer_time := 0.12

var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
var coyote_timer := 0.0
var jump_buffer := 0.0

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity.y += gravity * delta
		coyote_timer -= delta
	else:
		coyote_timer = coyote_time

	jump_buffer -= delta
	if Input.is_action_just_pressed("jump"):
		jump_buffer = jump_buffer_time

	if jump_buffer > 0.0 and coyote_timer > 0.0:
		velocity.y = jump_velocity
		jump_buffer = 0.0
		coyote_timer = 0.0

	if Input.is_action_just_released("jump") and velocity.y < 0.0:
		velocity.y *= 0.5            # variable jump height

	var dir := Input.get_axis("move_left", "move_right")
	velocity.x = dir * speed if dir else move_toward(velocity.x, 0.0, speed)

	move_and_slide()
	update_animation(dir)

func update_animation(dir: float) -> void:
	if dir != 0.0:
		sprite.flip_h = dir < 0.0
	if not is_on_floor():
		sprite.play("jump" if velocity.y < 0.0 else "fall")
	elif dir != 0.0:
		sprite.
…

20.4 Useful CharacterBody2D API

Current velocity (pixels/second) Move, slide along surfaces; returns true if collided

is_on_floor(), is_on_wall(), is_on_ceiling() Contact state after the last move

get_floor_normal(), get_wall_normal() Defines "floor" (default Vector2.UP) floor_max_angle, floor_snap_length

MOTION_MODE_GROUNDED (platformer) or MOTION_MODE_FLOATING (top-down/space)

get_slide_collision_count(), get_slide_collision(i) Collisions from the last move (KinematicCollision2D)

Move and stop at the first collision (returns KinematicCollision2D or null)

Godot 3 note: KinematicBody2D.move_and_slide(velocity, Vector2.UP) returned the new velocity. In 4.x set velocity, up_direction, then call move_and_slide() with no arguments.

for i in get_slide_collision_count():
	var col := get_slide_collision(i)
	if col.get_collider() is RigidBody2D:
		col.get_collider().apply_central_impulse(-col.get_normal() * 50.0)

# Bouncing projectile
var hit := move_and_collide(velocity * delta)
if hit:
	velocity = velocity.bounce(hit.get_normal())