Player Movement
Build movement as a readable controller scene, not as a mystery script that only works in one level.
Movement touches input, physics, animation, camera feel, and player trust. A clean controller gives you a stable base for every platform, enemy, and prototype after it.
Start with a GameObject, add Rigidbody2D, then spread input, physics, and animation across components.
Reach for CharacterMovementComponent behavior and expect a lot of built-in policy.
Choose the right body node, keep physics in _physics_process(), and expose only the tuning values designers actually need.
Setup checklist
- Create a Player scene with CharacterBody2D as root.
- Add Sprite2D, CollisionShape2D, Camera2D, and optional AnimationPlayer children.
- Define Input Map actions: move_left, move_right, move_up, move_down, jump.
- Tune speed, jump_force, gravity, and coyote_time from exported variables.
Scene tree shape
Player (CharacterBody2D) Sprite2D CollisionShape2D Camera2D AnimationPlayer
Tuning knobs
Platformer Controller
Gravity, horizontal input, coyote time, and a single move_and_slide() call.
extends CharacterBody2D
@export var speed := 300.0
@export var jump_force := -400.0
@export var coyote_time := 0.15
var gravity := ProjectSettings.get_setting("physics/2d/default_gravity") as float
var coyote_timer := 0.0
func _physics_process(delta: float) -> void:
velocity.y += gravity * delta
if is_on_floor():
coyote_timer = coyote_time
else:
coyote_timer -= delta
if Input.is_action_just_pressed("jump") and coyote_timer > 0.0:
velocity.y = jump_force
coyote_timer = 0.0
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()Top-Down 8-Direction
Use Input.get_vector() so diagonal movement is normalized for free.
extends CharacterBody2D
@export var speed := 220.0
@export var acceleration := 900.0
@export var friction := 1100.0
func _physics_process(delta: float) -> void:
var input_dir := Input.get_vector(
"move_left", "move_right",
"move_up", "move_down"
)
var target_velocity := input_dir * speed
var rate := acceleration if input_dir != Vector2.ZERO else friction
velocity = velocity.move_toward(target_velocity, rate * delta)
move_and_slide()Production pitfalls
- Do not put jump checks in _process() while movement runs in _physics_process().
- Do not hardcode keys if the Input Map should own bindings.
- Do not scale a CollisionShape2D to resize it; edit the shape resource.
Mini challenge
Add jump buffering: store a short timer when the player presses jump early, then consume it when the player lands.