godot-input
Skill8 starsChanged 5 months agoMITView on GitHub
Input handling patterns for Godot 4.x: Input map configuration, action polling vs events, gamepad/virtual joystick support, input remapping UI, dead zones, and touch/multi-touch. Use when implementing player controls, controller support, or input customization in Godot.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- MindsMaster/godot-claude-code-skillstheir own version, first seen Oct 25, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- MindsMaster/godot-claude-code-skills★ 3same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- godot-47-migrationSkill
- godot-animationSkill
- godot-architectureSkill
- godot-audioSkill
- godot-brainstormingSkill
- godot-code-reviewSkill
- godot-csharp-patternsSkill
- godot-debuggingSkill
- godot-dialog-systemsSkill
- godot-gdscript-patternsSkill
- godot-headless-workflowSkill
- godot-i18nSkill
- godot-inventory-economySkill
- godot-networkingSkill
- godot-performanceSkill
- godot-physicsSkill
- godot-project-setupSkill
- godot-save-systemsSkill
- godot-shaders-vfxSkill
- godot-state-managementSkill
- godot-uiSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 364
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Input Handling in Godot
Patterns for robust, accessible input systems.
Input Map Configuration
Define actions in Project Settings → Input Map or via code:
# Register actions at runtime (or use editor)
InputMap.add_action("move_left")
InputMap.action_add_event("move_left", InputEventKey.new())
var left_event = InputEventKey.new()
left_event.keycode = KEY_A
InputMap.action_add_event("move_left", left_event)
# Add gamepad button
var dpad_event = InputEventJoypadButton.new()
dpad_event.button_index = JOY_BUTTON_DPAD_LEFT
dpad_event.pressed = true
InputMap.action_add_event("move_left", dpad_event)Recommended Action Naming
move_left, move_right, move_up, move_down
jump, crouch, interact, attack
inventory, pause, menu_confirm, menu_cancelPolling vs Events
Two approaches to reading input:
Polling (Continuous — _physics_process)
For movement and analog input:
class_name PlayerController extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(delta: float) -> void:
# Input.get_action_strength returns 0.0 to 1.0
# Automatically combines keyboard + gamepad inputs
var input_x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
var input_y = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
var direction = Vector2(input_x, input_y).normalized()
velocity = direction * speed
move_and_slide()Events (Discrete — _unhandled_input)
For camera rotation, menu navigation, one-shot actions:
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("interact"):
interact_with_target()
elif event.is_action_pressed("pause"):
toggle_pause()
# Use _unhandled_input so UI can consume input first
# Use _input if this node should always receive inputWhen to Use Each
| Approach | Use For | Method |
| Polling | Movement, analog sticks, continuous actions | _physics_process / _process |
| Events | Camera rotation, one-shot actions, menu nav | _unhandled_input |
| Both | Games with movement + discrete actions | Combined |
Gamepad Support
Detecting Connected Controllers
func _ready() -> void:
var connected = Input.get_connected_joypads()
if connected.size() > 0:
print("Gamepad connected: ", connected[0])
# Enable rumble, vibrate, etc.Rumble / Haptic Feedback
# Simple rumble on action
func play_rumble(strength: float = 0.5, duration: float = 0.1) -> void:
Input.start_joy_vibration(0, strength, strength, duration)
# Dual-motor (separate weak/strong rumble)
Input.start_joy_vibration(0, 0.3, 0.8, 0.2) # weak, strong, durationDead Zone Configuration
Prevent drift from analog sticks:
# Set global dead zone (Project Settings → Input Devices → Keyboard/Mouse → Joystick Dead Zone)
# Or per-action in code:
Input.set_deadzone_value(0.15) # Default is typically 0.15-0.25
# Custom dead zone for specific input reading
func get_analog_input(action_name: String, dead_zone: float = 0.15) -> float:
var value = Input.get_action_strength(action_name)
if value < dead_zone:
return 0.0
# Remap from [dead_zone, 1.0] to [0.0, 1.0]
return (value - dead_zone) / (1.0 - dead_zone)Input Remapping UI
Let players customize controls:
class_name InputRemapButton extends Button
var current_action: String = ""
var waiting_for_input: bool = false
func _ready():
pressed.connect(_on_button_pressed)
func set_action(action: String) -> void:
current_action = action
update_display()
func _on_button_pressed() -> void:
if waiting_for_input:
return
waiting_for_input = true
text = "Press a key..."
# Grab focus so input events come here
grab_focus()
func _input(event: InputEvent) -> void:
if not waiting_for_input:
return
if event is InputEventKey or event is InputEventJoypadButton:
# Clear old bindings for this action
var old_events = InputMap.action_get_events(current_action)
for old_event in old_events:
InputMap.action_remove_event(current_action, old_event)
# Add new binding
InputMap.action_add_event(current_action, event.duplicate())
waiting_for_input = false
update_display()
get_viewport().set_input_as_handled() # Prevent game from receiving it
func update_display() -> void:
if not waiting_for_input:
var events = InputMap.action_get_events(current_action)
if events.size() > 0:
text = _event_to_string(events[0])
else:
text = "Not assigned"
func _event_to_string(event: InputEvent) -> String:
if event is InputEventKey:
return OS.get_keycode_string(event.keycode)
elif event is InputEventJoypadButton:
return "Gamepad Button %d" % event.button_index
return "Unknown"Saving Remapped Controls
class_name InputSettings extends Node
const SETTINGS_PATH := "user://input_settings.cfg"
func save_bindings() -> void:
var config = ConfigFile.new()
var actions = InputMap.get_actions()
for action in actions:
var events = InputMap.action_get_events(action)
var event_strings = []
for event in events:
event_strings.append(_event_to_string(event))
config.set_value("input", action, ",".join(event_strings))
config.save(SETTINGS_PATH)
func load_bindings() -> Error:
var config = ConfigFile.new()
if config.load(SETTINGS_PATH) != OK:
return Error.ERR_FILE_NOT_FOUND
var actions = InputMap.get_actions()
for action in actions:
var event_strings = config.get_value("input", action, "") as String
if event_strings.is_empty():
continue
# Clear and re-add
var old_events = InputMap.action_get_events(action)
for old_event in old_events:
InputMap.action_remove_event(action, old_event)
for str in event_strings.split(","):
var event = _string_to_event(str)
if event:
InputMap.action_add_event(action, event)
return Error.OK
func _event_to_string(event: InputEvent) -> String:
if event is InputEventKey:
return "key:%d" % event.keycode
elif event is InputEventJoypadButton:
return "joypad:%d" % event.button_index
return ""
func _string_to_event(str: String) -> InputEvent:
var parts = str.split(":")
if parts[0] == "key":
var event = InputEventKey.new()
event.keycode = int(parts[1])
return event
elif parts[0] == "joypad":
var event = InputEventJoypadButton.new()
event.button_index = int(parts[1])
event.pressed = true
return event
return nullTouch and Multi-Touch
Virtual Joystick (Godot 4.7+)
# Godot 4.7 includes VirtualJoystick control node
# Place in scene, configure via inspector:
# - Base texture, knob texture
# - Dead zone value
# - Action names for directional output
# Read virtual joystick input:
func _physics_process(delta: float) -> void:
var joystick = $VirtualJoystick as VirtualJoystick
if joystick and joystick.is_active():
var direction = joystick.get_vector() # Vector2 (-1 to 1)
velocity = direction * speedMulti-Touch Gestures
class_name TouchHandler extends Node2D
var _touch_points: Dictionary = {} # index -> Vector2
func _input(event: InputEvent) -> void:
if event is InputEventScreenDrag:
_handle_drag(event)
elif event is InputEventScreenTouch:
if event.pressed:
_touch_points[event.index] = event.position
else:
_touch_points.erase(event.index)
func _handle_drag(event: InputEventScreenDrag) -> void:
if _touch_points.has(event.index):
var delta = event.position - _touch_points[event.index]
_touch_points[event.index] = event.position
# Use delta for movement, camera pan, etc.
# Pinch-to-zoom with two fingers
func get_pinch_scale() -> float:
var indices = _touch_points.keys()
if indices.size() < 2:
return 1.0
var idx_a = indices[0]
var idx_b = indices[1]
var current_dist = _touch_points[idx_a].distance_to(_touch_points[idx_b])
# Compare to previous frame distance for zoom delta
return current_dist / maxf(1.0, _get_previous_distance())
var _previous_distance: float = 0.0
func _get_previous_distance() -> float:
return _previous_distanceAccessibility Patterns
Hold vs Toggle Actions
# Support both hold and toggle for accessibility
@export var use_toggle_jump: bool = false
var jump_toggled: bool = false
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("jump"):
if use_toggle_jump:
jump_toggled = !jump_toggled
# Movement code checks either:
# Input.is_action_pressed("jump") OR jump_toggledSensitivity Slider
# For camera or aim sensitivity
@export var sensitivity: float = 1.0
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
camera.rotate_y(-event.relative.x * sensitivity * 0.002)
camera.rotate_x(-event.relative.y * sensitivity * 0.002)Godot 4.7 Input Notes
- VirtualJoystick — New built-in control node for touch-based analog input
- InputActionEvent — Enhanced event structure for complex action queries
- Gamepad — Improved auto-detection and button mapping consistency
MCP Bridge Tools (Optional — Live Editor Integration)
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
project_info— Check configured input actions and settingsscript_read/script_edit— Read and modify input scripts
Note: MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
References
- Input Map Best Practices
- Gamepad Support Guide
- Touch Input Patterns
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

