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())