Voltar para o Blog
Quest Log

Area2D vs CharacterBody2D no Godot: qual usar em cada situação

Cena de jogo 2D mostrando um personagem colidindo com uma parede e atravessando uma zona de detecção

Area2D no Godot detecta, CharacterBody2D colide. Entenda a diferença na prática, com código GDScript e casos de uso reais para escolher certo.

Ferramenta:Godot

Area2D vs CharacterBody2D no Godot: qual usar em cada situação

Essa dúvida aparece em todo projeto de iniciante: a moeda do jogo deve ser uma Area2D ou um CharacterBody2D? E o projétil? E a zona de dano? Quem escolhe errado descobre do pior jeito, com o player travando em cima da moeda em vez de coletar, ou a bala empurrando o inimigo pela tela como se fosse um carrinho de supermercado.

A resposta curta cabe numa frase: Area2D detecta, body colide. Area2D é um sensor, sabe quando algo entrou e saiu dela, mas não bloqueia nada. CharacterBody2D (e os outros bodies) ocupa espaço físico de verdade: para, empurra, desliza, é parado.

A resposta longa é este artigo. Vou mostrar o que cada um faz por baixo, o código de cada caso, e uma regra de bolso pra você nunca mais hesitar na hora de criar a cena.

A diferença fundamental: detecção vs física

O Godot separa o mundo 2D em duas categorias de objeto que participam de colisão:

Bodies (PhysicsBody2D) são objetos sólidos. StaticBody2D, CharacterBody2D, RigidBody2D e AnimatableBody2D herdam todos de PhysicsBody2D. Quando dois bodies se encontram, a engine resolve a colisão: um para, desliza ou empurra o outro. Eles não se atravessam.

Area2D é uma região do espaço. Ela não tem massa, não bloqueia movimento, não participa da resolução física. O único trabalho dela é responder duas perguntas: "alguém entrou aqui?" e "alguém saiu daqui?". Ela faz isso emitindo sinais.

Os dois usam CollisionShape2D pra definir o formato, e os dois respeitam collision layers e masks. Por isso a confusão: no editor eles parecem quase iguais. A diferença está no que acontece no contato. Body resiste, Area2D só avisa.

Uma analogia que funciona: body é a parede da loja, Area2D é o sensor da porta automática. A parede te impede de passar. O sensor não te segura, mas dispara um evento quando você cruza.

O que isso significa em custo

Tem uma consequência de performance embutida nessa diferença. Resolver colisão física é caro: a engine precisa calcular ponto de contato, normal, resposta, e fazer isso de forma estável a cada tick de física. Detectar overlap é bem mais barato, é basicamente um teste de "essas duas shapes se sobrepõem?".

Então além de ser a ferramenta semanticamente certa, Area2D é a ferramenta mais leve sempre que você só precisa saber que algo encostou, sem precisar que algo seja bloqueado. Se quiser ver o que existe por trás desse trabalho de resolver colisão, vale conhecer o Box3D, o novo motor de física open source do criador do Box2D.

Quando usar Area2D no Godot

Use Area2D pra tudo que é gatilho, sensor ou zona. Os casos clássicos:

  • Itens coletáveis: moeda, vida, power-up. O player atravessa, o item some, o contador sobe.
  • Zonas de dano: espinho, lava, área de veneno. Encostou, perdeu vida, mas nada física e fisicamente segura o player ali.
  • Hitbox e hurtbox de ataque: a área da espada durante o golpe, a área vulnerável do inimigo.
  • Gatilhos de level: porta de transição de fase, checkpoint, ponto que dispara cutscene.
  • Campo de visão de inimigo: um cone ou círculo na frente do inimigo que detecta o player.
  • Projéteis simples: bala que voa reto e some no primeiro contato não precisa de física, precisa de detecção.

O padrão de código é sempre o mesmo: conectar o sinal body_entered (quando um body entra na área) ou area_entered (quando outra Area2D entra). Uma moeda completa:

extends Area2D

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

func _on_body_entered(body) -> void:
    if body.is_in_group("player"):
        body.adicionar_moeda()
        queue_free()

Três detalhes que valem destacar nesse código:

  1. O sinal dispara com qualquer body que entre na mask da moeda. O filtro por grupo (is_in_group("player")) garante que só o player coleta. Dá pra fazer o mesmo filtro só com layers e masks, deixando a moeda detectar apenas a camada do player, que é ainda mais limpo.
  2. queue_free() remove a moeda no fim do frame, do jeito seguro. Nunca delete um node no meio de um callback de física com free() direto.
  3. A moeda não tem um único pixel de colisão sólida. O player passa por cima dela sem desviar de trajetória, que é exatamente o comportamento esperado.

E o caso da zona de dano contínuo (lava, veneno), onde você quer machucar enquanto o player está dentro, não só na entrada? A própria Area2D mantém a lista de quem está dentro:

extends Area2D

@export var dano_por_segundo := 10.0

func _physics_process(delta: float) -> void:
    for body in get_overlapping_bodies():
        if body.has_method("receber_dano"):
            body.receber_dano(dano_por_segundo * delta)

O get_overlapping_bodies() retorna todos os bodies dentro da área naquele tick. Sem sinal, sem timer, sem estado extra pra gerenciar.

Quando usar CharacterBody2D

CharacterBody2D é pra coisa que se move por script e precisa colidir de verdade: player, inimigo que patrulha, NPC que anda. Ele não é o único body, mas é o que você mais usa, porque é o único feito pra movimento controlado por código com resolução de colisão automática.

O coração dele é o move_and_slide(): você define velocity, chama o método, e a engine move o corpo respeitando paredes, chão e teto, deslizando ao longo das superfícies em vez de travar nelas.

extends CharacterBody2D

const SPEED: float = 300.0
const JUMP_VELOCITY: float = -400.0

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta

    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    var direction = Input.get_axis("ui_left", "ui_right")
    if direction:
        velocity.x = direction * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)

    move_and_slide()

Nada disso existe na Area2D. Ela não tem velocity, não tem move_and_slide(), não tem is_on_floor(). Se você tentar fazer um player com Area2D, vai acabar reimplementando colisão na mão, mal, e atravessando parede em quina.

O contrário também vale: se você fizer a moeda com CharacterBody2D, ela vira um obstáculo sólido. O player tromba nela. Já vi projeto de aluno onde o "item coletável" funcionava como degrau pra pular mais alto. Bug? Tecnicamente não, o body fez exatamente o que body faz: ocupou espaço.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Os dois juntos: o padrão que todo jogo usa

Aqui está o pulo do gato que destrava o entendimento: não é Area2D ou body, é Area2D e body na mesma cena. Quase todo personagem de jogo de verdade é um CharacterBody2D com uma ou mais Area2D como filhas.

O exemplo canônico é o inimigo de patrulha que detecta o player de longe:

Inimigo (CharacterBody2D)        ← corpo sólido, anda e colide
├── CollisionShape2D             ← shape física do corpo
├── Sprite2D
└── CampoDeVisao (Area2D)        ← sensor, não colide
    └── CollisionShape2D         ← shape do alcance de detecção

O body cuida do movimento e da colisão com o cenário. A Area2D filha cuida de "o player entrou no meu alcance?". Cada node faz só o trabalho dele:

extends CharacterBody2D

const SPEED: float = 100.0
var alvo: Node2D = null

func _ready() -> void:
    $CampoDeVisao.body_entered.connect(_on_visao_body_entered)
    $CampoDeVisao.body_exited.connect(_on_visao_body_exited)

func _on_visao_body_entered(body) -> void:
    if body.is_in_group("player"):
        alvo = body

func _on_visao_body_exited(body) -> void:
    if body == alvo:
        alvo = null

func _physics_process(delta: float) -> void:
    if alvo:
        var direcao = global_position.direction_to(alvo.global_position)
        velocity = direcao * SPEED
    else:
        velocity = Vector2.ZERO

    move_and_slide()

O mesmo padrão se repete em todo lugar. O player de um jogo de ação costuma ser um CharacterBody2D com uma Area2D de hurtbox (onde ele toma dano) e outra de hitbox de ataque (que liga só durante o golpe). O carro de corrida é um body com uma Area2D na frente detectando a linha de chegada. O chefe tem áreas separadas pra cabeça (dano crítico) e corpo (dano normal).

Um cuidado importante nesse arranjo: configure a collision layer e a mask da Area2D filha separadas das do body pai. O campo de visão do inimigo só precisa detectar a camada do player; não faz sentido ele "ver" paredes e outros inimigos. Mask enxuta é menos processamento e menos filtro em código.

Regra de bolso e tabela de decisão

Quando bater a dúvida, faça uma pergunta só: esse objeto precisa bloquear ou ser bloqueado por alguma coisa?

  • Sim, precisa ser sólido → é um body. Se move por script, CharacterBody2D. Se nunca se move, StaticBody2D. Se a física controla (cai, rola, quica), RigidBody2D.
  • Não, só precisa saber que algo encostou → Area2D.

Casos comuns resolvidos:

ObjetoNode certoPor quê
Player, inimigo que andaCharacterBody2DMovimento por script com colisão sólida
Parede, chão, plataforma fixaStaticBody2DSólido e imóvel, o mais barato
Caixa que tomba, barril que rolaRigidBody2DA física controla o movimento
Moeda, power-up, itemArea2DDetecta a coleta, não bloqueia
Espinho, lava, zona de venenoArea2DAplica dano, não segura ninguém
Bala que some no primeiro impactoArea2DSó precisa detectar o acerto
Granada que quica antes de explodirRigidBody2DO quique é física de verdade
Porta de fim de fase, checkpointArea2DGatilho puro
Plataforma móvelAnimatableBody2DSólida, movida por script, empurra quem está em cima

Repare nos dois projéteis da tabela. A bala reta é Area2D porque o comportamento dela é "voou, encostou, causou dano, sumiu". A granada é RigidBody2D porque ela precisa quicar na parede e rolar no chão, e isso é resolução física que Area2D não faz. O mesmo conceito de jogo (projétil) usa nodes diferentes dependendo do comportamento. O node segue o comportamento, não a categoria do objeto.

Erros que eu vejo com frequência

Area2D como corpo do player. O sintoma é player atravessando parede ou colisão "feita na mão" cheia de caso especial. A correção é migrar pra CharacterBody2D e deixar o move_and_slide() trabalhar.

Body como item coletável. O sintoma é o player trombando em moeda. Item é Area2D, sempre.

Esquecer a CollisionShape2D. Tanto Area2D quanto body precisam de uma shape filha pra funcionar. Sem ela, o sinal nunca dispara e a colisão nunca acontece. O Godot até mostra um aviso amarelo no node, mas todo mundo ignora aviso amarelo um dia.

Sinal conectado no node errado. body_entered é sinal da Area2D, não da CollisionShape2D. A shape só define o formato, quem emite é a área.

Mask aberta demais. Area2D detectando todas as camadas dispara sinal pra todo encostão do jogo e te obriga a filtrar tudo em código. Restrinja a mask pra camada que interessa e metade dos if somem.

Fechando

A escolha entre Area2D e CharacterBody2D para de ser dúvida quando você enxerga os papéis: body é matéria, Area2D é sensor. Um ocupa espaço e resolve colisão, o outro observa uma região e avisa o que entra e sai. E nos jogos reais eles quase sempre trabalham juntos, com o body sendo o corpo e as áreas sendo os sentidos. Todos os exemplos aqui estão em GDScript, mas o mesmo raciocínio vale em C#, e se você ainda escolhe a linguagem, veja C# vs GDScript na Godot.

Pra fixar, abra um projeto vazio e monte o trio básico: um player CharacterBody2D, uma parede StaticBody2D e uma moeda Area2D. Depois pendure um campo de visão no inimigo. São vinte minutos de prática que valem mais que qualquer releitura deste texto. E se você ainda está montando a estrutura em volta desses nodes, o guia de como fazer um jogo 2D no Godot arma a cena e o personagem do zero.