../

Input & physics

Reading input and moving things in Godot 4.x: the input map, the Input singleton, input callbacks and events, physics bodies, collision layers, ray casts, the physics tick and interpolation, Jolt in 3D, and navigation. Node basics are in Nodes, scenes & signals; animating the results is in UI, animation & audio.

Input map & actions

Project Settings > Input Map: create named actions (jump, move_left) and bind keys, mouse buttons, gamepad buttons and axes to them. Code asks about actions, never raw keys, so rebinding and gamepads come for free.

Built-in actionDefault binding (used by Controls)
ui_acceptEnter, Space, gamepad A / Cross
ui_cancelEscape, gamepad B / Circle
ui_left ui_right ui_up ui_downarrows, D-pad, left stick
ui_focus_next / ui_focus_prevTab / Shift+Tab
ui_text_*, ui_graph_* ...text editing and editor controls
Action settingNotes
Deadzoneper action; analogue values below it read as 0
Physical keysbind "Physical Keycode" to keep WASD in place on AZERTY layouts
Device"All Devices" or a specific joypad index for local multiplayer
Show built-in actionstoggle in the Input Map tab to edit or remap ui_* actions
# Runtime rebinding
func rebind(action: StringName, ev: InputEvent) -> void:
    InputMap.action_erase_events(action)
    InputMap.action_add_event(action, ev)
 
# Define actions in code (tools, tests)
if not InputMap.has_action("dash"):
    InputMap.add_action("dash")
    var k := InputEventKey.new()
    k.physical_keycode = KEY_SHIFT
    InputMap.action_add_event("dash", k)

The Input singleton

Poll state anywhere (usually in _physics_process).

CallReturns
Input.is_action_pressed(a)held this frame
Input.is_action_just_pressed(a)went down this frame (or physics tick, when called there)
Input.is_action_just_released(a)went up this frame
Input.get_action_strength(a)0 to 1 (analogue triggers and sticks)
Input.get_axis(neg, pos)-1 to 1 from two actions
Input.get_vector(l, r, up, down)Vector2 with circular deadzone, length at most 1
Input.is_key_pressed(KEY_F)raw key (debug keys only)
Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)raw mouse button
Input.get_last_mouse_velocity()pixels per second
Input.mouse_modevisible, hidden, captured, confined
Input.get_connected_joypads()device ids; joy_connection_changed signal
Input.start_joy_vibration(dev, weak, strong, dur)rumble
Input.action_press(a) / action_release(a)fake input (touch buttons, tests)
Input.parse_input_event(ev)inject a full event into the pipeline
func _physics_process(delta: float) -> void:
    var move := Input.get_vector(
        "move_left", "move_right", "move_up", "move_down")
    var turn := Input.get_axis("turn_left", "turn_right")
    if Input.is_action_just_pressed("jump"):
        jump()

get_vector argument order is negative X, positive X, negative Y, positive Y. In 2D, "up" is negative Y, so move_up goes third.

Input callbacks & order

 event arrives (key, mouse, touch, joypad, action)
   │
   ├─► _input(event)              every node that defines it
   │     reverse depth-first: deepest / last child first
   ├─► Control._gui_input(event)  the Control under the mouse
   │     or with focus; mouse_filter decides propagation
   ├─► _shortcut_input(event)     keys, shortcuts, joy buttons
   ├─► _unhandled_key_input(event) key events only
   ├─► _unhandled_input(event)    gameplay input goes here
   └─► physics picking            CollisionObject input_event
                                  (if the viewport enables it)
 any step can stop it:
   get_viewport().set_input_as_handled()
   (or accept_event() inside _gui_input)
CallbackUse for
_inputglobal hotkeys that must work over the UI, debug consoles
_gui_inputcustom Controls
_shortcut_inputmenu shortcuts (Shortcut resources)
_unhandled_inputgameplay: the UI gets first refusal, so clicking a button does not also fire a gun
_physics_process pollingcontinuous movement
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("interact"):
        interact()
        get_viewport().set_input_as_handled()
    elif event.is_action_pressed("pause", false, true):
        pass  # (allow_echo, exact_match: no extra modifiers)

Events versus polling: events never miss a press between frames and carry details (position, echo), polling reads the current state. Use events for discrete actions in menus, polling for movement.

InputEvent types

ClassKey members
InputEventKeykeycode, physical_keycode, key_label, unicode, pressed, echo, shift_pressed, ctrl_pressed
InputEventMouseButtonbutton_index (MOUSE_BUTTON_LEFT, _WHEEL_UP ...), pressed, double_click, position, factor
InputEventMouseMotionposition, relative, velocity, button_mask, pressure
InputEventScreenTouchindex (finger), pressed, position, double_tap
InputEventScreenDragindex, position, relative, velocity
InputEventJoypadButtonbutton_index (JOY_BUTTON_A ...), pressed, device
InputEventJoypadMotionaxis (JOY_AXIS_LEFT_X ...), axis_value, device
InputEventActionsynthetic action events (action, pressed, strength)
InputEventMagnifyGesture, InputEventPanGesturetrackpad pinch and scroll
InputEventMIDIMIDI devices
Shared methodNotes
event.is_action_pressed(a, allow_echo, exact_match)action test on an event
event.is_action_released(a), event.is_action(a)release and any match
event.is_pressed(), event.is_echo()state
event.as_text()"Ctrl+S", for rebinding UIs
event.devicewhich controller or keyboard

Mouse positions in events are in the viewport's coordinates; in 2D worlds with a camera use get_global_mouse_position() (on any CanvasItem) instead.

Mouse, touch & gamepad

Input.mouse_modeEffect
MOUSE_MODE_VISIBLEnormal cursor
MOUSE_MODE_HIDDENhidden, still moves freely
MOUSE_MODE_CAPTUREDhidden and locked; only relative motion is meaningful (FPS cameras)
MOUSE_MODE_CONFINED / _CONFINED_HIDDENkept inside the window (RTS edge scrolling)
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
    elif event.is_action_pressed("ui_cancel"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
TopicNotes
Web capturebrowsers only allow capture and fullscreen from inside an input event handler
Touch as mouseProject Settings > Input Devices > Pointing > Emulate Mouse From Touch (on by default)
Mouse as touchEmulate Touch From Mouse, for testing touch code on desktop
Multi-touchtrack fingers by event.index in a Dictionary
On-screen controlsTouchScreenButton (2D node, can emit an action); VirtualJoystick node (4.7+)
Gamepad mappingsSDL-based database built in; add custom mappings with Input.add_joy_mapping()
Accumulated inputInput.use_accumulated_input (default true) merges mouse motion per frame; turn off for drawing apps

Physics bodies

Node (2D / 3D)Moved byUse for
StaticBody2D / 3Dnothing (or constant_linear_velocity for conveyors)walls, floors, level geometry
AnimatableBody2D / 3Dcode or AnimationPlayer; pushes othersmoving platforms, doors, elevators
CharacterBody2D / 3Dyour code via velocity + move_and_slide()players, NPCs: exact, non-physical control
RigidBody2D / 3Dthe physics engine (forces, impulses)crates, balls, ragdolls, debris
Area2D / 3Dnot a body: detects overlaps, overrides gravity/dampingtriggers, hit/hurtboxes, pickups, water
VehicleBody3D, SoftBody3Denginecars, cloth
PhysicalBone3Dengineragdoll bones

Every body needs at least one CollisionShape2D/3D or CollisionPolygon2D/3D child. Physics code belongs in _physics_process, where delta is constant (1/60 by default).

CharacterBody

player.gd (2D side view)
extends CharacterBody2D
 
const SPEED := 300.0
const JUMP_VELOCITY := -400.0     # up is negative Y
 
func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity += get_gravity() * delta
    var jump := Input.is_action_just_pressed("jump")
    if jump and is_on_floor():
        velocity.y = JUMP_VELOCITY
    var dir := Input.get_axis("move_left", "move_right")
    if dir:
        velocity.x = dir * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
    move_and_slide()             # uses velocity and delta
MemberNotes
velocityunits per second; move_and_slide() applies delta itself (do not multiply)
move_and_slide()moves, slides along surfaces, updates floor/wall state; returns true on collision
is_on_floor(), is_on_wall(), is_on_ceiling()valid after move_and_slide() in the same tick
get_floor_normal(), get_wall_normal(), get_floor_angle()contact info
get_slide_collision_count(), get_slide_collision(i)KinematicCollision2D: get_collider(), get_normal(), get_position()
get_real_velocity(), get_platform_velocity()actual motion, moving platform speed
up_directiondefines floor vs wall vs ceiling (Vector2.UP / Vector3.UP)
motion_modeMOTION_MODE_GROUNDED (platformers) or MOTION_MODE_FLOATING (top-down, space)
floor_max_anglesteepest walkable slope (45 degrees)
floor_snap_lengthdistance to stick to the floor on slopes and steps down; 0 disables
floor_stop_on_slope, floor_constant_speedno sliding when idle; same speed up and down slopes
apply_floor_snap()snap manually, for example after teleporting
move_and_collide(motion)lower-level: move once, stop at the first hit, return a KinematicCollision2D
get_gravity()project gravity plus any Area overrides (4.3+, on all physics bodies)
# Push rigid bodies after moving
for i in get_slide_collision_count():
    var c := get_slide_collision(i)
    var rb := c.get_collider() as RigidBody2D
    if rb:
        rb.apply_central_impulse(-c.get_normal() * 20.0)

move_and_slide() does not push RigidBodies; they act as walls unless you apply impulses like above.

RigidBody

extends RigidBody2D
 
var _respawn := false
var spawn_point := Vector2.ZERO
 
func _ready() -> void:
    contact_monitor = true          # needed for body_entered
    max_contacts_reported = 4
    body_entered.connect(_on_hit)
 
func kick(dir: Vector2) -> void:
    apply_central_impulse(dir * 300.0)  # instant
    apply_torque_impulse(50.0)
 
func _physics_process(_d: float) -> void:
    apply_central_force(Vector2(0, -20)) # continuous
 
# Safe place to teleport or edit the state directly
func _integrate_forces(
        state: PhysicsDirectBodyState2D) -> void:
    if _respawn:
        state.transform = Transform2D(0, spawn_point)
        state.linear_velocity = Vector2.ZERO
        _respawn = false
MemberNotes
mass, gravity_scale, physics_material_overridefriction, bounce, rough, absorbent
linear_velocity, angular_velocityread freely; write sparingly
apply_impulse(i, offset), apply_central_impulse(i)one-off kick
apply_force(f, offset), apply_central_force(f), apply_torque(t)per tick, continuous
constant_force, constant_torquepersistent forces
lock_rotationkeep upright (2D); axis_lock_* in 3D
freeze, freeze_modeFREEZE_MODE_STATIC or FREEZE_MODE_KINEMATIC (move it from code)
sleeping, can_sleepresting bodies sleep to save CPU
continuous_cdcontinuous collision detection for fast objects
linear_damp, angular_dampdrag
custom_integratorskip built-in integration and do it in _integrate_forces

Setting position on a RigidBody every frame fights the solver. Use forces, or _integrate_forces.

Areas

coin.gd
extends Area2D
 
func _ready() -> void:
    body_entered.connect(_on_body_entered)
 
func _on_body_entered(body: Node2D) -> void:
    if body is Player:
        EventBus.coin_collected.emit(1)
        queue_free()
MemberNotes
body_entered(body), body_exited(body)physics bodies (and TileMapLayer collision)
area_entered(area), area_exited(area)other areas: hitbox vs hurtbox
monitoringthis area detects others
monitorableothers can detect this area
get_overlapping_bodies(), get_overlapping_areas(), has_overlapping_bodies()updated once per physics tick, not right after moving
gravity_space_override, gravity, gravity_direction, gravity_pointwater, planets, wind zones
linear_damp_space_overridedrag zones
priorityorder when areas overlap
audio_bus_override, audio_bus_namereroute sound inside the area

Toggle monitoring or a shape's disabled from signal handlers with set_deferred.

Collision layers & masks

AspectCollision layerCollision mask
Meaningwhat this object iswhat this object looks for
Question it answers"which layers am I on?""which layers do I collide with or detect?"
Arealets other things detect itwhat it reports in body_entered / area_entered
CharacterBodywhat others bump intowhat move_and_slide stops against
RayCast / queriesn/awhat the ray can hit (collision_mask)

A detects or is blocked by B when B's layer is in A's mask. For two RigidBodies, contact happens if either scans the other.

LayerExample nameTypical mask
1world(static; mask empty)
2playerworld, enemies, pickups
3enemiesworld, player
4pickups(Area; mask: player)
5player_hurtboxenemy_hitbox
6enemy_hitboxplayer_hurtbox
# Layers are 1-based in the editor and these helpers
set_collision_layer_value(2, true)
set_collision_mask_value(3, true)
collision_mask = 0b101          # layers 1 and 3 (bit n-1)
var hits_enemies := get_collision_mask_value(3)
 
@export_flags_2d_physics var scan_layers := 1  # picker

Name layers in Project Settings > Layer Names > 2D Physics (and 3D Physics); the inspector then shows names.

Collision shapes

2D shape3D shapeNotes
CircleShape2DSphereShape3Dcheapest
RectangleShape2DBoxShape3D
CapsuleShape2DCapsuleShape3Dbest for characters (no snagging on seams)
n/aCylinderShape3D
ConvexPolygonShape2DConvexPolygonShape3Dany convex hull; fine on moving bodies
ConcavePolygonShape2DConcavePolygonShape3Dtriangle soup, static bodies only
SegmentShape2Dn/athin line
WorldBoundaryShape2DWorldBoundaryShape3Dinfinite plane (kill floors)
SeparationRayShape2DSeparationRayShape3Dray that pushes the body up (stairs, simple suspension)
n/aHeightMapShape3Dterrain
CollisionPolygon2DCollisionPolygon3Ddraw the polygon in the editor
TipDetail
Scalingnever scale shapes non-uniformly; change the shape's size instead
Meshesselect a MeshInstance3D > Mesh menu > Create Collision Shape (trimesh, convex, or primitive in 4.6+)
One-way platformsCollisionShape2D.one_way_collision (+ one_way_collision_margin); the direction is configurable in 4.7+
DebugDebug menu > Visible Collision Shapes, or --debug-collisions
Disableshape.disabled = true via set_deferred during physics callbacks

Ray casts & queries

# RayCast2D node: updated every physics tick
@onready var ray: RayCast2D = $RayCast2D
 
func _physics_process(_d: float) -> void:
    ray.target_position = Vector2(0, 32)   # local space
    if ray.is_colliding():
        var hit := ray.get_collider()
        var at := ray.get_collision_point()    # global
        var n := ray.get_collision_normal()
    # after moving the ray in the same tick:
    ray.force_raycast_update()
# One-off query through the physics space
func _physics_process(_d: float) -> void:
    var space := get_world_2d().direct_space_state
    var q := PhysicsRayQueryParameters2D.create(
        global_position, global_position + aim * 500.0)
    q.exclude = [get_rid()]          # Array[RID]
    q.collision_mask = 0b101
    q.collide_with_areas = true
    var hit := space.intersect_ray(q)
    if hit:                          # {} when nothing hit
        print(hit.collider, hit.position, hit.normal)
QueryReturns
intersect_ray(PhysicsRayQueryParameters*)Dictionary: position, normal, collider, collider_id, rid, shape (3D also face_index)
intersect_point(PhysicsPointQueryParameters2D, max)Array of hits under a point (2D)
intersect_shape(PhysicsShapeQueryParameters*, max)overlapping objects for a shape
cast_motion(params)safe and unsafe fractions of a sweep
get_rest_info(params)closest contact for a shape
RayCast* / ShapeCast* nodespersistent, visible in the editor, cheap to check every tick

direct_space_state is only safe to use inside _physics_process (or _integrate_forces). For clicks, store the click in _unhandled_input and query on the next physics tick (see Recipes).

Physics tick & interpolation

Setting / APIDefaultNotes
physics/common/physics_ticks_per_second60Engine.physics_ticks_per_second at runtime
physics/common/max_physics_steps_per_frame8cap on catch-up ticks after a slow frame
physics/common/physics_jitter_fix0.5legacy smoothing; automatically disabled when interpolation is on
physics/common/physics_interpolationoff2D since 4.3, 3D since 4.4
Engine.get_physics_interpolation_fraction()0 to 1 between ticks, for custom interpolation
Engine.time_scale1.0slow motion; scales delta
 time ──►   frame      frame      frame      frame
 render:    |    ·     |    ·     |   ·      |   (144 Hz)
 physics:   T0         .          T1         .   (60 Hz)
 without interpolation: renders show T0, T0, T1 ... (judder)
 with interpolation:    renders blend T0→T1 by the fraction

With physics interpolation on: move bodies only in _physics_process, call reset_physics_interpolation() after teleporting a node (or it visibly slides from the old spot), and move cameras in _process or set their process callback to Physics. Per-node opt-out: physics_interpolation_mode.

Jolt physics (3D)

FactDetail
WhatJolt Physics, the engine used in AAA titles, integrated into Godot 3D
Availabilitybuilt in since 4.4 (experimental there); default for new 3D projects since 4.6
SwitchProject Settings > Physics > 3D > Physics Engine: Jolt Physics or GodotPhysics3D, then restart
Existing projectskeep their engine when upgraded; switching is a setting, not a code change
2Dunaffected: 2D always uses Godot Physics 2D
Whyfaster, more stable stacking and many bodies, better ConcavePolygonShape3D handling
Differencesseveral joint properties unsupported, margins applied differently, ray face_index off by default
Settingsunder physics/jolt_physics_3d/ (collision margin fraction, world boundary size, kinematic contacts)

Test physics-heavy 3D projects after switching: joints and edge cases (tiny shapes, huge worlds) behave differently.

extends CharacterBody2D
 
@export var speed := 120.0
@onready var agent: NavigationAgent2D = $NavigationAgent2D
 
func _ready() -> void:
    agent.velocity_computed.connect(_on_safe_velocity)
    # The navigation map syncs on the first physics frame
    await get_tree().physics_frame
    agent.target_position = Vector2(600, 300)
 
func _physics_process(_d: float) -> void:
    if agent.is_navigation_finished():
        return
    var next := agent.get_next_path_position()
    var v := global_position.direction_to(next) * speed
    if agent.avoidance_enabled:
        agent.velocity = v      # result: velocity_computed
    else:
        _on_safe_velocity(v)
 
func _on_safe_velocity(v: Vector2) -> void:
    velocity = v
    move_and_slide()
PieceRole
NavigationRegion2D / 3Dholds a NavigationPolygon / NavigationMesh; bake from child geometry
TileMapLayer navigation layersper-tile navigation polygons in the TileSet
NavigationAgent2D / 3Dpath following: target_position, get_next_path_position(), is_navigation_finished()
Agent signalsnavigation_finished, target_reached, path_changed, velocity_computed
Avoidanceavoidance_enabled, radius, max_speed; NavigationObstacle2D / 3D for dynamic blockers
NavigationLink2D / 3Djumps, ladders, teleports between regions
NavigationServer2D.map_get_path(map, from, to, true)raw path query without an agent
DebugDebug menu > Visible Navigation

Recipes

Platformer with coyote time

Use for responsive side-scroller jumping: coyote time, jump buffering, variable jump height and acceleration.

platformer.gd
extends CharacterBody2D
 
@export var speed := 220.0
@export var accel := 1800.0
@export var friction := 2200.0
@export var jump_velocity := -420.0
@export var coyote_time := 0.1
@export var buffer_time := 0.12
 
var _coyote := 0.0
var _buffer := 0.0
 
func _physics_process(delta: float) -> void:
    if is_on_floor():
        _coyote = coyote_time
    else:
        _coyote -= delta
        velocity += get_gravity() * delta
 
    if Input.is_action_just_pressed("jump"):
        _buffer = buffer_time
    else:
        _buffer -= delta
 
    if _buffer > 0.0 and _coyote > 0.0:
        velocity.y = jump_velocity
        _buffer = 0.0
        _coyote = 0.0
 
    # Short hop: cut upward speed when jump is released
    if Input.is_action_just_released("jump") \
            and velocity.y < 0.0:
        velocity.y *= 0.5
 
    var dir := Input.get_axis("move_left", "move_right")
    var target := dir * speed
    var rate := accel if dir != 0.0 else friction
    velocity.x = move_toward(
        velocity.x, target, rate * delta)
    move_and_slide()

Top-down 8-way movement

Use for twin-stick, RPG and arena games; set motion_mode to Floating so there is no floor logic.

top_down.gd
extends CharacterBody2D
 
@export var speed := 200.0
@export var smoothing := 14.0    # higher = snappier
 
func _ready() -> void:
    motion_mode = MOTION_MODE_FLOATING
 
func _physics_process(delta: float) -> void:
    var input := Input.get_vector(
        "move_left", "move_right", "move_up", "move_down")
    var target := input * speed      # diagonals capped at 1
    var w := 1.0 - exp(-smoothing * delta)
    velocity = velocity.lerp(target, w)
    move_and_slide()
    if input != Vector2.ZERO:
        $Sprite2D.flip_h = input.x < 0.0
    look_at(get_global_mouse_position())  # aim at mouse

FPS controller (3D)

Use as a first-person base: captured mouse look on a yaw body and a pitch Head node holding the Camera3D.

fps_player.gd
extends CharacterBody3D
 
@export var speed := 5.0
@export var jump_velocity := 4.5
@export var sensitivity := 0.0025
@onready var head: Node3D = $Head    # Camera3D inside
 
func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
 
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion \
            and Input.mouse_mode \
            == Input.MOUSE_MODE_CAPTURED:
        rotate_y(-event.relative.x * sensitivity)
        head.rotate_x(-event.relative.y * sensitivity)
        head.rotation.x = clampf(
            head.rotation.x, deg_to_rad(-89), deg_to_rad(89))
    elif event.is_action_pressed("ui_cancel"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
 
func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity += get_gravity() * delta
    elif Input.is_action_just_pressed("jump"):
        velocity.y = jump_velocity
    var input := Input.get_vector(
        "move_left", "move_right",
        "move_forward", "move_back")
    var dir := transform.basis * Vector3(input.x, 0, input.y)
    velocity.x = dir.x * speed
    velocity.z = dir.z * speed
    move_and_slide()

Click to select (3D)

Use for RTS selection, point-and-click and editors: project the mouse into a ray and query on the next tick.

selector.gd
extends Node3D
 
signal selected(target: Node3D)
var _click: Variant = null       # Vector2 or null
 
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed \
            and event.button_index == MOUSE_BUTTON_LEFT:
        _click = event.position
 
func _physics_process(_d: float) -> void:
    if _click == null:
        return
    var cam := get_viewport().get_camera_3d()
    var from := cam.project_ray_origin(_click)
    var to := from + cam.project_ray_normal(_click) * 1000.0
    var q := PhysicsRayQueryParameters3D.create(from, to)
    var hit := get_world_3d().direct_space_state \
        .intersect_ray(q)
    _click = null
    if hit:
        selected.emit(hit.collider)

For 2D, query intersect_point at get_global_mouse_position(), or enable input_pickable on a CollisionObject2D and handle its input_event signal.

Hitbox and hurtbox

Use for melee and projectiles: two Areas on dedicated layers so hits never collide with movement.

hurtbox.gd
class_name Hurtbox
extends Area2D
 
signal hurt(amount: int, from: Node)
 
func _ready() -> void:
    collision_layer = 0
    set_collision_layer_value(5, true)  # player_hurtbox
    collision_mask = 0
    set_collision_mask_value(6, true)   # enemy_hitbox
    area_entered.connect(_on_area_entered)
 
func _on_area_entered(area: Area2D) -> void:
    if area is Hitbox:
        hurt.emit(area.damage, area.owner)
hitbox.gd
class_name Hitbox
extends Area2D
 
@export var damage := 1

References