teoria

Sinais (Signals): Comunicação entre Nodes

Aprenda sobre sinais (signals): comunicação entre nodes

25 min
Aula 2 de 5

Sinais (Signals): Comunicação entre Nodes

Bem-vindos à aula sobre Sinais no Godot! 🚀 Este é um dos conceitos mais fundamentais e poderosos para construir jogos robustos e flexíveis no Godot. Sinais permitem que seus Nodes se comuniquem de forma eficiente e desacoplada, o que é crucial para um bom design de jogo.

1. Introdução aos Sinais

No Godot, tudo é um Node. E esses Nodes precisam interagir uns com os outros: um botão precisa avisar que foi clicado, um inimigo precisa informar que foi derrotado, ou um jogador precisa notificar que sua saúde mudou. Como eles fazem isso sem criar uma teia de dependências complexas?

É aí que entram os Sinais! 📡

Sinais são um mecanismo de comunicação baseado em eventos. Imagine-os como um sistema de rádio:

  • Um Node (o emissor) "transmite" uma mensagem (um sinal) para o mundo.
  • Outros Nodes (os receptores) podem "sintonizar" essa transmissão e reagir a ela.

A grande vantagem é que o emissor não precisa saber quem está ouvindo, nem como eles vão reagir. Isso promove um design de código mais limpo, flexível e fácil de manter, conhecido como desacoplamento.

2. Explicação Detalhada com Exemplos

Vamos mergulhar nos detalhes de como os sinais funcionam.

2.1. Componentes de um Sinal

Um sistema de sinal envolve três partes principais:

  1. Emissor (Emitter): O Node que dispara o sinal quando algo acontece.
  2. Sinal (Signal): A "mensagem" ou "evento" que é disparado. Pode carregar argumentos para passar informações.
  3. Receptor (Receiver): O Node que escuta o sinal e executa um método em resposta.

2.2. Tipos de Sinais

Existem dois tipos principais de sinais no Godot:

a) Sinais Embutidos (Built-in Signals)

A maioria dos Nodes do Godot já vem com sinais pré-definidos para eventos comuns. Por exemplo:

  • Button tem o sinal pressed (quando o botão é clicado).
  • Area2D tem body_entered (quando um corpo entra na área).
  • Timer tem timeout (quando o tempo do timer acaba).

Você pode ver os sinais embutidos de qualquer Node selecionando-o na árvore de cena e indo para a aba Node no painel do Inspetor:

Godot Node Tab Signals

b) Sinais Personalizados (Custom Signals)

Você pode definir seus próprios sinais em seus scripts GDScript para eventos específicos do seu jogo. Isso é extremamente útil para criar lógicas complexas e modulares.

Para definir um sinal personalizado, use a palavra-chave signal:

# Player.gd
extends CharacterBody2D
 
# Define um sinal personalizado chamado 'health_changed' que passa um argumento 'new_health'
signal health_changed(new_health: int) 
signal game_over() # Um sinal que não passa argumentos
 
var _health: int = 100:
    set(value):
        _health = clampi(value, 0, 100) # Garante que a saúde esteja entre 0 e 100
        health_changed.emit(_health) # Emite o sinal com a nova saúde
        if _health <= 0:
            game_over.emit() # Emite o sinal de game over
 
func take_damage(amount: int):
    self.health -= amount
 
func heal(amount: int):
    self.health += amount

2.3. Conectando Sinais

Existem duas maneiras principais de conectar sinais:

a) Conectando Sinais via Editor (UI)

Esta é a maneira mais fácil para sinais embutidos e é ótima para prototipagem ou para conexões visuais claras:

  1. Selecione o Node que emite o sinal na sua árvore de cena (ex: um Button).
  2. Vá para a aba Node no painel do Inspetor.
  3. Na seção "Signals", clique duas vezes no sinal que você quer conectar (ex: pressed).
  4. Uma janela "Connect a Signal" aparecerá.
  5. No painel esquerdo, selecione o Node receptor (ex: seu Main ou UI Node).
  6. O Godot sugerirá um nome de método. Você pode aceitá-lo ou renomeá-lo (ex: _on_Button_pressed).
  7. Clique em "Connect".

O Godot criará automaticamente um novo método no script do Node receptor e conectará o sinal a ele.

# Exemplo de método gerado pelo editor no script do Node receptor
func _on_Button_pressed():
    print("Botão pressionado!")

b) Conectando Sinais via Código

Conectar sinais via código oferece mais flexibilidade e é essencial para sinais personalizados ou quando as conexões precisam ser dinâmicas.

Use o método connect() do Node emissor.

Sintaxe para Godot 4.x:

# emitter.connect(signal: String, target_callable: Callable, flags: int = 0)
# A 'Callable' é uma referência a um método ou função.
 
# Exemplo: Conectando um sinal embutido
func _ready():
    var button_node = get_node("Button") # Supondo que você tem um Button na cena
    if button_node:
        button_node.pressed.connect(_on_button_pressed) # Conecta o sinal 'pressed' ao método '_on_button_pressed'
 
func _on_button_pressed():
    print("Botão pressionado via código!")
 
# Exemplo: Conectando um sinal personalizado
func _ready():
    var player_node = get_node("Player") # Supondo que você tem um Player na cena com o script acima
    if player_node:
        player_node.health_changed.connect(_on_player_health_changed)
        player_node.game_over.connect(_on_player_game_over)
 
func _on_player_health_changed(new_health: int):
    print("Saúde do jogador mudou para: ", new_health)
 
func _on_player_game_over():
    print("GAME OVER!")

Sintaxe para Godot 3.x (para referência):

# emitter.connect(signal: String, target_node: Object, target_method: String, binds: Array = [], flags: int = 0)
 
# Exemplo: Conectando um sinal embutido (Godot 3.x)
func _ready():
    var button_node = get_node("Button")
    if button_node:
        button_node.connect("pressed", self, "_on_button_pressed") # self é o Node receptor, "_on_button_pressed" é o nome do método
 
func _on_button_pressed():
    print("Botão pressionado via código (Godot 3.x)!")
 
# Exemplo: Conectando um sinal personalizado (Godot 3.x)
func _ready():
    var player_node = get_node("Player")
    if player_node:
        player_node.connect("health_changed", self, "_on_player_health_changed")
        player_node.connect("game_over", self, "_on_player_game_over")

2.4. Emitindo Sinais

Para disparar um sinal, você usa o método emit() no próprio sinal (Godot 4.x) ou emit_signal() (Godot 3.x).

Sintaxe para Godot 4.x:

# No script do emissor (ex: Player.gd)
# ...
signal health_changed(new_health: int)
# ...
 
func take_damage(amount: int):
    self.health -= amount # O setter de 'health' já chama 'emit()'
    # Ou diretamente:
    # health_changed.emit(self.health)

Sintaxe para Godot 3.x (para referência):

# No script do emissor (ex: Player.gd)
# ...
signal health_changed(new_health)
# ...
 
func take_damage(amount: int):
    self.health -= amount
    emit_signal("health_changed", self.health) # Usar o nome do sinal como string

3. Código de Exemplo Oficial (Adaptado)

Vamos criar um exemplo prático que combina sinais embutidos e personalizados para um sistema de saúde de jogador e uma interface de usuário (UI).

Estrutura da Cena:

- Main (Node2D)
    - Player (CharacterBody2D)
        - CollisionShape2D
        - Sprite2D
    - UI (CanvasLayer)
        - HealthLabel (Label)
        - DamageButton (Button)
        - HealButton (Button)

Script Player.gd:

# Player.gd
extends CharacterBody2D
 
# Sinais personalizados
signal health_changed(new_health: int)
signal player_died()
 
var _health: int = 100:
    set(value):
        var old_health = _health
        _health = clampi(value, 0, 100) # Garante que a saúde esteja entre 0 e 100
        if _health != old_health: # Emite apenas se a saúde realmente mudou
            health_changed.emit(_health)
            if _health <= 0:
                player_died.emit()
                print("Player died!")
 
func _ready():
    # Inicializa a saúde para que o sinal seja emitido e a UI atualizada
    health_changed.emit(_health)
 
func take_damage(amount: int):
    self.health -= amount
    print("Player took %s damage. Health: %s" % [amount, self.health])
 
func heal(amount: int):
    self.health += amount
    print("Player healed %s. Health: %s" % [amount, self.health])

Script UI.gd:

# UI.gd
extends CanvasLayer
 
@onready var health_label: Label = $HealthLabel
@onready var damage_button: Button = $DamageButton
@onready var heal_button: Button = $HealButton
 
var player_node: CharacterBody2D
 
func _ready():
    # Encontra o Node Player na cena (assumindo que Main é o pai da UI e Player)
    player_node = get_tree().get_first_node_in_group("players") # Uma forma robusta de encontrar o player
    # Ou se Main for o pai direto do Player:
    # player_node = get_parent().get_node("Player") 
 
    if player_node:
        # Conecta o sinal personalizado 'health_changed' do Player ao método da UI
        player_node.health_changed.connect(_on_player_health_changed)
        player_node.player_died.connect(_on_player_died)
        
        # Conecta os sinais embutidos dos botões
        damage_button.pressed.connect(_on_damage_button_pressed)
        heal_button.pressed.connect(_on_heal_button_pressed)
        
        # Atualiza a UI com a saúde inicial do jogador
        # A linha 'health_changed.emit(_health)' em _ready do Player.gd já faz isso.
        # Mas para garantir, podemos chamar aqui também:
        # _on_player_health_changed(player_node.health)
    else:
        print("Erro: Player Node não encontrado!")
 
func _on_player_health_changed(new_health: int):
    health_label.text = "Saúde: %s" % new_health
    if new_health <= 20:
        health_label.modulate = Color.RED # Alerta de saúde baixa
    else:
        health_label.modulate = Color.WHITE
 
func _on_player_died():
    health_label.text = "GAME OVER!"
    health_label.modulate = Color.BLACK
    damage_button.disabled = true
    heal_button.disabled = true
    print("UI: Player morreu, botões desabilitados.")
 
func _on_damage_button_pressed():
    if player_node:
        player_node.take_damage(10)
 
func _on_heal_button_pressed():
    if player_node:
        player_node.heal(15)

Configuração Adicional:

  • Certifique-se de adicionar o Player Node ao grupo "players" (no Inspetor, aba Node, seção Groups). Isso facilita encontrá-lo via código.
  • Crie os Nodes de UI (CanvasLayer, Label, Button) e posicione-os.

Este exemplo demonstra como:

  • O Player emite um sinal personalizado (health_changed) sempre que sua saúde muda.
  • A UI se conecta a esse sinal e atualiza um Label.
  • A UI também se conecta aos sinais embutidos (pressed) dos Buttons para chamar métodos no Player.
  • O Player emite um sinal player_died quando a saúde chega a zero, e a UI reage a isso.

4. Exercícios/Desafios

Hora de colocar a mão na massa! 💪

Desafio: Sistema de Pontuação com Sinais

Crie um sistema simples onde o jogador pode coletar moedas, e a pontuação é atualizada na UI.

Tarefas:

  • Crie uma nova cena para uma Moeda (Coin).
    • Use um Area2D como Node raiz para a Moeda.
    • Adicione um CollisionShape2D e um Sprite2D para a visualização.
    • Anexe um script Coin.gd à Moeda.
    • No Coin.gd, defina um sinal personalizado: signal collected(value: int).
    • Quando um body_entered no Area2D da moeda (assumindo que o corpo seja o jogador), emita o sinal collected com um valor (ex: 10).
    • Após emitir o sinal, remova a moeda da cena (ex: queue_free()).
  • Modifique sua cena principal (Main) para incluir algumas instâncias da Moeda.
  • Crie um Node GameManager na sua cena principal (pode ser um Node simples).
    • Anexe um script GameManager.gd a ele.
    • No _ready() do GameManager, encontre todas as moedas na cena (dica: use get_tree().get_nodes_in_group("coins") e adicione as moedas a um grupo "coins" no editor).
    • Para cada moeda encontrada, conecte o sinal collected da moeda a um método no GameManager (ex: _on_coin_collected(value)).
    • No método _on_coin_collected, adicione o valor à pontuação total.
  • Modifique a UI para incluir um ScoreLabel (Label).
    • No script UI.gd (ou um novo GameUI.gd), conecte-se a um novo sinal score_changed(new_score: int) que o GameManager emitirá.
    • Atualize o ScoreLabel com a nova pontuação.
    • Certifique-se de que o GameManager emita o sinal score_changed sempre que a pontuação for atualizada.

Este desafio irá solidificar sua compreensão sobre como criar e gerenciar múltiplos emissores de sinais e um único receptor centralizado (GameManager) que, por sua vez, comunica-se com a UI.

5. Resumo e Próximos Passos

Parabéns! Você dominou um dos conceitos mais importantes do Godot: os Sinais!

🔑 Pontos Chave:

  • Sinais permitem comunicação desacoplada entre Nodes.
  • Existem sinais embutidos (da maioria dos Nodes) e sinais personalizados (que você define).
  • Você pode conectar sinais via Editor (UI) ou via Código (connect()).
  • Sinais são disparados usando emit() (Godot 4.x) ou emit_signal() (Godot 3.x).
  • Eles são a base para uma arquitetura de jogo modular e flexível.

Com os sinais, você pode criar interações complexas sem transformar seu código em um emaranhado de referências diretas. Isso é fundamental para a escalabilidade e manutenção do seu projeto.

Próximos Passos:

No próximo módulo, vamos explorar outros mecanismos importantes para a lógica do jogo, como Timers e como lidar com Eventos de Input do jogador. Continue praticando com os sinais, pois eles aparecerão em quase todos os seus projetos Godot!

© 2025 Escola All Dev. Todos os direitos reservados.

Sinais (Signals): Comunicação entre Nodes - Fundamentos do Godot: Desenvolvimento de games para iniciantes | escola.all.dev.br