Voltar para o Blog
Quest Log

Sinais no Godot 4: guia de signals para desacoplar seus nós

Diagrama de nós de uma cena do Godot ligados por linhas que representam conexões de eventos entre eles

Aprenda sinais (signals) no Godot 4 de verdade: o que são, sinais nativos, conectar pelo editor ou por código com .connect(), criar signal customizado e emitir com .emit().

Se você já programou alguma coisa no Godot, uma hora bateu aquela dúvida: como fazer um nó avisar outro que algo aconteceu sem escrever um get_node("../../../Player") gigante e frágil? A resposta são os sinais no Godot 4 (os famosos signals). Sinais são o jeito nativo da engine de dizer "ei, aconteceu isso aqui", e deixar que quem se interessar reaja. Neste tutorial você vai entender o que é um sinal, usar os sinais nativos dos nós, conectar pelo editor e por código, criar seus próprios sinais tipados e evitar os erros clássicos de quem está começando.

O que é um sinal e o padrão observer

Um sinal é um aviso que um nó emite quando algo acontece com ele. Um botão emite quando é clicado. Um Timer emite quando o tempo acaba. Uma Area2D emite quando um corpo entra nela. O nó não decide o que fazer com esse evento, ele só anuncia. Quem quiser reagir se "inscreve" para ouvir aquele aviso.

Esse desenho tem nome: padrão observer. Existe um objeto que fala (o sujeito, quem emite) e existem objetos que escutam (os observadores). O detalhe que muda tudo é que o emissor não conhece os ouvintes. O botão não tem a menor ideia de que o seu menu está escutando o clique dele. Ele só grita "fui clicado" e segue a vida.

Por que isso importa? Porque a alternativa é acoplamento. Sem sinais, o botão precisaria guardar uma referência ao menu e chamar menu.abrir_configuracoes() diretamente. Aí o botão só serve para aquele menu. Com sinais, o mesmo botão serve para qualquer coisa, porque quem decide a reação é quem escuta, não quem emite.

Sinais nativos: o que já vem pronto

Quase todo nó do Godot já emite sinais úteis de fábrica. Você não precisa criar nada, só escutar. Três exemplos que você vai usar o tempo todo:

  • Button emite pressed quando é clicado.
  • Area2D emite body_entered quando um corpo físico entra na área (passa o corpo como argumento).
  • Timer emite timeout quando a contagem chega a zero.

Repare que alguns sinais mandam informação junto. O body_entered não avisa só "alguém entrou", ele te entrega quem entrou. Isso é ouro para lógica de jogo: colisão de projétil, checkpoint, gatilho de armadilha, tudo passa por aí.

Para ver a lista completa de sinais de um nó, selecione ele na cena e abra a aba Node (do lado da aba Inspector), seção Signals. Ali está tudo que aquele nó sabe emitir.

Conectar um sinal pelo editor

O jeito mais visual de conectar é pelo próprio editor, e para começar ele é ótimo porque você enxerga a ligação acontecendo.

Digamos que você tem um Button e um script na cena. Selecione o botão, vá na aba Node > Signals, dê dois cliques em pressed. O Godot abre uma janela perguntando qual nó vai receber o sinal e qual método vai ser chamado. Ele sugere um nome automático no padrão _on_botao_pressed. Confirme, e o editor cria o método no seu script e registra a conexão.

O resultado no código fica assim:

extends Control

func _on_botao_pressed() -> void:
    print("Botão clicado pelo editor!")

Quando um sinal foi conectado pelo editor, aparece um ícone verde de conexão ao lado da declaração da função no script. É o sinal (trocadilho intencional) de que aquele método está ligado a um evento.

O editor é prático, mas tem um efeito colateiral: a conexão fica invisível no código. Quem lê só o script não sabe de onde vem a chamada. Por isso, conforme o projeto cresce, muita gente migra para conectar por código.

Conectar um sinal por código com .connect()

No Godot 4, conectar por código é limpo e legível. A sintaxe é objeto.nome_do_sinal.connect(metodo). Sinal é um membro de primeira classe do objeto, então você acessa botao.pressed e chama .connect() nele, passando a função que vai reagir.

extends Control

@onready var botao: Button = $Button

func _ready() -> void:
    botao.pressed.connect(_on_botao_pressed)

func _on_botao_pressed() -> void:
    print("Botão clicado por código!")

Faça isso dentro do _ready(), que roda uma vez quando o nó entra na cena. A vantagem é que a ligação está escrita ali, no script, à vista de todo mundo. Bateu o olho, entendeu o fluxo.

Se o sinal manda argumentos, seu método precisa receber os mesmos, na mesma ordem. O body_entered de uma Area2D passa o corpo que entrou:

extends Area2D

func _ready() -> void:
    body_entered.connect(_on_body_entered)

func _on_body_entered(corpo: Node2D) -> void:
    print("Entrou na área: ", corpo.name)
    if corpo.is_in_group("player"):
        print("Foi o jogador!")

E o Timer? Mesma ideia. Um caso comum é criar um cronômetro e reagir ao timeout:

extends Node

@onready var timer: Timer = $Timer

func _ready() -> void:
    timer.timeout.connect(_on_timer_timeout)
    timer.start(3.0)

func _on_timer_timeout() -> void:
    print("Três segundos se passaram.")
Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Cuidado: sinais do Godot 3 têm outra sintaxe

Se você achar tutoriais antigos, vai ver conexões escritas assim:

# SINTAXE DO GODOT 3, NÃO USE NO GODOT 4
botao.connect("pressed", self, "_on_botao_pressed")

Isso é Godot 3. Repare que o sinal é uma string ("pressed"), o método também é uma string ("_on_botao_pressed"), e ainda tem um self no meio. No Godot 4 isso foi substituído pela sintaxe de primeira classe, sem strings:

# GODOT 4, a forma correta
botao.pressed.connect(_on_botao_pressed)

A diferença não é só estética. Com string, um erro de digitação no nome do método só estoura em tempo de execução. Com a referência direta, o editor te avisa na hora e ainda oferece autocompletar. Sempre que copiar código da internet, cheque se ele está usando string nesse formato. Se estiver, é Godot 3 e precisa ser convertido.

Declarar e emitir seu próprio sinal

Aqui é onde os sinais deixam de ser detalhe e viram arquitetura. Você pode criar sinais próprios para os seus scripts, não só usar os que já vêm prontos.

Imagine um script de vida do jogador. Quando a vida muda, várias coisas na tela precisam saber: a barra de vida, um efeito de tela vermelha, talvez o som de dano. O jeito acoplado seria o Player chamar cada um desses. O jeito desacoplado é o Player emitir um sinal e deixar cada interessado escutar.

Declare o sinal no topo do script com a palavra-chave signal, e já defina os parâmetros tipados:

extends CharacterBody2D

signal vida_mudou(nova_vida: int)
signal morreu

var vida: int = 100

func tomar_dano(quantidade: int) -> void:
    vida -= quantidade
    vida_mudou.emit(vida)
    if vida <= 0:
        morreu.emit()

Três pontos importantes nesse trecho:

  1. signal vida_mudou(nova_vida: int) declara um sinal que carrega um inteiro. O tipo int documenta o que vai junto e ajuda o editor a te alertar se você emitir errado.
  2. vida_mudou.emit(vida) dispara o sinal e manda o valor atual. Essa é a forma moderna do Godot 4: nome_do_sinal.emit(argumentos). Nada de emit_signal("vida_mudou", vida), que é o formato antigo com string.
  3. Um sinal pode não ter parâmetro nenhum, como morreu. Aí você declara e emite sem argumentos.

Agora qualquer nó pode escutar o Player sem que o Player saiba que eles existem. A barra de vida, por exemplo:

extends ProgressBar

@onready var player: CharacterBody2D = $"../Player"

func _ready() -> void:
    player.vida_mudou.connect(_on_player_vida_mudou)

func _on_player_vida_mudou(nova_vida: int) -> void:
    value = nova_vida

O Player continua sem uma linha sequer mencionando a barra de vida. Quer adicionar um efeito de tela quando a vida muda? Cria outro nó, conecta no mesmo vida_mudou, pronto. O emissor não muda.

Por que sinais desacoplam seus nós

Volte na dor do começo: o get_node("../../../Player"). Esse caminho é uma bomba-relógio. No dia em que você arrastar o Player para outro lugar na árvore, ou renomear um nó pai, aquele ../../../ quebra e o jogo trava. Você amarrou seu código à estrutura exata da cena.

Sinais atacam o problema por outro lado. Em vez de o nó filho ir caçar o Player lá em cima, o Player avisa quando algo muda e os interessados reagem. A dependência aponta para "um evento aconteceu", não para "o nó que está três níveis acima e se chama Player".

Uma regra prática que ajuda muito: sinais sobem, chamadas descem. Um nó pai pode chamar métodos dos filhos diretamente, porque ele os criou e sabe que existem. Mas um filho falando com o pai deve, de preferência, emitir um sinal, porque o filho não deveria depender de quem é o pai dele. Isso mantém os componentes reutilizáveis. O mesmo Player funciona em qualquer cena, porque ele não presume nada sobre a vizinhança.

Quando o desacoplamento não resolve tudo (por exemplo, um sistema global que muitas cenas precisam acessar, como pontuação ou áudio), o caminho costuma ser um singleton. Vale entender como funciona um autoload no Godot, que combina bem com sinais: o autoload emite, e cenas espalhadas escutam.

O erro clássico: conectar duas vezes (ou esquecer de conectar)

Dois bugs de sinal aparecem em praticamente todo projeto iniciante.

O primeiro é conectar o mesmo sinal duas vezes. Acontece muito quando você conecta pelo editor e, sem lembrar, conecta de novo por código. O resultado é o método rodando duas vezes a cada evento: um clique conta como dois, o inimigo toma dano dobrado, o som toca em eco. Nas versões atuais do Godot isso pode até estourar um erro dizendo que a conexão já existe.

A regra é simples: conecte em um lugar só. Escolha editor ou código e siga esse padrão no projeto inteiro. Se você realmente precisa conectar em um ponto que roda mais de uma vez, cheque antes:

func _ready() -> void:
    if not botao.pressed.is_connected(_on_botao_pressed):
        botao.pressed.connect(_on_botao_pressed)

O segundo bug é o oposto: esquecer de conectar. Você escreve um método _on_botao_pressed lindo, roda o jogo, clica no botão e nada acontece. O método está lá, mas nunca foi ligado a nenhum sinal, então nunca é chamado. Não existe erro, o jogo só fica mudo. Quando um método _on_alguma_coisa não dispara, a primeira suspeita é sempre: será que eu conectei? Cheque o ícone verde no editor ou procure a linha .connect() no código.

Uma dica de ouro para não se perder: mantenha o padrão de nome _on_ + nome do nó + nome do sinal. Assim você olha _on_timer_timeout e sabe na hora que aquilo é reação ao timeout de um Timer. Consistência de nome é o que salva você três meses depois, quando voltar no projeto sem lembrar de nada.

Fechando o ciclo

Sinais são um daqueles conceitos que, depois que caem a ficha, mudam como você estrutura tudo. Recapitulando o essencial: um sinal é um aviso que um nó emite (padrão observer), os nós já vêm com sinais nativos como pressed, body_entered e timeout, você conecta pelo editor ou por código com .connect(), cria os seus com a palavra-chave signal e parâmetros tipados, e dispara com .emit(). O ganho real é desacoplamento: menos get_node frágil, mais componentes que funcionam sozinhos.

O próximo passo é praticar em algo concreto. Um menu principal é o cenário perfeito, cheio de botões emitindo pressed. Siga o passo a passo de como criar um menu principal no Godot e conecte cada botão via sinal. E se você ainda está decidindo em qual linguagem investir, vale comparar C# e GDScript no Godot, porque os sinais funcionam nas duas, com pequenas diferenças de sintaxe. Comece pequeno, um sinal de cada vez, e logo você vai desenhar cenas inteiras pensando em quem avisa o quê.

Perguntas frequentes

Qual a diferença entre sinais e chamar um método direto no Godot?

Chamar um método direto acopla os dois nós: quem chama precisa saber quem é o outro e onde ele está na árvore. Um sinal inverte isso. O nó que emite não sabe quem escuta, ele só avisa que algo aconteceu. Isso deixa o código flexível e fácil de reorganizar.

Devo usar .emit() ou emit_signal() no Godot 4?

No Godot 4 a forma recomendada é a sintaxe de primeira classe: nome_do_sinal.emit(argumentos). O antigo emit_signal("nome", argumentos) ainda funciona por compatibilidade, mas usa string e não tem autocompletar nem checagem, então prefira sempre o .emit().

Como conectar um sinal por código em vez de pelo editor?

Use a sintaxe objeto.nome_do_sinal.connect(metodo). Exemplo: botao.pressed.connect(_on_botao_pressed). O método passado recebe os mesmos argumentos que o sinal emite, na mesma ordem.

Por que meu sinal está disparando duas vezes?

Quase sempre é sinal conectado duas vezes: uma pelo editor e outra por código, ou um .connect() dentro de uma função que roda mais de uma vez. Conecte em um só lugar. Se precisar checar, use is_connected() antes de conectar.

Sinal customizado no Godot 4 pode ter parâmetros tipados?

Sim. Declare com signal vida_mudou(nova_vida: int). Os tipos servem de documentação e ajudam o editor a te avisar quando você emite ou conecta com o formato errado.

Preciso desconectar um sinal quando o nó é destruído?

Na maioria dos casos não. Se o nó que emite ou o que escuta sai da árvore e é liberado, o Godot limpa a conexão. Você só precisa desconectar manualmente em casos específicos, como reconectar dinamicamente objetos que continuam vivos.