InputEvent

这是怎么一回事?

管理输入通常很复杂,无论是操作系统还是平台。为了减轻这一点,提供了一种特殊的内置类型, InputEvent . 可以将此数据类型配置为包含多种类型的输入事件。输入事件通过引擎传输,根据用途,可以在多个位置接收。

下面是一个快速的例子,如果按下Escape键,游戏将结束:

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键和游戏板上的Start按钮)。然后,您可以更容易地在项目设置中更改此映射,而无需更新代码,甚至在其上构建一个键映射功能,以允许您的游戏在运行时更改键映射!

您可以在下面设置输入地图 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
    }
}

它是如何工作的?

每个输入事件都源于用户/播放器(尽管可以生成一个输入事件并将其反馈给引擎,这对于手势很有用)。每个平台的OS对象将从设备中读取事件,然后将它们馈送给mainloop。AS SceneTree 是默认的mainloop实现,事件被发送到它。godot提供了一个函数来获取当前scenetree对象: get_tree() .

但是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() 相关物理对象中的函数(默认情况下,实体接收此回调,但区域不接收)。可以通过以下方式配置 Area 属性)。

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

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

../../_images/input_event_scene_flow.png

GUI事件也会在场景树上传播,但由于这些事件针对特定的控件,因此只有目标控制节点的直接祖先才能接收事件。

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

子宫内口解剖

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

有几种专门类型的住院病患,如下表所述:

事件

类型索引

描述

InputEvent

NONE

空输入事件。

InputEventKey

KEY

包含scancode和unicode值以及修饰符。

InputEventMouseButton

MOUSE_BUTTON

包含单击信息,如按钮、修改器等。

InputEventMouseMotion

MOUSE_MOTION

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

InputEventJoypadMotion

JOYSTICK_MOTION

包含操纵杆/操纵手柄模拟轴信息。

InputEventJoypadButton

JOYSTICK_BUTTON

包含操纵杆/操纵手柄按钮信息。

InputEventScreenTouch

SCREEN_TOUCH

包含多点触控新闻/发布信息。(仅在移动设备上可用)

InputEventScreenDrag

SCREEN_DRAG

包含多点触控拖动信息。(仅在移动设备上可用)

InputEventAction

SCREEN_ACTION

包含一般操作。这些事件通常由程序员作为反馈生成。(更多内容见下文)

行动

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

  • 在不同的设备上使用相同的代码,具有不同的输入(例如,PC上的键盘、控制台上的游戏板)。

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

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

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

或者,可能需要向游戏提供来自游戏代码的操作(这方面的一个很好的例子是检测手势)。输入单例有一个方法: 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 singleton非常适合在运行时重新分配或创建不同的操作。此singleton未保存(必须手动修改),其状态从项目设置(project.godot)运行。因此,任何这种类型的动态系统都需要以程序员认为最合适的方式存储设置。