mcpField
I
input-handling

input-handling

github.com/jame581开发889183 次安装4 天前更新
代码仓库

关于 input-handling

面向 Godot 4.3+ 游戏项目的输入处理技能,提供 GDScript 和 C# 示例。内容涵盖事件传递与轮询,以及鼠标、控制器、触控和运行时重绑定。

主要功能

  1. 01输入事件与轮询
  2. 02Input Map 操作设计
  3. 03鼠标与游戏手柄输入
  4. 04触控与多点触控
  5. 05运行时操作重绑定
  6. 06输入传递与暂停处理

适用场景

  1. 01为游戏与 UI 输入选择回调
  2. 02添加键盘、鼠标和游戏手柄控制
  3. 03实现触控输入并在桌面端测试
  4. 04保存自定义按键绑定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill input-handling

安装后在 Claude Code 中输入/input-handling确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
action-rebinding.md
event-propagation.md
gamepad.md
input-buffering.md
mouse.md
touch.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_leftpress_a与物理按键解耦
attackleft_click适用于鼠标和游戏手柄
interactpress_e重新绑定时无需更改逻辑
sprinthold_shift不依赖具体输入设备
pausepress_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()Vector22D 方向,长度最大为 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://,并在启动游戏时从中加载

评论

支持 Markdown