Использование 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();
}

However, it is cleaner and more flexible to use the provided InputMap feature, which allows you to define input actions and assign them different keys. This way, you can define multiple keys for the same action (e.g. the keyboard escape key and the start button on a gamepad). You can then more easily change this mapping in the project settings without updating your code, and even build a key mapping feature on top of it to allow your game to change the key mapping at runtime!

Вы можете настроить свою InputMap в разделе 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 является основной реализацией MainLoop, события подаются в него. Godot предоставляет функцию для получения текущего объекта дерева сцены: get_tree().

Но SceneTree не знает, что делать с событием, поэтому он передаст его видовым экранам, начиная с "root" 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. Second, it will try to feed the input to the GUI, and see if any control can receive it. If so, the Control will be called via the virtual function Control._gui_input() and the signal "gui_input" will be emitted (this function is re-implementable by script by inheriting from it). If the control wants to "consume" the event, it will call Control.accept_event() and the event will not spread any more. Use the Control.mouse_filter property to control whether a Control is notified of mouse events via Control._gui_input() callback, and whether these events are propagated further.

  3. Если до сих пор никто не потреблял событие, то необработанный входной обратный вызов будет вызван, если он будет переопределен (и не отключен с помощью Node.set_process_unhandled_input()). Если какая-либо функция потребляет событие, она может вызвать SceneTree.set_input_as_handled(), и событие больше не будет распространяться. Необработанный входной обратный вызов идеально подходит для полноэкранных игровых событий, поэтому они не принимаются, когда GUI активен.

  4. If no one wanted the event so far, and a Camera is assigned to the Viewport with Object Picking turned on, a ray to the physics world (in the ray direction from the click) will be cast. (For the root viewport, this can also be enabled in Project Settings) If this ray hits an object, it will call the CollisionObject._input_event() function in the relevant physics object (bodies receive this callback by default, but areas do not. This can be configured through Area properties).

  5. Наконец, если событие не было обработано, оно будет передано в следующий Viewport в дереве, в противном случае оно будет проигнорировано.

При отправке событий на все прослушивающие узлы в пределах сцены окно просмотра будет делать это в обратном порядке глубины: начиная с узла в нижней части дерева сцены и заканчивая корневым узлом:

../../_images/input_event_scene_flow.png

События GUI также перемещаются вверх по дереву сцены, но, поскольку эти события нацелены на определенные Controls, только прямые предки целевого узла Controls получают событие.

В соответствии с node-дизайном Godot это позволяет специализированным дочерним узлам обрабатывать и потреблять определенные события, в то время как их предки и, в конечном счете, корень сцены могут обеспечить более обобщенное поведение, если это необходимо.

Анатомия InputEvent

InputEvent - это просто базовый встроенный тип, он ничего не представляет и содержит только некоторую базовую информацию, такую как ID события (который увеличивается для каждого события), индекс устройства и т. д.

Существует несколько специализированных типов InputEvent, описанных в таблице ниже:

Событие

Индекс типа

Описание

InputEvent

NONE

Пустое входное событие.

InputEventKey

KEY

Содержит скан-код и значение Unicode, а также модификаторы.

InputEventMouseButton

MOUSE_BUTTON

Содержит информацию о щелчке, такую как кнопка, модификаторы и т. д.

InputEventMouseMotion

MOUSE_MOTION

Содержит информацию о движении, такую как относительные, абсолютные позиции и скорость.

InputEventJoypadMotion

JOYSTICK_MOTION

Содержит информацию об аналоговой оси джойстика/джойпада.

InputEventJoypadButton

JOYSTICK_BUTTON

Содержит информацию о кнопке джойстика/джойпада.

InputEventScreenTouch

SCREEN_TOUCH

Содержит информацию о нажатии/отпускании с несколькими касаниями. (доступно только на мобильных устройствах)

InputEventScreenDrag

SCREEN_DRAG

Содержит информацию о перетаскивании с помощью мультисенсорного ввода. (доступно только на мобильных устройствах)

InputEventAction

SCREEN_ACTION

Содержит общее действие. Эти события часто генерируются программистом в виде обратной связи. (подробнее об этом ниже)

Действия

InputEvent может представлять или не представлять предопределенное действие. Действия полезны тем, что они абстрагируют устройство ввода при программировании игровой логики. Это позволяет:

  • Один и тот же код работать на разных устройствах с разным вводом (например, клавиатура на ПК, джойстик на консоли).

  • Перенастраивать ввод во время выполнения.

Действия можно создавать из меню Project Settings на вкладке Actions.

Любое событие имеет методы 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 идеально подходит для переназначения или создания различных действий во время выполнения. Этот синглтон не сохраняется (должен быть изменен вручную), и его состояние запускается из настроек проекта (project.godot). Поэтому любая динамическая система такого типа должна хранить настройки так, как считает нужным программист.