Grupos no Godot: Organizar e Chamar Vários Nós de Uma Vez

Aprenda a usar grupos no Godot 4 para organizar nós e controlar vários de uma vez: add_to_group, call_group e get_nodes_in_group na prática, com inimigos de verdade.
Grupos no Godot: Organizar e Chamar Vários Nós de Uma Vez
Toda hora aparece a mesma pergunta em jogo: "como eu faço alguma coisa com TODOS os inimigos ao mesmo tempo?". Pausar todos, dar dano em todos, contar quantos sobraram. O instinto de iniciante é guardar um array de referências na mão e atualizar ele a cada spawn e a cada morte. Funciona até o dia em que uma referência morta derruba o jogo. Os grupos no Godot resolvem exatamente isso, e resolvem de graça: é um sistema de etiquetas embutido na SceneTree que a engine mantém atualizado sozinha.
A ideia é simples. Você marca um nó com um nome de grupo ("inimigos", "moedas", "persistir") e, de qualquer lugar do código, pergunta pra árvore quem está naquele grupo ou manda todo mundo do grupo executar um método. Sem array manual, sem referência pendurada, sem get_node com caminho gigante.
Nesse tutorial eu cubro o jeito de criar grupos pelo editor e por código, os três métodos que você vai usar todo dia (get_nodes_in_group, call_group e is_in_group), um caso real de gerenciamento de inimigos e as armadilhas do Godot 4 que ninguém te conta. Todo código é GDScript do Godot 4.x e roda como está.
Como funcionam os grupos no Godot
Um grupo é só um nome associado a um nó dentro da SceneTree. Não é um node, não é uma classe, não aparece na hierarquia. Quando o nó entra na árvore, ele passa a contar pro grupo; quando sai (incluindo quando é destruído com queue_free()), a engine tira ele da lista sozinha. Essa é a grande vantagem sobre manter sua própria lista: você nunca segura referência de nó morto.
Um nó pode estar em quantos grupos quiser. Um chefe pode estar em "inimigos" e em "chefes" ao mesmo tempo, e responder às duas chamadas.
Criando grupos pelo editor
Selecione o nó, abra o painel Node (a mesma aba dos sinais) e clique em Groups. Digite o nome e adicione. O grupo fica salvo junto com a cena, então toda instância daquela cena já nasce no grupo.
Esse caminho é ótimo pra coisas estruturais: a cena Inimigo.tscn inteira pertence ao grupo "inimigos", e pronto, todo inimigo que você instanciar já está marcado.
Criando grupos por código
Por código, o trio é direto:
extends CharacterBody2D
func _ready():
add_to_group("inimigos")
func morrer():
# Sai do grupo antes de qualquer lógica de morte,
# pra não receber chamadas de grupo enquanto toca a animação.
remove_from_group("inimigos")
queue_free()
O remove_from_group antes do queue_free é opcional na maioria dos casos, porque o nó sai do grupo de qualquer forma quando deixa a árvore. Mas é útil quando a morte tem um meio-termo: o inimigo ainda existe na tela tocando animação de morte, e você não quer que ele continue tomando dano de área nesse intervalo.
Uma dica de organização: nomes de grupo são strings, e string com typo falha em silêncio. Centralizar os nomes em constantes elimina essa classe de bug inteira:
# grupos.gd, registrado como autoload
class_name Grupos
const INIMIGOS = "inimigos"
const MOEDAS = "moedas"
const PERSISTIR = "persistir"
Aí o resto do jogo usa add_to_group(Grupos.INIMIGOS) e o editor te avisa se você errar o nome da constante. Com a string solta, ninguém avisa nada: o grupo "inimgos" é criado feliz da vida e fica vazio pra sempre.
get_nodes_in_group: buscando todo mundo do grupo
O método mais usado. Pede pra SceneTree a lista de nós vivos do grupo:
func dar_dano_em_area(dano: int):
for inimigo in get_tree().get_nodes_in_group("inimigos"):
inimigo.tomar_dano(dano)
Repare que a chamada é na árvore (get_tree()), não no nó. Grupos pertencem à SceneTree, então qualquer nó dentro da árvore consegue consultar qualquer grupo.
Contar quantos sobraram é a mesma lista:
func _verificar_vitoria():
if get_tree().get_nodes_in_group("inimigos").is_empty():
carregar_proxima_fase()
E quando você quer só um nó, tipo a referência do player, existe um atalho que devolve o primeiro membro do grupo (ou null se o grupo está vazio):
@onready var player = get_tree().get_first_node_in_group("player")
Esse padrão de marcar o player com um grupo "player" e buscar por grupo é muito mais robusto que get_node("/root/Main/World/Player"). Se você reorganizar a cena amanhã, o caminho quebra; o grupo continua funcionando.
call_group: chamando um método em todos de uma vez
Em vez de buscar a lista e iterar, você pode mandar a árvore chamar um método em todos os membros:
get_tree().call_group("inimigos", "tomar_dano", 10)
O primeiro argumento é o grupo, o segundo é o nome do método, e o resto vira argumento da chamada. Pro call_group fazer sentido, todo nó do grupo deve implementar aquele método; trate isso como contrato do grupo. Se "inimigos" responde a tomar_dano(quantidade), todo script de inimigo tem essa função, mesmo que algum chefe a sobrescreva pra ignorar dano em certa fase.
Detalhe que muda tudo se você veio do Godot 3: no Godot 4 o call_group é imediato. Ele executa o método em todos os nós na hora, dentro do mesmo frame, na ordem da árvore. No Godot 3 ele era adiado por padrão. Se você quer o comportamento adiado (executar quando o frame atual terminar), pede explicitamente:
get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFERRED, "inimigos", "congelar")
Quando usar o adiado? Sempre que a chamada mexe com a física ou com a estrutura da árvore no meio de um callback de física. Destruir todos os inimigos de dentro de um body_entered, por exemplo, é o caso clássico: remover corpos durante o passo de física gera erro, e o GROUP_CALL_DEFERRED empurra a execução pra um momento seguro.
call_group ou iterar na mão?
Os dois chegam no mesmo lugar, então a escolha é prática:
call_group: quando a ação é igual pra todos e você não precisa de retorno. Pausar, congelar, mudar de estado. Uma linha.get_nodes_in_group+ loop: quando você precisa filtrar, calcular algo por nó ou coletar resultado. Dar dano só em quem está num raio, achar o inimigo mais próximo, somar XP.
func inimigo_mais_proximo() -> Node2D:
var mais_perto: Node2D = null
var menor_distancia := INF
for inimigo in get_tree().get_nodes_in_group("inimigos"):
var d = global_position.distance_squared_to(inimigo.global_position)
if d < menor_distancia:
menor_distancia = d
mais_perto = inimigo
return mais_perto
Isso aí não tem como fazer com call_group, porque cada nó pede uma conta diferente e o resultado é uma comparação entre todos.
Caso real: gerenciando uma horda de inimigos
Juntando as peças num cenário que aparece em quase todo jogo de ação: inimigos que nascem de spawners, um ataque especial que acerta todos, e uma condição de vitória quando a tela limpa.
A cena do inimigo se marca sozinha e expõe o contrato do grupo:
# inimigo.gd
extends CharacterBody2D
var vida := 30
func _ready():
add_to_group("inimigos")
func tomar_dano(quantidade: int):
vida -= quantidade
if vida <= 0:
morrer()
func congelar():
set_physics_process(false)
func descongelar():
set_physics_process(true)
func morrer():
queue_free()
O gerente da fase não guarda lista nenhuma. Ele só consulta o grupo quando precisa:
# fase.gd
extends Node2D
func _on_player_usou_bomba():
# Dano em área via call_group adiado: seguro mesmo se
# a bomba dispara de dentro de um sinal de física.
get_tree().call_group_flags(
SceneTree.GROUP_CALL_DEFERRED, "inimigos", "tomar_dano", 50
)
func _on_player_pegou_relogio():
get_tree().call_group("inimigos", "congelar")
await get_tree().create_timer(3.0).timeout
get_tree().call_group("inimigos", "descongelar")
func _process(_delta):
if get_tree().get_nodes_in_group("inimigos").is_empty():
set_process(false)
_vitoria()
Repare no que NÃO existe nesse código: nenhum array inimigos_vivos, nenhum sinal de "morri, me tira da lista", nenhum spawner avisando o gerente que criou alguém. O spawner instancia a cena do inimigo, o inimigo entra no grupo no _ready, e a partir dali o sistema inteiro já enxerga ele. Quando morre, some do grupo sem ninguém precisar avisar. É o tipo de arquitetura que continua simples quando o jogo cresce.
is_in_group: identificando quem é quem
A terceira ponta do sistema é perguntar se um nó específico pertence a um grupo. O uso mais comum é dentro de detecção de colisão:
func _on_area_2d_body_entered(body):
if body.is_in_group("player"):
body.tomar_dano(10)
Compare com as alternativas que você vê por aí: checar body.name == "Player" quebra na primeira vez que o Godot renomeia a instância pra "Player2", e checar body is Player exige class_name e acopla o script à classe exata. O is_in_group aceita qualquer coisa que você marcou como player, incluindo um segundo jogador no modo coop ou um clone temporário criado por uma habilidade.
Outro uso que vale conhecer: grupos como marcador de capacidade, não de tipo. Um grupo "persistir" pra tudo que entra no save, um grupo "pausavel" pra tudo que congela no menu de pause, um grupo "interagivel" pra tudo que reage ao botão de ação. O nó descreve o que ele sabe fazer, e os sistemas conversam com capacidades em vez de tipos:
func salvar_jogo():
var dados := []
for node in get_tree().get_nodes_in_group("persistir"):
dados.append(node.coletar_estado())
# ... escreve dados no arquivo
Esse padrão de save com grupo "persistir" é inclusive o que a documentação oficial do Godot recomenda no guia de salvar jogos.
Grupos, sinais ou referência direta?
Os três resolvem comunicação entre nós, e cada um tem seu lugar. Minha regra prática:
- Referência direta (
@onready var x = $Filho): pai falando com filho dentro da mesma cena. O acoplamento é local e a cena é dona dos próprios nós. - Sinal: filho avisando pra cima que algo aconteceu, sem saber quem ouve. O inimigo emite
morreu, e quem quiser reage. - Grupo: um pra muitos, quando o "muitos" muda em runtime. Comandos de cima pra baixo em coleções dinâmicas: inimigos, projéteis, itens, tudo que nasce e morre o tempo todo.
O erro comum é usar grupo pra tudo. Se a relação é fixa e local, grupo é indireção desnecessária: um HUD que mostra a vida do player não precisa buscar o player por grupo todo frame, conecta um sinal uma vez e pronto.
Armadilhas que valem o aviso
A lista não é ordenada de forma útil. O get_nodes_in_group devolve os nós na ordem da árvore, que muda conforme você instancia e remove coisas. Não dependa da ordem pra lógica de jogo.
Cuidado ao destruir dentro do loop. Iterar o grupo e chamar queue_free() é seguro, porque o queue_free só destrói no fim do frame. Mas chamar free() direto no meio da iteração mexe na lista enquanto você anda nela. Fique com queue_free().
Grupo vazio não é erro. call_group num grupo que não existe simplesmente não faz nada, e get_nodes_in_group devolve lista vazia. É conveniente, mas é também por isso que typo em nome de grupo passa batido. As constantes que mostrei lá em cima são a vacina.
Consultar grupo todo frame tem custo. O get_nodes_in_group monta um array a cada chamada. Pra checar vitória uma vez por frame, tranquilo. Pra rodar dentro do _physics_process de cada um dos 200 projéteis, não. Nesse caso, busque uma vez e guarde no frame, ou repense a abordagem.
Fechando
Grupos são daquelas features pequenas que mudam a forma de pensar a arquitetura. Em vez de cada sistema manter sua lista de quem importa, os nós se etiquetam e a SceneTree faz a contabilidade. Três métodos cobrem o uso real: get_nodes_in_group quando você precisa da lista, call_group quando todos fazem a mesma coisa, is_in_group quando você precisa identificar alguém.
Pra fixar, pegue um projeto seu que tenha algum array manual de inimigos ou de itens e troque por grupo. Você vai apagar mais código do que escrever, e o que sobra fica mais difícil de quebrar. É o tipo de refatoração que se paga na primeira tarde.


