Voltar para o Blog
Quest Log

Movimento Top-Down 2D na Unity: 8 Direções sem Diagonal Rápida

Personagem em pixel art visto de cima no centro de uma sala de masmorra, com oito setas em volta indicando as direções de movimento

Unity top down movement em C#: 8 direções com Input System, diagonal sem velocidade extra, animação por direção com Blend Tree e colisão com paredes do Tilemap.

Ferramenta:UnityC#

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 Vector2 completo, 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 flipX nã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 Scale 0, Collision Detection Continuous, Interpolate Interpolate, Constraints com Freeze Rotation Z marcado.
  • Collider: um CapsuleCollider2D com Direction Horizontal ou um BoxCollider2D, 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 0 no 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:

  1. No próprio Input System. A ação Move usa um composite 2D Vector para 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 processor Normalize Vector 2, que pode ser adicionado a uma binding na janela de actions, mas ele normaliza tudo, inclusive o analógico.
  2. input.normalized no código. Corrige a diagonal, mas transforma qualquer toque no analógico em velocidade máxima. Andar devagar com o controle deixa de existir.
  3. 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 modo Digital.

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.MoveTowards na velocidade. O personagem chega na velocidade alvo em moveSpeed / acceleration segundos (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 por rb.linearVelocity = targetVelocity;.
  • lastDirection só 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.

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

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.

  1. No Animator Controller, crie os parâmetros Float MoveX, MoveY e Speed.
  2. Clique com o botão direito no grafo, Create State > From New Blend Tree, renomeie para Idle. Dê dois cliques para entrar.
  3. No Inspector do Blend Tree, Blend Type: 2D Simple Directional, parâmetros MoveX e MoveY.
  4. 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.
  5. Repita com um Blend Tree Walk usando os clips de andar.
  6. Transições: Idle para Walk com condição Speed > 0.1; Walk para Idle com Speed < 0.1. Nas duas, desmarque Has Exit Time e coloque Transition Duration em 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:

  1. Selecione o Tilemap Walls e adicione Tilemap Collider 2D. Cada tile ganha uma forma de colisão.
  2. Adicione Composite Collider 2D no mesmo objeto. A Unity acrescenta um Rigidbody2D junto; troque o Body Type dele para Static, porque parede não se mexe.
  3. No Tilemap Collider 2D, mude Composite Operation para Merge. 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.
  4. Se sobrarem frestas entre tiles, aumente o Extrusion Factor do 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.