Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

使用 InputEvent

它是什么?

无论是在操作系统或平台上, 管理输入通常很复杂. 为了简化输入管理, 引擎提供了一个特殊的内置类型 InputEvent. 此类型可被设置成包含多种类型的输入事件. 输入事件通过引擎传递, 可在多个位置接收, 具体位置取决于目的.

这里有一个简单的示例,按下 ESC 键时关闭你的游戏:

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.keycode == KEY_ESCAPE:
            get_tree().quit()

但是,使用所提供的 InputMap 功能更简洁灵活,它允许你定义输入操作并分配不同的键。这样,你可以定义多个键的相同动作,例如键盘ESC键和游戏手柄上的启动按钮。然后,你可以在不更新代码的情况下轻松更改项目设置中的此映射,甚至在上面构建键映射功能,以便你的游戏在运行时更改键值映射!

你可以在项目 > 项目设置 > 按键映射下设置你的输入映射,这些动作的使用方法如下:

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        # Move right.

工作原理是怎样的?

每个输入事件都来源于用户/玩家(虽然也可以自己生成 InputEvent 并提供给引擎,多用于手势)。各个平台的 DisplayServer 都会从操作系统读取事件,然后提供给根 Window

窗口的 Viewport 会对收到的输入进行很多处理,依次为:

../../_images/input_event_flow.webp
  1. 如果该 Viewport 内嵌了 Window,则该 Viewport 会尝试以窗口管理器的身份解释事件(例如对 Window 进行大小调整和移动)。

  2. 接下来,如果存在聚焦的内嵌 Window,则会将事件发送给该 Window,在该窗口的 Viewport 中进行处理,然后将事件标记为已处理。如果不存在聚焦的内嵌 Window,则会将事件发送给当前视口中的节点,顺序如下。

  3. First of all, the standard Node._input() function will be called in any node that overrides it (and hasn't disabled input processing with Node.set_process_input()). If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. This ensures that you can filter all events of interest, even before the GUI. For gameplay input, Node._unhandled_input() is generally a better fit, because it allows the GUI to intercept the events.

  4. Second, it will try to feed the input to the GUI, and see if any control can receive it. If so, the Control will be called via the virtual function Control._gui_input() and the signal "gui_input" will be emitted (this function is re-implementable by script by inheriting from it). If the control wants to "consume" the event, it will call Control.accept_event() and the event will not spread any more. Use the Control.mouse_filter property to control whether a Control is notified of mouse events via Control._gui_input() callback, and whether these events are propagated further.

  5. If so far no one consumed the event, the Node._shortcut_input() callback will be called if overridden (and not disabled with Node.set_process_shortcut_input()). This happens only for InputEventKey, InputEventShortcut and InputEventJoypadButton. If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The shortcut input callback is ideal for treating events that are intended as shortcuts.

  6. If so far no one consumed the event, the Node._unhandled_key_input() callback will be called if overridden (and not disabled with Node.set_process_unhandled_key_input()). This happens only if the event is a InputEventKey. If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The unhandled key input callback is ideal for key events.

  7. If so far no one consumed the event, the Node._unhandled_input() callback will be called if overridden (and not disabled with Node.set_process_unhandled_input()). If any function consumes the event, it can call Viewport.set_input_as_handled(), and the event will not spread any more. The unhandled input callback is ideal for full-screen gameplay events, so they are not received when a GUI is active.

  8. If no one wanted the event so far, and Object Picking is turned on, the event is used for object picking. For the root viewport, this can also be enabled in Project Settings. In the case of a 3D scene if a Camera3D is assigned to the Viewport, a ray to the physics world (in the ray direction from the click) will be cast. If this ray hits an object, it will call the CollisionObject3D._input_event() function in the relevant physics object (bodies receive this callback by default, but areas do not. This can be configured through Area3D properties). In the case of a 2D scene, conceptually the same happens with CollisionObject2D._input_event().

将事件发送到场景中的所有侦听节点时, 视口将以反向深度优先顺序执行: 从场景树底部的节点开始, 到根节点结束。

../../_images/input_event_scene_flow.png

This order doesn't apply to Control._gui_input(), which uses a different method based on event location or focused Control.

Since Viewports don't send events to other SubViewports, one of the following methods has to be used:

  1. Use a SubViewportContainer, which automatically sends events to its child SubViewports after Node._input() or Control._gui_input().

  2. Implement event propagation based on the individual requirements.

GUI事件也在场景树上传播,但由于这些事件针对的是特定的控件,所以只有目标控件节点的第一个父节点才会收到该事件。

根据Godot基于节点的设计, 这使得专门的子节点能够处理和消费特定的事件, 而它们的父级节点, 以及最终的场景根节点, 可以在需要时提供更通用的行为.

InputEvent 剖析

InputEvent 只是一个基本的内置类型, 它不代表任何东西, 只包含一些基本信息, 如事件ID(每个事件增加), 设备索引等.

InputEvent有几种专门的类型, 如下表所述:

事件

描述

InputEvent

空输入事件.

InputEventKey

Contains a keycode and Unicode value, as well as modifiers.

InputEventMouseButton

包含点击信息, 例如按钮, 修饰键等.

InputEventMouseMotion

Contains motion information, such as relative and absolute positions and speed.

InputEventJoypadMotion

包含操纵杆/ Joypad模拟轴信息.

InputEventJoypadButton

包含操纵杆/ Joypad按钮信息.

InputEventScreenTouch

包含多点触控按下/释放信息. (仅适用于移动设备)

InputEventScreenDrag

包含多点触控拖动信息. (仅适用于移动设备)

InputEventMagnifyGesture

Contains a position, a factor as well as modifiers.

InputEventPanGesture

Contains a position, a delta as well as modifiers.

InputEventMIDI

Contains MIDI-related information.

InputEventShortcut

容器布局。

InputEventAction

包含一般动作. 这些事件通常由程序员作为反馈生成. (以下更多内容)

动作

Actions are a grouping of zero or more InputEvents into a commonly understood title (for example, the default "ui_left" action grouping both joypad-left input and a keyboard's left arrow key). They are not required to represent an InputEvent but are useful because they abstract various inputs when programming the game logic.

This allows for:

  • 相同的代码可以在具有不同输入的不同设备上工作(例如,PC上的键盘, 控制台上的Joypad).

  • 输入要在运行时重新配置.

  • Actions to be triggered programmatically at run-time.

Actions can be created from the Project Settings menu in the Input Map tab and assigned input events.

任何事件都有方法 InputEvent.is_action(), InputEvent.is_pressed() and InputEvent.

或者, 可能希望从游戏代码中向游戏提供一个动作, 一个很好的例子是检测手势.Input单例有一个方法来实现这个功能 Input.parse_input_event() . 通常这样使用它:

var ev = InputEventAction.new()
# Set as ui_left, pressed.
ev.action = "ui_left"
ev.pressed = true
# Feedback.
Input.parse_input_event(ev)

InputMap

经常需要的从代码中定制和重新映射输入. 如果你的整个运行流程依赖于动作, 那么 InputMap 单例是在运行时重新分配或创建不同动作的理想选择. 这个单例不被保存(必须手动修改), 其状态从项目设置进行(project.godot). 所以任何这种类型的动态系统, 都需要以程序员认为最合适的方式来存储设置.