Voltar para o Blog
Quest Log

Checkpoint e Respawn no Godot 4: Salvar a Posicao do Player

Personagem 2D tocando uma bandeira de checkpoint dentro do editor do Godot

Aprenda a montar checkpoint godot com Area2D, autoload guardando a posicao do player, sinal de morte e respawn certo. Tutorial pratico em GDScript.

Quase todo jogo de plataforma ou acao precisa de um lugar pra onde o player volta quando morre. Sem isso, qualquer queda joga o jogador de volta pro comeco da fase, e isso cansa rapido. Montar um sistema de checkpoint godot nao e dificil, mas tem alguns detalhes que decidem se ele vai funcionar de verdade ou te dar dor de cabeca: onde guardar a posicao salva, quando salvar, e como reposicionar o player sem quebrar o estado dele.

Checkpoint e Respawn no Godot 4: Salvar a Posicao do Player

Neste tutorial a gente vai montar um checkpoint com Area2D, guardar a ultima posicao num autoload (singleton), disparar um sinal quando o player morre e reposicionar ele usando global_position. No fim, voce vai ter um sistema que aguenta varios checkpoints na mesma fase. Tudo em GDScript de Godot 4.x, sem pseudo-codigo.

Por que um autoload guarda a posicao do checkpoint godot

A primeira pergunta e: onde a posicao salva fica? Se voce guardar ela no proprio player, perde tudo quando o player e destruido (e quando ele morre, normalmente some da cena). Se guardar no node do checkpoint, fica espalhado e dificil de consultar na hora do respawn.

A solucao mais limpa e um autoload. Um autoload e um script que o Godot carrega uma vez e mantem vivo durante toda a execucao do jogo, acessivel de qualquer lugar pelo nome. Ele sobrevive a troca de cena e a morte do player, entao e o lugar natural pra guardar "onde o player deve reaparecer".

Crie um script chamado CheckpointManager.gd:

extends Node

# Posicao onde o player vai reaparecer.
var respawn_position: Vector2 = Vector2.ZERO

# Guarda se ja existe um checkpoint ativado nesta fase.
var has_checkpoint: bool = false

func set_checkpoint(pos: Vector2) -> void:
    respawn_position = pos
    has_checkpoint = true

func get_respawn_position(fallback: Vector2) -> Vector2:
    # Se nenhum checkpoint foi tocado, volta pro ponto inicial.
    if has_checkpoint:
        return respawn_position
    return fallback

func reset() -> void:
    # Chame ao reiniciar a fase do zero.
    respawn_position = Vector2.ZERO
    has_checkpoint = false

Pra registrar como autoload, va em Project > Project Settings > Globals (aba Autoload), aponte pro arquivo CheckpointManager.gd e de o nome CheckpointManager. A partir dai voce chama CheckpointManager.set_checkpoint(...) de qualquer script. O fallback existe porque, no comeco da fase, o jogador ainda nao tocou em nada, e voce nao quer mandar ele pro Vector2.ZERO (canto da tela).

O checkpoint em si: um Area2D que detecta o player

O checkpoint precisa saber quando o player passa por ele. Area2D e ideal pra isso porque ele detecta sobreposicao sem empurrar nada, diferente de um StaticBody2D. O player atravessa a area normalmente e a area so avisa "alguem entrou".

Monte uma cena com esta estrutura:

Checkpoint (Area2D)
├── CollisionShape2D
└── Sprite2D (a bandeira, opcional)

No script do Area2D, conecte o sinal body_entered. Esse sinal dispara quando um PhysicsBody2D (seu player, por exemplo) encosta na area:

extends Area2D

# Marca se este checkpoint ja foi ativado, pra nao salvar toda hora.
var activated: bool = false

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

func _on_body_entered(body: Node2D) -> void:
    # So reage ao player. Use grupos pra identificar.
    if not body.is_in_group("player"):
        return
    if activated:
        return
    activated = true
    CheckpointManager.set_checkpoint(global_position)
    _play_feedback()

func _play_feedback() -> void:
    # Feedback visual simples: muda a cor da bandeira.
    var sprite := get_node_or_null("Sprite2D") as Sprite2D
    if sprite:
        sprite.modulate = Color(0.4, 1.0, 0.4)

Repare em dois detalhes. Primeiro, eu uso body.is_in_group("player") em vez de checar o nome do node. Nomes mudam, grupos sao estaveis. No player, adicione o node ao grupo "player" pelo editor (aba Node > Groups) ou por codigo com add_to_group("player"). Segundo, a flag activated evita que o checkpoint salve a posicao varias vezes enquanto o player fica em cima dele. Salvar uma vez basta.

Uma pergunta comum: salvar global_position do checkpoint ou do player? Use o do checkpoint. Assim o player sempre reaparece num ponto previsivel e seguro (em cima da bandeira), e nao num lugar aleatorio onde ele estava quando cruzou a borda da area.

O sinal de morte do player

Agora o lado do player. Quando ele morre, alguem precisa ser avisado pra cuidar do respawn. Em vez de o player tentar se reposicionar sozinho (ele vai estar no meio de uma animacao de morte, ou ate queue_free), o jeito mais organizado e o player emitir um sinal e deixar a fase decidir o que fazer.

No script do player, declare um sinal e emita ele quando a vida chega a zero. Se voce ja tem um sistema de vida e dano montado, e provavel que ja exista um ponto certo pra disparar isso.

extends CharacterBody2D

signal died

@export var max_health: int = 3
var health: int = max_health

func _ready() -> void:
    add_to_group("player")
    health = max_health

func take_damage(amount: int) -> void:
    health -= amount
    if health <= 0:
        die()

func die() -> void:
    # Evita emitir o sinal duas vezes em quadros seguidos.
    set_physics_process(false)
    died.emit()

O set_physics_process(false) para a fisica do player no instante da morte. Sem isso, o player pode continuar caindo ou recebendo dano enquanto a animacao de morte roda, o que gera bugs estranhos como morrer duas vezes no mesmo frame.

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

Reposicionando o player com global_position

Quem escuta o sinal died e a cena da fase (ou um node de controle do nivel). Ela decide se vai respawnar o player ou, dependendo do seu jogo, mostrar a tela de game over quando as vidas acabam. Aqui vamos focar no respawn direto.

No script da fase, conecte o sinal do player e mande ele de volta pro checkpoint:

extends Node2D

@onready var player: CharacterBody2D = $Player

# Ponto inicial caso nenhum checkpoint tenha sido tocado.
@export var start_position: Vector2 = Vector2(100, 100)

func _ready() -> void:
    player.died.connect(_on_player_died)

func _on_player_died() -> void:
    # Espera um instante pra animacao de morte aparecer.
    await get_tree().create_timer(0.8).timeout
    respawn_player()

func respawn_player() -> void:
    var pos := CheckpointManager.get_respawn_position(start_position)
    player.global_position = pos
    player.velocity = Vector2.ZERO
    player.health = player.max_health
    player.set_physics_process(true)

A linha que faz o trabalho e player.global_position = pos. Use global_position, nao position. A diferenca importa: position e relativo ao node pai, entao se o player estiver dentro de algum container ou nodo com offset, definir position coloca ele no lugar errado. global_position e a coordenada no mundo, exatamente o que voce salvou do checkpoint (que tambem usou global_position). Misturar os dois e a causa numero um de "meu player respawna no lugar errado".

Depois de mover, eu zero velocity. Sem isso, o player carrega a velocidade que tinha no momento da morte (geralmente caindo) e desliza ou cai de novo assim que reaparece. Tambem restauro a vida e religo set_physics_process(true), que tinha sido desligado no die().

Se voce quiser uma animacao de morte completa antes de sumir, mantenha o player na cena e so esconda/mostre o sprite, em vez de dar queue_free. Como a posicao salva mora no autoload, o respawn funciona dos dois jeitos, mas manter o mesmo node vivo e mais simples pra comecar.

Lidando com varios checkpoints na fase

O bom da abordagem com autoload e que multiplos checkpoints saem de graca. Cada Area2D de checkpoint, ao ser tocado, chama CheckpointManager.set_checkpoint(global_position) e sobrescreve a posicao anterior. O player sempre reaparece no ultimo que tocou. Voce nao precisa de codigo extra pra isso.

Tem um cuidado pratico, porem: e comum o jogador voltar e cruzar um checkpoint anterior ao ja ter ativado um mais a frente. Se cada area so guarda seu proprio estado, voltar pra tras nao deveria "regredir" o respawn. Como o nosso set_checkpoint simplesmente sobrescreve, andar pra tras e tocar um checkpoint antigo move o respawn pra tras tambem, o que pode nao ser o que voce quer.

Uma forma de evitar isso e dar uma ordem a cada checkpoint e so aceitar se for mais avancado que o atual:

extends Area2D

@export var order: int = 0

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

func _on_body_entered(body: Node2D) -> void:
    if not body.is_in_group("player"):
        return
    if order <= CheckpointManager.current_order:
        return
    CheckpointManager.set_checkpoint(global_position, order)

E no autoload, guarde a ordem junto:

extends Node

var respawn_position: Vector2 = Vector2.ZERO
var has_checkpoint: bool = false
var current_order: int = -1

func set_checkpoint(pos: Vector2, order: int) -> void:
    respawn_position = pos
    current_order = order
    has_checkpoint = true

func get_respawn_position(fallback: Vector2) -> Vector2:
    if has_checkpoint:
        return respawn_position
    return fallback

func reset() -> void:
    respawn_position = Vector2.ZERO
    has_checkpoint = false
    current_order = -1

Assim, voltar pra tras nunca diminui o progresso. Defina order em cada checkpoint pelo inspector (1, 2, 3...) e pronto. Se a sua fase nao tem backtracking, pode ignorar isso e usar a versao simples; nem todo jogo precisa.

Quando o jogo fecha: persistir entre sessoes

O autoload resolve a posicao dentro de uma sessao, mas ele zera quando o jogo fecha. Se voce quer que o jogador feche o jogo e volte no mesmo checkpoint depois, precisa gravar respawn_position e current_order num arquivo. Esse e um problema separado, de save de progresso, e da pra resolver com o mesmo autoload escrevendo num ConfigFile ou JSON. Se voce ainda nao tem essa base, o tutorial de salvar e carregar no Godot cobre exatamente como persistir esse tipo de estado entre sessoes.

Pra integrar, bastaria o CheckpointManager chamar uma funcao de save sempre que set_checkpoint rodar, e carregar esses valores no _ready. A logica de respawn que voce ja montou continua igual.

Fechamento

O esquema todo se resume a tres pecas que conversam por mensagem, nao por acoplamento direto. O Area2D detecta o player e avisa o autoload. O autoload guarda a posicao e sobrevive a morte e a troca de cena. O player emite died e deixa a fase reposicionar ele com global_position. Cada peca faz uma coisa so, e por isso fica facil de estender: adicionar mais checkpoints, ligar com a tela de game over, ou persistir em disco depois.

Comece pela versao simples (um autoload, um Area2D, um sinal) e so adicione ordem de checkpoint ou save em arquivo quando o seu jogo realmente pedir. Um sistema de checkpoint godot bom e aquele que o jogador nem percebe: ele morre, reaparece no lugar certo, e volta a jogar.