Using 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 回调接收鼠标事件的通知,以及是否进一步传播这些事件.

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

  4. 如果到目前为止没有人想要这个事件,并且一个 Camera 被分配给视区,将投射到物理世界的光线(从点击的光线方向). 如果此光线击中一个对象,它将调用相关物理对象中的 CollisionObject._input_event() 函数(默认情况下,物体接收此回调,区域不会接受.这可以通过以下方式配置 Area 属性).

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