Input & Player Movement
Godot Fundamentals
Chapter 5 · Input & Player Movement
Everything so far has moved on its own, automatically, in _physics_process. This chapter
connects that movement to the keyboard for the first time — genuinely the first moment in this course where
something responds to you pressing a key. Building a real, player-controlled character is the payoff for
every chapter that came before it.
The Input Map
Godot doesn't ask you to check for a raw key like "the right arrow" directly in most real code. Instead,
you define a named action — like "move_right" — and map one or more physical
keys/buttons to it. Your script then only ever asks about the action, never the specific key.
- Open Project > Project Settings > Input Map.
- Type a new action name (e.g.
move_right) and click "Add." - Click the "+" next to your new action and press the key or button you want mapped to it (e.g. the
right arrow, or
D). - Repeat for
move_left,move_up, andmove_down.
Input.is_action_pressed("move_right")
doesn't care whether that's currently bound to the right arrow, D, or a gamepad's right stick.
Rebinding controls, or adding gamepad support later, needs zero script changes — only the Input Map itself
changes.
Godot also ships with several built-in actions already defined, prefixed ui_ — like
ui_left, ui_accept — meant mainly for menu navigation. Gameplay input generally gets
its own custom action names instead, exactly as above.
Reading Input
| Method | Returns | Use it for |
|---|---|---|
Input.is_action_pressed("name") |
bool |
A single held button — true for as long as it's down |
Input.is_action_just_pressed("name") |
bool |
True for exactly one frame, the moment the button goes down — a jump or a menu confirm, not continuous movement |
Input.get_axis("neg", "pos") |
float, -1 to 1 |
One axis of movement — e.g. left/right only |
Input.get_vector("l", "r", "u", "d") |
Vector2 |
Full 2D movement — the usual choice for a top-down or platformer controller |
get_vector() normalizes diagonal movement automatically — pressing both
"right" and "down" at once returns a Vector2 with a length of at most 1, not one with a length of
sqrt(2). Without this, a character moving diagonally would move noticeably faster than one
moving in a straight line — a classic, easy-to-miss movement bug that get_vector() avoids
automatically.
Building a Real Player Controller
Putting the pieces together: read input every physics frame, turn it into a direction, and apply it to
position scaled by speed and delta — exactly the movement pattern from Chapter 4,
now driven by the keyboard instead of running on its own.
Run this attached to a visible sprite, with the four move_* actions bound to arrow keys or
WASD, and it moves smoothly in whichever direction is held — including smoothly diagonal, thanks to
get_vector()'s own automatic normalization.
CharacterBody2D and move_and_slide(), which respects collisions — but everything
about reading input stays exactly the same from here on.
Input.get_vector() doing its own normalization under the hood is the kind of thing you'd
otherwise write by hand in Python with something like direction / direction.length() (careful
to guard against dividing by zero when nothing is pressed) — Godot bakes that safety directly into the
built-in function, including handling the "nothing is pressed" case cleanly by returning a zero vector
rather than raising an error.
Coding Challenges
"jump" action bound to the spacebar. Write a script that
prints "Jump!" the moment the action is pressed — but only once per press, not repeatedly while
held.
move_left/move_right actions and write a script using
Input.get_axis() that moves a Sprite2D horizontally at 250 pixels per second in whichever
direction is held, with no movement when neither is pressed.
direction.x is negative, the sprite's flip_h (from Chapter 4) is set to
true, and false whenever it's positive — so the character visually faces the direction it's moving.
Quick Reference — Input & Player Movement
- Project > Project Settings > Input Map — define named actions, bind keys/buttons to them
- Scripts check the named action, never a raw key — decoupled, remappable
Input.is_action_pressed("name")— bool, held stateInput.get_axis("neg", "pos")— float, -1 to 1Input.get_vector("l", "r", "u", "d")— Vector2, normalized diagonal movementposition += direction * SPEED * deltain_physics_process