Voltar para o Blog
Quest Log

Agachar e deslizar (crouch) em plataforma 2D no Godot 4

Personagem 2D agachado embaixo de uma plataforma estreita em um jogo de plataforma no Godot.

Tutorial de agachar godot 2d: reduzir a colisao, checar o teto com RayCast antes de levantar e adicionar slide. GDScript real para CharacterBody2D no Godot 4.

Agachar parece um detalhe pequeno, mas muda a sensacao do seu jogo. Com agachar godot 2d voce abre passagens baixas, esquiva de projeteis e cria aquele slide rapido que deixa o movimento gostoso de controlar. O problema e que quase todo mundo erra na mesma coisa: reduz a colisao para agachar, mas esquece de checar se ha teto antes de levantar. O resultado e o personagem atravessando o chao de cima e ficando preso. Neste tutorial montamos um crouch completo num CharacterBody2D, com reducao de colisao, perda de velocidade, checagem de teto e um slide opcional.

Agachar e deslizar (crouch) em plataforma 2D no Godot 4

A ideia central de agachar godot 2d e simples: quando o jogador segura o botao de agachar, voce encolhe a forma de colisao e troca a animacao. Quando solta, voce devolve a forma original. O cuidado fica todo na hora de devolver, porque pode nao ter espaco.

Antes de escrever codigo, monte a cena. Um CharacterBody2D com:

  • CollisionShape2D (a colisao principal, em pe).
  • AnimatedSprite2D ou Sprite2D para a animacao.
  • ShapeCast2D apontando para cima, usado para detectar teto.

Vou usar uma CapsuleShape2D na colisao porque ela e facil de redimensionar pela altura. Se voce usar RectangleShape2D, o raciocinio e o mesmo, voce ajusta a propriedade size.

Estrutura basica do movimento

Comecamos com um movimento de plataforma padrao. Se voce ainda nao tem essa base, vale conferir o guia de movimento de personagem em plataforma 2D antes de seguir, porque aqui parto do principio que pular e andar ja funcionam.

extends CharacterBody2D

const SPEED := 200.0
const CROUCH_SPEED := 90.0
const JUMP_VELOCITY := -350.0

@onready var collision: CollisionShape2D = $CollisionShape2D
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var ceiling_cast: ShapeCast2D = $CeilingCast

var is_crouching := false

func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity += get_gravity() * delta

    _handle_crouch()
    _handle_jump()
    _handle_horizontal()
    _update_animation()

    move_and_slide()

Cada responsabilidade fica numa funcao separada. Isso deixa o agachar facil de ler e de ajustar depois.

Reduzindo a colisao ao agachar

A parte fisica do crouch e mudar a altura da CapsuleShape2D. Guardamos a altura original em constantes para nunca perder o valor de referencia.

const STAND_HEIGHT := 32.0
const CROUCH_HEIGHT := 16.0

func _set_collision_height(height: float) -> void:
    var shape := collision.shape as CapsuleShape2D
    shape.height = height
    # Reposiciona a colisao para os pes ficarem no mesmo lugar.
    collision.position.y = -height * 0.5

Reparou no ajuste de position.y? Sem ele, a capsula encolhe a partir do centro e o personagem parece flutuar ou afundar no chao. Ao mover a colisao para baixo na mesma proporcao, os pes continuam tocando o solo e so a parte de cima desce.

Um detalhe importante: collision.shape e compartilhado entre instancias por padrao no Godot. Se voce tiver varios personagens usando a mesma cena, ative Local to Scene no recurso da forma, ou duplique com collision.shape = collision.shape.duplicate() no _ready. Sem isso, agachar um inimigo agacha todos.

A logica do agachar e o teto

Aqui mora o erro mais comum. Quando o jogador solta o botao, voce so pode levantar se houver espaco. Para checar isso usamos o ShapeCast2D apontado para cima.

Configure o CeilingCast no editor: defina o shape como uma forma parecida com a colisao em pe (uma capsula ou retangulo da altura do personagem), aponte target_position para cima (por exemplo Vector2(0, -18)) e marque a layer das paredes e do chao em collision_mask. Tambem ative Enabled para ele atualizar todo frame.

func _handle_crouch() -> void:
    var wants_crouch := Input.is_action_pressed("crouch") and is_on_floor()

    if wants_crouch and not is_crouching:
        _enter_crouch()
    elif not wants_crouch and is_crouching:
        # So levanta se nao houver teto bloqueando.
        if not _has_ceiling():
            _exit_crouch()

func _enter_crouch() -> void:
    is_crouching = true
    _set_collision_height(CROUCH_HEIGHT)

func _exit_crouch() -> void:
    is_crouching = false
    _set_collision_height(STAND_HEIGHT)

func _has_ceiling() -> bool:
    ceiling_cast.force_shapecast_update()
    return ceiling_cast.is_colliding()

O force_shapecast_update() garante uma leitura fresca naquele instante, sem depender do timing do frame. Se houver teto, simplesmente ignoramos o pedido de levantar e o personagem continua agachado ate o jogador sair da passagem baixa. Esse e o detalhe que evita o bug de atravessar o teto: nunca devolvemos a altura cheia se o espaco acima estiver ocupado.

Um alerta sobre o erro classico: muita gente troca a altura da colisao direto no _input ou compara o botao sem checar o teto. Quando isso acontece embaixo de uma plataforma, o Godot expande a capsula para dentro do colisor de cima e o motor de fisica empurra o corpo para fora pelo caminho mais curto, que costuma ser para cima. O personagem teleporta atravessando o chao superior. A checagem com ShapeCast2D resolve porque trava o levantar enquanto nao ha folga.

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

Velocidade reduzida e movimento horizontal

Agachado, o personagem deve andar mais devagar. Basta escolher a velocidade conforme o estado.

func _handle_horizontal() -> void:
    var direction := Input.get_axis("move_left", "move_right")
    var current_speed := CROUCH_SPEED if is_crouching else SPEED

    if direction != 0.0:
        velocity.x = direction * current_speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, current_speed)

Note que tambem impedimos o pulo enquanto agachado dentro de um espaco apertado. Se o jogador estiver agachado mas com espaco livre, ele levanta primeiro e o pulo acontece no frame seguinte. Para deixar isso explicito:

func _handle_jump() -> void:
    if Input.is_action_just_pressed("jump") and is_on_floor():
        if is_crouching:
            # Tenta levantar antes de pular; se ha teto, ignora o pulo.
            if _has_ceiling():
                return
            _exit_crouch()
        velocity.y = JUMP_VELOCITY

Assim o pulo nunca atravessa o teto, porque ele depende da mesma checagem que o levantar.

Slide com impulso

O slide e a recompensa de agachar enquanto corre. Quando o jogador esta em velocidade alta e aperta agachar, damos um empurrao horizontal que decai com o tempo. Ele e opcional, mas deixa o movimento muito mais expressivo.

const SLIDE_SPEED := 320.0
const SLIDE_FRICTION := 600.0

var is_sliding := false

func _enter_crouch() -> void:
    is_crouching = true
    _set_collision_height(CROUCH_HEIGHT)
    # So desliza se estava correndo rapido o bastante.
    if absf(velocity.x) > SPEED * 0.8:
        is_sliding = true
        velocity.x = sign(velocity.x) * SLIDE_SPEED

func _exit_crouch() -> void:
    is_crouching = false
    is_sliding = false
    _set_collision_height(STAND_HEIGHT)

Durante o slide, ignoramos o controle horizontal normal e deixamos o atrito frear o personagem. Ajustamos o _handle_horizontal para respeitar esse estado.

func _handle_horizontal() -> void:
    if is_sliding:
        velocity.x = move_toward(velocity.x, 0.0, SLIDE_FRICTION * get_physics_process_delta_time())
        if absf(velocity.x) < CROUCH_SPEED:
            is_sliding = false
        return

    var direction := Input.get_axis("move_left", "move_right")
    var current_speed := CROUCH_SPEED if is_crouching else SPEED

    if direction != 0.0:
        velocity.x = direction * current_speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, current_speed)

Quando a velocidade do slide cai abaixo da velocidade de agachado, voltamos ao crouch normal e o jogador retoma o controle. Se ele soltar o botao e nao houver teto, levanta direto. Se houver teto, continua agachado, sem slide.

Trocando a animacao e o sprite

A parte visual fecha a experiencia. Com um AnimatedSprite2D, basta escolher a animacao certa conforme o estado.

func _update_animation() -> void:
    if is_sliding:
        sprite.play("slide")
    elif is_crouching:
        if absf(velocity.x) > 5.0:
            sprite.play("crouch_walk")
        else:
            sprite.play("crouch_idle")
    elif not is_on_floor():
        sprite.play("jump")
    elif absf(velocity.x) > 5.0:
        sprite.play("run")
    else:
        sprite.play("idle")

    # Vira o sprite na direcao do movimento.
    if velocity.x != 0.0:
        sprite.flip_h = velocity.x < 0.0

Garanta que as animacoes crouch_idle, crouch_walk e slide existam no seu SpriteFrames. Se ainda nao tiver arte de agachado, use o sprite normal com scale.y menor como placeholder, mas troque por arte de verdade depois, porque escalar deforma a leitura do personagem.

Checklist para nao quebrar

Antes de considerar pronto, valide estes pontos no seu projeto:

  • A colisao realmente encolhe e os pes ficam no chao (ajuste de position.y).
  • O ShapeCast2D cobre a altura cheia do personagem e mira para cima.
  • Levantar e pular sempre passam pela checagem de teto.
  • O slide so dispara em velocidade alta e decai sozinho.
  • As animacoes de crouch e slide estao no SpriteFrames.

Com isso voce tem um crouch solido, sem o bug de atravessar o teto, e ainda ganha um slide que da personalidade ao movimento. Da para evoluir o sistema com som de deslizar, particulas de poeira e ate dano por deslizar contra inimigos. Para continuar deixando seu personagem mais completo, o proximo passo natural e adicionar interacao com escadas em plataforma 2D, que combina bem com o agachar para criar niveis verticais mais ricos.