Voltar para o Blog
Quest Log

Plataformas Moveis no Godot 4: AnimatableBody2D e Path2D

Cena 2D no Godot mostrando uma plataforma deslizando sobre um caminho com o player em cima

Aprenda a criar plataforma movel godot com AnimatableBody2D, sync_to_physics e Path2D, levando o player junto sem bugs de fisica no Godot 4.

Ferramenta:Godot

Quase todo jogo de plataforma chega num ponto em que o chao precisa se mexer. Elevadores, plataformas que vao e voltam, esteiras que carregam o jogador para o outro lado de um abismo. Montar uma plataforma movel godot que funciona parece simples ate o player comecar a tremer, deslizar ou atravessar o chao na hora errada. O segredo nao esta em mover o objeto de qualquer jeito, e sim em escolher o no certo e mover ele de um jeito que a fisica entenda.

Plataformas Moveis no Godot 4: AnimatableBody2D e Path2D

Neste tutorial voce vai entender por que o AnimatableBody2D existe, o que o sync_to_physics faz por baixo dos panos, como mover a plataforma com Tween ou com Path2D/PathFollow2D, e como fazer o player ser carregado junto sem gambiarra. Tudo em Godot 4.x, com codigo que compila.

Por que nao usar um StaticBody2D ou mover na unha

A primeira ideia de quem esta comecando e pegar um StaticBody2D, colocar um collision e mexer na position dentro do _process. Funciona visualmente, mas a fisica nao concorda. Um StaticBody2D foi feito para nao se mexer. Quando voce muda a posicao dele direto, o motor de fisica nao calcula a velocidade do corpo, entao o CharacterBody2D em cima dele nao recebe nenhuma informacao de movimento. Resultado: o player fica parado enquanto o chao escorrega embaixo dele, ou trava de forma estranha.

O RigidBody2D tambem nao serve aqui. Ele responde a forcas e gravidade, e voce nao quer que sua plataforma caia ou seja empurrada quando o player pula em cima. Voce quer controle total do caminho, mas com a fisica ciente do movimento.

E ai que entra o AnimatableBody2D. Ele e um corpo cinematico pensado exatamente para isso: voce controla a posicao dele (por codigo, animacao ou tween), e o motor calcula a velocidade sintetica para que outros corpos reajam de forma correta.

AnimatableBody2D e o sync_to_physics

Crie a cena da plataforma assim:

# Estrutura da cena:
# Plataforma (AnimatableBody2D)
#  ├── CollisionShape2D
#  └── Sprite2D (ou um Polygon2D, AnimatedSprite2D, etc.)

No Inspector do AnimatableBody2D voce vai achar a propriedade Sync To Physics. Ela vem ligada por padrao no Godot 4 e e justamente o que faz a plataforma funcionar bem com um player em cima.

Quando sync_to_physics esta true, o motor faz duas coisas importantes:

  1. Move o corpo durante o passo de fisica (e nao no meio do frame de render), o que evita tremor.
  2. Calcula a velocidade do corpo a partir da mudanca de posicao, e essa velocidade e o que permite empurrar e carregar outros corpos.

A regra de ouro: com sync_to_physics ligado, mova o AnimatableBody2D dentro do _physics_process, nunca no _process. Se voce misturar render e fisica, o calculo de velocidade fica errado e o player volta a tremer.

extends AnimatableBody2D

@export var velocidade: float = 80.0
@export var distancia: float = 200.0

var _origem: Vector2
var _direcao: int = 1

func _ready() -> void:
    _origem = position

func _physics_process(delta: float) -> void:
    position.x += velocidade * _direcao * delta
    if absf(position.x - _origem.x) >= distancia:
        _direcao *= -1

Esse e o caso mais simples: uma plataforma de ida e volta no eixo X. Ela anda distancia pixels, inverte a _direcao e volta. Como estamos no _physics_process e o sync_to_physics esta ligado, o player em cima e levado junto sem voce escrever nada para isso.

Levando o player junto sem escorregar

Aqui vale entender o "por que". O CharacterBody2D do player nao recebe gravidade da plataforma; ele apenas detecta que esta apoiado num corpo que tem velocidade. Para o player ser carregado de forma limpa, duas coisas precisam estar certas no script do personagem.

Primeiro, use move_and_slide(). Ele ja considera a velocidade da plataforma por baixo (a chamada get_platform_velocity() existe justamente para isso). Segundo, mantenha floor_snap_length num valor razoavel para o player nao "pular" da plataforma quando ela desce.

extends CharacterBody2D

const VELOCIDADE := 130.0
const FORCA_PULO := -330.0

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

    if Input.is_action_just_pressed("pular") and is_on_floor():
        velocity.y = FORCA_PULO

    var direcao := Input.get_axis("esquerda", "direita")
    velocity.x = direcao * VELOCIDADE

    move_and_slide()

Repare que nao ha nenhuma mencao a plataforma neste script. O move_and_slide() cuida disso quando o chao e um AnimatableBody2D com sync_to_physics. Se voce ja seguiu o tutorial de movimento de personagem em plataforma 2D, esse codigo vai parecer familiar, porque a base e a mesma; o que muda e so o tipo de chao embaixo dos pes do player.

Uma dica honesta: se o player ainda escorregar um pouco quando a plataforma sobe na vertical, ative Platform On Leave como Add Upward Velocity (ou ajuste conforme o comportamento que voce quer ao sair dela). E um detalhe que so aparece quando voce testa de verdade.

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

Movendo com Tween em vez de codigo manual

Mover na unha funciona, mas para um vai e volta suave com pausa nas pontas, um Tween deixa o codigo mais legivel. O ponto de atencao: o Tween por padrao roda no processo de idle (render), e nos queremos fisica. Por isso usamos set_process_mode(Tween.TWEEN_PROCESS_PHYSICS).

extends AnimatableBody2D

@export var deslocamento: Vector2 = Vector2(200, 0)
@export var duracao: float = 2.0
@export var pausa_nas_pontas: float = 0.5

func _ready() -> void:
    var origem := position
    var destino := position + deslocamento

    var tween := create_tween()
    tween.set_loops()
    tween.set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
    tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)

    tween.tween_property(self, "position", destino, duracao)
    tween.tween_interval(pausa_nas_pontas)
    tween.tween_property(self, "position", origem, duracao)
    tween.tween_interval(pausa_nas_pontas)

O set_loops() sem argumento repete para sempre. O TRANS_SINE com EASE_IN_OUT da aquela desaceleracao nas pontas, que parece bem mais natural do que velocidade constante. As pausas usam tween_interval, que apenas espera sem mexer em nada.

Como o tween esta no modo de fisica e o no e um AnimatableBody2D, o calculo de velocidade continua valendo e o player segue sendo carregado. Se voce ainda nao tinha visto tweens encadeados, vale a leitura sobre animar pelo codigo com Tween, que cobre o encadeamento e os tipos de transicao em mais detalhe.

Seguindo um caminho com Path2D e PathFollow2D

Vai e volta em linha reta resolve muita coisa, mas as vezes voce quer um trajeto curvo, em L, ou um loop. Para isso existe a dupla Path2D + PathFollow2D. O Path2D guarda a curva (que voce desenha no editor), e o PathFollow2D e um no que anda ao longo dela conforme voce muda a propriedade progress ou progress_ratio.

A estrutura fica assim:

# Path2D (com a Curve2D desenhada no editor)
#  └── PathFollow2D
#       └── Plataforma (AnimatableBody2D)
#            ├── CollisionShape2D
#            └── Sprite2D

Um ponto que pega muita gente: o AnimatableBody2D precisa ficar como filho do PathFollow2D para acompanhar o caminho. Mas o PathFollow2D tambem gira a plataforma seguindo a curva. Na maioria das plataformas voce nao quer que a colisao rotacione, entao desligue Rotates no PathFollow2D.

O script abaixo move o PathFollow2D de ida e volta usando progress_ratio (que vai de 0.0 a 1.0 ao longo da curva). Coloque ele no PathFollow2D:

extends PathFollow2D

@export var velocidade: float = 0.3  # fracao da curva por segundo
@export var ida_e_volta: bool = true

var _direcao: float = 1.0

func _physics_process(delta: float) -> void:
    progress_ratio += velocidade * _direcao * delta

    if ida_e_volta:
        if progress_ratio >= 1.0:
            progress_ratio = 1.0
            _direcao = -1.0
        elif progress_ratio <= 0.0:
            progress_ratio = 0.0
            _direcao = 1.0

Se voce quiser um loop continuo num caminho fechado em vez de ida e volta, deixe ida_e_volta como false, ligue Loop no PathFollow2D e deixe o progress_ratio so crescer; ele volta para 0 sozinho ao completar a volta.

A vantagem do Path2D e poder editar o trajeto visualmente no editor sem tocar no codigo. Quer que a plataforma faca uma curva em S? Arraste os pontos da curva e pronto. O script continua o mesmo.

Plataforma que desmorona quando o player pisa

Um caso clássico de jogo de plataforma e metroidvania é a plataforma que começa a cair alguns instantes depois que o player encosta nela. A base continua sendo o AnimatableBody2D, mas agora entra um Timer para o atraso e um sinal para detectar o pé do jogador. A estrutura ganha uma Area2D filha, que serve de sensor logo acima da colisão:

# Estrutura:
# PlataformaFragil (AnimatableBody2D)
#  ├── CollisionShape2D
#  ├── Sprite2D
#  ├── Sensor (Area2D)
#  │    └── CollisionShape2D
#  └── TimerQueda (Timer, one_shot)
extends AnimatableBody2D

@export var atraso_queda: float = 0.5
@export var velocidade_queda: float = 300.0
@export var reaparece_em: float = 2.0

var _caindo: bool = false
var _origem: Vector2
var _timer: Timer

func _ready() -> void:
    _origem = position
    _timer = $TimerQueda
    _timer.wait_time = atraso_queda
    _timer.timeout.connect(_comecar_queda)
    $Sensor.body_entered.connect(_ao_pisar)

func _ao_pisar(corpo: Node2D) -> void:
    if corpo.is_in_group("player") and not _caindo and _timer.is_stopped():
        _timer.start()

func _comecar_queda() -> void:
    _caindo = true

func _physics_process(delta: float) -> void:
    if _caindo:
        position.y += velocidade_queda * delta
        if position.y - _origem.y > 2000.0:
            _resetar()

func _resetar() -> void:
    _caindo = false
    await get_tree().create_timer(reaparece_em).timeout
    position = _origem

Repare que a queda também acontece no _physics_process para o sync_to_physics continuar calculando velocidade: se o player ainda estiver em cima quando a plataforma despenca, ele é carregado para baixo junto, que é exatamente a sensação de "o chão sumiu". O is_group("player") evita que um inimigo ou um projétil dispare a queda. E o _resetar() devolve a plataforma à posição original depois de um tempo, o que permite usar a mesma peça num loop de desafio contínuo, como o de um endless runner em que o cenário se repete.

Um detalhe honesto de ajuste fino: se quiser avisar o jogador antes da queda, faça o Sprite2D piscar durante o atraso_queda com um Tween no modulate. Telegrafar a queda é o que transforma uma armadilha injusta numa armadilha justa.

Qual abordagem escolher

Nao existe a melhor opcao para tudo. O que existe e a opcao certa para cada situacao:

  • Movimento reto e simples (elevador, vai e volta no eixo): mover a position no _physics_process resolve com pouco codigo.
  • Movimento suave com pausas e easing: Tween no modo de fisica deixa o codigo claro e facil de ajustar.
  • Trajeto curvo, em L, circular ou que voce quer editar no olho: Path2D + PathFollow2D.

Em todos os tres, a peca que segura tudo e o AnimatableBody2D com sync_to_physics ligado e o movimento acontecendo dentro do passo de fisica. Esse e o ponto que decide se o player vai ser carregado direito ou se vai tremer.

Detalhes que evitam dor de cabeca

Algumas coisas que so aparecem quando voce testa o jogo de verdade:

Plataforma esmagando o player contra uma parede. O move_and_slide() do player tenta deslizar, mas se a plataforma prensa ele contra um colisor estatico, pode haver penetracao. Se isso for um problema no seu jogo, trate a colisao com um sinal body_entered numa Area2D para aplicar dano ou empurrar o player.

Plataforma desaparecendo em cena longa. Se a plataforma faz parte de um cenario com rolagem de fundo, lembre que ela e um objeto de fisica e o fundo nao. Misturar camadas de profundidade com um parallax background ajuda a dar sensacao de movimento sem afetar a colisao, ja que o parallax e puramente visual.

Performance. AnimatableBody2D movido por _physics_process ou por Tween no modo de fisica e barato. Voce pode ter dezenas deles numa fase sem preocupacao. Onde o custo aparece e em colisoes complexas, entao prefira CollisionShape2D com formas simples (retangulos e capsulas) em vez de poligonos detalhados.

Fechando

Uma plataforma movel godot bem feita nao depende de truque, depende de escolher o AnimatableBody2D, manter o sync_to_physics ligado e mover o corpo dentro da fisica, seja por codigo direto, Tween ou Path2D. Com isso o player e carregado de forma natural e voce ganha liberdade para desenhar trajetos do simples ao curvo. Comece pelo vai e volta reto, confirme que o player anda junto sem tremer, e so depois parta para os caminhos mais elaborados. Testar cedo e o que separa uma plataforma que parece certa de uma que so parece certa ate alguem pular em cima dela.