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.

Ferramenta:Godot

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.

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

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". Essa mesma tecnica de reposicionar com global_position e a base de um teleporte ou portal 2D no Godot; o que muda e so quem dispara o salto.

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.