InputEvent

What is it?

Управлять вводом обычно сложно, независимо от ОС или платформы. Чтобы немного упростить это, предоставляется специальный встроенный тип 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 на геймпаде). Затем вы можете более легко изменить это сопоставление в настройках проекта, не обновляя свой код, и даже создать функцию сопоставления ключей поверх него, чтобы ваша игра могла изменять соответствия ключей во время выполнения!

Вы можете настроить свою 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. Во-вторых, он попытается передать входные данные в 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 назначена Viewport, то будет брошен луч в физический мир (в направлении луча от щелчка). Если этот луч попадет в объект, он вызовет функцию CollisionObject._input_event() в соответствующем физическом объекте (тела получают этот обратный вызов по умолчанию, а области - нет. Это можно настроить с помощью свойств 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). Поэтому любая динамическая система такого типа должна хранить настройки так, как считает нужным программист.