使用输入事件
输入事件 InputEvent
可简化在不同操作系统或平台上的输入管理,IdeaXR的输入事件系统基于内置类型 InputEvent
实现。此类型可被设置成包含多种类型的输入事件,输入事件通过引擎传递, 可在多个位置接收,具体位置取决于目的。
什么是输入事件
输入事件示例
在介绍InputEvent之前,让我先来看一个简单的示例。在应用程序中,通过按下键盘上的ESC
键,退出并关闭我们的程序
func _unhandled_input(event):
if event is InputEventKey:
if event.pressed and event.scancode == KEY_ESCAPE:
get_tree().quit()
这里,我们使用IdeaXR
提供的InputMap
,功能更加简洁灵活,它允许定义输入操作并分配不同的键。这样,您可以定义多个键的相同动作,例如键盘ESC
键和游戏手柄上的启动按钮。然后,你可以在不更新代码的情况下轻松更改项目设置中的此映射,甚至在上面构建键映射功能,以便您的内容在运行时更改键值映射!
您可以在 编辑 > 项目设置 > 按键映射 下设置您的输入映射。
这些动作的使用方法如下:
func _process(delta):
if Input.is_action_pressed("ui_right"):
# Move right.
输入事件的工作原理
在3D应用中,每个输入事件都来源于用户/角色。每个平台的操作对象都将从设备读取事件,然后将它们发送到MainLoop
以处理这些事件。因为SceneTree
是默认的MainLoop
实现,所以事件会被提交给它。
在IdeaXR中,提供了一个获取当前SceneTree
对象的函数:get_tree()
。但事实上,SceneTree
并不知道如何处理这个事件,所以SceneTree
会把它交给viewport
。Viewport
会对接收到的输入做很多事情,依次为:
1️⃣首先,标准的 Node._input()
函数将在任何重写该函数的节点中被调用。如果任何函数响应了该输入事件,它可以调用 SceneTree
的set_input_as_handled()
,该事件将不再传播。这让您可以在 GUI
响应之前过滤事件。Node._unhandled_input()
通常对于交互性的输入更合适,因为它允许 GUI
拦截事件。
info
Node._input()
函数被调用的前提是,在没有被 Node.set_process_input()
禁用的情况下。
2️⃣然后,它会尝试将输入提供给 GUI
,并查看是否有任何控件可以接收它。如果有,Control
将通过虚函数 _gui_input()
被调用并发出gui_input
信号。如果控件想要响应该事件,它将调用accept_event()
阻止事件的传播。用mouse_filter
属性来控制 Control
是否通过 Control._gui_input()
回调接收鼠标事件的通知,以及是否进一步传播这些事件。
3️⃣如果到目前为止没有函数响应该事件,则在被覆盖时将调用未处理回调。如果任何函数消耗了该事件,它可以调用 SceneTree.set_input_as_handled()
来设置已经处理了该事件,它就将不再传播。未处理的输入回调是全屏游戏事件的理想选择,因为当 GUI 处于激活状态时不会收到它们。
info
未处理回调被调用的前提是未通过 Node.set_process_unhandled_input() 禁用。
4️⃣如果到目前为止没有人想要这个事件,并且 Viewport
中分配的 Camera
启用了对象拾取,就会从点击的射线方向往物理世界中投射一条射线。如果这条射线命中了某个对象,就会调用相关物理对象的 CollisionObject._input_event()
函数。物理实体默认接受这个回调,区域则不会。可以使用 Area 的属性进行设置。
5️⃣最后,如果事件未被处理,它将被传递给树中的下一个 Viewport,否则将被忽略。
输入事件的处理顺序
将事件发送到场景中的所有侦听节点时, 视区将以反向深度优先顺序执行: 从场景树底部的节点开始, 到根节点结束,下图中的序号极为节点的执行顺序。
另外,应用中的GUI
事件也在场景树上传播以进行处理,但由于这些事件针对的是特定的控件,所以只有目标控件节点的第一个父节点才会收到该事件。IdeaXR
基于节点的设计, 这使得专门的子节点能够处理和消费特定的事件,而它们的父级节点,以及最终的场景根节点,可以在需要时提供更通用的行为。
输入事件剖析
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 | 包含一般动作. 这些事件通常由程序员作为反馈生成. |
动作
InputEvent
可能代表也可能不代表预定义的动作。动作很有用,因为它们在编写游戏逻辑时抽象输入设备。这允许:
相同的代码可以在具有不同输入的不同设备上工作(例如,PC上的键盘, 控制台上的Joypad)
输入要在运行时重新配置
动作可以在 项目设置 菜单的 动作 选项卡中创建。在IdeaXR中,任何事件都有以下三种方法:
InputEvent.is_action()
,InputEvent.is_pressed()
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)
输入映射
如果你的整个运行流程依赖于动作,那么 InputMap
单例是在运行时重新分配或创建不同动作的理想选择. 这个单例不被保存(必须手动修改),其状态从项目设置进行(project.ideavr
)。所以任何这种类型的动态系统, 都需要以程序员认为最合适的方式来存储设置。
📄️ 输入示例
在本教程中,将为大家分享IdeaXR的 输入事件 系统捕获玩家输入。您的应用可以使用多种不同类型的输入——键盘, 游戏手柄, 鼠标等等。本章节将向您展示一些最常见的场景, 您也可以将其作为您自己项目的起点。
📄️ 处理退出请求
大多数平台都有要求应用程序退出的选项。在桌面系统中,通常是通过窗口标题栏上的❌图标来实现的。在 Android 系统上,当在主屏幕上时,后退按钮被用来退出,否则就返回。