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.

  1. Open Project > Project Settings > Input Map.
  2. Type a new action name (e.g. move_right) and click "Add."
  3. 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).
  4. Repeat for move_left, move_up, and move_down.
Why name actions instead of checking raw keys? The same decoupling idea from Chapter 3's signals shows up again here — a script that checks 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

MethodReturnsUse 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
func _physics_process(delta: float) -> void: if Input.is_action_pressed("jump"): print("Jumping!") var horizontal := Input.get_axis("move_left", "move_right") print(horizontal) # -1.0, 0.0, or 1.0
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.

extends Sprite2D const SPEED = 200.0 func _physics_process(delta: float) -> void: var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down") position += direction * SPEED * delta

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.

Still not "real" movement — this script moves the sprite straight through walls and other objects, exactly like Chapter 4's own automatic movement. Chapter 6 replaces this pattern with CharacterBody2D and move_and_slide(), which respects collisions — but everything about reading input stays exactly the same from here on.
Coming from Python 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

Challenge 1
In the Input Map, create a "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.
→ Solution
Challenge 2
Create 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.
→ Solution
Challenge 3
Build the full four-directional player controller from this chapter. Extend it so that whenever 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.
→ Solution

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 state
  • Input.get_axis("neg", "pos") — float, -1 to 1
  • Input.get_vector("l", "r", "u", "d") — Vector2, normalized diagonal movement
  • position += direction * SPEED * delta in _physics_process