Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. GDScript
  4. GDScript Input Handling Cheat Sheet
GDScriptCheat Sheet

GDScript Input Handling Cheat Sheet

Quick-reference for Godot 4 input handling. Each section includes copy-ready snippets with inline output comments for player movement, shooting, and UI controls.

On this page
  1. 1Action-Based Input
  2. 2Axis and Vector Input
  3. 3_input vs _unhandled_input
  4. 4InputEvent Types
  5. 5Mouse Input
  6. 6Custom Mouse Cursor
  7. 7Gamepad and Controller
  8. 8Input Action Mapping in Code
  9. 9Input Best Practices
Action-Based InputAxis and Vector Input_input vs _unhandled_inputInputEvent TypesMouse InputCustom Mouse CursorGamepad and ControllerInput Action Mapping in CodeInput Best Practices

Action-Based Input

Define input actions in Project Settings > Input Map. Use action names instead of raw keycodes for remappable controls.

is_action_pressed — held down check
func _physics_process(delta: float) -> void:
    if Input.is_action_pressed("move_right"):
        velocity.x = speed
    if Input.is_action_pressed("sprint"):
        velocity.x *= 2.0
is_action_just_pressed — single tap
func _physics_process(delta: float) -> void:
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_force

    if Input.is_action_just_pressed("attack"):
        swing_sword()
is_action_just_released — on key up
func _physics_process(delta: float) -> void:
    if Input.is_action_just_released("charge_attack"):
        release_charged_attack()

Axis and Vector Input

get_axis() returns a float from -1 to 1. get_vector() returns a Vector2 — perfect for 2D movement.

get_axis() for one dimension
# Returns -1, 0, or 1 (or analog value)
var horizontal := Input.get_axis("move_left", "move_right")
# -1 = left, 0 = idle, 1 = right
velocity.x = horizontal * speed
get_vector() for 2D movement
# Returns a Vector2 — already handles diagonal normalization
var input := Input.get_vector(
    "move_left", "move_right",
    "move_up", "move_down"
)
velocity = input * speed
move_and_slide()

get_vector() auto-normalizes diagonal input. No need to call .normalized() yourself.

Analog stick (controller)
# get_vector works with analog sticks too
var input := Input.get_vector(
    "move_left", "move_right",
    "move_up", "move_down"
)
# input.length() ranges 0.0 to 1.0 with analog sticks
if input.length() > 0.2:  # deadzone
    velocity = input * speed

_input vs _unhandled_input

Godot routes input events through the tree. _input catches everything; _unhandled_input only gets events that UI nodes did not consume.

_unhandled_input — game controls
# Use for gameplay actions (movement, shooting, etc.)
# UI elements like Button consume events first
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("interact"):
        interact_with_nearest()
_input — UI-level input
# Use for global shortcuts that should work even over UI
func _input(event: InputEvent) -> void:
    if event.is_action_pressed("pause"):
        toggle_pause()
        get_viewport().set_input_as_handled()
Consume events to stop propagation
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("interact"):
        open_dialog()
        # Prevent other nodes from also handling this event
        get_viewport().set_input_as_handled()

Call set_input_as_handled() to stop the event from propagating to other nodes.

InputEvent Types

Every input arrives as an InputEvent subclass. Check the type to handle specific devices.

Common event types
func _input(event: InputEvent) -> void:
    if event is InputEventKey:
        print("Keyboard: ", event.keycode)
    elif event is InputEventMouseButton:
        print("Mouse button: ", event.button_index)
    elif event is InputEventMouseMotion:
        print("Mouse moved: ", event.relative)
    elif event is InputEventJoypadButton:
        print("Gamepad button: ", event.button_index)
    elif event is InputEventJoypadMotion:
        print("Gamepad axis: ", event.axis, " value: ", event.axis_value)
Check for specific key
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventKey and event.pressed:
        match event.keycode:
            KEY_ESCAPE:
                toggle_pause()
            KEY_F11:
                toggle_fullscreen()

Mouse Input

Handle mouse position, clicks, scroll wheel, and cursor visibility.

Get mouse position
# Screen coordinates
var screen_pos := get_viewport().get_mouse_position()

# World coordinates (2D)
var world_pos := get_global_mouse_position()

# Aim at mouse
look_at(get_global_mouse_position())
Mouse button events
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
            shoot()
        elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
            use_ability()
        elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
            zoom_in()
Mouse motion for camera or aiming
func _input(event: InputEvent) -> void:
    if event is InputEventMouseMotion:
        # FPS camera look
        rotate_y(-event.relative.x * mouse_sensitivity)
        $Camera3D.rotate_x(-event.relative.y * mouse_sensitivity)

Custom Mouse Cursor

Replace the default mouse cursor with a game-specific crosshair or pointer.

Set a custom cursor image
func _ready() -> void:
    var crosshair := load("res://assets/ui/crosshair.png")
    Input.set_custom_mouse_cursor(crosshair, Input.CURSOR_ARROW, Vector2(16, 16))
    # Vector2(16, 16) = hotspot (center of 32x32 cursor)
Hide and capture mouse (FPS games)
func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
    # Mouse is hidden and locked to center

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("pause"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
Confine mouse to window
# Mouse visible but cannot leave the game window
Input.mouse_mode = Input.MOUSE_MODE_CONFINED

MOUSE_MODE_CAPTURED is for FPS. MOUSE_MODE_CONFINED keeps cursor visible but trapped.

Gamepad and Controller

Godot automatically maps standard controllers. Use the same action names for keyboard and gamepad.

Action map works for both keyboard and gamepad
# In Project Settings > Input Map:
# "jump" -> Space key + Joypad Button 0 (A/Cross)
# "attack" -> Left Click + Joypad Button 2 (X/Square)

# Code is the same regardless of device:
if Input.is_action_just_pressed("jump"):
    jump()
Detect which device triggered input
func _input(event: InputEvent) -> void:
    if event is InputEventJoypadButton or event is InputEventJoypadMotion:
        show_gamepad_prompts()
    elif event is InputEventKey or event is InputEventMouseButton:
        show_keyboard_prompts()
Gamepad vibration (haptic feedback)
# device_id is usually 0 for first controller
Input.start_joy_vibration(0, 0.5, 0.5, 0.3)
# (device, weak_magnitude, strong_magnitude, duration)

# Stop vibration
Input.stop_joy_vibration(0)

Input Action Mapping in Code

Add or modify input actions at runtime for rebindable controls.

Add an action in code
func _ready() -> void:
    if not InputMap.has_action("screenshot"):
        InputMap.add_action("screenshot")
        var event := InputEventKey.new()
        event.keycode = KEY_F12
        InputMap.action_add_event("screenshot", event)
Rebind an action at runtime
func rebind_action(action_name: String, new_event: InputEvent) -> void:
    # Clear existing bindings
    InputMap.action_erase_events(action_name)
    # Add new binding
    InputMap.action_add_event(action_name, new_event)
Get current bindings for display
func get_action_key_name(action: String) -> String:
    var events := InputMap.action_get_events(action)
    if events.size() > 0 and events[0] is InputEventKey:
        return OS.get_keycode_string(events[0].keycode)
    return "Unbound"

Input Best Practices

Patterns that avoid common input bugs in Godot 4.

Physics input in _physics_process
# Movement and physics-affecting input:
func _physics_process(delta: float) -> void:
    var input := Input.get_vector("left", "right", "up", "down")
    velocity = input * speed
    move_and_slide()

# One-shot actions can go in _unhandled_input:
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("interact"):
        interact()

Continuous input (movement) goes in _physics_process. One-shot actions (interact, pause) go in _unhandled_input.

Disable input during cutscenes
var input_enabled := true

func _physics_process(delta: float) -> void:
    if not input_enabled:
        return
    # Normal input handling...

func start_cutscene() -> void:
    input_enabled = false

func end_cutscene() -> void:
    input_enabled = true
Input buffer for responsive jumping
var jump_buffer_timer := 0.0
const JUMP_BUFFER := 0.1  # 100ms buffer

func _physics_process(delta: float) -> void:
    if Input.is_action_just_pressed("jump"):
        jump_buffer_timer = JUMP_BUFFER

    jump_buffer_timer -= delta

    if jump_buffer_timer > 0.0 and is_on_floor():
        velocity.y = jump_force
        jump_buffer_timer = 0.0

Input buffering lets the player press jump slightly before landing and still get the jump.

Learn GDScript in Depth
GDScript Foundations Practice →GDScript Vector Math Practice →
Warm-up1 / 2

Can you write this from memory?

Define the lifecycle callback that runs at a fixed rate for physics.

See Also
Signals →Vectors →

Start Practicing GDScript

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.