Skip to content
Learni
View all tutorials
Unity

How to Implement NavMesh for AI in Unity 2026

Lire en français

Introduction

NavMesh, or Navigation Mesh, is Unity's pathfinding navigation system, essential for simulating realistic AI behaviors in 3D games. Imagine a horde of enemies intelligently navigating around your barricades or an NPC reaching a specific point without passing through walls: that's NavMesh generating a navigation mesh from your scene geometry.

In 2026, with Unity 2023 LTS and its AI Navigation 2.0 optimizations, NavMesh handles dynamic open worlds, real-time obstacles, and mutual agent avoidance. This intermediate tutorial guides you step by step: from initial baking to advanced multi-agent scripts. Why is it crucial? Without NavMesh, your AI slides or clips; with it, it feels alive. By the end, you'll deploy a complete scene with 10 agents avoiding obstacles and each other. Estimated time: 45 min. Ready to boost your gameplay?

Prerequisites

  • Unity 2023.2 LTS or higher (downloadable via Unity Hub).
  • Basic knowledge of C# and Unity components (Transform, Update()).
  • A simple 3D scene: flat ground (scaled Cube), walls (Cubes), lighting.
  • AI Navigation package enabled (Window > Package Manager > Unity Registry > AI Navigation).

Step 1: Set Up and Bake the Basic NavMesh

Open Window > AI > Navigation to display the Navigation window.

  1. Select your ground: Navigation > Static > Navigation Static (check to include in the bake).
  2. Add NavMesh Surface (GameObject > AI > NavMesh Surface) to an empty GameObject named 'NavMeshHolder'.
  3. In the NavMesh Surface inspector:
- Agent Type: Humanoid (radius 0.5m, height 2m). - Collect Objects: All. - Click Bake: a blue surface appears (visualize in Scene view > Shaders > NavMesh).

Analogy: Baking is like generating a road map from your city (geometry). Test by adding a Capsule with NavMesh Agent: it now navigates intelligently!

Simple Agent Script to Fixed Target

Assets/Scripts/SimpleNavAgent.cs
using UnityEngine;
using UnityEngine.AI;

public class SimpleNavAgent : MonoBehaviour
{
    [SerializeField] private Transform target;
    private NavMeshAgent agent;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        if (target == null)
        {
            Debug.LogError("Assign a target in the inspector!");
            return;
        }
        agent.SetDestination(target.position);
    }

    void Update()
    {
        // Stop if arrived (distance < 0.5m)
        if (!agent.pathPending && agent.remainingDistance < 0.5f)
        {
            agent.ResetPath();
        }
    }
}

This complete script assigns a NavMeshAgent to a GameObject (Capsule) and directs it to a fixed Transform target. It handles arrival by resetting the path to avoid infinite loops. Pitfall: Forget GetComponent and the agent slides; always check target != null in production.

Step 2: Interactive Navigation (Mouse Click)

For player control: add a Camera in TPS/FPS view. Mouse clicks set the destination.

Bake improvements: Increase Voxel Size to 0.3 for precision, Max Slope to 45° for ramps. Re-bake.

Test: Hit Play, click the ground – the agent follows the optimal path, avoiding walls. Visualize the path in green (Gizmos > Show NavMesh).

Player Controller with Click Destination

Assets/Scripts/PlayerNavController.cs
using UnityEngine;
using UnityEngine.AI;

public class PlayerNavController : MonoBehaviour
{
    [SerializeField] private Camera playerCamera;
    [SerializeField] private LayerMask groundLayer = 1 << 6; // Layer 'Ground'
    private NavMeshAgent agent;
    private RaycastHit hit;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        if (playerCamera == null) playerCamera = Camera.main;
        agent.updateRotation = false; // Manual rotation
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 100f, groundLayer))
            {
                NavMeshHit navHit;
                if (NavMesh.SamplePosition(hit.point, out navHit, 1.0f, -1))
                {
                    agent.SetDestination(navHit.position);
                }
            }
        }
    }
}

This controller raycasts the mouse click on the Ground layer, samples a valid NavMesh position (avoids invalid edges), and sets the destination. Use NavMesh.SamplePosition for reliability. Pitfall: Without layerMask, clicks go through everything; assign Layer 6 to your ground.

Step 3: Dynamic Obstacles with NavMeshObstacle

For moving crates: Add NavMeshObstacle (GameObject > AI > NavMesh Obstacle) to an enemy Cube.

  • Shape: Box, Carve: true (carves the NavMesh at runtime).
  • Move Threshold: 0.5m for updates.
Agents detour instantly. For toggling: see script below.

Dynamic Obstacle Toggle Script

Assets/Scripts/DynamicObstacle.cs
using UnityEngine;
using UnityEngine.AI;

public class DynamicObstacle : MonoBehaviour
{
    private NavMeshObstacle obstacle;
    [SerializeField] private float moveSpeed = 2f;
    [SerializeField] private Vector3 patrolPoint;

    void Start()
    {
        obstacle = GetComponent<NavMeshObstacle>();
        patrolPoint = new Vector3(5f, 0.5f, 5f); // Patrol point
    }

    void Update()
    {
        // Simple patrol movement
        transform.position = Vector3.MoveTowards(transform.position, patrolPoint, moveSpeed * Time.deltaTime);

        // Toggle carve if speed > threshold
        if (obstacle.velocity.magnitude > 0.1f)
        {
            obstacle.carveOnlyStationary = false;
        }
        else
        {
            obstacle.carveOnlyStationary = true;
        }
    }
}

This script animates an obstacle on patrol, enabling Carve during movement to dynamically update the NavMesh. Velocity check optimizes performance. Pitfall: Without carveOnlyStationary, CPU spikes; test with 20+ obstacles.

Step 4: Multi-Agent Avoidance

Duplicate 5 agents. In Navigation window > Agents > Humanoid > Avoidance Priority: 50 (medium).

Obstacle Avoidance Type: High Quality Obstacle Avoidance.

Agents push past each other without physics collisions. Boost with random AI script.

AI with Random Destinations + Avoidance

Assets/Scripts/AIAgentRandom.cs
using UnityEngine;
using UnityEngine.AI;

public class AIAgentRandom : MonoBehaviour
{
    [SerializeField] private float patrolRadius = 10f;
    [SerializeField] private float waitTime = 3f;
    private NavMeshAgent agent;
    private Vector3 startPos;
    private float waitTimer;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        agent.avoidancePriority = Random.Range(0, 99);
        startPos = transform.position;
        SetRandomDestination();
    }

    void Update()
    {
        if (!agent.pathPending && agent.remainingDistance < 0.5f)
        {
            waitTimer += Time.deltaTime;
            if (waitTimer > waitTime)
            {
                SetRandomDestination();
                waitTimer = 0;
            }
        }
    }

    void SetRandomDestination()
    {
        Vector3 randomDir = Random.insideUnitSphere * patrolRadius;
        randomDir += startPos;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(randomDir, out hit, patrolRadius, -1))
        {
            agent.SetDestination(hit.position);
        }
    }
}

Generates random destinations within a radius, with pauses and unique avoidance priorities (0-99). SamplePosition ensures validity. Pitfall: Without random avoidancePriority, herd collisions occur; scales to 50+ agents.

Advanced Path Query for Validation

Assets/Scripts/PathValidator.cs
using UnityEngine;
using UnityEngine.AI;

public class PathValidator : MonoBehaviour
{
    [SerializeField] private Transform target;
    private NavMeshAgent agent;
    private NavMeshPath path;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        path = new NavMeshPath();
    }

    void Update()
    {
        if (target != null)
        {
            NavMesh.CalculatePath(transform.position, target.position, NavMesh.AllAreas, path);
            if (path.status == NavMeshPathStatus.PathComplete)
            {
                Debug.Log($"Valid path, length: {path.CalculateCornerLength()}");
                agent.SetPath(path);
            }
            else
            {
                Debug.LogWarning("Path impossible!");
            }
        }
    }

    void OnDrawGizmos()
    {
        if (path != null)
        {
            Gizmos.color = Color.green;
            NavMeshVisualization.DrawPathGizmos(path);
        }
    }
}

Calculates and validates path before SetDestination, logs status/length. Gizmos visualize (add using UnityEngine.AI.NavMeshVisualization if custom). Pitfall: Ignoring CalculatePath leads to bad paths; use for closed doors.

Step 5: Runtime NavMesh for Procedural Worlds

NavMesh Surface > Build > Advanced > Use Geometry > Render Meshes.

For proc-gen: Call surface.BuildNavMesh() after spawning. Limit: Use async for performance.

Async Runtime NavMesh Baking

Assets/Scripts/RuntimeNavMeshBuilder.cs
using UnityEngine;
using UnityEngine.AI;

public class RuntimeNavMeshBuilder : MonoBehaviour
{
    [SerializeField] private NavMeshSurface navMeshSurface;

    [ContextMenu("Build NavMesh")]
    public async void BuildNavMeshAsync()
    {
        // Simulate procedural spawn
        GameObject procGround = GameObject.CreatePrimitive(PrimitiveType.Plane);
        procGround.transform.position = new Vector3(10, 0, 10);
        procGround.AddComponent<NavMeshModifier>().OverrideArea = 0;

        await System.Threading.Tasks.Task.Run(() =>
        {
            navMeshSurface.BuildNavMesh();
        });

        Debug.Log("NavMesh rebaked at runtime!");
    }
}

Builds NavMesh async after procedural spawn (Plane + Modifier). Task.Run offloads CPU. Pitfall: Sync build freezes the game; always async for >1000 polys.

Best Practices

  • Voxel Size < 0.3 for precision, but test FPS (bake time x10).
  • Avoidance priorities: Random 0-99 per agent, boss at 0 (high priority).
  • Areas & Costs: Multi-layers (water cost 3.0) for tactical AI.
  • Cache paths with NavMesh.CalculatePath in coroutine.
  • Profiler > AI: Aim for <1ms/frame with 50 agents.

Common Errors to Avoid

  • No Navigation Static: Empty NavMesh, static agents.
  • Agent radius too large: Stuck in narrow doors; match geometry.
  • Bake without SamplePosition: Invalid destinations, teleporting.
  • Forget carve on moving obstacles: Agents pass through crates.
  • Sync runtime bake: Lag spikes; force async.

Next Steps