xiaohongshu
xiaohongshu
godot-modernize-input
Use when handling input in Godot 4.x projects and need to modernize patterns. Generates Input Map configurations from hardcoded key checks, sets up joypad rumble (Godot 4.x feature), implements gyroscope/accelerometer input on mobile, creates context-sensitive input handling for different game st...
Full skill instructions
Input handling should be device-agnostic, responsive, and context-aware. Hardcoded key checks, missing device support, and unbuffered input create poor player experiences across platforms.
Converts hardcoded key detection to Input Map actions:
Before:
# Hardcoded key checks - NOT recommended
func _input(event):
if event is InputEventKey:
if event.keycode == KEY_SPACE:
jump()
if event.keycode == KEY_ESCAPE:
pause_game()
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
shoot()
After:
# Modern Input Map usage
func _input(event: InputEvent) -> void:
if event.is_action_pressed("jump"):
jump()
if event.is_action_pressed("pause"):
pause_game()
if event.is_action_pressed("shoot"):
shoot()
Generated Input Map (project.godot):
[input]
jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
]
}
shoot={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
]
}
Implements haptic feedback for controllers:
# InputRumbler.gd - Autoload singleton
extends Node
const WEAK_RUMBLE: float = 0.3
const MEDIUM_RUMBLE: float = 0.6
const STRONG_RUMBLE: float = 1.0
const SHORT_DURATION: float = 0.1
const MEDIUM_DURATION: float = 0.3
const LONG_DURATION: float = 0.8
var current_device: int = 0
func _ready() -> void:
Input.joy_connection_changed.connect(_on_joy_connection_changed)
func _on_joy_connection_changed(device: int, connected: bool) -> void:
if connected:
current_device = device
print("Controller connected: ", Input.get_joy_name(device))
func rumble(weak_magnitude: float, strong_magnitude: float, duration: float) -> void:
if Input.get_joy_name(current_device).is_empty():
return
Input.start_joy_vibration(current_device, weak_magnitude, strong_magnitude, duration)
func rumble_impact() -> void:
rumble(WEAK_RUMBLE, STRONG_RUMBLE, SHORT_DURATION)
func rumble_damage() -> void:
rumble(MEDIUM_RUMBLE, MEDIUM_RUMBLE, MEDIUM_DURATION)
func rumble_explosion() -> void:
rumble(STRONG_RUMBLE, STRONG_RUMBLE, LONG_DURATION)
func rumble_heartbeat() -> void:
rumble(WEAK_RUMBLE, 0.0, 0.5)
await get_tree().create_timer(0.6).timeout
rumble(WEAK_RUMBLE, 0.0, 0.5)
Implements motion controls for mobile devices:
# MotionController.gd
extends Node
@export var tilt_sensitivity: float = 2.0
@export var shake_threshold: float = 15.0
signal tilt_detected(direction: Vector2)
signal shake_detected
var accelerometer_enabled: bool = false
var gyroscope_enabled: bool = false
func _ready() -> void:
# Check if running on mobile
if OS.has_feature("mobile") or OS.has_feature("android") or OS.has_feature("ios"):
enable_motion_sensors()
func enable_motion_sensors() -> void:
if DisplayServer.has_feature(DisplayServer.FEATURE_TOUCHSCREEN):
# Enable accelerometer (gravity + user acceleration)
if DisplayServer.has_feature(DisplayServer.FEATURE_SENSOR_ACCELEROMETER):
DisplayServer.accelerometer_set_mode(DisplayServer.ACCELEROMETER_MODE_COMBINED)
accelerometer_enabled = true
# Enable gyroscope for rotation rate
if DisplayServer.has_feature(DisplayServer.FEATURE_SENSOR_GYROSCOPE):
gyroscope_enabled = true
func _process(delta: float) -> void:
if not (accelerometer_enabled or gyroscope_enabled):
return
# Handle tilt-based movement
if accelerometer_enabled:
var accel: Vector3 = Input.get_accelerometer()
var tilt: Vector2 = Vector2(accel.x, -accel.y) * tilt_sensitivity
if tilt.length() > 0.1:
tilt_detected.emit(tilt)
# Handle shake detection
if gyroscope_enabled:
var gyro: Vector3 = Input.get_gyroscope()
if gyro.length() > shake_threshold:
shake_detected.emit()
func get_tilt_direction() -> Vector2:
if not accelerometer_enabled:
return Vector2.ZERO
var accel: Vector3 = Input.get_accelerometer()
# Normalize for device orientation
return Vector2(accel.x, -accel.y).normalized()
func calibrate_center() -> void:
# Store current orientation as neutral position
var current_accel: Vector3 = Input.get_accelerometer()
# Implementation: Store offset and apply to future readings
Manages different input contexts (gameplay, menu, dialogue, etc.):
# InputContextManager.gd - Autoload singleton
extends Node
enum Context {
GAMEPLAY,
MENU,
DIALOGUE,
PAUSED,
INVENTORY
}
var current_context: Context = Context.GAMEPLAY
var context_stack: Array[Context] = []
# Context-specific action mappings
var context_actions: Dictionary = {
Context.GAMEPLAY: ["move_left", "move_right", "jump", "shoot", "interact"],
Context.MENU: ["ui_up", "ui_down", "ui_accept", "ui_cancel", "ui_left", "ui_right"],
Context.DIALOGUE: ["ui_accept", "ui_cancel", "skip_dialogue"],
Context.PAUSED: ["ui_accept", "ui_cancel", "resume"],
Context.INVENTORY: ["inventory_navigate", "inventory_use", "inventory_close"]
}
func set_context(new_context: Context) -> void:
context_stack.push_back(current_context)
current_context = new_context
_update_input_processing()
print("Input context changed to: ", Context.keys()[new_context])
func restore_previous_context() -> void:
if context_stack.size() > 0:
current_context = context_stack.pop_back()
_update_input_processing()
func reset_to_gameplay() -> void:
context_stack.clear()
current_context = Context.GAMEPLAY
_update_input_processing()
func _update_input_processing() -> void:
# Enable/disable action processing based on context
var enabled_actions: Array = context_actions.get(current_context, [])
# This works with built-in UI actions too
match current_context:
Context.GAMEPLAY:
get_tree().paused = false
Context.PAUSED:
get_tree().paused = true
Context.MENU, Context.INVENTORY:
get_viewport().set_input_as_handled()
func is_action_allowed(action: StringName) -> bool:
var enabled_actions: Array = context_actions.get(current_context, [])
return action in enabled_actions or action.begins_with("ui_")
# Example usage in player controller
func _input(event: InputEvent) -> void:
if not InputContextManager.is_action_allowed("jump"):
return
if event.is_action_pressed("jump"):
jump()
Adds input buffering for responsive gameplay:
# InputBuffer.gd - Component for responsive input
extends Node
@export var buffer_duration: float = 0.15 # 150ms buffer window
var buffered_actions: Dictionary = {}
func _ready() -> void:
# Initialize buffer for common actions
buffered_actions = {
"jump": 0.0,
"shoot": 0.0,
"dash": 0.0,
"interact": 0.0
}
func _process(delta: float) -> void:
# Decay buffer timers
for action in buffered_actions.keys():
if buffered_actions[action] > 0:
buffered_actions[action] -= delta
func _input(event: InputEvent) -> void:
for action in buffered_actions.keys():
if event.is_action_pressed(action):
buffer_action(action)
func buffer_action(action: StringName) -> void:
if action in buffered_actions:
buffered_actions[action] = buffer_duration
func is_buffered(action: StringName) -> bool:
return action in buffered_actions and buffered_actions[action] > 0
func consume_buffer(action: StringName) -> bool:
if is_buffered(action):
buffered_actions[action] = 0.0
return true
return false
# Example usage in player controller
func _physics_process(delta: float) -> void:
# Check buffered jump (allows jump before hitting ground)
if input_buffer.is_buffered("jump") and is_on_floor():
input_buffer.consume_buffer("jump")
velocity.y = jump_velocity
Scans for:
InputEventKey with specific keycode valuesInputEventMouseButton with specific button_index valuesInput.is_key_pressed() callsOld Godot 3.x Pattern:
if event is InputEventKey and event.scancode == KEY_SPACE:
jump()
Modern Godot 4.x:
if event.is_action_pressed("jump"):
jump()
| Input Type | Desktop | Mobile | Console | Implementation |
|---|---|---|---|---|
| Keyboard | ✅ | ❌ | ❌ | Input Map |
| Mouse | ✅ | ⚠️ Touch | ❌ | Input Map + Touch emulation |
| Touch | ❌ | ✅ | ❌ | Touch events + Virtual joystick |
| Gamepad | ✅ | ✅ (MFi) | ✅ | Input Map with deadzone |
| Gyroscope | ❌ | ✅ | ❌ (PS only) | MotionController singleton |
| Rumble | ✅ | ❌ | ✅ | InputRumbler singleton |
Your game only supports keyboard/mouse and needs gamepad compatibility.
Need to add touch controls, motion sensors, and adapt input for mobile devices.
Players complain about missed inputs; need buffering for precise timing.
Menu navigation interferes with gameplay; need context-sensitive handling.
Never hardcode key checks in gameplay code. Use actions for:
# In Input Map configuration (project.godot)
move_left={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
func _input(event: InputEvent) -> void:
# Handle keyboard/controller
if event.is_action_pressed("jump"):
jump()
# Handle touch (mobile)
if event is InputEventScreenTouch and event.pressed:
if _is_touch_in_jump_zone(event.position):
jump()
# One-shot actions (button presses)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("shoot"):
shoot()
# Continuous actions (held buttons)
func _process(delta: float) -> void:
var move_direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity.x = move_direction.x * speed
Works with:
Don't modernize if:
# MobileInputHandler.gd
extends Node
@onready var virtual_joystick: VirtualJoystick = $VirtualJoystick
@onready var touch_buttons: Control = $TouchButtons
func _ready() -> void:
# Only show touch UI on mobile
var is_mobile: bool = OS.has_feature("mobile") or OS.has_feature("android") or OS.has_feature("ios")
virtual_joystick.visible = is_mobile
touch_buttons.visible = is_mobile
func get_movement_input() -> Vector2:
# Combine gamepad and touch input
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
if virtual_joystick.visible:
input += virtual_joystick.output
return input.normalized()
# RemapButton.gd
extends Button
@export var action: StringName
func _ready() -> void:
pressed.connect(_on_pressed)
update_display()
func _on_pressed() -> void:
text = "Press any key..."
set_process_input(true)
func _input(event: InputEvent) -> void:
if event.is_pressed() and not event.is_echo():
# Clear existing events for this action
InputMap.action_erase_events(action)
InputMap.action_add_event(action, event)
set_process_input(false)
update_display()
func update_display() -> void:
var events: Array[InputEvent] = InputMap.action_get_events(action)
if events.size() > 0:
text = events[0].as_text()
else:
text = "Not assigned"
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience