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:
- Crie um GameObject vazio
Fundona raiz da cena. - Dentro dele, um filho por camada:
Ceu,Montanhas,Colinas,Arvores. Cada um com um SpriteRenderer. - 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. - 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.
- 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 aCamera.main. Em projeto com Cinemachine, aCamera.mainé a câmera que tem o CinemachineBrain, que é quem de fato se move.factorXefactorY: 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:
tileWidthvem desprite.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.- 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.
Mathf.Floorcom 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.
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.



