Использование InputEvent

Что это?

Управлять вводом обычно сложно, независимо от ОС или платформы. Чтобы немного упростить это, предоставляется специальный встроенный тип InputEvent. Этот тип данных может быть настроен на несколько типов входных событий. Входные события проходят через движок и могут быть получены в нескольких местах, в зависимости от цели.

Вот короткий пример, закрывающий вашу игру, если нажата клавиша Escape:

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.scancode == KEY_ESCAPE:
            get_tree().quit()

Однако более чистым и гибким является использование предоставляемой функции InputMap, которая позволяет определить действия ввода и назначить им различные клавиши. Таким образом, вы можете определить несколько клавиш для одного и того же действия (например, клавиша "escape" на клавиатуре и кнопка "start" на геймпаде). Затем вы можете легко изменить это сопоставление в настройках проекта, не обновляя код, и даже создать функцию сопоставления клавиш поверх этой функции, чтобы ваша игра могла изменять сопоставление клавиш во время выполнения!

Вы можете настроить свою InputMap в разделе Project > Project Settings > Input Map, а затем использовать эти действия следующим образом:

func _process(delta):
    if Input.is_action_pressed("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. Во-вторых, он попытается подать входные данные в GUI и посмотрит, может ли какой-либо элемент управления принять их. Если да, то Control будет вызван через виртуальную функцию Control._gui_input() и будет выдан сигнал "gui_input" (эта функция повторно реализуется скриптом, наследуясь от него). Если элемент управления захочет "поглотить" событие, он вызовет Control.accept_event() и событие больше не будет распространяться. Используйте свойство Control.mouse_filter для управления тем, получает ли Control уведомления о событиях мыши через Control._gui_input() обратный вызов, и распространяются ли эти события дальше.

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

  4. Если событие пока никому не нужно, а во вьюпорте назначена Camera с включенным Object Picking, будет брошен луч в мир физики (в направлении луча от щелчка). (Для корневого вьюпорта это также можно включить в Project Settings) Если этот луч попадет в объект, он вызовет функцию CollisionObject._input_event() в соответствующем физическом объекте (тела получают этот обратный вызов по умолчанию, а области - нет. Это можно настроить через свойства Area).

  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)

InputMаp

Часто требуется настройка и повторное отображение входных данных из кода. Если весь ваш рабочий процесс зависит от действий, синглтон InputMap идеально подходит для переназначения или создания различных действий во время выполнения. Этот синглтон не сохраняется (должен быть изменен вручную), и его состояние запускается из настроек проекта (project.godot). Поэтому любая динамическая система такого типа должна хранить настройки так, как считает нужным программист.