Usando sinais

Nesta lição, veremos os sinais. São mensagens que os nós emitem quando algo específico acontece com eles, como um botão sendo pressionado. Outros nós podem se conectar a esse sinal e chamar uma função quando o evento ocorrer.

Os sinais são um mecanismo de delegação embutido no Godot que permite que um objeto do jogo reaja a uma mudança em outro sem que eles façam referência um ao outro. O uso de sinais limita o acoplamento e mantém seu código flexível.

Por exemplo, você pode ter uma barra de vida na tela que representa a saúde do jogador. Quando o jogador sofre dano ou usa uma poção de cura, você deseja que a barra reflita a mudança. Para fazer isso, em Godot, você usaria sinais.

Nota

Conforme mencionado na introdução, os sinais são a versão de Godot do padrão do observador. Você pode aprender mais sobre isso aqui: https://gameprogrammingpatterns.com/observer.html

Vamos agora usar um sinal para fazer nosso ícone Godot da lição anterior (Capturando os controles de entrada do jogador) se mover e parar ao pressionar um botão.

Configuração da cena

Para adicionar um botão ao nosso jogo, criaremos uma nova cena "principal" que incluirá um botão e a cena Sprite.tscn que criamos em lições anteriores.

Crie uma nova cena acessando o menu Cena -> Nova cena.

../../_images/signals_01_new_scene.png

No painel Cena, clique no botão Cena 2D. Isso adicionará um Node2D como nossa raiz.

../../_images/signals_02_2d_scene.png

No painel Arquivos, clique e arraste o arquivo Sprite.tscn que você salvou anteriormente para o Node2D para instanciá-lo.

../../_images/signals_03_dragging_scene.png

Queremos adicionar outro nó como irmão do Sprite. Para fazer isso, clique com o botão direito do mouse em Node2D e selecione Adicionar Nó Filho.

../../_images/signals_04_add_child_node.png

Pesquise o tipo de nó Button e adicione-o.

../../_images/signals_05_add_button.png

O nó é pequeno por padrão. Clique e arraste a alça inferior direita do Button na viewport para redimensioná-lo.

../../_images/signals_06_drag_button.png

Se você não vir as alças, verifique se a ferramenta de seleção está ativa na barra de ferramentas.

../../_images/signals_07_select_tool.png

Clique e arraste o botão para aproximá-lo do sprite.

Você também pode escrever um rótulo no Button editando sua propriedade Texto no Inspector. Digite "Alternar movimento".

../../_images/signals_08_toggle_motion_text.png

Sua árvore da cena e o Viewport devem se parecer com isso.

../../_images/signals_09_scene_setup.png

Salve sua cena recém-criada. Você pode executá-lo com F6. No momento, o botão estará visível, mas nada acontecerá se você pressioná-lo.

Conectando um sinal no editor

Aqui, queremos conectar o sinal "pressionado"(pressed) do botão ao nosso Sprite e queremos chamar uma nova função que ativará e desativará seu movimento. Precisamos ter um script anexado ao nó Sprite, o que fizemos na lição anterior.

Você pode conectar sinais no painel do Nó. Selecione o nó Botão e, no lado direito do editor, clique na aba "Nó" ao lado do Inspetor.

../../_images/signals_10_node_dock.png

O painel exibe uma lista de sinais disponíveis no nó selecionado.

../../_images/signals_11_pressed_signals.png

Clique duas vezes no sinal "pressed" para abrir a janela de conexão do nó.

../../_images/signals_12_node_connection.png

Nela, você pode conectar o sinal ao nó Sprite. O nó precisa de um método receptor, uma função que o Godot chamará quando o Button emitir o sinal. O editor gera um para você. Por convenção, chamamos esses métodos de callback de "_on_NodeName_signal_name". Aqui, será "_on_Button_pressed".

Nota

Ao conectar sinais por meio do encaixe do nó do editor, você pode usar dois modos. O simples permite apenas conectar-se a nós que possuem um script anexado a eles e cria uma nova função de retorno de chamada neles.

../../_images/signals_advanced_connection_window.png

O avançado permite conectar-se a qualquer nó e qualquer função integrada, adicionar argumentos ao retorno de chamada e definir opções. Você pode alternar o modo no canto inferior esquerdo da janela clicando no botão Avançado.

Clique no botão Conectar para concluir a conexão do sinal e vá para a área de trabalho Script. Você deve ver o novo método com um ícone de conexão na margem esquerda.

../../_images/signals_13_signals_connection_icon.png

Se você clicar no ícone, uma janela aparecerá e exibirá informações sobre a conexão. Este recurso está disponível apenas ao conectar nós no editor.

../../_images/signals_14_signals_connection_info.png

Vamos substituir a linha com a palavra-chave pass com o código que alternará o movimento do nó.

Nosso Sprite se move graças ao código na função _process(). Godot fornece um método para ativar e desativar o processamento: Node.set_process(). Outro método da classe Node, is_processing(), retorna true se o processamento ocioso estiver ativo. Podemos usar a palavra-chave not para inverter o valor.

func _on_Button_pressed():
    set_process(not is_processing())

Esta função alternará o processamento e, por sua vez, o movimento do ícone ligado e desligado ao pressionar o botão.

Antes de testar o jogo, precisamos simplificar nossa função _process() para mover o nó automaticamente e não esperar pela entrada do usuário. Substitua-o pelo seguinte código, que vimos duas lições atrás:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

O seu código ``Sprite.gd``completo deve se parecer com o seguinte.

extends Sprite

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())

Execute a cena agora e clique no botão para ver o sprite andar e parar.

Conectando um sinal via código

Você pode conectar sinais via código em vez de usar o editor. Isso é necessário quando você cria nós ou instancia cenas dentro de um script.

Vamos usar um nó diferente aqui. Godot tem um nó Timer que é útil para implementar o tempo de resfriamento de habilidades, recarga de armas e muito mais.

No Espaço de Trabalho 3D, você pode trabalhar com malhas (meshes), luzes e projetar níveis para jogos 3D. Pressione Ctrl + F2 (ou Alt + 2 no macOS) para acessá-lo.

No painel da Cena, clique com o botão direito do mouse no nó Sprite e adicione um novo nó filho. Pesquise por Timer e adicione o nó correspondente. Sua cena agora deve ficar assim.

../../_images/signals_15_scene_tree.png

Com o nó Timer selecionado, vá para o Inspetor e marque a propriedade Autostart.

../../_images/signals_18_timer_autostart.png

Clique no ícone de script ao lado de Sprite para voltar ao espaço de trabalho de script.

../../_images/signals_16_click_script.png

Precisamos fazer duas operações para conectar os nós via código:

  1. Obter uma referência ao Timer do Sprite.

  2. Chamar o método connect() do Timer.

Nota

Para se conectar a um sinal via código, você precisa chamar o método connect() do nó que deseja escutar. Neste caso, queremos ouvir o sinal de "timeout" do Timer.

Queremos conectar o sinal quando a cena for instanciada, e podemos fazer isso usando a função interna Node._ready(), que é chamada automaticamente pelo mecanismo quando um nó é totalmente instanciado .

Para obter uma referência a um nó relativo ao atual, usamos o método Node.get_node(). Podemos armazenar a referência em uma variável.

func _ready():
    var timer = get_node("Timer")

A função get_node() olha para os filhos do Sprite e obtém os nós pelo nome. Por exemplo, se você renomeou o nó Timer para "BlinkingTimer" no editor, você teria que mudar a chamada para get_node("BlinkingTimer").

Agora podemos conectar o Timer ao Sprite com a função _ready().

func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")

A linha fica assim: conectamos o sinal "timeout" do Timer ao nó ao qual o script está anexado (self). Quando o Timer emitir "timeout", queremos chamar a função "_on_Timer_timeout", que precisamos definir. Vamos adicioná-lo na parte inferior do nosso script e usá-lo para alternar a visibilidade do nosso sprite.

func _on_Timer_timeout():
    visible = not visible

A propriedade visible é um booleano que controla a visibilidade do nosso nó. A linha visible = not visible alterna o valor. Se visible for true, torna-se false, e vice-versa.

Se você executar a cena agora, verá que o sprite liga e desliga, em intervalos de um segundo.

Script completo

Isso é tudo para a nossa pequena demonstração de ícone do Godot em movimento e piscando! Aqui está o arquivo Sprite.gd completo para referência.

extends Sprite

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.connect("timeout", self, "_on_Timer_timeout")


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_Button_pressed():
    set_process(not is_processing())


func _on_Timer_timeout():
    visible = not visible

Sinais personalizados

Nota

Esta seção é uma referência sobre como definir e usar seus próprios sinais e não se baseia no projeto criado nas lições anteriores.

Você pode definir sinais personalizados em um script. Digamos, por exemplo, que você deseja mostrar uma tela de game over quando a saúde do jogador chegar a zero. Para fazer isso, você pode definir um sinal chamado "died" ou "health_depleted" quando a saúde chegar a 0.

extends Node2D

signal health_depleted

var health = 10

Nota

Como os sinais representam eventos que acabaram de ocorrer, geralmente usamos um verbo de ação no pretérito em seus nomes.

Seus sinais funcionam da mesma forma que os integrados: eles aparecem na guia Nó e você pode se conectar a eles como qualquer outro.

../../_images/signals_17_custom_signal.png

Para emitir um sinal em seus scripts, chame emit_signal().

func take_damage(amount):
    health -= amount
    if health <= 0:
        emit_signal("health_depleted")

Um sinal também pode opcionalmente declarar um ou mais argumentos. Especifique os nomes dos argumentos entre parênteses:

extends Node

signal health_changed(old_value, new_value)

Nota

Os argumentos de sinal aparecem na aba nó do editor e o Godot pode usá-los para gerar funções de retorno de chamada para você. No entanto, você ainda pode emitir qualquer número de argumentos ao emitir sinais. Então cabe a você emitir os valores corretos.

Para emitir valores junto com o sinal, adicione-os como argumentos extras à função emit_signal():

func take_damage(amount):
    var old_health = health
    health -= amount
    emit_signal("health_changed", old_health, health)

Resumo

Qualquer nó em Godot emite sinais quando algo específico acontece com eles, como um botão sendo pressionado. Outros nós podem se conectar a sinais individuais e reagir a eventos selecionados.

Os sinais têm muitos usos. Com eles, você pode reagir a um nó entrando ou saindo do mundo do jogo, a uma colisão, a um personagem entrando ou saindo de uma área, a um elemento da interface que muda de tamanho e muito mais.

Por exemplo, um Area2D representando uma moeda emite um sinal body_entered sempre que o corpo físico do jogador entra em forma de colisão, permitindo que você saiba quando o jogador a coletou.

Na próxima seção, doc_your_first_game, você criará um jogo completo contendo vários usos de sinais para conectar diferentes componentes do jogo.