Area3D na Godot: gatilhos e detecção em 3D

Guia prático de Area3D na Godot 4 para detecção em 3D: sinais body_entered, CollisionShape3D, collision layers e masks, zona de cura e de dano.
O Area3D é o node da Godot 4 que resolve uma pergunta muito comum: "entrou alguém nesta região?". Ele não é um corpo sólido, não empurra e não bloqueia nada. A função dele é vigiar um volume no espaço e avisar por sinal quando um corpo ou outra área entra ou sai. Isso faz do Area3D na Godot a ferramenta certa para quase todo gatilho de um jogo 3D: coletar um item ao encostar nele, uma zona que tira vida enquanto você está dentro, um checkpoint que salva o progresso quando você passa, uma porta que abre sozinha quando o player se aproxima. Neste tutorial eu mostro como montar, configurar e usar o Area3D na prática, tudo em GDScript tipado e sem API inventada.
A ideia central é separar duas responsabilidades que iniciantes costumam misturar. Um corpo sólido (StaticBody3D, CharacterBody3D) existe para ocupar espaço e colidir de verdade. O Area3D existe só para observar. Ele detecta sem interferir no movimento, e por isso é o node que você usa toda vez que quer que algo aconteça quando objetos se tocam, mas sem travar ninguém no lugar.
Montando um Area3D do zero
Um Area3D sozinho não detecta nada: ele precisa de uma forma que defina o volume vigiado. Essa forma vem de um CollisionShape3D filho. A estrutura mínima de cena é sempre esta:
ZonaGatilho (Area3D)
└── CollisionShape3D
No Inspector do CollisionShape3D, você atribui uma forma à propriedade shape. Para uma zona retangular use um BoxShape3D, para um raio esférico use um SphereShape3D, para um corredor cilíndrico use um CylinderShape3D. Esse volume é a região de detecção: tudo que encostar nele dispara os sinais da área. Um erro clássico é adicionar o CollisionShape3D e esquecer de atribuir a forma; sem shape, a área fica cega e nenhum sinal sai.
O Area3D tem duas propriedades que controlam se ele participa da detecção:
monitoring: quando ligado (padrão), a área observa ativamente quem entra e sai dela e dispara os sinais. Desligue para "pausar" a detecção sem remover o node.monitorable: quando ligado (padrão), esta área pode ser detectada por outras áreas. Se um outro Area3D precisa enxergar esta, mantenha ligado. Para um gatilho que só observa mas nunca precisa ser observado, pode desligar e economizar processamento.
Na prática: o monitoring responde "esta área enxerga os outros?" e o monitorable responde "os outros enxergam esta área?". São independentes.
Os sinais: body_entered, area_entered e os de saída
O Area3D trabalha por sinais, não por consulta frame a frame. Você conecta o sinal uma vez e a Godot chama sua função quando o evento acontece. Os quatro que importam no dia a dia:
body_entered(body): um PhysicsBody3D entrou na região.body_exited(body): um PhysicsBody3D saiu da região.area_entered(area): outro Area3D entrou na região.area_exited(area): outro Area3D saiu da região.
A distinção entre body e area é a chave. Um CharacterBody3D (seu player) ou um RigidBody3D (uma caixa, um barril) dispara os sinais de body. Outro Area3D (a hitbox de um golpe, por exemplo) dispara os sinais de area. Se você entender quando usar cada um, essa diferença decide metade do seu design de colisão. O tema de qual node usar para cada papel está bem detalhado no guia de Area2D vs corpos físicos na Godot, e o raciocínio vale igual no 3D.
Conectar um sinal por código é direto. No _ready, você liga o sinal da área à sua função de callback:
extends Area3D
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D) -> void:
print("Entrou: ", body.name)
func _on_body_exited(body: Node3D) -> void:
print("Saiu: ", body.name)
Repare que o parâmetro vem tipado como Node3D: é o que o sinal entrega e é o tipo mais amplo que cobre qualquer corpo. A partir dele você decide o que fazer com quem entrou.
Detectar só o player com layers e masks
O Area3D vai disparar o sinal para qualquer corpo que a sua collision mask permita. Se o sinal dispara quando um inimigo ou uma caixa encosta na zona, e você queria só o player, o filtro certo não é um if no código: é a configuração de collision layers e masks.
A regra é a de sempre na física da Godot. Cada objeto fica em uma ou mais camadas (layer), e cada objeto define quais camadas ele observa (mask). O Area3D detecta um corpo só quando a collision_mask da área inclui a collision_layer do corpo. Então, se você coloca o player sozinho em uma camada dedicada (digamos a camada 2) e configura o Area3D para ter só a camada 2 na mask, a zona de gatilho passa a ignorar todo o resto do mundo e reagir apenas ao player, sem nenhuma checagem no script.
Vale montar as camadas do projeto com nomes claros desde o começo, porque é fácil se perder em qual bit é o quê. O passo a passo completo de como organizar isso está no guia de collision layers e masks na Godot, e configurar certo ali resolve a maioria dos "meu gatilho dispara para a coisa errada".
Quando mesmo assim você quer um filtro extra no código (por exemplo, o player está numa camada compartilhada com NPCs), use grupos. Marque o player no grupo "player" e cheque dentro do callback:
extends Area3D
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node3D) -> void:
if not body.is_in_group("player"):
return
print("Player entrou na zona")
O is_in_group é barato e deixa a intenção explícita. Mas prefira sempre resolver pela mask quando der: filtrar na física é mais rápido do que deixar o sinal disparar e descartar no código.
Exemplo completo: zona de cura e zona de dano
Agora o caso prático que junta tudo: uma região do mapa que afeta a vida do player enquanto ele está dentro. O mesmo molde serve tanto para uma poça de veneno que tira vida quanto para uma fonte que cura, só muda o sinal do valor.
A diferença importante aqui é que não basta reagir ao body_entered uma vez. Dano contínuo precisa saber que o player continua dentro para aplicar o efeito a cada intervalo. O padrão é: no body_entered, marque que o player entrou e comece a contar; no body_exited, pare. A cada ciclo, se o player ainda está dentro, aplique o efeito.
extends Area3D
@export var dano_por_tick: int = 10
@export var intervalo: float = 1.0
var player_dentro: Node3D = null
var tempo_acumulado: float = 0.0
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
player_dentro = body
tempo_acumulado = 0.0
func _on_body_exited(body: Node3D) -> void:
if body == player_dentro:
player_dentro = null
func _physics_process(delta: float) -> void:
if player_dentro == null:
return
tempo_acumulado += delta
if tempo_acumulado >= intervalo:
tempo_acumulado -= intervalo
if player_dentro.has_method("levar_dano"):
player_dentro.levar_dano(dano_por_tick)
A zona de dano guarda a referência do corpo no player_dentro e só processa enquanto ela não é nula. O tempo_acumulado controla o ritmo: a cada intervalo de segundos, um tick de dano. O has_method("levar_dano") é a checagem defensiva que garante que você só chama o método em quem sabe recebê-lo; se o player implementa levar_dano(valor), o dano chega, senão nada quebra.
Para transformar essa zona de dano em zona de cura, a mudança é mínima. Troque a chamada por um método de cura e use um valor positivo de vida:
@export var cura_por_tick: int = 15
func _physics_process(delta: float) -> void:
if player_dentro == null:
return
tempo_acumulado += delta
if tempo_acumulado >= intervalo:
tempo_acumulado -= intervalo
if player_dentro.has_method("curar"):
player_dentro.curar(cura_por_tick)
A estrutura é idêntica: entrou, acumula tempo, aplica no ritmo, saiu, para. Esse molde de "efeito enquanto dentro" cobre uma porção enorme de mecânicas: campo de lentidão, área de buff, zona de escuridão, radiação.
Gatilhos de uma vez só: item, checkpoint e porta
Nem todo gatilho é contínuo. Coletar um item, bater num checkpoint ou abrir uma porta são eventos de uma vez só: você reage no body_entered e pronto, não precisa do _physics_process.
Um coletável remove a si mesmo depois de ser pego:
extends Area3D
@export var valor: int = 1
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
if body.has_method("coletar_moeda"):
body.coletar_moeda(valor)
queue_free()
O queue_free() tira o item da cena com segurança no fim do frame. Para um checkpoint, a lógica é parecida, mas em vez de sumir você guarda a posição como ponto de respawn e normalmente desliga o monitoring para não disparar de novo:
extends Area3D
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
GerenciadorJogo.definir_checkpoint(global_position)
monitoring = false
Desligar o monitoring depois de ativar evita que o checkpoint reprocesse toda vez que o player passa por cima. Uma porta automática segue o mesmo esqueleto, só que ela precisa dos dois sinais: abre no body_entered e fecha no body_exited, acionando a animação correspondente.
Quando o gatilho precisa de uma mira precisa (apertar um botão enquanto olha para um objeto, por exemplo), o Area3D não é a melhor ferramenta, porque ele detecta sobreposição de volume, não direção. Para "estou olhando para isto?", o caminho é o raio: o guia de raycast 3D na Godot cobre exatamente esse caso de interação por foco, e os dois nodes costumam andar juntos num jogo bem montado.
Fechando
O Area3D na Godot 4 é um daqueles nodes de aprendizado rápido e uso constante. Toda a lógica gira em torno de um volume (o CollisionShape3D), dos sinais de entrada e saída (body_entered, body_exited, area_entered, area_exited) e do par monitoring/monitorable que liga ou desliga a detecção. Para filtrar quem a zona enxerga, resolva no nível da física com collision layers e masks antes de recorrer a checagens no código. Com esse molde você cobre o essencial: coletáveis, checkpoints, portas automáticas e efeitos contínuos de cura ou dano, tudo sem travar o movimento de ninguém. Se você está decidindo entre começar seu projeto na Godot ou em outro motor, vale ler a comparação completa entre Godot e Unity antes de investir tempo. Comece por uma zona de dano simples, que é a mais fácil de ver funcionando na tela, e vá empilhando os outros gatilhos em cima. Bons projetos na CursoGame.Dev.
Perguntas frequentes
Qual a diferença entre Area3D e StaticBody3D na Godot?
O StaticBody3D é um corpo sólido: outros corpos batem nele e param, como uma parede ou o chão. O Area3D não tem solidez nenhuma, ele não bloqueia nada. A função dele é só detectar quem entrou ou saiu da região e avisar por sinal. Use StaticBody3D quando quer barrar o movimento e Area3D quando quer disparar um evento, tipo coletar um item ou aplicar dano, sem travar o personagem.
Por que o body_entered do meu Area3D não dispara na Godot?
Quase sempre é layer e mask desencontradas ou o monitoring desligado. O Area3D precisa ter o monitoring ligado e a collision_mask dele tem que incluir a camada onde o corpo que você quer detectar está. Se o player está na camada 2 e a mask do Area3D não marca a camada 2, o sinal nunca sai. Confira também se o CollisionShape3D tem uma forma de verdade atribuída e não está vazio.
Qual a diferença entre body_entered e area_entered no Area3D?
O body_entered dispara quando um PhysicsBody3D (CharacterBody3D, RigidBody3D, StaticBody3D) entra na região. O area_entered dispara quando outro Area3D entra. Use body_entered para detectar o player ou inimigos que se movem como corpos, e area_entered quando você quer que duas zonas de gatilho conversem entre si, como uma hitbox de ataque encontrando a hurtbox de um inimigo.


