Voltar para o Blog
Quest Log

Parallax na Unity 2D: Fundo em Camadas por Script

Cenário 2D de floresta em quatro camadas de profundidade, com montanhas ao fundo e árvores escuras na frente

Unity parallax em 2D com um script por camada: fator por fundo, repetição infinita no eixo X, LateUpdate e Cinemachine 3 sem tremedeira. Código C# do Unity 6.

Ferramenta:UnityC#

Parallax na Unity 2D: Fundo em Camadas por Script

Unity parallax em 2D é isto: cada camada do fundo se move numa fração do deslocamento da câmera, e essa diferença de velocidade engana o olho e cria profundidade. Você resolve com um script curto (ParallaxLayer.cs) em cada camada, com um fator entre 0 e 1, mais uma regra para repetir o fundo infinitamente no eixo X. No fim deste post você tem esse script pronto, sabe por que ele precisa rodar depois da câmera e como ligar isso ao Cinemachine 3 para o fundo não tremer.

A tremedeira é o motivo de existir este post. O efeito básico qualquer tutorial mostra. O que quebra no projeto de verdade é a ordem de atualização entre o seu script, a física do personagem e a câmera.

Como o efeito parallax funciona

Pense numa janela de carro. O poste na beira da estrada passa voando, a árvore no meio do pasto passa devagar e a montanha no horizonte parece parada. A câmera do jogo é o carro.

Em código, isso vira um número por camada, que vou chamar de fator:

  • Fator 0: a camada se comporta como parte do mundo. A câmera anda e a camada passa na tela na mesma velocidade do chão. É o plano do personagem.
  • Fator 1: a camada anda junto com a câmera. Na tela ela fica parada, como um céu infinitamente longe.
  • Entre 0 e 1: quanto maior o fator, mais longe a camada parece. Montanhas em 0.8, colinas em 0.5, árvores de fundo em 0.3.

Fator negativo também funciona: a camada passa mais rápido que o chão e parece estar entre a câmera e o personagem (grama em primeiro plano, galhos, névoa).

Em Godot a lógica é a mesma, só muda o nó; se você usa as duas engines, o post de parallax background no Godot mostra o equivalente. E se quiser a parte de arte (como desenhar as camadas para elas encaixarem), parallax background em camadas cobre o lado do desenho.

Preparar as camadas de fundo

Antes do script, a cena:

  1. Crie um GameObject vazio Fundo na raiz da cena.
  2. Dentro dele, um filho por camada: Ceu, Montanhas, Colinas, Arvores. Cada um com um SpriteRenderer.
  3. Ordene com Sorting Layer (crie uma chamada Background) e Order in Layer: céu -40, montanhas -30, colinas -20, árvores -10. Não dependa de posição Z numa câmera ortográfica.
  4. Para cada sprite que vai repetir, abra o Import Settings e mude Mesh Type para Full Rect. Sem isso, o modo Tiled mostra um aviso e pode recortar o sprite errado.
  5. No SpriteRenderer de cada camada repetível, mude Draw Mode para Tiled e Tile Mode para Continuous. Ajuste Width para três vezes a largura original do sprite. Assim sempre há imagem sobrando à esquerda e à direita da câmera.

A arte precisa ser seamless no eixo X: a borda direita do PNG tem que continuar na borda esquerda. Teste antes de importar, colando duas cópias lado a lado no editor de imagem.

Script de parallax na Unity: fator por camada

Crie o arquivo ParallaxLayer.cs:

using UnityEngine;

[RequireComponent(typeof(SpriteRenderer))]
public class ParallaxLayer : MonoBehaviour
{
    [SerializeField] private Transform cameraTransform;
    [SerializeField, Range(-1f, 1f)] private float factorX = 0.5f;
    [SerializeField, Range(-1f, 1f)] private float factorY = 0f;
    [SerializeField] private bool infiniteX = true;
    [SerializeField] private bool updateInLateUpdate = true;
    [SerializeField] private float pixelsPerUnit = 0f;

    private Vector3 lastCameraPosition;
    private float tileWidth;

    private void Start()
    {
        if (cameraTransform == null)
        {
            cameraTransform = Camera.main.transform;
        }

        lastCameraPosition = cameraTransform.position;

        SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
        tileWidth = spriteRenderer.sprite.bounds.size.x * transform.lossyScale.x;
    }

    private void LateUpdate()
    {
        if (updateInLateUpdate)
        {
            ApplyParallax();
        }
    }

    public void ApplyParallax()
    {
        Vector3 cameraPosition = cameraTransform.position;
        Vector3 delta = cameraPosition - lastCameraPosition;

        Vector3 position = transform.position;
        position.x += delta.x * factorX;
        position.y += delta.y * factorY;

        if (infiniteX && tileWidth > 0f)
        {
            float distance = cameraPosition.x - position.x;
            if (Mathf.Abs(distance) >= tileWidth)
            {
                float steps = Mathf.Floor(Mathf.Abs(distance) / tileWidth) * Mathf.Sign(distance);
                position.x += steps * tileWidth;
            }
        }

        if (pixelsPerUnit > 0f)
        {
            position.x = Mathf.Round(position.x * pixelsPerUnit) / pixelsPerUnit;
            position.y = Mathf.Round(position.y * pixelsPerUnit) / pixelsPerUnit;
        }

        transform.position = position;
        lastCameraPosition = cameraPosition;
    }
}

Coloque o script em cada camada e ajuste o factorX. Os campos:

  • cameraTransform: se ficar vazio, usa a Camera.main. Em projeto com Cinemachine, a Camera.main é a câmera que tem o CinemachineBrain, que é quem de fato se move.
  • factorX e factorY: o fator explicado acima, por eixo.
  • updateInLateUpdate: deixe ligado sem Cinemachine. Com Cinemachine, desligue e use o sincronizador da seção sobre tremedeira.
  • pixelsPerUnit: zero desliga o arredondamento. Em pixel art, coloque o mesmo PPU dos sprites.

Por que trabalhar com delta da câmera em vez de posição absoluta? Porque o delta não depende de onde a camada começou. Você pode posicionar cada fundo à mão no editor e ele mantém esse alinhamento no primeiro frame. A conta com posição absoluta (posInicial + camera.x * fator) funciona também, mas briga com a repetição infinita, que precisa mover a camada por fora da fórmula.

O delta é por frame, mas o script não multiplica por Time.deltaTime: ele já está copiando um deslocamento que aconteceu, não gerando velocidade. Se essa diferença ainda confunde, o post sobre delta time e movimento por frame explica quando multiplicar e quando não.

Fundo infinito: repetir sprite sem emenda

A repetição usa o fato de o desenho ser periódico. Se a camada tem um padrão que se repete a cada tileWidth unidades, mover a camada exatamente tileWidth para a direita deixa a imagem idêntica. Então, quando a câmera se afasta mais de uma largura de tile do centro da camada, o script pula a camada uma (ou mais) larguras na direção da câmera. O jogador não vê o salto.

Três detalhes fazem isso funcionar:

  1. tileWidth vem de sprite.bounds.size.x, que é a largura do sprite original em unidades, não a largura do SpriteRenderer em Tiled. É a distância de um ciclo do padrão.
  2. A largura do SpriteRenderer em Tiled precisa cobrir a tela mais uma largura de tile de cada lado. Três tiles resolvem para tile maior que meia tela. Se o seu sprite é estreito, aumente o Width até passar com folga da largura visível da câmera.
  3. Mathf.Floor com o sinal permite saltos de várias larguras de uma vez. Isso evita o fundo sumir num teleporte de checkpoint ou numa câmera que corta para outra parte da fase.

Fazer o fundo repetir sozinho numa cena de teste é rápido. Juntar parallax, câmera, física do personagem, Pixel Perfect e troca de fase sem nenhum desses sistemas brigar com o outro é onde muita gente para, porque cada peça vem de um tutorial diferente com uma premissa diferente. A próxima seção mostra a briga mais comum, e ver um jogo 2D inteiro montado com essas peças conversando é outro nível de aprendizado.

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

LateUpdate, Cinemachine e tremedeira

A Unity chama Update em todos os scripts e só depois chama LateUpdate. A própria documentação diz que câmera de seguir deve rodar em LateUpdate, porque o alvo se moveu no Update. O parallax depende da posição final da câmera, então ele precisa rodar depois da câmera.

Sem Cinemachine, com uma câmera que segue o jogador por script em LateUpdate, você tem dois LateUpdate e a ordem entre eles não é garantida. Resolva em Edit > Project Settings > Script Execution Order: coloque ParallaxLayer com um número maior que o script da câmera.

Com Cinemachine 3 (pacote com.unity.cinemachine), o CinemachineBrain tem o campo Update Method, com Smart Update como recomendado: cada câmera atualiza no ritmo do alvo dela. Na prática, a câmera final se move num LateUpdate do Brain, e o seu parallax pode rodar antes ou depois dele. Quando roda antes, o fundo usa a posição da câmera do frame anterior, fica um frame atrasado e treme a cada mudança de velocidade.

O jeito limpo é escutar o evento que o Cinemachine dispara depois de atualizar a câmera. Crie ParallaxCinemachineSync.cs, coloque num objeto qualquer (o próprio Fundo) e desligue updateInLateUpdate nas camadas:

using Unity.Cinemachine;
using UnityEngine;

public class ParallaxCinemachineSync : MonoBehaviour
{
    [SerializeField] private ParallaxLayer[] layers;

    private void OnEnable()
    {
        CinemachineCore.CameraUpdatedEvent.AddListener(OnCameraUpdated);
    }

    private void OnDisable()
    {
        CinemachineCore.CameraUpdatedEvent.RemoveListener(OnCameraUpdated);
    }

    private void OnCameraUpdated(CinemachineBrain brain)
    {
        for (int i = 0; i < layers.Length; i++)
        {
            layers[i].ApplyParallax();
        }
    }
}

CinemachineCore.CameraUpdatedEvent dispara depois que um Brain atualiza a câmera, então o fundo sempre lê a posição final. Se a cena tiver mais de um Brain (tela dividida), filtre pelo brain recebido.

O outro lado da tremedeira é o personagem. Se ele usa Rigidbody2D, ligue Interpolate no componente. Sem interpolação, o corpo só muda de posição no passo de física, e a câmera segue degraus irregulares que o parallax amplifica nas camadas da frente.

Parallax vertical e com Pixel Perfect Camera

Vertical. Em plataforma com pulo alto ou fase vertical, use factorY. Um valor de metade do factorX costuma ficar natural: o céu não precisa subir tanto quanto anda. Para repetição infinita no Y, a lógica é a mesma do X com sprite.bounds.size.y, mas a maioria dos jogos só precisa de um céu alto o bastante.

Pixel Perfect. Com o componente Pixel Perfect Camera do URP na câmera, os sprites do mundo são encaixados na grade de pixels, mas a sua camada se move por frações de unidade. O resultado é o fundo "nadando" meio pixel em relação ao chão. Preencha o campo pixelsPerUnit do ParallaxLayer com o mesmo Assets Pixels Per Unit do Pixel Perfect Camera. O arredondamento final encaixa a camada na grade. Com Cinemachine, adicione também a extensão CinemachinePixelPerfect na CinemachineCamera, para a câmera virtual respeitar o mesmo encaixe.

Erros comuns: emenda visível, fundo deslizando

Linha vertical na emenda. A arte não é seamless, ou o sprite tem 1 pixel de borda transparente. Abra o PNG e confira. Também ajuda: Filter Mode em Point para pixel art, compressão None no fundo.

Aviso no SpriteRenderer em Tiled. O sprite está com Mesh Type em Tight. Troque para Full Rect no Import Settings.

Fundo deslizando para longe. O lastCameraPosition não é atualizado, ou dois scripts movem a mesma camada (o seu parallax e uma animação, por exemplo). Uma camada, um dono do transform.

Fundo some no checkpoint. A câmera teleportou mais de uma largura de tile e a sua versão de repetição só pula uma. A versão do post usa Mathf.Floor para pular várias.

Parallax trava em zigue-zague. Ordem de atualização. Veja a seção de Cinemachine e ligue Interpolate no Rigidbody2D.

Camada da frente parece "colada" na tela. Fator positivo alto demais. Primeiro plano usa fator negativo.

Próximo passo

Monte uma cena de teste com quatro camadas e um quadrado que anda com o Input System. Ajuste os fatores até a profundidade parecer certa (começar com 0.9, 0.6, 0.3 e -0.2 dá uma boa base). Depois troque a câmera por uma CinemachineCamera com Position Composer, desligue o LateUpdate das camadas e ligue o sincronizador. Se o fundo continuar firme quando o personagem pular e cair, a parte técnica está resolvida e o resto é arte.

Perguntas frequentes

Como fazer um parallax background na Unity 2D?

Separe o fundo em camadas (céu, montanhas, árvores), cada uma num GameObject com SpriteRenderer. Um script em cada camada move o objeto por uma fração do deslocamento da câmera: quanto mais longe a camada, mais ela acompanha a câmera e menos parece se mover na tela.

Por que o parallax treme quando uso Cinemachine?

Porque o script do fundo roda antes da câmera terminar de se mover naquele frame. O Cinemachine 3 atualiza a câmera no LateUpdate, e a ordem entre dois LateUpdate não é garantida. Escute o evento CinemachineCore.CameraUpdatedEvent e mova o fundo depois que a câmera já foi posicionada.

Como fazer um fundo infinito na Unity sem emenda?

Use um sprite que se repete sem costura, coloque o SpriteRenderer em Draw Mode Tiled com largura de pelo menos três vezes o sprite e, quando a câmera se afastar mais de uma largura de tile, reposicione a camada exatamente uma largura de tile. Como o desenho é periódico, o salto não aparece.

Dá para fazer parallax sem script na Unity?

Dá para simular com várias câmeras ou com a posição Z numa câmera em perspectiva, mas em jogo 2D com câmera ortográfica o script por camada é o caminho mais simples de controlar e o que funciona com Pixel Perfect Camera.