Usando 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()

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.

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" 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. Em segundo lugar, ele tentará alimentar a entrada à GUI, e ver se algum controle pode recebê-la. Se assim for, o Control será chamado através da função virtual Control._gui_input() e o sinal "gui_input" será emitido (esta função é reimplementável por script ao herdar dele). Se o controle quer "consumir" o evento, ele irá chamar Control.accept_event() e o evento não irá mais se espalhar. Use a propriedade Control.mouse_filter para controlar se um Control é notificado de eventos de mouse através da chamada de retorno Control, e se esses eventos são propagados ainda mais.

  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 queria o evento até agora, e uma Camera é atribuída ao Viewport com Object Picking ligado, um ray para o mundo da física (na direção do ray a partir do clique) será lançado. (Para o viewport raiz, isto também pode ser ativado em Project Settings) Se este ray atingir um objeto, ele chamará o CollisionObject._input_event() function in the relevant physics object (corpos recebem esta chamada de retorno por padrão, mas áreas não recebem. Isto pode ser configurado através de Área propriedades).

  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:

Evento

Índice de Tipo

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)

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.