Voltar para o Blog
Quest Log

NavMesh na Unity 6: AI Navigation e NavMeshSurface

Fase 3D vista de cima com a área andável pintada em azul, um inimigo traçando caminho até o jogador e um link de pulo entre plataformas

Unity NavMesh no Unity 6 com o pacote AI Navigation: NavMeshSurface, NavMeshAgent com SetDestination, Modifier, Obstacle com carving e NavMesh Link de pulo.

Ferramenta:UnityC#

NavMesh na Unity 6: AI Navigation e NavMeshSurface

O Unity NavMesh é a malha que define onde personagens controlados pela IA podem andar, e no Unity 6 ele vem do pacote AI Navigation: você adiciona um componente NavMesh Surface, clica em Bake, coloca um NavMeshAgent no inimigo e chama SetDestination por script. Aqui você monta isso do zero no Unity 6.3, com inimigo seguindo o jogador, porta e caixa que bloqueiam o caminho com NavMesh Obstacle, pulo entre plataformas com NavMesh Link e rebake em tempo de execução.

No Unity 6 o bake fica num componente na cena, e não na janela Navigation que aparece em vídeos velhos. Vários nomes de campo também não batem com esses vídeos, e copiar tutorial desatualizado é a origem da maioria dos erros que este post resolve no final.

O sistema de navegação tem duas partes:

  • Runtime (UnityEngine.AI): o NavMeshAgent, o NavMeshObstacle e as consultas de caminho. Isso faz parte do motor.
  • Ferramentas de autoria (Unity.AI.Navigation): o pacote AI Navigation traz os componentes NavMesh Surface, NavMesh Modifier, NavMesh Modifier Volume e NavMesh Link. É com eles que você define o que entra no bake.

A janela Window > AI > Navigation continua existindo, com duas abas: Agents (tipos de agente, com Radius, Height, Step Height e Max Slope, mais Drop Height e Jump Distance para links gerados) e Areas (tipos de área e custo). O bake em si é feito no componente.

Vantagem desse modelo: você pode ter várias superfícies na mesma cena (uma por tipo de agente, uma por andar de um prédio) e reconstruir só uma em tempo de execução.

Se você também usa Godot, o equivalente lá é o NavigationRegion3D com NavigationAgent3D; o post de pathfinding 3D no Godot mostra o mesmo fluxo.

Instalar o pacote e adicionar NavMeshSurface

  1. Window > Package Management > Package Manager > Unity Registry, busque AI Navigation e instale. Muitos templates 3D já trazem o pacote; confira em In Project.
  2. Organize a fase sob um GameObject pai, por exemplo Nivel, com chão, paredes, rampas e plataformas.
  3. Crie um GameObject vazio NavMesh (ou use o próprio Nivel) e adicione Add Component > Navigation > NavMesh Surface.
  4. Configure:
    • Agent Type: Humanoid (o padrão).
    • Collect Objects: All Game Objects para a cena toda, Current Object Hierarchy para só os filhos deste objeto, Volume para uma caixa.
    • Include Layers: deixe só as layers de cenário. Tire Player, Enemy e qualquer coisa que se move.
    • Use Geometry: Render Meshes usa o visual; Physics Colliders usa os colliders. Prefira Physics Colliders: é a forma em que o jogador realmente pisa.
  5. Clique em Bake. A área andável aparece em azul na Scene view.

O bake gera um asset com os dados da malha ao lado da cena. Clear remove esse asset.

Bake: agent radius, height e step

O tipo de agente define a forma da malha. Na aba Agents da janela Navigation:

  • Radius: quão perto da parede o centro do agente chega. A malha azul fica recuada desse valor em relação às paredes. Inimigo gordo, raio maior, malha mais estreita.
  • Height: altura do agente. Passagens mais baixas que isso somem da malha.
  • Step Height: o maior degrau que ele sobe sem rampa.
  • Max Slope: a rampa mais íngreme, em graus.

Crie um tipo para cada tamanho de inimigo (um Humanoid e um Golem grande, por exemplo) e um NavMesh Surface para cada tipo. O agente só anda em superfície do mesmo Agent Type.

Para tirar objetos do bake sem mexer em layer, use NavMesh Modifier: com Mode em Remove Object, o objeto (e os filhos, se Apply to Children estiver ligado) é ignorado. Com Override Area você marca, por exemplo, uma poça de lama com uma área de custo maior, e os agentes passam a preferir dar a volta.

No inimigo, adicione NavMeshAgent. Os campos mais importantes: Speed, Angular Speed, Acceleration, Stopping Distance e Auto Braking. O Agent Type precisa bater com o do NavMesh Surface.

Crie EnemyNavChaser.cs:

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class EnemyNavChaser : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private float repathInterval = 0.2f;
    [SerializeField] private float repathMinTargetMove = 0.5f;

    private NavMeshAgent agent;
    private float repathTimer;
    private Vector3 lastTargetPosition;

    private void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
    }

    private void Start()
    {
        if (target == null)
        {
            GameObject player = GameObject.FindWithTag("Player");
            if (player != null)
            {
                target = player.transform;
            }
        }

        if (!agent.isOnNavMesh)
        {
            Debug.LogWarning($"{name} não está sobre o NavMesh. Confira o bake e a posição inicial.", this);
        }
    }

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

        repathTimer -= Time.deltaTime;
        if (repathTimer > 0f)
        {
            return;
        }

        repathTimer = repathInterval;

        float sqrMove = (target.position - lastTargetPosition).sqrMagnitude;
        if (agent.hasPath && sqrMove < repathMinTargetMove * repathMinTargetMove)
        {
            return;
        }

        lastTargetPosition = target.position;
        agent.SetDestination(target.position);
    }
}

Por que não chamar SetDestination em todo frame? Porque cada chamada pede um novo cálculo de caminho. Com 30 inimigos e 60 frames por segundo, são 1800 cálculos por segundo, a maioria inútil. Recalcular a cada 0,2 segundo, e só quando o alvo andou meio metro, mantém a perseguição fluida com uma fração do custo.

A checagem de isOnNavMesh evita o erro mais comum do sistema, que aparece no fim do post. E o FindWithTag no Start é aceitável porque roda uma vez; nunca coloque busca de objeto no Update.

A lógica completa de perseguição (raio de detecção, desistir, voltar) está no post do inimigo que persegue o jogador, em 2D e 3D.

Uma porta fechada ou uma caixa empurrável precisam bloquear o caminho sem você refazer o bake. Adicione NavMesh Obstacle:

  • Shape: Box ou Capsule, com Center e Size (ou Radius e Height).
  • Carve: ligado, o obstáculo abre um buraco na malha e os agentes planejam a rota em volta. Desligado, os agentes só tentam desviar localmente, o que funciona para obstáculo em movimento, mas pode deixar o agente empurrando a caixa sem achar a volta.
  • Com Carve ligado aparecem Move Threshold, Time To Stationary e Carve Only Stationary. Esta última faz o obstáculo só recortar a malha quando parar de se mover, o que poupa processamento.

Regra prática: porta e caixa que ficam paradas a maior parte do tempo, Carve ligado com Carve Only Stationary. Carrinho que anda sem parar, Carve desligado.

Para abrir a porta, basta desligar o componente: navMeshObstacle.enabled = false;. O buraco fecha e os agentes recalculam a rota passando por ela.

Fazer o inimigo andar até o jogador numa cena de teste leva meia hora. Fazer ele perseguir, atacar, desistir, voltar a patrulhar e não travar em porta, escada e plataforma, junto com animação, combate e o resto do jogo, é o que trava quem aprende sozinho, porque cada tutorial resolve um pedaço numa cena vazia. Ver isso montado num jogo inteiro muda a forma como você organiza a IA.

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

Duas plataformas separadas por um vão viram duas ilhas na malha. Para o agente atravessar, use um NavMesh Link:

  1. Crie um GameObject Link_Pulo e adicione NavMesh Link.
  2. Posicione Start Point na borda de uma plataforma e End Point na outra (ou arraste objetos para Start Transform e End Transform).
  3. Width: largura da passagem. Bidirectional: ligado se o agente pode pular nos dois sentidos. Area Type: deixe Jump se quiser dar um custo diferente ao pulo.

Para gerar links automaticamente em quedas e vãos curtos, ligue Generate Links no NavMesh Surface e ajuste Drop Height e Jump Distance do tipo de agente.

Por padrão, o agente atravessa o link deslizando em linha reta, sem arco. Para um pulo de verdade, desligue a travessia automática e controle o movimento. Crie NavMeshJumpTraversal.cs:

using System.Collections;
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class NavMeshJumpTraversal : MonoBehaviour
{
    [SerializeField] private float jumpHeight = 1.5f;
    [SerializeField] private float jumpDuration = 0.5f;

    private NavMeshAgent agent;
    private bool isTraversing;

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

    private void Update()
    {
        if (agent.isOnOffMeshLink && !isTraversing)
        {
            StartCoroutine(JumpAcrossLink());
        }
    }

    private IEnumerator JumpAcrossLink()
    {
        isTraversing = true;

        OffMeshLinkData linkData = agent.currentOffMeshLinkData;
        Vector3 start = transform.position;
        Vector3 end = linkData.endPos + Vector3.up * agent.baseOffset;
        float progress = 0f;

        while (progress < 1f)
        {
            progress = Mathf.Min(progress + Time.deltaTime / jumpDuration, 1f);
            float arc = jumpHeight * 4f * progress * (1f - progress);
            transform.position = Vector3.Lerp(start, end, progress) + Vector3.up * arc;
            yield return null;
        }

        agent.CompleteOffMeshLink();
        isTraversing = false;
    }
}

A curva 4 * t * (1 - t) vale 0 no começo e no fim e 1 no meio, então o agente sobe até jumpHeight no centro do pulo. CompleteOffMeshLink devolve o controle ao agente na outra ponta. Aqui é o lugar para disparar a animação de pulo.

Rebake em tempo de execução

Se o cenário muda de verdade (ponte que desaba, parede destruída, construção do jogador), o Obstacle não basta e você precisa reconstruir a malha. Crie NavMeshRuntimeRebaker.cs:

using Unity.AI.Navigation;
using UnityEngine;

public class NavMeshRuntimeRebaker : MonoBehaviour
{
    [SerializeField] private NavMeshSurface surface;

    public void RebuildAsync()
    {
        if (surface.navMeshData == null)
        {
            surface.BuildNavMesh();
            return;
        }

        surface.UpdateNavMesh(surface.navMeshData);
    }
}

BuildNavMesh gera tudo do zero e trava o frame enquanto calcula. UpdateNavMesh reconstrói de forma assíncrona e só nas regiões que mudaram, então é o que você chama durante o jogo. Use Collect Objects em Volume ou Current Object Hierarchy para limitar a área e deixar o rebake mais rápido.

Erros comuns: agente parado, 'not close enough to NavMesh'

"SetDestination" can only be called on an active agent that has been placed on a NavMesh. O agente não está sobre a malha. Causas: bake não feito, agente começando no ar ou longe da área azul, Agent Type diferente do NavMesh Surface, ou GameObject desativado. Teste agent.isOnNavMesh antes de chamar.

Failed to create agent because it is not close enough to the NavMesh. O agente foi instanciado fora da malha. Use NavMesh.SamplePosition para achar o ponto válido mais próximo e agent.Warp(ponto) para posicioná-lo.

Agente parado sem erro. Destino fora da malha (o jogador pulou numa plataforma sem link), Stopping Distance maior que a distância até o alvo ou isStopped ligado por outro script.

Agente andando em cima do jogador ou da caixa. Esses objetos entraram no bake. Tire as layers deles de Include Layers e refaça.

Malha some em passagens estreitas. Radius do agente grande demais para a passagem. Diminua o raio ou alargue a porta.

Agente desliza no link sem pular. Esperado com autoTraverseOffMeshLink ligado. Use o script de travessia acima.

Próximo passo

Monte uma fase com duas salas, uma porta com Obstacle, uma plataforma com link de pulo e um inimigo com EnemyNavChaser. Feche e abra a porta durante o Play e veja o agente recalcular. Depois disso, o próximo passo é dar critério à IA: quando perseguir e quando desistir, que é a base de todo inimigo de ação, como no post sobre IA de inimigo: perseguir e patrulhar.

Perguntas frequentes

Onde fica o bake do NavMesh no Unity 6?

No componente NavMesh Surface, do pacote AI Navigation. Adicione o componente a um GameObject (Add Component > Navigation > NavMesh Surface) e clique em Bake. A janela Window > AI > Navigation continua existindo, mas para configurar tipos de agente e áreas, não para fazer o bake.

Como resolver o erro "SetDestination can only be called on an active agent that has been placed on a NavMesh"?

O agente não está sobre a malha de navegação. Confira se o bake foi feito, se o agente começa encostado na área azul, se o Agent Type do agente é o mesmo do NavMesh Surface e se o GameObject está ativo. No código, teste agent.isOnNavMesh antes de chamar SetDestination.

Qual a diferença entre NavMeshAgent e NavMeshSurface?

O NavMeshSurface gera e guarda a malha de navegação de uma área. O NavMeshAgent é o componente do personagem que calcula o caminho sobre essa malha e move o objeto por ele. Você precisa dos dois.

NavMesh Obstacle com Carve ou sem Carve?

Com Carve, o obstáculo abre um buraco na malha e os agentes planejam a rota em volta dele, o que é melhor para objetos parados, como caixas e portas fechadas. Sem Carve, os agentes só desviam localmente, o que serve para objetos que se movem o tempo todo.