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()
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" Viewport (o primeiro nó da árvore de cenas). A Viewport faz muitas coisas com a entrada recebida, na ordem:
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.
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.
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.
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).
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:
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 |
NONE |
Evento de Entrada vazio. |
|
KEY |
Contém um scancode e valor Unicode, assim como modificadores. |
|
MOUSE_BUTTON |
Contém informações de clique, como botão, modificadores, etc. |
|
MOUSE_MOTION |
Contém informações de movimento, como posições relativas e absolutas e velocidade. |
|
JOYSTICK_MOTION |
Contém informações de eixo analógico do Joystick/Joypad. |
|
JOYSTICK_BUTTON |
Contém informações de botão do Joystick/Joypad. |
|
SCREEN_TOUCH |
Contém informações de pressionamento/liberação multitoque. (disponível apenas em dispositivos móveis) |
|
SCREEN_DRAG |
Contém informações de arrasto multitoque. (disponível apenas em dispositivos móveis) |
|
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.