关于 input-handling
面向 Godot 4.3+ 游戏项目的输入处理技能,提供 GDScript 和 C# 示例。内容涵盖事件传递与轮询,以及鼠标、控制器、触控和运行时重绑定。
主要功能
- 01输入事件与轮询
- 02Input Map 操作设计
- 03鼠标与游戏手柄输入
- 04触控与多点触控
- 05运行时操作重绑定
- 06输入传递与暂停处理
适用场景
- 01为游戏与 UI 输入选择回调
- 02添加键盘、鼠标和游戏手柄控制
- 03实现触控输入并在桌面端测试
- 04保存自定义按键绑定
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill input-handling安装后在 Claude Code 中输入/input-handling确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/action-rebinding.mdevent-propagation.mdgamepad.mdinput-buffering.mdmouse.mdtouch.md详细介绍
Godot 4.3+ 中的输入处理
所有示例均适用于 Godot 4.3+,不使用已弃用的 API。先展示 GDScript,再展示 C#。
相关技能: player-controller 用于由输入驱动的移动,godot-ui 用于 UI 输入焦点和导航,save-load 用于持久化自定义按键绑定,responsive-ui 用于触屏与桌面输入适配,xr-development 用于 XR 控制器和手部追踪输入,mobile-development 用于移动设备传感器和应用生命周期。
1. 核心概念
输入流程
Hardware Event (key, mouse, gamepad)
↓
Engine converts to InputEvent
↓
_input() ← raw input, runs first
↓
UI Control nodes ← _gui_input: buttons, sliders consume events
↓
_shortcut_input() ← for global shortcuts
↓
_unhandled_key_input() ← unhandled key-only events
↓
_unhandled_input() ← game input (movement, actions)
在哪里处理输入
| 方法 | 用途 | 运行时机 |
|---|---|---|
_input() | 摄像机视角、全局热键 | 最先——早于其他所有处理 |
_shortcut_input() | 全局快捷键(暂停、截图) | UI 处理后、未处理输入前 |
_unhandled_key_input() | UI 未消费的按键事件 | UI 处理后,仅处理按键 |
_unhandled_input() | 游戏操作(跳跃、攻击、交互) | 最后——UI 消费输入后 |
Input.is_action_pressed() in _physics_process() | 连续移动 | 不适用——轮询,而非事件驱动 |
经验法则: 使用 _unhandled_input() 处理离散游戏操作(跳跃、攻击)。在 _physics_process() 中使用 Input 轮询处理连续移动。仅当需要在 UI 消费输入前处理输入时(例如鼠标视角控制),才使用 _input()。
InputEvent 层级结构
InputEvent
├── InputEventFromWindow
│ ├── InputEventWithModifiers
│ │ ├── InputEventKey ← keyboard
│ │ ├── InputEventMouse
│ │ │ ├── InputEventMouseButton ← mouse clicks
│ │ │ └── InputEventMouseMotion ← mouse movement
│ │ └── InputEventGesture ← Magnify / Pan gestures
│ ├── InputEventScreenTouch ← touchscreen tap
│ └── InputEventScreenDrag ← touchscreen drag
├── InputEventJoypadButton ← gamepad buttons
├── InputEventJoypadMotion ← gamepad sticks/triggers
├── InputEventAction ← synthetic action events
├── InputEventMIDI ← MIDI devices
└── InputEventShortcut
2. 输入映射设置
在 项目 > 项目设置 > 输入映射 中定义操作,而不是检查原始键码。这样可以将游戏逻辑与特定按键解耦,并支持重新绑定。
项目默认操作
Godot 内置了 ui_* 操作:ui_accept、ui_cancel、ui_left、ui_right、ui_up、ui_down 等。这些操作由 UI 控件用于键盘导航。你可以将它们用于游戏操作,但更推荐创建自定义操作,以避免冲突。
在代码中添加操作
可以在运行时使用 InputMap.add_action() + InputMap.action_add_event() 创建操作——通常在自动加载脚本的 _ready() 中执行,并通过 InputMap.has_action() 进行检查。在编辑器的输入映射中定义操作;仅在需要动态生成绑定或支持模组时,才在代码中添加操作。
请参阅 references/action-rebinding.md 中的 GDScript 和 C# 代码片段。
推荐的操作名称
使用具有描述性且针对具体游戏的名称,而不要使用按键名称:
| 好的示例 | 不好的示例 | 原因 |
|---|---|---|
move_left | press_a | 与物理按键解耦 |
attack | left_click | 适用于鼠标和游戏手柄 |
interact | press_e | 重新绑定时无需更改逻辑 |
sprint | hold_shift | 不依赖具体输入设备 |
pause | press_escape | 也可以映射到游戏手柄的 Start 按钮 |
3. 读取输入——事件与轮询
事件驱动(离散操作)
使用 _unhandled_input() 处理一次性操作:跳跃、攻击、交互、暂停。
GDScript
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("jump"):
_jump()
get_viewport().set_input_as_handled() # prevent further propagation
if event.is_action_pressed("interact"):
_interact()
if event.is_action_pressed("pause"):
get_tree().paused = not get_tree().paused
get_viewport().set_input_as_handled()
C#
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("jump"))
{
Jump();
GetViewport().SetInputAsHandled();
}
if (@event.IsActionPressed("interact"))
Interact();
if (@event.IsActionPressed("pause"))
{
GetTree().Paused = !GetTree().Paused;
GetViewport().SetInputAsHandled();
}
}
轮询(连续输入)
在 _physics_process() 中使用 Input 单例处理按住的按钮和模拟轴。
GDScript
func _physics_process(delta: float) -> void:
# Movement vector from 4 directional actions
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = direction * speed
# Check if a button is held
if Input.is_action_pressed("sprint"):
velocity *= 1.5
move_and_slide()
C#
public override void _PhysicsProcess(double delta)
{
Vector2 direction = Input.GetVector("move_left", "move_right", "move_up", "move_down");
Velocity = direction * Speed;
if (Input.IsActionPressed("sprint"))
Velocity *= 1.5f;
MoveAndSlide();
}
主要输入方法
| 方法 | 返回值 | 用途 |
|---|---|---|
Input.is_action_pressed() | bool | 持续按住的按钮(冲刺、蹲下、开火) |
Input.is_action_just_pressed() | bool | 单次触发(跳跃、互动) |
Input.is_action_just_released() | bool | 松开时触发(可变跳跃截断) |
Input.get_action_strength() | float | 模拟压力(0.0–1.0) |
Input.get_axis() | float | 单轴(-1.0 到 1.0) |
Input.get_vector() | Vector2 | 2D 方向,长度最大为 1 |
event.is_action_pressed() | bool | 在 _unhandled_input 回调中检查 |
event.is_action_released() | bool | 在 _unhandled_input 回调中检查 |
在
_physics_process()中调用Input.is_action_just_pressed(),会在下一个物理帧报告按下事件,即使按键已经松开也一样——较低的帧率不会漏掉轻点,但同一帧内的多次按下只计一次。如果每次按下都很重要,请在_unhandled_input()中捕获事件并设置标志。
输入缓冲
缓冲离散操作,这样在操作生效前不久按下的按键(例如落地前刚按下跳跃)仍会生效:在 _unhandled_input() 中捕获操作,设置一个带短计时器的标志(通常为 0.1 s),并在 _physics_process() 中使用该标志。
完整的 GDScript 和 C# 跳跃缓冲实现,请参阅 references/input-buffering.md。
4. 鼠标输入
使用 InputEventMouseMotion.relative 控制摄像机视角(配合 Input.MOUSE_MODE_CAPTURED),使用 InputEventMouseButton 处理点击。鼠标模式:VISIBLE、HIDDEN、CAPTURED、CONFINED。使用 Input.set_custom_mouse_cursor(texture, shape, hotspot) 设置自定义光标。
完整的 GDScript 和 C# 操作示例(摄像机视角控制,包含灵敏度和反转切换、鼠标模式切换、按钮事件、带形状变体的自定义光标),请参阅 references/mouse.md。
5. 控制器 / 游戏手柄支持
使用 Input.get_connected_joypads() 在运行时检测设备,使用 Input.joy_connection_changed 信号处理热插拔。使用带有游戏手柄按钮事件的 Input Map 操作以确保可移植性。模拟摇杆:Input.get_vector("left", "right", "up", "down", deadzone) 返回经过长度限制且内置死区的 Vector2。
GDScript 和 C# 操作示例(控制器检测、死区模拟输入读取、通过
start_joy_vibration震动、运动传感器、检测最后使用的输入设备以切换 UI 提示),请参阅 references/gamepad.md。
**Godot 4.7+:**游戏手柄运动传感器——
Input.get_joy_accelerometer(device)/get_joy_gyroscope(device)(均返回Vector3),需通过has_joy_motion_sensors()检查,并使用set_joy_motion_sensors_enabled()启用;操作示例见 references/gamepad.md。现在可以查询震动状态——使用Input.has_joy_vibration(device)以及get_joy_vibration_strength/duration/remaining_duration()。JoyButton新增JOY_BUTTON_MISC2(21)至JOY_BUTTON_MISC6(25)(C#:JoyButton.Misc2……)。新增项目设置input_devices/joypads/ignore_joypad_on_unfocused_application(默认值为false),当应用失去焦点时忽略游戏手柄输入(包括运动传感器)和 LED 变化,并停止震动。
6. 触摸输入
使用 InputEventScreenTouch 处理轻点 / 松开,使用 InputEventScreenDrag 处理手指拖动;通过 event.index 跟踪多点触摸。要在桌面端测试,请启用 Project Settings → Input Devices → Pointing → Emulate Touch From Mouse;默认启用 Emulate Mouse From Touch,因此 UI 可在触摸屏上正常工作。
触摸事件操作示例(GDScript 和 C#)以及屏幕上的
VirtualJoystick摇杆(Godot 4.7+),请参阅 references/touch.md。
7. 运行时重新绑定操作
分为三个步骤:(1)在“重新绑定”模式下,通过 _input 捕获用户选择的按键;(2)调用 InputMap.action_erase_events(action),然后调用 InputMap.action_add_event(action, new_event);(3)通过 ConfigFile 保存设置,并在启动时重新加载。
完整的 GDScript 和 C# 重新绑定流程(包括
ConfigFile的保存和加载,以及典型的“按下一个按键”捕获界面),请参阅 references/action-rebinding.md。
8. 消费和传播输入
输入会按反向场景树顺序传播(Scene 面板中位置最低的节点最先处理,根节点最后处理);消费事件后,调用 get_viewport().set_input_as_handled(),以阻止事件传递给其他节点。暂停期间,只有 process_mode 为 PROCESS_MODE_ALWAYS 或 PROCESS_MODE_WHEN_PAUSED 的节点会接收输入。
有关 GDScript 和 C# 的处理方法(停止传播、节点处理顺序、暂停期间接收输入),请参阅 references/event-propagation.md。
9. 常见问题
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 操作未被识别 | 输入映射中未定义操作名称 | 在 Project > Project Settings > Input Map 中添加操作 |
| 快速连续按键只计为一次 | 在 _physics_process 中轮询 is_action_just_pressed();多次按键发生在同一帧中 | 改为在 _unhandled_input() 中捕获离散操作 |
| 打开 UI 时输入仍然触发 | 使用了 _input(),而不是 _unhandled_input() | 改用 _unhandled_input(),让 UI 先处理事件 |
| 打开菜单时鼠标视角仍然生效 | 在 _input() 中处理鼠标移动,但没有检查模式 | 使用 if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED 进行条件判断 |
| 游戏手柄摇杆出现漂移 | 死区过低或未设置 | 在输入映射中为每个操作设置死区(0.2 是不错的默认值) |
| 未检测到控制器 | 游戏启动前未连接控制器 | 连接 joy_connection_changed 信号,并处理热插拔 |
| 按键重新绑定时捕获了修饰键 | 未过滤单独按下的 Shift/Ctrl/Alt | 跳过键码为修饰键的事件 |
| 触摸输入在桌面端不起作用 | “Emulate Touch From Mouse”已禁用 | 在 Project Settings > Input Devices > Pointing 中启用 |
| 暂停时暂停菜单忽略输入 | 节点的 process_mode 为 INHERIT(会随父节点一起暂停) | 将暂停菜单的模式设为 PROCESS_MODE_ALWAYS |
| 每次按键都会触发两次操作 | 在 _input 和 _unhandled_input 中检查了同一个操作 | 为每个操作只选择一个回调 |
⚠️ Godot 4.7 中的更改: 鼠标和键盘设备 ID 从
0改为InputEvent.DEVICE_ID_MOUSE(32)和InputEvent.DEVICE_ID_KEYBOARD(16),因为某些游戏手柄会将0用作设备 ID。检查event.device == 0来检测键盘或鼠标输入的代码会悄无声息地失效——请改为与常量比较,或检查事件类型(例如event is InputEventKey)。请参阅 4.7 迁移指南。
10. 实现检查清单
- 所有游戏操作都在 Input Map 中定义——游戏逻辑中不使用原始键码
- 离散操作(跳跃、攻击)使用
_unhandled_input(),而不是在_physics_process()中轮询 - 连续输入(移动、冲刺)在
_physics_process()中使用Input.get_vector()/Input.is_action_pressed() - 鼠标视角控制需检查
Input.mouse_mode == MOUSE_MODE_CAPTURED,避免在菜单中转动视角 - 每个 Input Map 操作都同时绑定键盘和游戏手柄,以支持控制器
- 在 Input Map 中为每个操作单独设置游戏手柄死区(新操作:4.3 中设为 0.5,4.4 起设为 0.2)
- 暂停菜单节点的
process_mode = PROCESS_MODE_ALWAYS,以便在暂停时接收输入 - 消耗不应继续传播的事件后调用
get_viewport().set_input_as_handled() - 如果要显示键盘或游戏手柄的 UI 提示,则需检测输入设备
- 按键重绑定后保存到
user://,并在启动游戏时从中加载