Voltar para o Blog
Quest Log

NavMesh 2D na Unity: Pathfinding em Jogo Top-Down

Mapa top-down de masmorra em pixel art com o chão andável destacado em azul e um inimigo seguindo uma linha de caminho até o herói

Unity NavMesh 2D em jogo top-down: por que o NavMesh nativo não lê Tilemap, como usar o NavMeshPlus, travar a rotação do agente e quando um grid resolve.

Ferramenta:UnityC#

NavMesh 2D na Unity: Pathfinding em Jogo Top-Down

Unity NavMesh 2D não existe como recurso nativo pronto: o NavMesh da Unity é calculado num plano 3D a partir de malhas e colliders 3D, e não enxerga Tilemap nem Collider2D. Para um jogo top-down, o caminho mais usado é o NavMeshPlus, um projeto aberto que gera a malha a partir do Tilemap e deixa o NavMeshAgent normal andar sobre ela, com duas linhas de configuração para o agente não girar o sprite. Aqui você monta isso numa masmorra de Tilemap, vê as alternativas (A* Pathfinding Project e um grid próprio) e sai sabendo quando cada uma vale.

A parte honesta: qualquer solução de NavMesh em 2D é uma adaptação. Saber os limites dela antes de basear a IA do jogo inteiro nisso evita retrabalho no meio do projeto.

Por que o NavMesh da Unity não funciona direto no 2D

O bake do NavMesh (no pacote AI Navigation, pelo componente NavMesh Surface) coleta render meshes ou colliders 3D e gera polígonos andáveis num plano horizontal, o plano XZ. Um jogo 2D vive no plano XY, com sprites e Tilemaps que não têm malha 3D com volume e colliders que são 2D. Resultado: o bake não encontra nada, ou encontra uma malha deitada no plano errado.

Mesmo que você gire a superfície, o NavMeshAgent foi feito para 3D. Ele rotaciona o objeto para olhar na direção do movimento e alinha o "para cima" do objeto à malha. Num sprite, isso vira o personagem deitado de lado ou girando no eixo Z.

No Unity 6.3 LTS, o pacote AI Navigation não traz coleta de Tilemap nem de Collider2D. A busca por essa solução é tão comum que existe ecossistema em volta dela.

Opções: NavMeshPlus, A* Pathfinding Project, A* próprio

OpçãoCustoBom paraLimite
NavMeshPlusgrátis, código aberto (MIT)top-down com movimento livre, usando o NavMeshAgent da Unityprojeto da comunidade, sem garantia de compatibilidade oficial
A* Pathfinding Projectversão gratuita e versão Proprojetos maiores, grid ou navmesh, 2D nativoa versão gratuita não tem recast, avoidance local nem múltiplas threads
A* ou BFS própriográtismovimento de tile em tile, roguelike, táticovocê mantém o código

Sobre o A* Pathfinding Project: a tabela oficial de comparação, consultada em outubro de 2026, mostra a versão gratuita com grid graphs, suporte a pathfinding 2D (com física 2D para grid e point graphs), navmesh graphs, grafos hexagonais e isométricos e suavização de caminho. Ficam só na Pro, que custava US$ 100 na mesma página, a geração automática de navmesh por recast, avoidance local entre agentes e pathfinding em várias threads. Para um top-down comum, a versão gratuita com grid graph já resolve.

O resto do post usa o NavMeshPlus, porque ele mantém o fluxo do NavMeshAgent que você já usa em 3D. Se você vem do Godot, lá o 2D tem navegação nativa (NavigationRegion2D e NavigationAgent2D), como mostra o post de pathfinding 2D no Godot.

Instalar. Em Window > Package Management > Package Manager, clique em + > Install package from git URL e cole https://github.com/h8man/NavMeshPlus.git. O pacote se chama NavMeshPlus (com.h8man.2d.navmeshplus). O repositório estava ativo em outubro de 2026, mas não publica tabela de compatibilidade com o Unity 6.3: instale numa cópia do projeto (ou numa branch do Git) antes de adotar.

O NavMeshPlus traz o próprio NavMeshSurface, no namespace NavMeshPlus.Components, diferente do Unity.AI.Navigation do pacote oficial. No Add Component, os dele aparecem como Navigation Surface, Navigation Modifier e Navigation CollectSources2d. Não misture a superfície dele com a do AI Navigation na mesma cena.

Montar a cena. Uma masmorra típica tem um Grid com dois Tilemaps: Chao e Paredes (este com TilemapCollider2D).

  1. Crie um GameObject vazio NavMesh2D na raiz da cena.
  2. Adicione Navigation Surface e, em seguida, Navigation CollectSources2d.
  3. No CollectSources2d, clique em Rotate Surface to XY. A superfície gira para -90 em X e passa a olhar para a câmera 2D.
  4. No Tilemap Chao, adicione Navigation Modifier, ligue Override Area e escolha Walkable.
  5. No Tilemap Paredes, adicione Navigation Modifier, ligue Override Area e escolha Not Walkable.
  6. Na Navigation Surface, deixe Use Geometry em Physics Colliders se as paredes têm collider, ou Render Meshes para usar os sprites dos tiles.
  7. Clique em Bake. O chão fica azul e as paredes ficam de fora.

Obstáculos soltos (baús, colunas feitas de sprite) também levam Navigation Modifier com Not Walkable. O pacote tem ainda o Navigation Modifier Tilemap, que marca tiles específicos de um Tilemap com áreas diferentes, útil para lama e água rasa com custo maior.

Fazer o bake da masmorra é um bom marco. Transformar isso num jogo, com vários tipos de inimigo, salas geradas, portas que abrem, combate e uma IA que não trava nos cantos, é onde muita gente para, porque juntar sistemas é diferente de aprender cada um. Ver um jogo 2D completo sendo construído é o atalho para essa parte.

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

Configurar NavMeshAgent para 2D: updateRotation e updateUpAxis

O inimigo usa o NavMeshAgent padrão da Unity (UnityEngine.AI). Adicione o componente e ajuste Radius para a metade da largura do sprite em unidades. Depois, desligue as duas coisas que o agente faz pensando em 3D. Crie TopDownNavAgent.cs:

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class TopDownNavAgent : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private float repathInterval = 0.25f;

    private NavMeshAgent agent;
    private float repathTimer;

    private void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        agent.updateRotation = false;
        agent.updateUpAxis = false;
    }

    private void Update()
    {
        if (target == null || !agent.isOnNavMesh)
        {
            return;
        }

        repathTimer -= Time.deltaTime;
        if (repathTimer <= 0f)
        {
            repathTimer = repathInterval;
            Vector3 destination = target.position;
            destination.z = transform.position.z;
            agent.SetDestination(destination);
        }

        Vector3 velocity = agent.velocity;
        if (spriteRenderer != null && Mathf.Abs(velocity.x) > 0.05f)
        {
            spriteRenderer.flipX = velocity.x < 0f;
        }
    }
}

O que cada linha resolve:

  • updateRotation = false: o agente para de girar o Transform na direção do movimento. Sem isso, o sprite gira em Z como um ponteiro de relógio.
  • updateUpAxis = false: segundo a documentação, essa propriedade define se o agente se alinha ao eixo "para cima" da malha ou do link onde está. Na superfície rotacionada para XY, alinhar faria o objeto deitar. Desligado, o sprite fica de pé.
  • destination.z = transform.position.z: mantém o destino no mesmo plano do agente. Um Z diferente entre jogador e inimigo pode fazer o SetDestination falhar silenciosamente.
  • agent.velocity para virar o sprite: o agente já calcula a velocidade, e o sinal de X diz para que lado olhar.

Se o seu inimigo tem Animator, passe agent.velocity.magnitude para um parâmetro de velocidade e deixe um Blend Tree escolher entre parado e andando.

Obstáculos e paredes do Tilemap

Três situações aparecem em toda masmorra:

Paredes finas somem do bake. Se o Radius do agente é grande e o tile de parede é estreito, a malha pode atravessar. Use paredes de um tile inteiro de espessura, ou diminua o raio.

Corredor de um tile não aparece. O raio do agente encolhe a malha nas bordas. Corredor de 1 unidade com agente de raio 0,5 vira zero. Para personagem de um tile, raio entre 0,3 e 0,4 mantém o corredor.

Porta que abre. Para obstáculos que mudam durante o jogo, use NavMesh Obstacle com Carve ligado no objeto da porta. Ele recorta a malha sem precisar de novo bake e funciona com o NavMeshAgent da Unity normalmente. Ao abrir, desligue o componente.

Para mudanças grandes (parede destruída, sala gerada na hora), chame UpdateNavMesh(surface.navMeshData) na Navigation Surface do NavMeshPlus, que reconstrói de forma assíncrona.

Quando um grid A* simples resolve melhor

Se o seu jogo é de grade (roguelike por turnos, tático, puzzle, inimigo que anda de tile em tile), NavMesh é ferramenta demais. O Tilemap já é um grafo: cada célula é um nó e os vizinhos são as quatro células ao lado. Uma busca em largura (BFS) acha o menor caminho em número de passos. Crie TilemapPathfinder.cs:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class TilemapPathfinder : MonoBehaviour
{
    [SerializeField] private Tilemap floor;
    [SerializeField] private Tilemap walls;
    [SerializeField] private int maxSearchNodes = 4000;

    private static readonly Vector3Int[] Directions =
    {
        Vector3Int.up, Vector3Int.down, Vector3Int.left, Vector3Int.right
    };

    public bool IsWalkable(Vector3Int cell)
    {
        return floor.HasTile(cell) && !walls.HasTile(cell);
    }

    public List<Vector3> FindPath(Vector3 fromWorld, Vector3 toWorld)
    {
        Vector3Int start = floor.WorldToCell(fromWorld);
        Vector3Int goal = floor.WorldToCell(toWorld);
        List<Vector3> path = new List<Vector3>();

        if (!IsWalkable(start) || !IsWalkable(goal))
        {
            return path;
        }

        Queue<Vector3Int> frontier = new Queue<Vector3Int>();
        Dictionary<Vector3Int, Vector3Int> cameFrom = new Dictionary<Vector3Int, Vector3Int>();
        frontier.Enqueue(start);
        cameFrom[start] = start;

        while (frontier.Count > 0 && cameFrom.Count < maxSearchNodes)
        {
            Vector3Int current = frontier.Dequeue();
            if (current == goal)
            {
                break;
            }

            foreach (Vector3Int direction in Directions)
            {
                Vector3Int next = current + direction;
                if (!cameFrom.ContainsKey(next) && IsWalkable(next))
                {
                    cameFrom[next] = current;
                    frontier.Enqueue(next);
                }
            }
        }

        if (!cameFrom.ContainsKey(goal))
        {
            return path;
        }

        Vector3Int step = goal;
        while (step != start)
        {
            path.Add(floor.GetCellCenterWorld(step));
            step = cameFrom[step];
        }

        path.Reverse();
        return path;
    }
}

A função devolve a lista de centros de tile do início ao destino (sem o tile de partida). O inimigo anda até o primeiro, remove da lista e segue para o próximo, com Vector2.MoveTowards. Recalcule só quando o jogador muda de célula, não a cada frame. O maxSearchNodes impede uma busca infinita num mapa enorme.

Para custos diferentes por terreno (lama mais lenta), troque a fila por uma fila de prioridade e você tem o A*. A lógica de grade com heurística está explicada no post de AStarGrid2D no Godot, e o raciocínio é o mesmo em C#.

Erros comuns: agente girando para o eixo Z, bake vazio

Sprite gira ou deita. Faltou updateRotation = false e updateUpAxis = false. Configure no Awake, antes do primeiro frame.

Bake vazio, nada azul. A superfície não foi rotacionada para XY, o Tilemap de chão não tem Navigation Modifier com Walkable, ou faltou o componente Navigation CollectSources2d.

Erro dizendo que o agente não está no NavMesh. O agente está num Z diferente da malha ou começa em cima de uma parede. Coloque o inimigo no centro de um tile de chão e mantenha o Z igual.

Agente atravessa paredes. As paredes entraram como Walkable ou o raio do agente é grande para a espessura delas.

Conflito depois de instalar. Dois pacotes de componentes de navegação no mesmo projeto podem disputar o editor. Use a superfície de um só por cena e confira o namespace dos scripts que referenciam a superfície.

Próximo passo

Monte a masmorra com dois Tilemaps, faça o bake com o NavMeshPlus e ponha três inimigos com o TopDownNavAgent perseguindo o jogador. Depois, num jogo de grade, troque pelo TilemapPathfinder e compare: qual dos dois combina com o movimento do seu jogo? Para o lado 3D do mesmo sistema, com links, obstáculos e rebake, o post de NavMesh na Unity 6 cobre o pacote oficial.

Perguntas frequentes

A Unity tem NavMesh 2D oficial?

Não como componente pronto para Tilemap e sprites. O NavMesh da Unity trabalha num plano horizontal 3D e coleta malhas e colliders 3D. Para jogo 2D, a solução mais usada é o NavMeshPlus, um projeto aberto que estende os componentes de NavMesh para ler Tilemap, sprites e Collider2D.

O que é o NavMeshPlus?

Um fork de código aberto (licença MIT) dos componentes de NavMesh da Unity com extensões para 2D. Ele gera a malha de navegação a partir de Tilemaps, sprites e colliders 2D, e o NavMeshAgent padrão da Unity anda sobre ela. Instala pelo Package Manager com a URL do repositório.

Por que o NavMeshAgent gira o sprite no eixo errado no 2D?

Porque o agente assume que o "para cima" é o eixo Y de um mundo 3D e tenta alinhar o objeto à malha. Em 2D, desligue agent.updateRotation e agent.updateUpAxis logo no Awake; a orientação do sprite fica por conta do seu script.

Preciso de NavMesh para pathfinding em jogo 2D de grid?

Nem sempre. Se o personagem anda de tile em tile, uma busca em largura ou um A* sobre o próprio Tilemap resolve com poucas linhas e sem pacote externo. NavMesh compensa quando o movimento é livre, sem grade, e o mapa tem formas irregulares.