InputEvent

它是什么?

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

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

GDScript

C#

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

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

You can set up your InputMap under Project > Project Settings > Input Map and then use those actions like this:

GDScript

C#

  1. func _process(delta):
  2. if Input.is_action_pressed("ui_right"):
  3. # Move right.
  1. public override void _Process(float delta)
  2. {
  3. if (Input.IsActionPressed("ui_right"))
  4. {
  5. // Move right.
  6. }
  7. }

它的工作原理是什么?

每个输入事件都来源于用户/游戏角色(尽管可以生成一个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() 被调用并发出信号 “input_event”(此函数可通过以下方式重新实现: 脚本继承自它)。 如果控件想“消耗”该事件,它将调用 Control.accept_event() 使得该事件不再传播。 使用 Control.mouse_filter 属性来控制是否 Control 通过以下方式通知鼠标事件 Control._gui_input() 回调,以及是否这些事件进一步传播。
  3. 如果到目前为止没有函数消耗该事件,则在被覆盖时将调用未处理的输入回调(并且未使用以下命令禁用 Node.set_process_unhandled_input())。 如果任何函数使用该事件,它可以调用 SceneTree.set_input_as_handled(),该事件将不再传播。 未处理的输入回调是全屏游戏事件的理想选择,因此当GUI处于活动状态时不会收到它们。
  4. 如果到目前为止没有人想要这个事件,并且一个 Camera 被分配给视区,将投射到物理世界的光线(从点击的光线方向)。 如果此光线击中一个对象,它将调用相关物理对象中的 CollisionObject._input_event() 函数(默认情况下,物体接收此回调,区域不会接受。这可以通过以下方式配置 :ref :Area <class_Area> 属性)。
  5. 最后,如果事件未被处理,它将被传递到树的下一个视区中,否则将被忽略。

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

../../_images/input_event_scene_flow.png

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

In accordance with Godot’s node-based design, this enables specialized child nodes to handle and consume particular events, while their ancestors, and ultimately the scene root, can provide more generalized behavior if needed.

InputEvent剖析

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

There are several specialized types of InputEvent, described in the table below:

事件类型索引描述
InputEventNONE空输入事件。
InputEventKeyKEYContains a scancode and Unicode value, as well as modifiers.
InputEventMouseButtonMOUSE_BUTTON包含点击信息,例如按钮,修改器等。
InputEventMouseMotionMOUSE_MOTION包含运动信息,例如相对位置,绝对位置和速度。
InputEventJoypadMotionJOYSTICK_MOTION包含操纵杆/ Joypad模拟轴信息。
InputEventJoypadButtonJOYSTICK_BUTTON包含操纵杆/ Joypad按钮信息。
InputEventScreenTouchSCREEN_TOUCH包含多点触控按下/释放信息。 (仅适用于移动设备)
InputEventScreenDragSCREEN_DRAG包含多点触控拖动信息。 (仅适用于移动设备)
InputEventActionSCREEN_ACTION包含一般动作。 这些事件通常由程序员作为反馈生成。 (以下更多内容)

Actions

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

  • 相同的代码可以在具有不同输入的不同设备上工作(例如,PC上的键盘,控制台上的Joypad)。
  • 输入要在运行时重新配置。

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

Any event has the methods InputEvent.is_action(), InputEvent.is_pressed() and InputEvent.

Alternatively, it may be desired to supply the game back with an action from the game code (a good example of this is detecting gestures). The Input singleton has a method for this: Input.parse_input_event(). You would normally use it like this:

GDScript

C#

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

InputMap

通常需要通过代码定制和重新映射输入。 如果整个工作流程依赖于行为, InputMap 单例非常适合在运行时重新分配或创建不同的行为。 此单例不会保存(必须手动修改),其状态从项目设置(project.godot)运行。 因此,这种类型的动态系统需要以程序员最适合的方式存储设置。