Voltar para o Blog
Quest Log

Camera Godot 2D: Como Fazer a Câmera Seguir o Player com Camera2D

Personagem de jogo 2D no centro da tela com a câmera enquadrando a fase ao redor

Tutorial de camera godot 2d: configure a Camera2D para seguir o player com smoothing, limites de cena, drag margins, zoom suave e screen shake.

Camera Godot 2D: Como Fazer a Câmera Seguir o Player com Camera2D

Câmera ruim estraga jogo bom. Se ela gruda no player de forma rígida, cada pulo sacode a tela inteira. Se ela mostra o vazio fora da fase, o jogo parece quebrado. E o pior: o jogador não sabe dizer o que está errado, ele só sente que o jogo é "duro".

A boa notícia é que camera godot 2d é um problema bem resolvido. O node Camera2D já traz quase tudo pronto: seguir o player, suavizar o movimento, respeitar os limites da fase e controlar zoom. Você só precisa saber quais propriedades ligar e por quê. É exatamente isso que esse tutorial cobre, com GDScript do Godot 4 que roda como está.

O setup mínimo: Camera2D como filha do player

O jeito mais rápido de ter uma câmera que segue o player é colocar a Camera2D como filha dele:

Player (CharacterBody2D)
├── CollisionShape2D
├── Sprite2D
└── Camera2D

Pronto, funciona. A Camera2D herda a posição do pai, então onde o player vai, a câmera vai junto. No Godot 4 a câmera fica ativa automaticamente se for a única da cena; se você tiver mais de uma, controla qual está valendo pela propriedade enabled ou chamando make_current() por código.

Só que rodando assim você já sente o problema: a tela copia cada movimento do player, pixel por pixel. Pulou, a tela pula. Caiu, a tela despenca. É funcional e é desconfortável. Os próximos ajustes existem pra resolver isso.

Position smoothing: o ajuste que muda tudo

O smoothing faz a câmera perseguir o player em vez de copiar ele. O player se move na hora, a câmera vai atrás com um pequeno atraso, e esse atraso é o que deixa o movimento de tela gostoso de olhar.

No Inspector da Camera2D, em Position Smoothing, ligue Enabled. Ou por código:

extends Camera2D

func _ready():
    position_smoothing_enabled = true
    position_smoothing_speed = 5.0

O position_smoothing_speed controla o quão rápido a câmera alcança o alvo. Valor baixo (2 a 4) dá uma câmera preguiçosa, boa pra exploração e jogo contemplativo. Valor alto (8 a 12) dá uma câmera colada, melhor pra plataforma de precisão onde o jogador precisa ver o que vem pela frente. Não existe número certo: roda o jogo, sente, ajusta.

Um detalhe que economiza bug: quando você teleporta o player (respawn, troca de sala), a câmera suavizada atravessa o mapa inteiro deslizando até o novo ponto. Pra ela aparecer já no lugar, chame reset_smoothing() logo depois do teleporte:

func respawn(ponto: Vector2):
    player.global_position = ponto
    camera.reset_smoothing()

Limites de cena: a câmera para onde a fase acaba

Por padrão a Camera2D segue o player pra qualquer lugar, inclusive pra fora da fase. O jogador chega na borda do mapa e a tela mostra metade de fase, metade de vazio. Os limites resolvem isso: são quatro valores em pixels que dizem até onde a câmera pode enquadrar.

No Inspector ficam em Limit (Left, Top, Right, Bottom). Por código:

func _ready():
    limit_left = 0
    limit_top = 0
    limit_right = 3840
    limit_bottom = 1080

Com isso a câmera segue o player normalmente no meio da fase e trava na borda quando ele se aproxima do fim, deixando o player se deslocar até o canto da tela sem nunca mostrar o lado de fora.

Dica que combina com o smoothing: ligue Limit > Smoothed (limit_smoothed = true). Sem isso, a câmera suavizada dá uma freada seca ao encostar no limite. Com isso, ela desacelera de forma natural.

Calculando os limites a partir do TileMap

Chumbar os valores na mão funciona, mas quebra toda vez que você redesenha a fase. Se o seu nível é um TileMapLayer, dá pra calcular os limites direto do retângulo de tiles usados:

extends Camera2D

@export var tilemap: TileMapLayer

func _ready():
    var usado := tilemap.get_used_rect()
    var tam := tilemap.tile_set.tile_size

    limit_left = usado.position.x * tam.x
    limit_top = usado.position.y * tam.y
    limit_right = usado.end.x * tam.x
    limit_bottom = usado.end.y * tam.y

Arraste o TileMapLayer pro campo exportado no Inspector e os limites passam a acompanhar o tamanho real da fase. Editou o mapa, os limites se ajustam sozinhos no próximo play.

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

Drag margins: zona morta no centro da tela

Em alguns jogos você não quer a câmera reagindo a todo passinho do player. O drag margin cria uma "caixa" invisível no centro da tela: enquanto o player se move dentro dela, a câmera fica parada. Só quando ele empurra a borda da caixa é que a câmera acompanha.

func _ready():
    drag_horizontal_enabled = true
    drag_vertical_enabled = true

    # Valores de 0 a 1, fração do tamanho da tela.
    drag_left_margin = 0.2
    drag_right_margin = 0.2
    drag_top_margin = 0.2
    drag_bottom_margin = 0.2

Com margens de 0.2, o player anda livre nos 40% centrais da tela antes da câmera se mexer. É ótimo pra jogo de exploração e pra top-down estilo Zelda clássico. Pra plataforma de ação rápida, costuma ser melhor deixar desligado na horizontal: o jogador precisa de espaço de visão na direção em que está correndo, não atrás dele.

Pra visualizar as margens e os limites enquanto ajusta, selecione a Camera2D e ligue Editor > Draw Drag Margin e Draw Limits no Inspector. O editor desenha as linhas na viewport e o tuning fica visual em vez de chute.

Câmera como node separado: controle total por script

Colocar a câmera dentro do player resolve a maioria dos casos, mas amarra as duas coisas. Cutscene, câmera que mostra um chefe, transição entre salas, tudo fica mais simples quando a câmera é um node irmão do player, com um script de follow próprio:

Fase (Node2D)
├── Player (CharacterBody2D)
└── GameCamera (Camera2D)
extends Camera2D

@export var alvo: Node2D
@export var velocidade_follow := 5.0

func _physics_process(delta):
    if alvo == null:
        return
    # Interpolação exponencial: suave e independente do framerate.
    var peso := 1.0 - exp(-velocidade_follow * delta)
    global_position = global_position.lerp(alvo.global_position, peso)

Duas observações sobre esse código. Primeiro, ele roda em _physics_process porque o player (um CharacterBody2D) também se move lá; misturar os dois loops causa tremor. Segundo, o peso do lerp usa 1.0 - exp(-velocidade * delta) em vez de um valor fixo tipo 0.1. Com valor fixo, a suavização muda conforme o FPS; com a forma exponencial, a câmera se comporta igual a 30, 60 ou 144 frames por segundo.

Como o smoothing agora é feito no script, deixe o position_smoothing_enabled da câmera desligado nesse setup, senão você suaviza duas vezes e a câmera fica boiando.

Com o alvo exportado, trocar o foco da câmera vira uma linha: aponta alvo pro chefe na hora da apresentação dele, devolve pro player depois. Sem reparentar node, sem segunda câmera.

Lookahead: olhar pra onde o player vai

Um refinamento que jogos de plataforma bons usam: deslocar a câmera um pouco na direção do movimento, pra abrir visão pra onde o jogador está indo.

@export var lookahead := 120.0
@export var velocidade_lookahead := 3.0

var _desloc_x := 0.0

func _physics_process(delta):
    if alvo == null:
        return

    var direcao := 0.0
    if alvo is CharacterBody2D:
        direcao = signf(alvo.velocity.x)

    var peso := 1.0 - exp(-velocidade_lookahead * delta)
    _desloc_x = lerpf(_desloc_x, direcao * lookahead, peso)

    var destino := alvo.global_position + Vector2(_desloc_x, 0)
    var peso_follow := 1.0 - exp(-velocidade_follow * delta)
    global_position = global_position.lerp(destino, peso_follow)

O deslocamento também é interpolado, então quando o player inverte a direção a câmera desliza de um lado pro outro em vez de teleportar. Teste com lookahead entre 80 e 150 pixels; acima disso o player fica colado demais na borda da tela.

Zoom no Godot 4

O zoom da Camera2D é um Vector2, e no Godot 4 a lógica é direta: valor maior aproxima. Vector2(2, 2) mostra tudo com o dobro do tamanho, Vector2(0.5, 0.5) afasta e mostra o dobro de área. Quem veio do Godot 3 precisa reaprender, porque lá era o inverso.

Setar o zoom seco funciona, mas o corte é brusco. Pra zoom suave, um Tween resolve:

func aplicar_zoom(nivel: float, duracao := 0.4):
    var tween := create_tween()
    tween.tween_property(self, "zoom", Vector2(nivel, nivel), duracao)\
        .set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)

Casos de uso clássicos: aproximar (aplicar_zoom(1.5)) num diálogo ou momento de tensão, afastar (aplicar_zoom(0.8)) quando o player entra numa arena grande e precisa ver o espaço. Em pixel art, prefira manter o zoom em valores que preservem a grade de pixels (1, 2, 3) ou aceite que vai haver distorção nos sprites durante a transição.

Screen shake com offset

Tremida de tela vende impacto: explosão, dano, queda pesada. O jeito limpo de fazer é usar a propriedade offset da câmera, que desloca o enquadramento sem mexer na posição real nem brigar com o follow:

var _shake_forca := 0.0
@export var shake_decaimento := 8.0

func tremer(forca: float):
    _shake_forca = forca

func _process(delta):
    if _shake_forca > 0:
        offset = Vector2(
            randf_range(-_shake_forca, _shake_forca),
            randf_range(-_shake_forca, _shake_forca)
        )
        _shake_forca = move_toward(_shake_forca, 0, shake_decaimento * delta)
    else:
        offset = Vector2.ZERO

Qualquer script do jogo chama camera.tremer(6.0) num hit leve ou camera.tremer(16.0) numa explosão, e a força decai sozinha até zero. O shake roda em _process de propósito: é efeito visual, não física, e fica mais suave acompanhando o framerate de renderização.

Uma regra de bom senso que aprendi apanhando: shake é tempero, não prato principal. Se tudo treme, nada tem peso. Reserve as tremidas fortes pros momentos que merecem e adicione uma opção de desligar nas configurações, porque tem jogador que enjoa com isso.

Fechando

Câmera 2D no Godot se resume a uma escolha de estrutura e meia dúzia de propriedades. Camera2D filha do player com position_smoothing_enabled e limites configurados cobre a maioria dos jogos. Quando o projeto pede cutscene, lookahead ou troca de alvo, promova a câmera a node separado com o script de follow.

O caminho que recomendo pra fixar: cria uma cena de plataforma simples e vai ligando uma coisa de cada vez. Primeiro a câmera rígida, pra sentir o incômodo. Depois o smoothing. Depois os limites calculados do TileMapLayer. Depois drag margin, lookahead e shake. Cada etapa leva minutos e você enxerga exatamente o que cada propriedade muda na sensação do jogo. Câmera é game feel, e game feel só se aprende com o jogo rodando.