Voltar para o Blog
Quest Log

Plataforma Móvel na Unity 2D sem o Jogador Escorregar

Personagem 2D em pixel art em cima de uma plataforma flutuante que desliza entre dois pontos marcados, com setas indicando o trajeto

Unity moving platform 2D com Rigidbody2D kinematic e MovePosition, duas formas de o jogador não escorregar e plataforma one-way com Platform Effector 2D, em C#.

Ferramenta:UnityC#

Plataforma Móvel na Unity 2D sem o Jogador Escorregar

Uma unity moving platform 2D que funciona tem duas partes: a plataforma, que deve ser um Rigidbody2D Kinematic movido com MovePosition no FixedUpdate, e o jogador, que precisa ser levado junto. A primeira parte é fácil. A segunda é onde quase todo mundo trava, porque o personagem fica parado no mundo enquanto a plataforma escorrega por baixo dele. Este tutorial monta a plataforma, explica por que o escorregão acontece e compara as duas soluções clássicas (tornar o jogador filho da plataforma ou herdar a velocidade dela), com C# do Unity 6.3 LTS. No fim, você ainda configura uma plataforma one-way com Platform Effector 2D.

Unity moving platform 2d: como montar

Crie a plataforma:

  1. Um GameObject MovingPlatform com SpriteRenderer (ou um Tilemap pequeno).
  2. BoxCollider2D do tamanho do sprite.
  3. Rigidbody2D com Body Type Kinematic e Interpolate Interpolate.
  4. Dois ou mais objetos vazios, fora da plataforma, marcando os pontos do trajeto: PointA, PointB.

Por que Kinematic? Um corpo Kinematic não sofre gravidade nem é empurrado por colisões; ele vai exatamente para onde o script manda. Ao mesmo tempo, ele participa da física: corpos Dynamic (o jogador) colidem com ele e são empurrados. Um corpo Static não serve, porque mover um collider estático a cada frame é caro e não gera contato correto. Mover pelo transform também não serve, pelo mesmo motivo que o personagem atravessa paredes quando anda por transform: a física só descobre o movimento depois que ele aconteceu.

Na documentação do Unity 6.3, Rigidbody2D.useFullKinematicContacts controla se um corpo Kinematic colide com outros Kinematic e Static. Para a plataforma ele não importa: o padrão já gera contato entre Kinematic e Dynamic. Ele só entra em cena se o seu jogador também for Kinematic.

Mover a plataforma com Rigidbody2D kinematic

Crie MovingPlatform2D.cs:

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class MovingPlatform2D : MonoBehaviour
{
    [SerializeField] private Transform[] waypoints;
    [SerializeField] private float speed = 3f;
    [SerializeField] private float waitTime = 0.5f;
    [SerializeField] private Rigidbody2D rb;

    private int targetIndex;
    private float waitUntil;

    public Vector2 Velocity { get; private set; }

    private void Awake()
    {
        if (rb == null)
        {
            rb = GetComponent<Rigidbody2D>();
        }

        rb.bodyType = RigidbodyType2D.Kinematic;
    }

    private void FixedUpdate()
    {
        if (waypoints == null || waypoints.Length < 2 || Time.time < waitUntil)
        {
            Velocity = Vector2.zero;
            return;
        }

        Vector2 current = rb.position;
        Vector2 target = waypoints[targetIndex].position;
        Vector2 next = Vector2.MoveTowards(current, target, speed * Time.fixedDeltaTime);

        Velocity = (next - current) / Time.fixedDeltaTime;
        rb.MovePosition(next);

        if ((next - target).sqrMagnitude < 0.0001f)
        {
            targetIndex = (targetIndex + 1) % waypoints.Length;
            waitUntil = Time.time + waitTime;
        }
    }
}

Arraste PointA e PointB no array Waypoints. A plataforma vai e volta, parando waitTime segundos em cada ponta.

O detalhe que importa para o resto do post é a propriedade Velocity. Ela é calculada a partir do deslocamento real do passo, então reflete exatamente o que a plataforma fez, inclusive o zero durante a espera. O jogador vai usar esse valor.

Por que o jogador escorrega

Coloque o personagem em cima da plataforma e dê Play. Duas coisas acontecem:

  • Na horizontal, a plataforma sai de baixo dele. O script de movimento do jogador define a velocidade X a cada FixedUpdate: sem input, ela vai para zero. Mesmo que o atrito tentasse arrastar o personagem, o script zera o arrasto no passo seguinte. E se você seguiu o script de movimento 2D na Unity, o collider do jogador tem atrito zero (para não grudar em paredes), então não há nem arrasto para zerar.
  • Na vertical, ele quica na descida. Quando a plataforma desce mais rápido do que a gravidade acelera o personagem, ela se afasta dos pés dele, ele fica alguns frames no ar, o ground check falha e a animação de queda pisca.

Ou seja: o problema não é a plataforma, é que o jogador não sabe que está em cima de algo que se move. As duas soluções ensinam isso a ele de formas diferentes.

Solução 1: tornar filho da plataforma

A ideia: quando o jogador pisa, vira filho da plataforma na hierarquia; quando sai, volta para a raiz. Como filho, ele herda o movimento do pai.

using UnityEngine;

public class PlatformParenting2D : MonoBehaviour
{
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (!collision.collider.CompareTag("Player"))
        {
            return;
        }

        if (collision.GetContact(0).normal.y < -0.5f)
        {
            collision.transform.SetParent(transform);
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.collider.CompareTag("Player") && collision.transform.parent == transform)
        {
            collision.transform.SetParent(null);
        }
    }
}

O teste da normal garante que só vira filho quem encostou por cima: no ponto de vista da plataforma, a normal do contato aponta para baixo (y negativo) quando o jogador está em cima.

É a solução mais curta e funciona bem para um jogador Kinematic ou com movimento lento. Os problemas aparecem com um jogador Dynamic:

  • Física e hierarquia brigam. Um Rigidbody2D Dynamic é simulado no espaço do mundo; mover o pai por MovePosition não move o filho de forma física. Dependendo da ordem de atualização, o jogador treme ou fica um passo atrasado.
  • Escala do pai contamina o filho. Se a plataforma tiver escala diferente de 1 (por exemplo, esticada no X), o personagem fica deformado enquanto estiver em cima.
  • Efeitos colaterais. Destruir ou desativar a plataforma leva o jogador junto. Um DontDestroyOnLoad no jogador quebra enquanto ele é filho de algo da cena.

Repare que a solução robusta, a seguir, mexe em scripts que você já considerava prontos: movimento, pulo e ground check. Plataforma móvel é um bom exemplo de como uma peça simples atravessa o projeto. Num jogo inteiro isso se repete a cada mecânica nova (esteira, vento, água, gelo), e sem uma estrutura clara o código vira remendo em cima de remendo. Aprender essa estrutura dentro de um projeto completo, em vez de colar soluções soltas, é o que o curso de Unity trabalha do começo ao fim.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 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 Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Solução 2: herdar a velocidade

A solução robusta é o jogador somar a velocidade da plataforma à dele. Primeiro, um componente no jogador que descobre em qual plataforma ele está, PlatformRider2D.cs:

using UnityEngine;

public class PlatformRider2D : MonoBehaviour
{
    private MovingPlatform2D currentPlatform;

    public Vector2 PlatformVelocity => currentPlatform != null ? currentPlatform.Velocity : Vector2.zero;
    public bool OnPlatform => currentPlatform != null;

    private void OnCollisionStay2D(Collision2D collision)
    {
        if (!collision.collider.TryGetComponent(out MovingPlatform2D platform))
        {
            return;
        }

        for (int i = 0; i < collision.contactCount; i++)
        {
            if (collision.GetContact(i).normal.y > 0.5f)
            {
                currentPlatform = platform;
                return;
            }
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.collider.TryGetComponent(out MovingPlatform2D platform) && platform == currentPlatform)
        {
            currentPlatform = null;
        }
    }
}

Aqui a normal é vista pelo jogador, então "estou em cima" é normal com y positivo.

Depois, no FixedUpdate do seu script de movimento, a velocidade horizontal passa a ser calculada relativa à plataforma. Adicione um campo [SerializeField] private PlatformRider2D rider; e troque o cálculo por:

Vector2 platformVelocity = rider != null ? rider.PlatformVelocity : Vector2.zero;

float currentSpeed = rb.linearVelocity.x - platformVelocity.x;
float newSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, rate * Time.fixedDeltaTime);
float newY = rb.linearVelocity.y;

if (rider != null && rider.OnPlatform && platformVelocity.y < 0f && newY <= 0f)
{
    newY = platformVelocity.y;
}

rb.linearVelocity = new Vector2(newSpeed + platformVelocity.x, newY);

O que cada parte faz:

  • currentSpeed relativo. A aceleração e a desaceleração do jogador passam a valer em relação à plataforma. Parado em cima, ele fica parado em cima. Andando, anda em cima dela com a mesma sensação do chão firme.
  • + platformVelocity.x no fim. É o que leva o jogador junto.
  • newY na descida. Se a plataforma desce e o jogador não está pulando, a velocidade vertical acompanha a dela. Fim do quique.
  • Momentum de graça. Ao pular de uma plataforma em movimento, PlatformVelocity vira zero, mas a velocidade X do jogador ainda inclui a da plataforma. O script de movimento desacelera isso aos poucos, e o pulo sai com o impulso que o jogador espera, como em qualquer plataforma clássico.

Qual escolher? Para jogador Dynamic, que é o caso deste blog e da maioria dos plataformas em Unity, a solução 2. A solução 1 serve para protótipo rápido ou para objetos sem física (um baú decorativo em cima da plataforma). A versão da mesma mecânica no Godot, que resolve o problema de outro jeito porque o CharacterBody2D já herda a velocidade da plataforma, está no post de plataforma móvel no Godot, e a comparação ajuda a entender o que a engine faz por você em cada caso.

Plataforma one-way com Platform Effector 2D

A plataforma one-way é a que o jogador atravessa pulando de baixo e pisa vindo de cima, clássica em jogos de plataforma com vários andares. Na Unity ela é só configuração:

  1. Na plataforma, no BoxCollider2D, marque Used By Effector.
  2. Adicione o componente Platform Effector 2D no mesmo objeto.
  3. Deixe Use One Way ligado.
  4. Surface Arc: 180 (padrão). É o ângulo, centrado no "cima" da plataforma, em que ela é sólida. Com 180, toda a metade de cima segura o jogador; diminuir para 160 evita que o jogador fique preso na quina lateral ao pular encostado.
  5. Desmarque Use Side Friction se o jogador estiver grudando na lateral.

Funciona junto com o MovingPlatform2D: uma plataforma pode ser móvel e one-way ao mesmo tempo.

Para descer de uma one-way apertando para baixo, desligue a colisão entre os dois colliders por um instante:

using System.Collections;
using UnityEngine;

public class OneWayDropDown2D : MonoBehaviour
{
    [SerializeField] private Collider2D playerCollider;
    [SerializeField] private float ignoreTime = 0.3f;

    public void DropThrough(Collider2D platformCollider)
    {
        StartCoroutine(IgnoreRoutine(platformCollider));
    }

    private IEnumerator IgnoreRoutine(Collider2D platformCollider)
    {
        Physics2D.IgnoreCollision(playerCollider, platformCollider, true);
        yield return new WaitForSeconds(ignoreTime);
        Physics2D.IgnoreCollision(playerCollider, platformCollider, false);
    }
}

Chame DropThrough quando o jogador apertar baixo + pulo estando sobre uma plataforma com PlatformEffector2D (o mesmo OnCollisionStay2D do PlatformRider2D pode guardar qual collider está embaixo).

Erros comuns

Plataforma treme. Falta Interpolate no Rigidbody2D da plataforma, ou ela está sendo movida no Update em vez do FixedUpdate.

Jogador é esmagado e atravessa. Uma plataforma Kinematic empurra com força infinita. Se ela prende o jogador contra uma parede ou teto, ele é espremido para dentro do collider. Desenhe os trajetos sem pontos de esmagamento, ou trate o esmagamento como morte.

Jogador escorrega mesmo com a solução 2. O script de movimento ainda calcula currentSpeed a partir de rb.linearVelocity.x sem subtrair a plataforma, ou o rider não foi arrastado no Inspector.

One-way não deixa atravessar. O collider não está com Used By Effector marcado.

Jogador fica preso dentro da one-way. Surface Arc grande demais com a plataforma fina. Diminua o arco ou aumente a espessura do collider.

Próximo passo

Monte uma fase de teste com três plataformas: uma horizontal, uma vertical e uma diagonal, uma delas one-way. Pule de uma para outra em movimento e confira se o impulso de saída parece natural. Depois, se quiser comparar com a mesma mecânica sem Rigidbody, o post de movimento de personagem de plataforma 2D mostra a base de aceleração e fricção que torna esse tipo de ajuste previsível.

Perguntas frequentes

Por que meu personagem escorrega da plataforma móvel na Unity?

Porque o script de movimento do personagem define a velocidade dele a cada passo de física sem levar a plataforma em conta. A plataforma anda e o personagem fica parado no mundo, então ela sai de baixo dele. A solução é tornar o personagem filho da plataforma ou somar a velocidade da plataforma à dele.

Como fazer uma plataforma one-way na Unity 2D?

Adicione um Platform Effector 2D na plataforma, marque Used By Effector no collider dela e deixe Use One Way ligado. O jogador atravessa vindo de baixo e pisa vindo de cima. Surface Arc em 180 define que só a face de cima é sólida.

Como descer de uma plataforma one-way apertando para baixo?

Desligue a colisão entre o collider do jogador e o da plataforma por uns 0,3 segundo com Physics2D.IgnoreCollision e depois religue. O jogador cai através dela e a plataforma volta a ser sólida para os próximos pulos.

MovePosition funciona em Rigidbody2D Dynamic?

MovePosition foi pensado para corpos Kinematic, que é o tipo certo para plataformas: eles seguem o caminho exato do script e empurram corpos Dynamic sem serem empurrados de volta. Num corpo Dynamic, prefira controlar a velocidade.