Pet no Godot 4: como criar um companheiro que segue o jogador

Aprenda a criar um companheiro que segue o jogador no Godot 4: follow suave, atraso estilo Zelda, NavigationAgent2D e teleporte. Com código completo.
Um companheiro que segue o jogador no Godot é daqueles recursos que dão vida instantânea ao jogo: um cachorro, um fantasminha, um drone flutuante. E é também uma armadilha clássica de iniciante, porque a versão ingênua resulta num pet grudado no personagem, tremendo em cima dele a cada frame. Neste tutorial você vai montar um pet 2D completo no Godot 4: follow suave com distância mínima, follow com atraso estilo Zelda, desvio de obstáculos com NavigationAgent2D e uma máquina de estados com teleporte de segurança, que é o padrão usado em jogos comerciais. Todo o código usa GDScript com tipagem estática.
Montando a cena do pet
A estrutura é a mesma de qualquer personagem 2D no Godot 4:
Pet (CharacterBody2D)
├── Sprite2D
└── CollisionShape2D
Crie uma cena nova com um CharacterBody2D como raiz e renomeie para Pet. Adicione um Sprite2D com a textura do bichinho e um CollisionShape2D com um CircleShape2D pequeno. Um detalhe que evita dor de cabeça: se você não quer que o pet empurre o jogador nem trombe nele, coloque o pet numa camada de colisão própria e configure a collision_mask para colidir apenas com o cenário, não com o player.
Salve a cena como pet.tscn e instancie dentro da cena principal do jogo, como irmão do player, não como filho. Se o pet for filho do player, ele herda a transformação do pai e o follow perde o sentido.
Para o script, a referência ao jogador entra via @export, que é a forma mais direta de ligar os dois no editor:
extends CharacterBody2D
@export var player: CharacterBody2D
@export var velocidade_max: float = 160.0
@export var aceleracao: float = 800.0
@export var distancia_minima: float = 48.0
@onready var sprite: Sprite2D = $Sprite2D
Arraste o nó do player para o campo player no inspetor e a base está pronta.
Companheiro que segue o jogador no Godot: follow com distância mínima
A versão ingênua do follow é mover o pet em direção ao jogador todo frame, sem condição nenhuma. O resultado: o pet chega no destino, passa do ponto, corrige, passa de novo, e fica vibrando em cima do personagem. A solução é um limiar de distância. O pet só se move quando está longe demais, e desacelera suavemente quando entra no raio de conforto:
func _physics_process(delta: float) -> void:
if player == null:
return
var distancia: float = global_position.distance_to(player.global_position)
if distancia > distancia_minima:
var direcao: Vector2 = global_position.direction_to(player.global_position)
velocity = velocity.move_toward(direcao * velocidade_max, aceleracao * delta)
else:
velocity = velocity.move_toward(Vector2.ZERO, aceleracao * delta)
move_and_slide()
_atualizar_flip()
func _atualizar_flip() -> void:
if absf(velocity.x) > 1.0:
sprite.flip_h = velocity.x < 0.0
Três pontos importantes aqui. Primeiro, o move_toward na velocity faz o pet acelerar e frear com inércia em vez de mudar de velocidade instantaneamente, o mesmo princípio de suavização que você usa numa câmera 2D que segue o jogador. Se preferir um acabamento mais elástico, dá para trocar por velocity = velocity.lerp(direcao * velocidade_max, 1.0 - exp(-8.0 * delta)), que converge de forma exponencial e independente de framerate. Segundo, quando o pet entra no raio de distancia_minima, a velocidade não é zerada de uma vez: ela decai até Vector2.ZERO, o que elimina a parada seca. Terceiro, o flip do sprite lê a velocity real, não o input, então o bichinho sempre olha para onde está andando. O limiar de 1.0 no absf evita que ele fique virando de lado quando está praticamente parado.
Rode a cena: o pet corre atrás do jogador, freia a uma distância confortável e espera. Já é utilizável. Mas dá para ficar bem melhor.
Follow com atraso: a fila de posições estilo Zelda
O follow direto tem um comportamento meio robótico: o pet corta caminho em linha reta, sempre. Jogos como os Zelda clássicos e vários RPGs usam outro truque para os companheiros: o pet refaz o caminho exato que o jogador percorreu, só que alguns frames atrás. O efeito é o de um rastro, como se o bichinho pisasse nas pegadas do dono.
A implementação é uma fila de posições. A cada frame de física, você guarda a posição global do jogador num Array[Vector2]. Quando a fila passa de N elementos, a posição mais antiga sai da frente e vira o alvo do pet:
extends CharacterBody2D
@export var player: CharacterBody2D
@export var velocidade_max: float = 200.0
@export var frames_de_atraso: int = 25
@onready var sprite: Sprite2D = $Sprite2D
var historico: Array[Vector2] = []
func _physics_process(_delta: float) -> void:
if player == null:
return
historico.push_back(player.global_position)
if historico.size() > frames_de_atraso:
var alvo: Vector2 = historico.pop_front()
if global_position.distance_to(alvo) > 4.0:
var direcao: Vector2 = global_position.direction_to(alvo)
velocity = direcao * velocidade_max
move_and_slide()
if absf(velocity.x) > 1.0:
sprite.flip_h = velocity.x < 0.0
Com frames_de_atraso = 25 e a física padrão de 60 ticks por segundo, o pet segue a posição de aproximadamente 0,4 segundo atrás. Aumente o valor para um companheiro mais preguiçoso, diminua para um mais colado. Repare que quando o jogador para, a fila para de andar naturalmente: as posições novas empilhadas são todas iguais, o alvo converge para o mesmo ponto e o pet estaciona sozinho, sem precisar de limiar de distância elaborado. O check de 4.0 pixels só evita micro tremida no destino final.
Esse padrão escala de graça: quer três pets em fila indiana? Cada um segue a fila com um atraso maior que o do anterior, tipo 25, 50 e 75 frames, todos lendo o mesmo histórico.
Uma ressalva: esse método ignora colisão com o cenário no trajeto, porque o pet anda por cima do caminho que o jogador validou. Em mapas abertos isso é perfeito. Em mapas com paredes e o pet nascendo longe do jogador, você precisa do próximo passo.
Desvio de obstáculos com NavigationAgent2D
Se o cenário tem paredes, o follow em linha reta deixa o pet preso na primeira quina. A resposta do Godot 4 para isso é a dupla NavigationRegion2D no mapa e NavigationAgent2D no pet. O funcionamento em detalhe, incluindo como assar o polígono de navegação, está no tutorial de pathfinding com NavigationAgent2D no Godot. Aqui vai a aplicação direta ao pet.
Adicione um nó NavigationAgent2D como filho do Pet e configure no inspetor path_desired_distance e target_desired_distance em torno de 8.0. O script fica assim:
extends CharacterBody2D
@export var player: CharacterBody2D
@export var velocidade_max: float = 160.0
@export var distancia_minima: float = 48.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
@onready var sprite: Sprite2D = $Sprite2D
func _physics_process(_delta: float) -> void:
if player == null:
return
var distancia: float = global_position.distance_to(player.global_position)
if distancia <= distancia_minima:
velocity = Vector2.ZERO
return
nav_agent.target_position = player.global_position
if nav_agent.is_navigation_finished():
velocity = Vector2.ZERO
else:
var proximo: Vector2 = nav_agent.get_next_path_position()
var direcao: Vector2 = global_position.direction_to(proximo)
velocity = direcao * velocidade_max
move_and_slide()
if absf(velocity.x) > 1.0:
sprite.flip_h = velocity.x < 0.0
A lógica central: você atualiza target_position com a posição do jogador e, em vez de mirar nele diretamente, mira no ponto intermediário que get_next_path_position() devolve. O agente recalcula a rota conforme o alvo se move, e o pet contorna paredes sozinho. A distância mínima continua lá, cumprindo o mesmo papel de antes.
Dica de performance: setar target_position todo frame força recálculo de caminho todo frame. Para um pet, dá para atualizar o alvo a cada 0,2 segundo com um Timer e ninguém percebe a diferença.
Estados do pet: IDLE, FOLLOW e TELEPORT
Agora a estrutura que jogos reais usam de verdade. Um pet comercial não tem só "seguir": ele descansa quando o jogador está perto, segue quando afasta e, crucialmente, teleporta para perto do dono quando fica longe demais. O teleporte parece gambiarra, mas é o padrão da indústria: World of Warcraft, Torchlight e praticamente todo jogo com pet fazem isso, porque nenhum pathfinding cobre 100% dos casos de pet preso atrás de porta ou caído de plataforma.
Uma máquina de estados com enum organiza tudo:
extends CharacterBody2D
enum Estado { IDLE, FOLLOW, TELEPORT }
@export var player: CharacterBody2D
@export var velocidade_max: float = 160.0
@export var aceleracao: float = 800.0
@export var distancia_minima: float = 48.0
@export var distancia_teleporte: float = 480.0
@onready var sprite: Sprite2D = $Sprite2D
var estado: Estado = Estado.IDLE
func _physics_process(delta: float) -> void:
if player == null:
return
var distancia: float = global_position.distance_to(player.global_position)
_atualizar_estado(distancia)
match estado:
Estado.IDLE:
velocity = velocity.move_toward(Vector2.ZERO, aceleracao * delta)
Estado.FOLLOW:
var direcao: Vector2 = global_position.direction_to(player.global_position)
velocity = velocity.move_toward(direcao * velocidade_max, aceleracao * delta)
Estado.TELEPORT:
_teleportar_para_perto()
return
move_and_slide()
if absf(velocity.x) > 1.0:
sprite.flip_h = velocity.x < 0.0
func _atualizar_estado(distancia: float) -> void:
if distancia > distancia_teleporte:
estado = Estado.TELEPORT
elif distancia > distancia_minima:
estado = Estado.FOLLOW
else:
estado = Estado.IDLE
func _teleportar_para_perto() -> void:
var deslocamento: Vector2 = Vector2(randf_range(-32.0, 32.0), randf_range(-32.0, 32.0))
global_position = player.global_position + deslocamento
velocity = Vector2.ZERO
estado = Estado.IDLE
O _atualizar_estado centraliza as transições em um lugar só, e o match executa o comportamento do estado atual. O teleporte solta o pet num ponto aleatório num raio de 32 pixels ao redor do jogador, para não aparecer sempre exatamente em cima dele. Dois refinamentos que valem a pena em produção: só teleportar quando o pet está fora da tela, checando com um nó VisibleOnScreenNotifier2D, e tocar um efeitinho de fumaça ou partícula no destino para disfarçar o pulo.
Essa estrutura de enum é o esqueleto para qualquer evolução. Companheiro de combate? Adicione Estado.ATTACK e uma Area2D de detecção de inimigos. Pet que dorme? Estado.SLEEP depois de X segundos parado.
Bônus: pet que interage com o mundo
Um toque rápido que transforma o pet de enfeite em mecânica: fazer ele coletar itens próximos. Adicione uma Area2D chamada AlcanceColeta como filha do pet, com um CircleShape2D maior que a colisão do corpo, e conecte o sinal area_entered:
func _on_alcance_coleta_area_entered(area: Area2D) -> void:
if area.is_in_group("coletaveis"):
area.queue_free()
# aqui voce soma a moeda/item no inventario do jogador
Basta colocar as moedas e itens do jogo no grupo coletaveis e o pet vira um aspirador ambulante, um quality of life que jogadores adoram. Outra variação comum: em vez de coletar, o pet corre até segredos próximos e late, apontando paredes falsas ou baús escondidos. A mesma Area2D, detectando um grupo segredos, trocando o alvo do follow temporariamente.
Resumo e próximos passos
Você montou quatro camadas de um companheiro 2D: follow com distância mínima para o movimento básico sem tremida, fila de posições para o rastro estilo Zelda, NavigationAgent2D para mapas com paredes e uma máquina de estados com teleporte de segurança, que é o que separa protótipo de jogo publicável. Misture as camadas conforme o seu jogo pede: num mapa aberto, a fila de posições sozinha resolve; num dungeon crawler, navegação mais teleporte é o combo certo.
Se você está começando na engine e quer entender o panorama antes de se aprofundar, veja o guia sobre vale a pena aprender Godot. E se quiser levar o pet para o próximo nível, o caminho natural é dar utilidade de combate a ele: detecção de inimigos, um estado de ataque e cooldowns. A base de estados que você acabou de escrever já aguenta tudo isso.
Perguntas frequentes
Como faço o pet parar de grudar e tremer em cima do jogador no Godot?
Defina uma distância mínima e só mova o pet quando a distância até o jogador passar desse limiar. Dentro do raio, desacelere a velocity até zero com move_toward em vez de zerar de uma vez. Sem esse limiar, o pet ultrapassa o alvo a cada frame e fica oscilando em cima do personagem.
Como criar um follow com atraso estilo Zelda no Godot 4?
Guarde a posição global do jogador a cada frame de física num Array[Vector2], funcionando como uma fila. Quando a fila passa de N posições, remova a mais antiga com pop_front e use essa posição como alvo do pet. Assim o companheiro refaz o caminho exato do jogador com N frames de atraso, em vez de cortar em linha reta.
Quando preciso usar NavigationAgent2D no pet em vez do follow direto?
Use o NavigationAgent2D quando o cenário tem paredes ou obstáculos entre o pet e o jogador. O follow direto move em linha reta e deixa o companheiro preso em quinas. Com uma NavigationRegion2D assada e o agente pedindo get_next_path_position a cada frame, o pet contorna os obstáculos sozinho. Em salas abertas, o follow direto basta e é mais barato.
Por que jogos teleportam o pet para perto do jogador?
Porque nenhum sistema de follow cobre todos os casos: o pet pode ficar preso atrás de uma porta, cair de uma plataforma ou ficar para trás numa transição. O teleporte quando a distância passa de um limite garante que o companheiro nunca se perde de vez. Jogos comerciais fazem isso o tempo todo, de preferência quando o pet está fora da tela.
Esse sistema serve para um companheiro de combate?
Serve como base. O estado FOLLOW deste tutorial vira o comportamento padrão, e você adiciona um estado ATTACK ao enum que dispara quando um inimigo entra numa Area2D de detecção. A estrutura de máquina de estados com enum foi pensada justamente para crescer: cada comportamento novo é um valor no enum e um bloco no match.


