Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
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 isso, no Godot, você usaria sinais.
Assim como métodos (Callable), sinais são um tipo de dado de primeira classe desde a Godot 4.0. Isso significa que você pode passá-los diretamente como argumentos de método sem precisar usá-los como strings, o que permite um melhor autocompletar e reduz a chance de erros. Consulte a referência da classe Signal para ver uma lista do que você pode fazer diretamente com o tipo "Signal".
Ver também
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: Padrões de Programações de Jogos.
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.
Nota
Para esse projeto, vamos seguir as convenções de nomeação do Godot.
GDScript: Classes (nós) utilizam PascalCase, variáveis e funções utilizam snake_case e constantes utilizam letras maiúsculas (veja Guia de Estilo GDScript).
C#: Classes, variáveis exportadas e métodos usam PascalCase; campos privados usam _camelCase; variáveis locais e parâmetros usam camelCase (consulte Guia de Estilo C#). Tome cuidado para digitar os nomes dos métodos exatamente ao conectar sinais.
Configuração da cena
Para adicionar um botão ao nosso jogo, criaremos uma nova cena que incluirá tanto um Button quanto a cena sprite_2d.tscn que criamos na lição Criando seu primeiro script.
Crie uma nova cena acessando o menu .
Na aba Scene, clique no botão . Isso adicionará um Node2D como nossa raiz.
No painel Sistema de Arquivos, clique e arraste o arquivo sprite_2d.tscn que você salvou anteriormente para o Node2D para instanciá-lo.
Queremos adicionar outro nó como irmão (sibling) do Sprite2D. Para fazer isso, clique com o botão direito em Node2D e selecione .
Procure pelo nó Button e adicione-o.
O nó é pequeno por padrão. Clique e arraste a alça inferior direita do Button na viewport para redimensioná-lo.
Se você não vir as alças, verifique se a ferramenta de seleção está ativa na barra de ferramentas.
Clique e arraste o botão para aproximá-lo do sprite.
Você também pode escrever um rótulo no Botão editando sua propriedade Text no Inspector. Digite Toggle motion.
Sua árvore da cena e o Viewport devem se parecer com isso.
Salve sua cena recém-criada como node_2d.tscn, se ainda não o fez. Você pode então executá-la com F6 (Cmd + R no macOS). No momento, o botão estará visível, mas nada acontecerá se você o pressionar.
Conectando um sinal no editor
Aqui, queremos conectar o sinal "pressed" do Button ao nosso Sprite2D e queremos chamar uma nova função que alternará seu movimento entre ativado e desativado. Precisamos ter um script anexado ao nó Sprite2D, o que já fizemos na lição anterior.
Você pode conectar sinais na aba Signals. Selecione o nó Button e, no lado direito do editor, clique na aba chamada Signals ao lado do Inspector.
O painel exibe uma lista de sinais disponíveis no nó selecionado.
Clique duas vezes no sinal "pressed" para abrir a janela de conexão do nó.
Lá, você pode conectar o sinal ao nó Sprite2D. 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, nomeamos esses métodos de callback como "_on_nome_do_no_nome_do_sinal". Aqui, será "_on_button_pressed".
Nota
Ao conectar sinais pelo painel Signals do editor, você pode usar dois modos. O modo simples permite conectar apenas a nós que tenham um script anexado e cria uma nova função de callback neles.
The advanced view lets you connect to any node and any built-in function, add arguments to the callback, and set options. You can toggle the mode in the window's bottom-left by clicking the button.
Nota
Se você estiver usando um editor externo (como o VS Code), essa geração automática de código pode não funcionar. Nesse caso, você precisa conectar o sinal por código, conforme explicado na próxima seção.
Clique no botão para concluir a conexão do sinal e saltar para a área de trabalho Script. Você deve ver o novo método com um ícone de conexão na margem esquerda.
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.
Vamos substituir a linha com a palavra-chave pass com o código que alternará o movimento do nó.
Nosso Sprite2D se move graças ao código na função _process(). O 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())
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
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
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
Seu código sprite_2d.gd completo deve ser parecido com o seguinte.
extends Sprite2D
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())
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
Execute a cena atual pressionando F6 (Cmd + R no macOS) e clique no botão para ver o sprite iniciar 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.
Volte para a área de trabalho 2D. Você pode clicar no texto "2D" na parte superior da janela ou pressionar Ctrl + F1 (Ctrl + Cmd + 1 no macOS).
No painel Scene, clique com o botão direito no nó Sprite2D e adicione um novo nó filho. Procure por Timer e adicione o nó correspondente. Sua cena agora deve se parecer com isto.
Com o nó Timer selecionado, vá ao Inspector e ative a propriedade Autostart.
Clique no ícone de script ao lado de Sprite2D para voltar ao ambiente de scripts.
Precisamos fazer duas operações para conectar os nós via código:
Obtenha uma referência ao Timer a partir do Sprite2D.
Chame o método
connect()no sinal "timeout" do Timer.
Nota
Para conectar-se a um sinal por código, você precisa chamar o método connect() do sinal que deseja escutar. Neste caso, queremos escutar o sinal "timeout" do Timer.
Queremos conectar o sinal quando a cena for instanciada, e podemos fazer isso usando a função integrada Node._ready(), que é chamada automaticamente pela engine quando um nó está 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")
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
}
A função get_node() examina os filhos do Sprite2D e obtém nós pelo nome. Por exemplo, se você renomeasse o nó Timer para "BlinkingTimer" no editor, teria que alterar a chamada para get_node("BlinkingTimer").
Agora podemos conectar o Timer ao Sprite2D na função _ready().
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
A linha lê-se assim: conectamos o sinal "timeout" do Timer ao nó ao qual o script está anexado. Quando o Timer emitir timeout, queremos chamar a função _on_timer_timeout(), que precisamos definir. Vamos adicioná-la na parte inferior do nosso script e usá-la para alternar a visibilidade do nosso sprite.
Nota
Por convenção, nomeamos estes métodos de callback em GDScript como "_on_nome_do_no_nome_do_sinal" e em C# como "OnNomeDoNoNomeDoSinal". Aqui, ele será "_on_timer_timeout para GDScript e OnTimerTimeout() para C#.
func _on_timer_timeout():
visible = not visible
private void OnTimerTimeout()
{
Visible = !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 Node2D agora, verá que o sprite pisca ligando e desligando em intervalos de um segundo.
Script completo
Isso conclui nossa pequena demonstração do ícone do Godot se movendo e piscando! Aqui está o arquivo completo sprite_2d.gd para referência.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_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
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !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
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepletedEventHandler();
private int _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 aba Signals e você pode se conectar a eles como a qualquer outro.
Para emitir um sinal em seus scripts, chame emit() no sinal.
func take_damage(amount):
health -= amount
if health <= 0:
health_depleted.emit()
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(SignalName.HealthDepleted);
}
}
Um sinal também pode opcionalmente declarar um ou mais argumentos. Especifique os nomes dos argumentos entre parênteses:
extends Node2D
signal health_changed(old_value, new_value)
var health = 10
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
private int _health = 10;
}
Nota
Os argumentos do sinal aparecem no painel Signals do editor, e o Godot pode usá-los para gerar funções de callback para você. No entanto, você ainda pode emitir qualquer quantidade de argumentos ao emitir sinais. Portanto, cabe a você emitir os valores corretos.
Para emitir valores junto com o sinal, adicione-os como argumentos extras à função emit():
func take_damage(amount):
var old_health = health
health -= amount
health_changed.emit(old_health, health)
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(SignalName.HealthChanged, oldHealth, _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, Seu primeiro jogo 2D, você criará um jogo 2D completo e colocará em prática tudo o que aprendeu até agora.