Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

使用 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.

工作原理是怎样的?

Every input event is originated from the user/player (though it's possible to generate an InputEvent and feed them back to the engine, which is useful for gestures). The DisplayServer for each platform will read events from the operating system, then feed them to the root Window.

The window's Viewport does quite a lot of stuff with the received input, in order:

../../_images/input_event_flow.webp
  1. If the Viewport is embedding Windows, the Viewport tries to interpret the event in its capability as a Window-Manager (e.g. for resizing or moving Windows).

  2. Next if an embedded Window is focused, the event is sent to that Window and processed in the Windows Viewport and afterwards treated as handled. If no embedded Window is focused, the event is sent to the nodes of the current viewport in the following order.

  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. 然后,它会尝试将输入提供给 GUI,并查看是否有任何控件可以接收它。如果有,Control 将通过虚函数 Control._gui_input() 被调用并发出“gui_input”信号(此函数可通过继承它的脚本重新实现)。如果控件想“消耗”该事件,它将调用 Control.accept_event() 阻止事件的传播。用 Control.mouse_filter 属性来控制 Control 是否通过 Control._gui_input() 回调接收鼠标事件的通知,以及是否进一步传播这些事件。

  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().

When sending events to its child and descendant nodes, the viewport will do so, as depicted in the following graphic, in a reverse depth-first order, starting with the node at the bottom of the scene tree, and ending at the root node. Excluded from this process are Windows and SubViewports.

../../_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

Contains a shortcut.

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). 所以任何这种类型的动态系统, 都需要以程序员认为最合适的方式来存储设置.