Read the keyboard, mouse, and gamepad through Godot’s input actions, then move a character. Build a top-down and a platformer controller — the foundations of nearly every game.
Rather than hardcoding specific keys, Godot uses input actions — named actions like "move_left" or "jump" that you map to keys, mouse buttons, and gamepad inputs in project settings. Your code checks the action, not the key, so remapping and gamepad support come for free. Defining actions and querying them is the right way to read input in any Godot game.
# define actions in Project Settings > Input Map (e.g. "move_left" -> A + Left arrow)
# then query the ACTION, never a raw key:
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("move_right"):
pass # held down this frame
if Input.is_action_just_pressed("jump"):
pass # pressed THIS frame only (edge) -> good for jumps/actions
# a -1..1 axis from two actions -> smooth directional input (+gamepad sticks):
var direction := Input.get_axis("move_left", "move_right")
# actions abstract over keyboard/mouse/gamepad -> free remapping + controller support.A top-down character moves freely in all directions. You build a direction vector from the input actions, normalize it (so diagonal movement is not faster), scale by speed, and let CharacterBody2D’s move_and_slide handle moving and sliding along walls. This compact controller — read input, set velocity, move_and_slide — is the template for top-down games.
extends CharacterBody2D
@export var speed := 200.0
func _physics_process(delta: float) -> void:
# a 2D direction from the four movement actions
var direction := Input.get_vector("move_left", "move_right",
"move_up", "move_down")
velocity = direction * speed # get_vector is already normalized
move_and_slide() # moves + slides along walls, handles collisions
# read input -> set velocity -> move_and_slide(). The top-down template.
# get_vector normalizes, so diagonal isn't faster than straight movement.A platformer adds gravity and jumping. Each physics step you apply gravity to the vertical velocity, set horizontal velocity from input, and — when the jump action is pressed and the body is on the floor — set an upward velocity. is_on_floor() (available after move_and_slide) makes ground checks trivial. This is the skeleton every 2D platformer builds on.
extends CharacterBody2D
@export var speed := 250.0
@export var jump_velocity := -400.0 # negative = up
var gravity := 980.0
func _physics_process(delta: float) -> void:
# apply gravity while in the air
if not is_on_floor():
velocity.y += gravity * delta
# jump only when on the ground
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# horizontal movement from input
velocity.x = Input.get_axis("move_left", "move_right") * speed
move_and_slide()
# gravity + jump-when-grounded + horizontal input = the platformer skeleton.What separates a great platformer from a stiff one is "game feel" — small forgiveness mechanics like coyote time (a brief window to jump after leaving a ledge), jump buffering (registering a jump pressed just before landing), and variable jump height. These do not change the core loop but make controls feel responsive and fair. Tuning feel is a defining skill of good game development.
"Game feel" — small tweaks that make controls feel great:
COYOTE TIME allow a jump for ~0.1s AFTER walking off a ledge
(players press jump slightly late — forgive it)
JUMP BUFFERING if jump is pressed just BEFORE landing, jump on landing
VARIABLE JUMP release the button early -> shorter jump (cut velocity)
ACCELERATION ramp speed up/down instead of instant start/stop
SCREEN SHAKE / hit-stop on impact for punch
None change the core loop; all make it feel responsive + fair. Tuning feel
(by playtesting) is a core game-dev skill — the difference between stiff and
satisfying controls.