Using InputEvent
它是什么?
无论是在操作系统或平台上, 管理输入通常很复杂.为了简化输入管理,引擎提供了一个特殊的内置类型 InputEvent.此类型可被设置成包含多种类型的输入事件.输入事件通过引擎传递,可在多个位置接收,具体位置取决于目的.
这里有一个简单的示例,Esc键被触发时关闭您的游戏:
GDScript
C#
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 下设置您的输入映射,然后使用以下操作:
GDScript
C#
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对接收到的输入做了很多事情,顺序如下:
首先,标准 Node._input() 函数将在任何覆写它的节点中被调用(没有使用 Node.set_process_input() 来禁用输入处理 ). 如果任何函数消耗了该输入事件,它可以调用 SceneTree.set_input_as_handled() ,该事件将不再传播. 这可确保您可以在GUI之前过滤所有感兴趣的事件. 在游戏输入中, Node._unhandled_input() 通常更合适,因为它允许GUI侦听.
然后,它会尝试将输入提供给GUI,并查看是否有任何控件可以接收它. 如果有, Control 将通过虚函数 Control._gui_input() 被调用并发出信号 “gui_input”(此函数可通过继承它的脚本重新实现). 如果控件想”消耗”该事件,它将调用 Control.accept_event() 阻止事件的传播.用 Control.mouse_filter 属性来控制 Control 回调接收鼠标事件的通知,以及是否进一步传播这些事件.
如果到目前为止没有函数消耗该事件,则在被覆盖时将调用未处理的输入回调(并且未使用以下命令禁用 Node.set_process_unhandled_input()). 如果任何函数使用该事件,它可以调用 SceneTree.set_input_as_handled(),该事件将不再传播. 未处理的输入回调是全屏游戏事件的理想选择,因此当GUI处于活动状态时不会收到它们.
如果到目前为止没有人想要这个事件,并且一个 Camera 被分配给视区,将投射到物理世界的光线(从点击的光线方向). 如果此光线击中一个对象,它将调用相关物理对象中的 CollisionObject._input_event() 函数(默认情况下,物体接收此回调,区域不会接受.这可以通过以下方式配置 Area 属性).
最后,如果事件未被处理,它将被传递到树的下一个视区中,否则将被忽略.
将事件发送到场景中的所有侦听节点时,视区将以反向深度优先顺序执行:从场景树底部的节点开始,到根节点结束:
GUI事件也沿着场景树进行的,但由于这些事件以特定控件为目标,因此只有目标控制节点的父辈节点才会接收事件.
根据Godot基于节点的设计,这使得专门的子节点能够处理和消费特定的事件,而它们的父级节点,以及最终的场景根节点,可以在需要时提供更通用的行为.
InputEvent剖析
InputEvent 只是一个基本的内置类型,它不代表任何东西,只包含一些基本信息,如事件ID(每个事件增加),设备索引等.
InputEvent有几种专门的类型,如下表所述:
事件 | 类型索引 | 描述 |
NONE | 空输入事件. | |
键 | 包含一个键盘扫描码和Unicode值,以及修饰键. | |
MOUSE_BUTTON | 包含点击信息,例如按钮,修饰键等. | |
MOUSE_MOTION | 包含运动信息,例如相对位置,绝对位置和速度. | |
JOYSTICK_MOTION | 包含操纵杆/ Joypad模拟轴信息. | |
JOYSTICK_BUTTON | 包含操纵杆/ Joypad按钮信息. | |
SCREEN_TOUCH | 包含多点触控按下/释放信息. (仅适用于移动设备) | |
SCREEN_DRAG | 包含多点触控拖动信息. (仅适用于移动设备) | |
SCREEN_ACTION | 包含一般动作. 这些事件通常由程序员作为反馈生成. (以下更多内容) |
Actions
InputEvent可能代表也可能不代表预定义的动作. 动作很有用,因为它们在编写游戏逻辑时抽象输入设备. 这允许:
相同的代码可以在具有不同输入的不同设备上工作(例如,PC上的键盘,控制台上的Joypad).
输入要在运行时重新配置.
可以从 操作
选项卡的 项目设置
菜单中创建操作.
任何事件都有方法 InputEvent.is_action(), InputEvent.is_pressed() and InputEvent.
或者,可能希望从游戏代码中向游戏提供一个动作,一个很好的例子是检测手势.Input单例有一个方法来实现这个功能 Input.parse_input_event() .通常这样使用它:
GDScript
C#
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).所以任何这种类型的动态系统,都需要以程序员认为最合适的方式来存储设置.