Voltar para o Blog
Quest Log

Escadas (Ladders) em Plataforma 2D no Godot 4

Personagem pixel art subindo uma escada de madeira entre duas plataformas em um jogo 2D

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.

Ferramenta:Godot

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)
    • CollisionShape2D com um RectangleShape2D cobrindo 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á.

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

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_exited da escada chama exit_ladder e 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.

Depurando a escada que não detecta ou que gruda errado

Quase todo bug de escada cai em uma de quatro categorias, e a forma mais rápida de achar o culpado é imprimir o estado interno a cada frame enquanto você testa. Adicione um helper de debug e chame dentro do _physics_process, só enquanto estiver caçando o problema:

@export var debug_escada: bool = false

func imprimir_debug_escada() -> void:
    if not debug_escada:
        return
    var nome_estado: String = State.keys()[state]
    print("estado=%s | can_use=%s | ladder=%s | vel=%v | no_chao=%s" % [
        nome_estado,
        can_use_ladder,
        current_ladder != null,
        velocity,
        is_on_floor(),
    ])

Agora o mapa de diagnóstico, do sintoma à causa:

  • O player atravessa a escada e can_use_ladder nunca vira true. É configuração de colisão. A Area2D da escada precisa estar numa layer que a mask do player monitore, e o player tem que ser um corpo físico que a área detecta. Revise collision layers e masks no Godot, porque noventa por cento dos casos param aqui.
  • Entra na escada mas continua caindo. A gravidade está vazando. Confirme que process_ladder nunca soma gravity e que o match state realmente desvia para ele. Um return esquecido ou um bloco de gravidade fora do match reintroduz a queda.
  • Sobe tremendo ou escorrega para fora. Falta o alinhamento horizontal. O global_position.x = current_ladder.global_position.x em enter_ladder centraliza o personagem; sem ele, qualquer colisão lateral empurra o corpo para fora da zona de detecção.
  • Solta da escada sozinho no meio da subida. O body_exited disparou cedo. A Area2D está curta demais ou o CollisionShape2D do player é mais alto que a escada. Estenda a área alguns pixels para cima e para baixo.

Um teste que vale ouro: coloque um Label na cena mostrando State.keys()[state] em tempo real. Ver a palavra NORMAL piscar para LADDER e voltar no momento exato em que o bug acontece economiza muito print. Esse mesmo padrão de visualizar o estado na tela é o que torna qualquer máquina de estados fácil de depurar, e serve bem além da escada: movimentos como plataforma móvel no Godot sofrem dos mesmos sintomas de transição mal resolvida.

Animando a subida sem bugar a direção

Com a lógica de movimento pronta, falta o personagem parecer que sobe de verdade. O detalhe que trava muita gente é a animação continuar rodando quando ele está parado na escada, ou o sprite virar pro lado errado ao subir.

A regra é amarrar a animação ao estado e à velocidade, não ao input cru. No estado escada, se velocity.y for diferente de zero, toque a animação de subir; se for zero, pause no quadro atual em vez de ficar escalando no vazio:

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D

func atualizar_animacao_escada() -> void:
    if absf(velocity.y) > 0.0:
        sprite.play("climb")
    else:
        sprite.pause()

Chame atualizar_animacao_escada() no fim de process_ladder. O sprite.pause() congela no quadro em que estava, então o personagem fica numa pose de agarrado à escada, não numa animação travada no primeiro frame.

Sobre a direção: na escada, não mexa no flip_h do sprite com o input horizontal, porque o movimento lateral está travado e o personagem ficaria virando à toa. Trave o flip_h no valor que ele tinha ao entrar na escada, ou force uma direção fixa (de costas pra câmera é o mais comum em plataforma). Só volte a controlar o flip pelo input quando o estado voltar pra NORMAL. Esse cuidado é o que separa uma escada que parece polida de uma que "treme" visualmente a cada toque.

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.

Escada é uma forma explícita de subir. Uma variação mais fluida, muito comum em plataforma e metroidvania, é agarrar a beirada e subir sozinho: veja como fazer ledge climb e escalar a borda no Godot 2D, que reaproveita a mesma ideia de estado com gravidade congelada que você acabou de montar aqui.