InputEvent

O que é isso?

O gerenciamento de entrada normalmente é complexo, não importa o SO ou a plataforma. Para facilitar isto um pouco, um tipo especial embutido é fornecido, InputEvent. Este tipo de dado pode ser configurado para conter vários tipos de eventos de entrada. Eventos de entrada viajam pela engine e podem ser recebidos em vários locais, dependendo da finalidade.

Eis um exemplo rápido, que fecha seu jogo se a tecla Escape for pressionada:

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();
}

No entanto, é mais limpo e flexível usar o recurso fornecido InputMap, que o permite definir ações de entrada e atribuir-lhes diferentes teclas. Desta forma você pode definir várias teclas para a mesma ação (por exemplo, a tecla Esc do teclado e o botão Start em um controle). Você então pode alterar mais facilmente este mapeamento nas configurações do projeto sem atualizar seu código, e até fazer um recurso por cima para permitir seu jogo alterar o mapeamento de teclas em tempo de execução!

Você pode configurar seu InputMap em Projeto > Configurações do Projeto > Mapa de Entrada e usar essas ações como esta:

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.
    }
}

Como funciona?

Cada evento de entrada é originado do usuário/jogador (embora seja possível gerar um InputEvent e enviá-lo de volta à engine, o que é útil para gestos). O objeto OS para cada plataforma irá ler os eventos do dispositivo, então enviá-los ao MainLoop. Como ref:`SceneTree <class_SceneTree>`é a implementação MainLoop padrão, os eventos são enviados para ela. O Godot fornece uma função para obter o objeto SceneTree atual: get_tree().

Mas a SceneTree não sabe o que fazer com o evento, então ela o dará às viewports, começando pela "root" :ref: Viewport <class_Viewport> (o primeiro nó da árvore de cenas). A Viewport faz muitas coisas com a entrada recebida, na ordem:

../../_images/input_event_flow.png
  1. Em primeiro lugar, a função padrão Node._input() será chamada em qualquer nó que a substitui (e não desativou o processamento de entrada com Node.set_process_input()). Se alguma função consume o evento, ela pode chamar SceneTree.set_input_as_handled(), e o evento não irá mais se espalhar. Isto garante que você possa filtrar todos os eventos de interesse, até antes da GUI. Para entrada de gameplay, Node._unhandled_input() geralmente é um ajuste melhor, porque permite que a GUI intercepte os eventos.

  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. Se até agora ninguém consumiu o evento, a chamada de retorno de entrada não tratada será chamada se substituída (e não desativada com Node.set_process_unhandled_input()). Se alguma função consome o evento, ela pode chamar SceneTree.set_input_as_handled(), e o evento não vai mais se espalhar. A chamada de retorno de entrada não tratada é ideal para eventos de gameplay em tela-cheia, assim eles não são recebidos quando uma GUI está ativa.

  4. Se ninguém quis o evento até agora, e uma Camera for atribuída ao Viewport, um raio para o mundo da física (na direção do raio do clique) será convertido. Se este raio atingir um objeto, ele chamará a função CollisionObject._input_event () no objeto físico relevante (os corpos recebem esse retorno de chamada por padrão, mas as áreas não. Isso pode ser configurado pelas propriedades da Area).

  5. Finalmente, se o evento não foi tratado, ele setá passado para a pŕoxima Viewport na árvore, caso contrário será ignorado.

Ao enviar eventos para todos os nós ouvintes em uma cena, a viewport fará isso em uma ordem inversa de profundidade: começando com o nó na parte inferior da árvore da cena e terminando no nó raiz:

../../_images/input_event_scene_flow.png

Os eventos da GUI também sobem a árvore de cena, mas, uma vez que esses eventos visam Controls específicos, apenas os ancestrais diretos do nó de Control de destino recebem o evento.

De acordo com o design baseado em nós do Godot, isto permite que nós filhos especializados tratem e consumam eventos específicos, enquanto seus ancestrais e, em última instância, a raíz da cena, podem fornecer um comportamento mais generalizado, se necessário.

Anatomia de um InputEvent

InputEvent é apenas um tipo embutido base, não representa nada e contém apenas algumas informações básicas, como o ID do evento (que é aumentado para cada evento), índice do dispositivo, etc.

Existem vários tipos especializados de InputEvent, descritos na tabela abaixo:

Event

Type Index

Descrição

InputEvent

NONE

Evento de Entrada vazio.

InputEventKey

KEY

Contém um scancode e valor Unicode, assim como modificadores.

InputEventMouseButton

MOUSE_BUTTON

Contém informações de clique, como botão, modificadores, etc.

InputEventMouseMotion

MOUSE_MOTION

Contém informações de movimento, como posições relativas e absolutas e velocidade.

InputEventJoypadMotion

JOYSTICK_MOTION

Contém informações de eixo analógico do Joystick/Joypad.

InputEventJoypadButton

JOYSTICK_BUTTON

Contém informações de botão do Joystick/Joypad.

InputEventScreenTouch

SCREEN_TOUCH

Contém informações de pressionamento/liberação multitoque. (disponível apenas em dispositivos móveis)

InputEventScreenDrag

SCREEN_DRAG

Contém informações de arrasto multitoque. (disponível apenas em dispositivos móveis)

InputEventAction

SCREEN_ACTION

Contém uma ação genérica. Esses eventos são frequentemente gerados pelo programador como feedback. (mais sobre isso abaixo)

Ações

Um InputEvent pode ou não representar uma ação predefinida. Ações são úteis porque elas abstraem o dispositivo de entrada ao programar a lógica do jogo. Isto permite:

  • O mesmo código para trabalhar em diferentes dispositivos com diferentes entradas (por exemplo, teclado no PC, Joypad no console).

  • Entrada a ser reconfigurada em tempo de execução.

Ações podem ser criadas a partir do menu Configurações do Projeto na aba Ações.

Qualquer evento tem os métodos InputEvent.is_action(), InputEvent.is_pressed() e InputEvent.

Alternativamente, pode ser desejado fornecer ao jogo uma ação do código do jogo (um bom exemplo disso é a detecção de gestos). O singleton Input tem um método para isso: Input.parse_input_event(). Você normalmente usaria assim:

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

A personalização e o remapeamento da entrada do código são frequentemente desejados. Se todo o seu fluxo de trabalho depende de ações, o singleton InputMap é ideal para reatribuir ou criar ações diferentes em tempo de execução. Este singleton não é salvo (deve ser modificado manualmente) e seu estado é executado a partir das configurações do projeto (project.godot). Portanto, qualquer sistema dinâmico desse tipo precisa armazenar as configurações da maneira que o programador achar melhor.