Escadas (Ladders) em Plataforma 2D no Godot 4

Aprenda a implementar escada godot 2d com CharacterBody2D, Area2D e máquina de estados. Subir, descer, sair no topo e ignorar a gravidade na prática.
Escada é um daqueles sistemas que parecem triviais até você sentar para programar. O personagem precisa parar de cair, grudar na escada, subir e descer com input vertical, e sair de forma limpa no topo. Neste guia você vai montar uma escada godot 2d completa em um jogo de plataforma usando CharacterBody2D, uma Area2D para a zona da escada e uma máquina de estados bem simples com enum. Sem mágica, só lógica clara que você consegue ler e adaptar.
Escadas (Ladders) em Plataforma 2D no Godot 4
A ideia central é separar dois modos de movimento. No modo normal, a gravidade puxa o personagem para baixo e ele anda na horizontal. No modo escada, a gravidade é ignorada, o personagem se move na vertical seguindo o input e o movimento horizontal é travado ou bem reduzido. A transição entre esses dois modos é o que faz a escada parecer boa ou quebrada.
Se você ainda não tem uma base de andar e pular, vale revisar primeiro o movimento de personagem em plataforma 2D, porque a escada entra como uma camada em cima desse movimento.
Montando a cena da escada
A escada física no mundo é uma Area2D. Ela não colide com nada, só detecta quando o player entra na região. Estruture assim:
Ladder(Area2D)CollisionShape2Dcom umRectangleShape2Dcobrindo a área da escada
Coloque a Area2D no grupo ladder. No editor, selecione o nó, vá em Node > Groups e adicione ladder. Assim o player não precisa saber o nome de cada escada da fase, ele só pergunta se a área pertence a esse grupo.
Sobre as collision layers da Area2D: a escada deve viver em uma layer própria, separada do chão e dos inimigos. O player precisa monitorar essa layer para detectar a escada, mas a escada não deve bloquear o movimento. Se você ainda está confuso com layers e masks, dá uma olhada em collision layers e masks no Godot antes de seguir, porque configurar isso errado é a causa número um de escada que não detecta nada.
Uma boa convenção: deixe a escada na layer 4, com mask zerada (ela não precisa detectar nada, só ser detectada). O player, por sua vez, terá uma Area2D detectora ou usará a sinalização da própria escada.
Detectando o player na escada
O jeito mais limpo é a escada avisar o player quando ele entra e sai. Conecte os sinais body_entered e body_exited da Area2D para um script na própria escada:
extends Area2D
func _on_body_entered(body: Node2D) -> void:
if body.has_method("set_ladder"):
body.set_ladder(self, true)
func _on_body_exited(body: Node2D) -> void:
if body.has_method("set_ladder"):
body.set_ladder(self, false)
E no player, métodos para receber esse aviso:
var current_ladder: Area2D = null
var can_use_ladder: bool = false
func set_ladder(ladder: Area2D, entered: bool) -> void:
if entered:
current_ladder = ladder
can_use_ladder = true
else:
can_use_ladder = false
current_ladder = null
if state == State.LADDER:
exit_ladder()
Repare que sair da área força o player para fora do estado escada. Isso evita o bug clássico de continuar grudado no ar depois que a escada acabou.
A máquina de estados
Vamos usar um enum com poucos estados. Para escada bastam dois: o estado normal e o estado escada. Você pode ter outros (pulo, dash), mas aqui o foco é a transição para a escada.
extends CharacterBody2D
enum State { NORMAL, LADDER }
@export var speed: float = 130.0
@export var jump_velocity: float = -320.0
@export var climb_speed: float = 90.0
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
var state: State = State.NORMAL
A variável state decide qual bloco de física roda a cada frame. Manter isso explícito facilita debugar: a qualquer momento você sabe em que modo o personagem está.
Entrando e saindo do modo escada
O player entra na escada quando está dentro da área e aperta cima ou baixo. Centralizamos a lógica de entrada e saída em métodos próprios para não espalhar a regra pelo código:
func enter_ladder() -> void:
state = State.LADDER
velocity.x = 0.0
velocity.y = 0.0
# alinha o player ao centro horizontal da escada
if current_ladder:
global_position.x = current_ladder.global_position.x
func exit_ladder() -> void:
state = State.NORMAL
Alinhar o global_position.x ao centro da escada é um detalhe que muda muito a sensação. Sem isso, o player sobe torto e pode escorregar para fora da área. Se você não quiser o snap rígido, interpole com lerp para um efeito mais suave.
O loop de física
Agora o coração do sistema dentro de _physics_process. Cada estado tem sua própria função:
func _physics_process(delta: float) -> void:
match state:
State.NORMAL:
process_normal(delta)
State.LADDER:
process_ladder(delta)
move_and_slide()
O estado normal é o movimento de plataforma de sempre, com a checagem extra para entrar na escada:
func process_normal(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# entra na escada apertando cima ou baixo dentro da area
if can_use_ladder:
var climb := Input.get_axis("move_up", "move_down")
if climb != 0.0:
enter_ladder()
E o estado escada, onde a gravidade some por completo:
func process_ladder(_delta: float) -> void:
var climb := Input.get_axis("move_up", "move_down")
velocity.y = climb * climb_speed
# movimento horizontal travado na escada
velocity.x = 0.0
# pular sai da escada com impulso
if Input.is_action_just_pressed("jump"):
exit_ladder()
velocity.y = jump_velocity
return
# chegou ao topo e pisou no chao, sai naturalmente
if is_on_floor() and climb > 0.0:
exit_ladder()
Note que dentro de process_ladder nunca somamos gravity. Esse é o ponto mais importante de toda a implementação: enquanto o estado for LADDER, a gravidade simplesmente não existe. Se você esquecer e deixar a gravidade rodando em paralelo, o personagem desce sozinho e a escada vira uma luta contra a física.
Se quiser permitir um leve ajuste horizontal na escada (alguns jogos deixam o personagem se inclinar um pouco), troque o velocity.x = 0.0 por algo como velocity.x = Input.get_axis("move_left", "move_right") * speed * 0.3. Reduzir para 30 por cento mantém a sensação de escada sem prender o jogador.
O cuidado com o topo da escada
O topo é onde a maioria das implementações quebra. Em geral a plataforma do topo da escada é uma plataforma one-way (StaticBody2D com one_way_collision ativo no CollisionShape2D, ou um TileMap com colisão de subida só por um lado). O comportamento esperado é: o player sobe pela escada atravessando essa plataforma por baixo e, ao chegar no topo, pisa firme nela.
Como a plataforma é one-way, o player consegue subir através dela sem ser bloqueado. Quando a velocity.y é negativa (subindo) e ele cruza a borda, o is_on_floor() passa a retornar verdadeiro assim que ele apoia em cima. Foi por isso que adicionamos a checagem if is_on_floor() and climb > 0.0 em process_ladder. Subiu o suficiente para pisar no chão, então sai da escada já em pé, sem cair de volta.
Um detalhe prático: estenda a Area2D da escada um pouco acima da plataforma do topo, uns poucos pixels. Isso dá margem para o player completar a subida antes de sair da zona de detecção. Se a área terminar exatamente na borda da plataforma, o body_exited dispara cedo demais e o personagem perde o controle da escada no pior momento.
Para descer a partir do topo, o player precisa primeiro sair da plataforma one-way. A solução comum é: estando parado em cima da escada e apertando para baixo, force a entrada na escada e desligue a colisão one-way por um instante, ou empurre o player alguns pixels para dentro da área antes de entrar no estado escada. Uma versão simples:
func try_enter_from_top() -> void:
if can_use_ladder and is_on_floor():
if Input.is_action_just_pressed("move_down"):
# empurra um pouco para dentro da escada
global_position.y += 4.0
enter_ladder()
Chame try_enter_from_top dentro de process_normal se quiser esse comportamento. Ajuste os 4 pixels conforme a altura do seu personagem e da plataforma.
Saídas possíveis da escada
Vale ter o mapa mental claro de todas as formas de sair do estado escada, porque cada uma precisa funcionar:
- Pulo: aperta jump, sai com impulso vertical. Bom para soltar da escada no meio.
- Topo: chega em cima, pisa no chão, sai em pé automaticamente.
- Saída pela área: o
body_exitedda escada chamaexit_laddere devolve o player ao modo normal. - Base da escada: ao descer e tocar o chão, a checagem
is_on_floor()no estado normal já segura o personagem na próxima vez que a gravidade voltar.
Se a sua escada está bugando, quase sempre é uma dessas saídas que não está coberta. Teste cada caminho isoladamente.
Polindo a sensação
Algumas melhorias rápidas que valem o tempo. Pause a animação de subida quando velocity.y for zero, para o personagem ficar parado na escada em vez de fazer a animação de climbing no vazio. Toque um som de passo a cada intervalo enquanto sobe. E considere desligar a habilidade de dash ou de double jump enquanto estiver no estado escada, senão o jogador escapa por caminhos que você não planejou. Se o seu jogo já tem movimentação aérea avançada, dá para ver como isolar essas mecânicas no guia de double jump e wall jump no Godot.
Com a Area2D no grupo ladder, as collision layers certas, a gravidade ignorada no estado escada e o cuidado com a plataforma one-way do topo, você tem um sistema sólido e previsível. O segredo não é código complicado, é deixar cada transição de estado explícita e testar todas as saídas. A partir daqui é só ajustar climb_speed e os pixels de folga ao feel do seu jogo.


