Voltar para o Blog
Quest Log

Lerp na Unity: Movimento, Cor e Rotação Suaves

Objeto deslizando entre dois pontos com rastro em degradê de cor e uma curva de suavização desenhada acima

Unity Lerp do jeito certo: t de 0 a 1 com timer, o Lerp com deltaTime que nunca chega, a fórmula independente de FPS e quando usar SmoothDamp, Slerp ou cor.

Ferramenta:UnityC#

Lerp na Unity: Movimento, Cor e Rotação Suaves

Unity Lerp é a interpolação linear: Mathf.Lerp(a, b, t) devolve um valor entre a e b, onde t = 0 dá a, t = 1 dá b e t = 0.5 dá o meio. Com isso você move um objeto, troca uma cor ou gira algo suavemente. O detalhe que quase todo tutorial pula: existe o Lerp do jeito certo, com t indo de 0 a 1 ao longo de um tempo, e o Lerp que parece funcionar, com Time.deltaTime no t, que desacelera, depende do FPS e nunca chega.

Aqui você vai ver os dois, a fórmula que conserta o segundo, e quando trocar Lerp por SmoothDamp, Slerp ou MoveTowards em casos reais de UI e câmera. Código para a Unity 6.3 LTS.

Unity lerp: o que é interpolação

Interpolar é calcular um valor intermediário. A conta do Lerp é a + (b - a) * t. Se a = 0, b = 10 e t = 0.3, o resultado é 3.

A referência de Vector3.Lerp registra um comportamento importante: o t é limitado ao intervalo de 0 a 1. Valores acima de 1 viram 1; abaixo de 0, viram 0. Isso significa que um Lerp nunca passa do destino nem volta antes da origem, mesmo que o seu timer passe do ponto. Quando você quer extrapolar (um efeito de mola que passa do ponto e volta), existe LerpUnclamped, que aceita qualquer t.

O papel do t é o que define tudo. Ele não é velocidade, nem tempo em segundos: é a fração do caminho. Toda a diferença entre o Lerp certo e o errado está em como você calcula essa fração.

Mathf.Lerp, Vector3.Lerp e Color.Lerp

A mesma ideia existe para vários tipos:

  • Mathf.Lerp(float a, float b, float t): números. Volume, zoom de câmera, valor de uma barra.
  • Vector2.Lerp e Vector3.Lerp: posições e escalas.
  • Color.Lerp: cores, canal por canal (vermelho, verde, azul e alfa).
  • Quaternion.Lerp e Quaternion.Slerp: rotações.

Um uso que aparece em todo jogo é piscar o personagem de vermelho ao tomar dano, voltando à cor normal. Arquivo DamageFlash.cs:

using UnityEngine;

public class DamageFlash : MonoBehaviour
{
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private Color flashColor = Color.red;
    [SerializeField] private float duration = 0.25f;

    private Color baseColor;
    private float elapsed;

    private void Awake()
    {
        baseColor = spriteRenderer.color;
        elapsed = duration;
    }

    public void Flash()
    {
        elapsed = 0f;
    }

    private void Update()
    {
        if (elapsed >= duration)
        {
            return;
        }

        elapsed += Time.deltaTime;
        float t = elapsed / duration;
        spriteRenderer.color = Color.Lerp(flashColor, baseColor, t);
    }
}

O sprite começa vermelho e volta à cor normal em exatamente 0,25 segundo. Esse é o padrão do Lerp certo, explicado na próxima seção.

Lerp com t controlado por tempo

Para ir de A até B em um tempo definido, os dois pontos ficam fixos e o t cresce de 0 a 1. Arquivo MoveOverTime.cs:

using UnityEngine;

public class MoveOverTime : MonoBehaviour
{
    [SerializeField] private Vector3 endOffset = new Vector3(3f, 0f, 0f);
    [SerializeField] private float duration = 1f;

    private Vector3 startPosition;
    private Vector3 endPosition;
    private float elapsed;

    private void Start()
    {
        startPosition = transform.position;
        endPosition = startPosition + endOffset;
    }

    private void Update()
    {
        if (elapsed >= duration)
        {
            return;
        }

        elapsed += Time.deltaTime;
        float t = Mathf.Clamp01(elapsed / duration);
        transform.position = Vector3.Lerp(startPosition, endPosition, t);
    }
}

Três propriedades desse padrão:

  1. Duração exata. O objeto chega em duration segundos, nem mais nem menos.
  2. Chega de verdade. Quando elapsed passa de duration, t é 1 e a posição é exatamente endPosition.
  3. Independe do FPS. A 30 ou a 144 FPS, o t em cada momento é o mesmo, porque vem do tempo decorrido.

O ponto de partida é guardado uma vez em startPosition. Se você usasse transform.position como primeiro argumento, o ponto de partida mudaria a cada frame, e o comportamento seria outro, o da próxima seção.

O Lerp com deltaTime que nunca chega

Este é o código que aparece em metade das respostas de fórum:

transform.position = Vector3.Lerp(transform.position, target.position, 5f * Time.deltaTime);

E ele "funciona": o objeto vai até o alvo, rápido no começo e desacelerando no fim. Muita câmera de jogo indie é feita assim. Mas ele tem três problemas.

Nunca chega. A cada frame o objeto anda uma fração do que falta. Se falta 1 e a fração é 0,08, falta 0,92; depois 0,846; depois 0,778. A distância encolhe para sempre e nunca zera. Na tela, depois de um tempo o movimento fica imperceptível, mas a posição continua mudando por bem mais tempo do que parece. O == de Vector3 considera iguais vetores a menos de 0,00001 de distância, então position == target só dispara bem depois do que você vê; com float comparado por ==, a conta pode empacar a um passo do alvo e nunca disparar.

Depende do FPS. Parece corrigido pelo Time.deltaTime, mas não está. A 30 FPS a fração é 0,167 por frame; a 144 FPS, 0,035 por frame. Faça a conta para um segundo: a 30 FPS, sobra 0,833 elevado a 30 do caminho, cerca de 0,4%; a 144 FPS, sobra 0,965 elevado a 144, cerca de 0,6%. A diferença é pequena com fator 5 e cresce com fatores maiores, e a câmera fica com sensações diferentes em máquinas diferentes.

Pode pular direto para o alvo. Num frame longo (um travamento de 0,25 s), 5f * 0.25f dá 1,25. O Lerp limita para 1 e o objeto teleporta para o alvo. Com LerpUnclamped, passaria.

A correção mantém o efeito de "suavização exponencial" e torna o resultado igual em qualquer FPS. A fórmula é t = 1 - exp(-k * dt):

using UnityEngine;

public class SmoothFollow : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private float sharpness = 8f;

    private void LateUpdate()
    {
        if (target == null)
        {
            return;
        }

        float t = 1f - Mathf.Exp(-sharpness * Time.deltaTime);
        transform.position = Vector3.Lerp(transform.position, target.position, t);
    }
}

Com essa fórmula, a fração do caminho que sobra depois de um segundo é sempre exp(-sharpness), não importa quantos frames houve nesse segundo. E o t nunca passa de 1, mesmo num frame enorme. Continua sem chegar matematicamente no alvo, então, se precisar de chegada exata, adicione um if que encaixa a posição quando a distância for menor que um valor pequeno.

Lerp, câmera e flash de dano são o tipo de detalhe que dá sensação de jogo polido. Num projeto inteiro, essas suavizações precisam conviver com física, input e animação sem uma brigar com a outra, e afinar tudo isso junto é onde quem aprende sozinho costuma ficar preso.

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

Lerp vs Slerp vs SmoothDamp

Três ferramentas, três usos:

Vector3.SmoothDamp é o que você quer para câmera e para qualquer coisa que persegue um alvo em movimento. O método recebe a posição atual, o alvo, uma variável de velocidade passada por ref, e o smoothTime. Segundo a documentação, smoothTime é aproximadamente o tempo que leva para alcançar o alvo, e o movimento não ultrapassa o alvo.

using UnityEngine;

public class CameraSmoothDamp : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 offset = new Vector3(0f, 1f, -10f);
    [SerializeField] private float smoothTime = 0.2f;

    private Vector3 velocity;

    private void LateUpdate()
    {
        if (target == null)
        {
            return;
        }

        Vector3 desired = target.position + offset;
        transform.position = Vector3.SmoothDamp(transform.position, desired, ref velocity, smoothTime);
    }
}

A variável velocity precisa ser um campo, não uma variável local, porque o SmoothDamp guarda nela o estado entre frames. É isso que dá a aceleração e a frenagem naturais. O script completo de câmera, com limites de fase e zona morta, é tema de câmera que segue o jogador na Unity.

Quaternion.Slerp e Vector3.Slerp interpolam por um arco, não por uma reta. Para rotação, Slerp mantém a velocidade angular constante ao longo do giro, enquanto Lerp de quaternion acelera no meio. Para uma porta que abre em 0,4 s, use Quaternion.Slerp(fechada, aberta, t) com o mesmo t controlado por tempo da seção anterior.

Lerp fica para valores com duração definida: fade, flash, barra de vida que esvazia, zoom em 0,3 s.

E quando o que você quer é velocidade constante até um ponto, nenhum dos três: use MoveTowards, que anda um passo fixo e para no alvo.

Easing com AnimationCurve

Lerp com t linear dá movimento mecânico: velocidade constante, para de repente. Para ter aceleração e desaceleração, passe o t por uma curva. A Unity tem AnimationCurve, que aparece no Inspector como um editor de curva:

using UnityEngine;

public class EasedPopup : MonoBehaviour
{
    [SerializeField] private AnimationCurve curve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
    [SerializeField] private float duration = 0.35f;
    [SerializeField] private Vector3 hiddenScale = Vector3.zero;
    [SerializeField] private Vector3 shownScale = Vector3.one;

    private float elapsed;
    private bool playing;

    public void Show()
    {
        elapsed = 0f;
        playing = true;
    }

    private void Update()
    {
        if (!playing)
        {
            return;
        }

        elapsed += Time.unscaledDeltaTime;
        float t = Mathf.Clamp01(elapsed / duration);
        float eased = curve.Evaluate(t);
        transform.localScale = Vector3.LerpUnclamped(hiddenScale, shownScale, eased);

        if (t >= 1f)
        {
            playing = false;
        }
    }
}

No Inspector, edite a curva para passar um pouco de 1 antes de voltar, e o popup ganha um "salto" no fim. Por isso o código usa LerpUnclamped: com o Lerp normal, a parte da curva acima de 1 seria cortada. O unscaledDeltaTime faz a animação funcionar com o jogo pausado, o que é o normal para UI. Esse tipo de detalhe é o coração de game feel e juice.

Erros comuns com Lerp

Ponto de partida que muda. Lerp(transform.position, alvo, t) com t de 0 a 1 por tempo não respeita a duração: quase todo o caminho acontece logo no começo, porque a origem anda junto. Guarde a origem.

t em segundos. Lerp(a, b, elapsed) com elapsed indo até 2 segundos: depois do primeiro segundo, t vale 1 e nada mais acontece. Divida pelo tempo total.

Fração fixa sem deltaTime. Lerp(atual, alvo, 0.1f) por frame é dependente de FPS de forma escancarada. Use a fórmula com Mathf.Exp.

Lerp para física. Mover um Rigidbody2D com transform.position = Lerp(...) briga com a física. Use MovePosition no FixedUpdate ou mexa na velocidade.

Esperar igualdade. Código que espera position == target depois de um Lerp exponencial roda muito depois do que você vê, ou nunca, se for comparação de float. Use distância pequena ou MoveTowards.

Próximo passo

Faça três versões do mesmo movimento numa cena: Lerp com t por tempo, Lerp exponencial com Mathf.Exp e SmoothDamp. Ponha um alvo que muda de lugar quando você aperta uma tecla e sinta a diferença. Depois aplique o DamageFlash e o EasedPopup no seu projeto. Se o conceito de multiplicar ou não por delta ainda está nebuloso, delta time e movimento por frame fecha essa base.

Perguntas frequentes

O que faz o Lerp na Unity?

Lerp (interpolação linear) devolve um valor entre a e b de acordo com t. Com t = 0 devolve a, com t = 1 devolve b, com t = 0,5 devolve o meio. Existe para float (Mathf.Lerp), vetores (Vector3.Lerp), cores (Color.Lerp) e rotações (Quaternion.Lerp).

Por que meu Lerp nunca chega no destino?

Porque você está usando a posição atual como ponto de partida e uma fração fixa como t, por exemplo Lerp(atual, alvo, 5f * Time.deltaTime). A cada frame o objeto anda uma fração do que falta, então a distância encolhe sem nunca zerar. Para chegar, controle t de 0 a 1 com um timer ou use MoveTowards.

Qual a diferença entre Lerp e Slerp na Unity?

Lerp interpola em linha reta. Slerp interpola ao longo de um arco, mantendo a velocidade angular constante, e é o certo para rotações e direções. Para posição comum, Lerp basta; para rotação, Quaternion.Slerp dá o giro mais natural.

Lerp ou SmoothDamp para câmera?

SmoothDamp costuma ser melhor para câmera: ele guarda a velocidade entre frames, acelera e desacelera de forma natural e não ultrapassa o alvo. O parâmetro smoothTime é aproximadamente o tempo que a câmera leva para alcançar o alvo.