Movimento Top-Down 2D na Unity: 8 Direções sem Diagonal Rápida
Unity top down movement é o movimento visto de cima, como em Zelda, Stardew Valley ou um shooter de arena: o personagem anda em 8 direções, sem gravidade, e para quando o jogador solta o direcional. O script tem três armadilhas clássicas: a diagonal mais rápida que as retas, a animação que volta a olhar para baixo toda vez que o personagem para, e o personagem enganchando nas emendas das paredes do Tilemap. Este tutorial resolve as três com C# do Unity 6.3 LTS e o Input System.
No fim você terá o TopDownMovement.cs completo, um Animator com Blend Tree de 4 ou 8 direções e uma parede de Tilemap que segura o personagem sem prender.
Unity top down movement: o que muda do plataforma
Num jogo de plataforma, o eixo Y pertence à gravidade e ao pulo, e o input só controla X. No top-down, o Y da tela é "norte e sul" do mundo, e os dois eixos são do jogador. Isso muda quatro coisas:
- Sem gravidade. O Rigidbody2D não pode puxar o personagem para a parte de baixo da tela.
- Os dois eixos ao mesmo tempo. O input vira um
Vector2completo, e a diagonal passa a existir, com o problema de comprimento que vem junto. - Direção não é só esquerda e direita. O personagem precisa olhar para cima, para baixo e para os lados, então virar o sprite com
flipXnão basta: você precisa de animações por direção. - Colisão por todos os lados. Parede de cima, de baixo, quina. O collider do personagem costuma ser menor que o sprite e ficar nos pés, para o personagem poder "passar na frente" de objetos altos.
Rigidbody2D sem gravidade
Monte o Player assim:
- Rigidbody2D: Body Type
Dynamic, Gravity Scale0, Collision DetectionContinuous, InterpolateInterpolate, Constraints comFreeze Rotation Zmarcado. - Collider: um
CapsuleCollider2Dcom DirectionHorizontalou umBoxCollider2D, cobrindo só a metade de baixo do sprite (os pés e a sombra). Num sprite de 16x24 pixels a 16 pixels por unidade, algo como 0,8 por 0,5 unidade com offset para baixo. - Physics Material 2D com Friction
0no collider, para o personagem deslizar ao longo da parede quando andar na diagonal contra ela, em vez de parar seco.
Por que Dynamic e não Kinematic? Um corpo kinematic não é empurrado por colisão: você teria que detectar e resolver a parede à mão. Com Dynamic e gravidade zero, a física cuida das paredes e o seu script só define a velocidade.
Ler input em 8 direções
Com o Input System dos projetos novos do Unity 6, a ação Move do asset InputSystem_Actions já entrega um Vector2 com WASD, setas e analógico esquerdo:
InputAction moveAction = InputSystem.actions.FindAction("Move");
Vector2 input = moveAction.ReadValue<Vector2>();
O teclado gera 8 direções (as 4 retas e as 4 diagonais). O analógico gera infinitas, com intensidade de 0 a 1. Se o seu jogo quer só 4 direções (estilo Zelda clássico ou um RPG de 16 bits), você zera o eixo menor:
if (Mathf.Abs(input.x) > Mathf.Abs(input.y))
{
input.y = 0f;
}
else
{
input.x = 0f;
}
O script completo abaixo traz isso como uma opção no Inspector.
Diagonal rápida: normalizar o vetor
Se você monta o vetor à mão, com Keyboard.current.dKey e Keyboard.current.wKey, o resultado de W+D é (1, 1), cujo comprimento é cerca de 1,41. Multiplicado pela velocidade, o personagem anda 41% mais rápido na diagonal. Em jogo de ação, o jogador descobre isso em minutos e passa a fugir de inimigos só em diagonal.
Três formas de corrigir, e quando usar cada uma:
- No próprio Input System. A ação
Moveusa um composite2D Vectorpara as teclas, e o modo padrão desse composite éDigital Normalized: o vetor final do teclado já sai com comprimento 1. Se você usa a ação padrão, a diagonal do teclado já está certa. Também existe o processorNormalize Vector 2, que pode ser adicionado a uma binding na janela de actions, mas ele normaliza tudo, inclusive o analógico. input.normalizedno código. Corrige a diagonal, mas transforma qualquer toque no analógico em velocidade máxima. Andar devagar com o controle deixa de existir.Vector2.ClampMagnitude(input, 1f)no código. Só encurta vetores maiores que 1. A diagonal do teclado vira 1, o analógico pela metade continua pela metade. É a opção do script, porque funciona com qualquer fonte de input, inclusive se alguém trocar o composite para o modoDigital.
A matemática por trás (comprimento de vetor, por que 1,41, o que normalizar significa) está no post de matemática para jogos: vetores.
O script TopDownMovement completo
Crie TopDownMovement.cs:
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody2D))]
public class TopDownMovement : MonoBehaviour
{
private static readonly int MoveXHash = Animator.StringToHash("MoveX");
private static readonly int MoveYHash = Animator.StringToHash("MoveY");
private static readonly int SpeedHash = Animator.StringToHash("Speed");
[Header("Movimento")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float acceleration = 60f;
[SerializeField] private bool fourDirectionsOnly = false;
[Header("Referências")]
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Animator animator;
private InputAction moveAction;
private Vector2 input;
private Vector2 lastDirection = Vector2.down;
public Vector2 LastDirection => lastDirection;
private void Awake()
{
if (rb == null)
{
rb = GetComponent<Rigidbody2D>();
}
rb.gravityScale = 0f;
rb.freezeRotation = true;
moveAction = InputSystem.actions.FindAction("Move");
}
private void Update()
{
input = Vector2.ClampMagnitude(moveAction.ReadValue<Vector2>(), 1f);
if (fourDirectionsOnly)
{
input = SnapToFourDirections(input);
}
if (input.sqrMagnitude > 0.01f)
{
lastDirection = input.normalized;
}
UpdateAnimator();
}
private void FixedUpdate()
{
Vector2 targetVelocity = input * moveSpeed;
rb.linearVelocity = Vector2.MoveTowards(
rb.linearVelocity,
targetVelocity,
acceleration * Time.fixedDeltaTime);
}
private Vector2 SnapToFourDirections(Vector2 value)
{
if (Mathf.Abs(value.x) > Mathf.Abs(value.y))
{
return new Vector2(value.x, 0f);
}
return new Vector2(0f, value.y);
}
private void UpdateAnimator()
{
if (animator == null)
{
return;
}
animator.SetFloat(MoveXHash, lastDirection.x);
animator.SetFloat(MoveYHash, lastDirection.y);
animator.SetFloat(SpeedHash, input.magnitude);
}
}
Por partes:
Vector2.MoveTowardsna velocidade. O personagem chega na velocidade alvo emmoveSpeed / accelerationsegundos (com 5 e 60, menos de um décimo de segundo). Isso dá um mínimo de peso sem tirar a precisão. Para controle totalmente seco, troque a linha porrb.linearVelocity = targetVelocity;.lastDirectionsó muda com input. Quando o jogador solta o direcional, o vetor de input vai a zero, mas a última direção fica guardada. É ela que o Animator usa para escolher a pose de parado.Animator.StringToHash. Converter os nomes dos parâmetros uma vez evita comparar strings todo frame e pega erro de digitação num lugar só.
Um personagem andando em 8 direções numa sala vazia é o começo. Um top-down de verdade pede inimigos que perseguem, tiro ou ataque com direção, salas conectadas, câmera com limites, inventário. Cada peça é um tutorial; o difícil é fazer as peças conversarem num jogo que fecha. É essa montagem, do primeiro script até o jogo publicado, que o curso de Unity trabalha em projeto.
Animação por direção
O jeito padrão na Unity é um Blend Tree 2D que escolhe a animação pelo vetor de direção.
- No Animator Controller, crie os parâmetros Float
MoveX,MoveYeSpeed. - Clique com o botão direito no grafo,
Create State > From New Blend Tree, renomeie paraIdle. Dê dois cliques para entrar. - No Inspector do Blend Tree, Blend Type:
2D Simple Directional, parâmetrosMoveXeMoveY. - Adicione 4 Motion Fields (ou 8, se você desenhou diagonais) com os clips de parado e as posições: baixo
(0, -1), cima(0, 1), esquerda(-1, 0), direita(1, 0). Para diagonais,(0.7, 0.7)e equivalentes. - Repita com um Blend Tree
Walkusando os clips de andar. - Transições:
IdleparaWalkcom condiçãoSpeed > 0.1;WalkparaIdlecomSpeed < 0.1. Nas duas, desmarqueHas Exit Timee coloqueTransition Durationem 0, porque animação de pixel art não deve misturar quadros.
Se você só desenhou a direita, economize um clip: use a animação da direita para a esquerda virando o SpriteRenderer.flipX quando lastDirection.x < 0. Muitos jogos top-down fazem 3 direções desenhadas (baixo, cima, lado) mais o flip.
Colisão com paredes do Tilemap
Com o cenário num Tilemap, a parede é um Tilemap separado (por exemplo, Walls) com colisão. O passo a passo no Unity 6.3:
- Selecione o Tilemap
Wallse adicioneTilemap Collider 2D. Cada tile ganha uma forma de colisão. - Adicione
Composite Collider 2Dno mesmo objeto. A Unity acrescenta umRigidbody2Djunto; troque o Body Type dele paraStatic, porque parede não se mexe. - No
Tilemap Collider 2D, mude Composite Operation paraMerge. No Unity 6 essa é a opção que substitui o antigo checkbox de "usar pelo composite": as formas de cada tile se fundem em contornos contínuos. - Se sobrarem frestas entre tiles, aumente o
Extrusion Factordo Tilemap Collider 2D um pouco (ele só aparece com Composite Operation diferente de None).
Por que o composite importa? Sem ele, a parede é uma fileira de quadrados encostados. O collider do personagem, ao deslizar rente à parede, bate na quina interna entre dois quadrados e engancha. Com o Merge, a parede vira uma linha só e o deslizamento fica liso.
No Composite Collider 2D, o Geometry Type Outlines gera só o contorno (bom para paredes) e Polygons gera áreas preenchidas (necessário se você usa OverlapPoint para saber se um ponto está dentro da parede). Para a montagem do Tilemap em si, com paleta, regras e camadas, veja o tutorial de Tilemap na Unity.
Erros comuns
O personagem cai para baixo da tela. Gravity Scale ficou em 1. Zere no Inspector ou deixe o Awake do script fazer isso.
O personagem gira ao bater na quina. Faltou Freeze Rotation Z.
A diagonal é mais rápida. Você leu as teclas à mão ou trocou o composite para Digital. O ClampMagnitude resolve.
Ao parar, o personagem sempre olha para baixo. O Animator está recebendo o input atual, que é zero, em vez da última direção. Use lastDirection.
A animação pisca entre direções na diagonal. Num Blend Tree de 4 direções, a diagonal exata fica no meio de dois clips. Adicione os clips de diagonal ou use o modo de 4 direções do script.
Engancha em parede. Falta o Composite Collider 2D com Merge, ou o collider do personagem é um box com quinas vivas. Uma cápsula ajuda.
Treme ao andar com a câmera seguindo. Ligue Interpolate no Rigidbody2D e mova a câmera no LateUpdate, ou use Cinemachine.
Próximo passo
Com o movimento pronto, o próximo sistema natural do top-down é o ataque ou o tiro na direção de LastDirection, que o script já expõe. Se você quer ver como um jogo completo de tiro top-down se organiza (inimigos, ondas, câmera), o post sobre como fazer jogo de tiro top-down mostra a estrutura no Godot, e o raciocínio de sistemas vale igual na Unity.
Exercício: faça a mesma sala com três configurações de acceleration (15, 60 e infinito) e jogue um minuto em cada. Você vai sentir a diferença entre um personagem deslizante, um responsivo e um robótico, e isso vai guiar a escolha para o seu jogo.
Perguntas frequentes
Como tirar a gravidade do personagem top-down na Unity?
No Rigidbody2D do personagem, coloque Gravity Scale em 0 e marque Freeze Rotation Z em Constraints. Assim ele não cai para a parte de baixo da tela e não gira quando encosta numa quina.
Normalized ou ClampMagnitude para corrigir a diagonal?
ClampMagnitude(input, 1f). Ele só encurta vetores maiores que 1, então corrige a diagonal do teclado sem estragar o analógico. O normalized transforma qualquer toque leve no analógico em velocidade máxima.
Como fazer movimento de RPG top-down estilo Zelda na Unity?
É o mesmo script deste post: Rigidbody2D sem gravidade, input em 8 direções limitado a comprimento 1 e animação por direção com Blend Tree. Para estilo Pokémon, com passos de tile em tile, o movimento em grade é outro sistema, com coroutine de passo e checagem de parede antes de andar.
Por que meu personagem top-down engancha nas paredes do Tilemap?
O Tilemap Collider 2D sozinho cria uma forma por tile, e o personagem prende nas emendas. Adicione um Composite Collider 2D no mesmo objeto e coloque o Composite Operation do Tilemap Collider 2D em Merge para fundir tudo em contornos contínuos.



