Game Dev Cheat Sheet

Unity NavMesh Reference

NavMeshAgent setup, baking, runtime queries, and obstacle avoidance with copy-paste C# code.

Unity's NavMesh system handles pathfinding and obstacle avoidance for AI agents. This reference covers the most common properties, methods, and patterns you need when working with NavMeshAgent, NavMeshSurface, and runtime queries. All code targets the built-in NavMesh system and the AI Navigation package.

NavMeshAgent Properties

Key public properties on NavMeshAgent. Use the search box to filter.

PropertyTypeDefaultDescription
speedfloat3.5Maximum movement speed in world units per second.
angularSpeedfloat120Maximum rotation speed in degrees per second.
accelerationfloat8How quickly the agent reaches top speed.
stoppingDistancefloat0Agent stops when this close to the destination.
radiusfloat0.5Agent avoidance radius. Also affects baked NavMesh clearance.
heightfloat2Agent height for NavMesh clearance calculations.
isStoppedboolfalseWhen true, the agent halts but keeps its current path.
hasPathbool(read-only)True when the agent has a valid, calculated path.
remainingDistancefloat(read-only)Distance left along the current path to the destination.
pathStatusNavMeshPathStatus(read-only)PathComplete, PathPartial, or PathInvalid.
areaMaskint-1 (all)Bitmask controlling which NavMesh areas the agent can traverse.

Setting a Destination

Call SetDestination to send an agent to a world position. Always check pathPending before reading path data, and use remainingDistance to know when the agent has arrived.

using UnityEngine;
using UnityEngine.AI;

public class AgentMovement : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private float arrivalThreshold = 0.5f;

    private NavMeshAgent _agent;

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

    void Update()
    {
        // Set the destination each frame (or on demand)
        _agent.SetDestination(target.position);

        // Wait until the path is fully calculated
        if (_agent.pathPending) return;

        // Check if the agent has arrived
        if (_agent.remainingDistance <= arrivalThreshold)
        {
            Debug.Log("Agent arrived at destination.");
        }
    }
}

Runtime Baking

Runtime NavMesh baking requires the AI Navigation package (com.unity.ai.navigation). Add a NavMeshSurface component and call BuildNavMesh() or UpdateNavMesh() at runtime. This is essential for procedurally generated levels or destructible environments.

Requires package: Install via Package Manager or add com.unity.ai.navigation to your manifest.

using UnityEngine;
using Unity.AI.Navigation;

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

    void Start()
    {
        // Build the entire NavMesh at startup
        surface.BuildNavMesh();
    }

    /// <summary>
    /// Call after modifying the environment (e.g. placing a building).
    /// UpdateNavMesh only recalculates dirty regions, so it is
    /// faster than a full rebuild.
    /// </summary>
    public void RefreshNavMesh()
    {
        surface.UpdateNavMesh(surface.navMeshData);
    }
}

NavMesh Query

Use static methods on NavMesh to sample positions, calculate paths ahead of time, and validate destinations before sending agents.

SamplePosition

Find the closest point on the NavMesh within a given radius. Useful for snapping spawned objects or validating click targets.

// Find the nearest point on the NavMesh within 10 units
if (NavMesh.SamplePosition(worldPosition, out NavMeshHit hit, 10f, NavMesh.AllAreas))
{
    Vector3 validPosition = hit.position;
    agent.SetDestination(validPosition);
}
else
{
    Debug.LogWarning("No NavMesh found near " + worldPosition);
}

CalculatePath

Pre-calculate a path without moving an agent. Check NavMeshPathStatus to see if the destination is reachable.

NavMeshPath path = new NavMeshPath();
bool found = NavMesh.CalculatePath(
    transform.position,
    destination,
    NavMesh.AllAreas,
    path
);

switch (path.status)
{
    case NavMeshPathStatus.PathComplete:
        Debug.Log("Full path found.");
        break;
    case NavMeshPathStatus.PathPartial:
        Debug.Log("Only a partial path is reachable.");
        break;
    case NavMeshPathStatus.PathInvalid:
        Debug.Log("No path exists.");
        break;
}

NavMeshObstacle

NavMeshObstacle blocks agent paths without rebaking. It has two modes: carving and non-carving. Choosing the right mode matters for performance.

Non-Carving (default)

Agents steer around the obstacle using local avoidance only. The NavMesh itself is unchanged. Best for moving obstacles like patrolling enemies or rolling boulders.

Carving

Cuts a hole in the NavMesh so agents path around it globally. Use for stationary or rarely moving obstacles like placed barricades. Has a performance cost when the obstacle moves because the NavMesh is re-carved each time.

using UnityEngine;
using UnityEngine.AI;

public class BarricadeObstacle : MonoBehaviour
{
    private NavMeshObstacle _obstacle;

    void Awake()
    {
        _obstacle = GetComponent<NavMeshObstacle>();

        // Enable carving for a stationary barricade
        _obstacle.carving = true;

        // Only re-carve when the obstacle moves more than 0.1 units
        _obstacle.carvingMoveThreshold = 0.1f;

        // Wait 0.5 seconds after stopping before carving
        // (prevents expensive re-carves while sliding into place)
        _obstacle.carvingTimeToStationary = 0.5f;
    }
}

Area Masks

Setting Area Costs

NavMesh areas let you assign different traversal costs to surfaces (e.g. roads are cheap, mud is expensive). Agents use area masks as a bitfield to filter which areas they can walk on.

// Make the agent prefer roads over mud
NavMeshAgent agent = GetComponent<NavMeshAgent>();

// Area indices are defined in Navigation > Areas tab
// Default areas: 0 = Walkable, 1 = Not Walkable, 2 = Jump
int mudAreaIndex = 3;   // Custom area
int roadAreaIndex = 4;  // Custom area

agent.SetAreaCost(mudAreaIndex, 5f);   // Mud is 5x more expensive
agent.SetAreaCost(roadAreaIndex, 1f);  // Roads are cheap

Filtering Areas with Bitmask

Use areaMask to prevent an agent from entering certain areas entirely. Each area index corresponds to a bit in the mask.

// Only allow Walkable (bit 0) and Road (bit 4)
agent.areaMask = (1 << 0) | (1 << 4);

// Allow everything except Water (bit 5)
agent.areaMask = NavMesh.AllAreas & ~(1 << 5);

// Allow all areas (default)
agent.areaMask = NavMesh.AllAreas;

Common Patterns

Copy-paste patterns for the most frequent NavMesh tasks.

Patrol Between Waypoints

Cycle through an array of waypoints. The agent moves to each in order, then loops back to the first.

using UnityEngine;
using UnityEngine.AI;

public class PatrolAgent : MonoBehaviour
{
    [SerializeField] private Transform[] waypoints;
    [SerializeField] private float waitTime = 1f;

    private NavMeshAgent _agent;
    private int _currentIndex;
    private float _waitTimer;

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

    void Start()
    {
        if (waypoints.Length > 0)
            _agent.SetDestination(waypoints[0].position);
    }

    void Update()
    {
        if (_agent.pathPending) return;

        if (_agent.remainingDistance <= _agent.stoppingDistance)
        {
            _waitTimer += Time.deltaTime;
            if (_waitTimer >= waitTime)
            {
                _waitTimer = 0f;
                _currentIndex = (_currentIndex + 1) % waypoints.Length;
                _agent.SetDestination(waypoints[_currentIndex].position);
            }
        }
    }
}

Flee From Target

Move the agent away from a threat by sampling the NavMesh in the opposite direction.

using UnityEngine;
using UnityEngine.AI;

public class FleeAgent : MonoBehaviour
{
    [SerializeField] private Transform threat;
    [SerializeField] private float fleeDistance = 10f;

    private NavMeshAgent _agent;

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

    public void Flee()
    {
        // Direction away from the threat
        Vector3 fleeDir = (transform.position - threat.position).normalized;
        Vector3 fleeTarget = transform.position + fleeDir * fleeDistance;

        // Snap to the nearest valid NavMesh point
        if (NavMesh.SamplePosition(fleeTarget, out NavMeshHit hit, fleeDistance, NavMesh.AllAreas))
        {
            _agent.SetDestination(hit.position);
        }
    }
}

Stop and Resume

Pause an agent without clearing its path. Set isStopped to true to halt, then false to resume along the same path.

// Stop the agent (keeps the current path)
agent.isStopped = true;

// Resume movement along the existing path
agent.isStopped = false;

// To fully cancel navigation, also reset the path:
agent.ResetPath();

When the built-in NavMesh runs out of road, A* Pathfinding Project is the usual next step. Quick comparison.

Unity NavMeshA* Pathfinding Project
CostFree, built inFree or paid (Pro)
SetupBake meshes from geometryGrid, navmesh, or point graph
Best for3D worlds, first/third personGrids, hex maps, very large worlds
Runtime bakingNavMeshSurface packageBuilt in
Custom behaviourLimitedHighly extensible

Frequently asked questions

How do I bake a NavMesh at runtime in Unity?
Use the NavMeshSurface component from the AI Navigation package. Call surface.BuildNavMesh() at runtime to bake. For procedurally generated levels, give each chunk its own NavMeshSurface and rebuild as chunks load.
NavMesh vs A* Pathfinding Project: which should I use?
Unity NavMesh is built in, well integrated, and good for most third-person and first-person games. A* Pathfinding Project handles grid-based games, hex grids, and very large or dynamic worlds better, and gives more control over pathfinding behaviour. Use NavMesh first; switch to A* if you hit its limits.
How do I make a NavMeshAgent stop smoothly?
Set agent.isStopped = true to halt movement, but this stops abruptly. For smooth deceleration, instead reduce agent.speed over a short duration with DOTween or a coroutine, then set isStopped. You can also lower agent.acceleration to soften the change.
Why is my NavMeshAgent ignoring obstacles?
Three common causes: the obstacle has no NavMeshObstacle component, the obstacle does not have Carve enabled (so the NavMesh is not updated around it), or the obstacle is not on a layer included in the bake. Static obstacles should be marked Navigation Static and rebaked.

Last updated: