使用输入事件InputEvent

它是什么?

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

这里有一个简单的示例,Esc键被触发时关闭您的游戏:

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.scancode == KEY_ESCAPE:
            get_tree().quit()
public override void _UnhandledInput(InputEvent @event)
{
    if (@event is InputEventKey eventKey)
        if (eventKey.Pressed && eventKey.Scancode == (int)KeyList.Escape)
            GetTree().Quit();
}

但是, 使用引擎提供的 InputMap 将更简洁, 更灵活, 它允许自定义输入操作并为它们分配不同的按键. 这样, 您可以为同一个动作定义多个键(例如键盘escape键和手柄上的开始按钮). 然后您可以很容易地在项目设置中更改这个映射, 而无需更新您的代码, 甚至可以在它之上构建一个键映射特性, 以允许您的游戏在运行时更改键映射!

您可以在 Project > Project Settings > Input Map 下设置您的输入映射, 然后使用以下操作:

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        # Move right.
public override void _Process(float delta)
{
    if (Input.IsActionPressed("ui_right"))
    {
        // Move right.
    }
}

它的工作原理是什么?

每个输入事件都来源于用户/游戏角色(尽管可以生成一个InputEvent并将其反馈给引擎, 这在手势操作中非常有用). 每个平台的操作对象都将从设备读取事件, 然后将它们发送到MainLoop. 因为 SceneTree 是默认的主循环实现, 所以事件被提交给它.Godot提供了一个获取当前SceneTree对象的函数 : get_tree() .

但是SceneTree不知道如何处理这个事件, 所以SceneTree把它交给视区, 从 "根" Viewport (场景树的第一个节点)开始查找.Viewport对接收到的输入做了很多事情, 顺序如下:

../../_images/input_event_flow.png
  1. 首先, 标准 Node._input() 函数将在任何覆写它的节点中被调用(没有使用 Node.set_process_input() 来禁用输入处理 ). 如果任何函数消耗了该输入事件, 它可以调用 SceneTree.set_input_as_handled() , 该事件将不再传播. 这可确保您可以在GUI之前过滤所有感兴趣的事件. 在游戏输入中, Node._unhandled_input() 通常更合适, 因为它允许GUI侦听.

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

  3. 如果到目前为止没有函数消耗该事件, 则在被覆盖时将调用未处理的输入回调(并且未使用以下命令禁用 Node.set_process_unhandled_input()). 如果任何函数使用该事件, 它可以调用 SceneTree.set_input_as_handled(), 该事件将不再传播. 未处理的输入回调是全屏游戏事件的理想选择, 因此当GUI处于活动状态时不会收到它们.

  4. If no one wanted the event so far, and a Camera is assigned to the Viewport with Object Picking turned on, a ray to the physics world (in the ray direction from the click) will be cast. (For the root viewport, this can also be enabled in Project Settings) If this ray hits an object, it will call the CollisionObject._input_event() function in the relevant physics object (bodies receive this callback by default, but areas do not. This can be configured through Area properties).

  5. 最后, 如果事件未被处理, 它将被传递到树的下一个视区中, 否则将被忽略.

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

../../_images/input_event_scene_flow.png

GUI事件也沿着场景树进行的, 但由于这些事件以特定控件为目标, 因此只有目标控制节点的父辈节点才会接收事件.

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

InputEvent剖析

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

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

事件

类型索引

描述

InputEvent

NONE

空输入事件.

InputEventKey

包含一个键盘扫描码和Unicode值, 以及修饰键.

InputEventMouseButton

MOUSE_BUTTON

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

InputEventMouseMotion

MOUSE_MOTION

包含运动信息, 例如相对位置, 绝对位置和速度.

InputEventJoypadMotion

JOYSTICK_MOTION

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

InputEventJoypadButton

JOYSTICK_BUTTON

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

InputEventScreenTouch

SCREEN_TOUCH

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

InputEventScreenDrag

SCREEN_DRAG

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

InputEventAction

SCREEN_ACTION

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

Actions

InputEvent可能代表也可能不代表预定义的动作. 动作很有用, 因为它们在编写游戏逻辑时抽象输入设备. 这允许:

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

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

可以从 操作 选项卡的 项目设置 菜单中创建操作.

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

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

var ev = InputEventAction.new()
# Set as move_left, pressed.
ev.action = "move_left"
ev.pressed = true
# Feedback.
Input.parse_input_event(ev)
var ev = new InputEventAction();
// Set as move_left, pressed.
ev.SetAction("move_left");
ev.SetPressed(true);
// Feedback.
Input.ParseInputEvent(ev);

InputMap

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