Overview
Unity’s NavMesh system is the engine’s built-in navigation framework for 3D agents. It represents walkable space as a navigation mesh, then lets NavMeshAgent components query paths, follow them, and avoid one another locally. For most 3D games, this is the right default before considering custom pathfinding. The underlying ideas are the same ones discussed in navigation-mesh literature and AI textbooks, but Unity packages them into editor components and APIs that are practical for student and production use. (Game AI Pro 360: Guide to Movement and Pathfinding, see source-game-ai-pro-360-movement-pathfinding)
Setup
The core Unity NavMesh toolset consists of four parts:
| Tool | Purpose |
|---|---|
| NavMesh Surface | Defines which geometry contributes to the baked walkable area |
| NavMeshAgent | Moves an NPC over the baked mesh |
| NavMeshObstacle | Marks dynamic blockers and can carve holes in the mesh |
| NavMeshLink | Creates explicit connections such as jumps, drops, or ladders |
Where the components come from. NavMeshAgent and NavMeshObstacle are built into the engine (namespace UnityEngine.AI). NavMesh Surface, NavMesh Modifier, NavMesh Modifier Volume and NavMesh Link come from the AI Navigation package (com.unity.ai.navigation, version 2.0.x for Unity 6, namespace Unity.AI.Navigation). Unity’s upgrade guide says navigation “is handled by the AI Navigation package as of Unity 2022.2”, and the old OffMesh Link component is deprecated in favour of NavMesh Link (Unity Technologies, AI Navigation manual: Upgrade projects). If NavMesh Surface is missing from Add Component, install the package from the Package Manager.
Typical setup:
- Add a NavMesh Surface to a manager object.
- Choose which layers/objects count as navigation geometry.
- Set agent radius, height, step height, and slope limits.
- Bake the mesh.
- Add NavMeshAgent to NPCs that need autonomous movement.
Usage
Minimal scripting workflow:
using UnityEngine;
using UnityEngine.AI;
public class EnemyMover : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private float repathDistance = 0.5f; // re-path only when the target has moved this far
private NavMeshAgent agent;
private Vector3 lastGoal = Vector3.positiveInfinity;
private void Awake() => agent = GetComponent<NavMeshAgent>();
private void Update()
{
if (target == null) return;
if ((target.position - lastGoal).sqrMagnitude > repathDistance * repathDistance)
{
agent.SetDestination(target.position);
lastGoal = target.position;
}
}
}The agent handles path query, waypoint following, turning, and basic local avoidance. Higher-level systems such as behaviour-trees, utility-ai, or goal-oriented-action-planning decide when to move and which destination to request.
Calling agent.SetDestination(target.position) unconditionally in every Update also works, and many tutorials do it, but it asks for a new path every frame even when the target has barely moved. The version above only re-paths when the target has moved more than repathDistance. “Smooth movement and retargeting” below explains why this matters for smoothness as well as cost.
From decision to motion
Game AI is usually described in three layers: a decision layer chooses what to do, a steering layer works out a trajectory, and a locomotion layer moves the body (see game-ai-agent-design). In Unity, NavMesh sits between the decision and the body. Most problems with “jittery” or “indecisive” NPCs come from a muddled hand-off between these layers rather than from the pathfinding itself.
The move leaf: set a destination, then report progress
A behaviour tree leaf, a utility action or a state-machine state should set a destination and then watch the agent, not drive the agent frame by frame. The leaf returns Running while the agent is still travelling, Success when it arrives, and Failure if no path exists:
// Behaviour tree leaf: request a path when the goal is new or has moved, then report progress.
private Vector3 requestedGoal = Vector3.positiveInfinity;
private NodeState MoveTo(Vector3 goal)
{
if ((goal - requestedGoal).sqrMagnitude > 0.25f)
{
if (!agent.SetDestination(goal)) return NodeState.Failure; // goal not near the NavMesh
requestedGoal = goal;
}
if (agent.pathPending) return NodeState.Running; // still computing; keep going
if (agent.pathStatus == NavMeshPathStatus.PathInvalid) return NodeState.Failure;
if (agent.remainingDistance > agent.stoppingDistance) return NodeState.Running;
// At the end of the path: a complete path means we arrived; a partial one means we got stuck short.
return agent.pathStatus == NavMeshPathStatus.PathComplete ? NodeState.Success : NodeState.Failure;
}Three Scripting API details make this work. SetDestination returns true if “the destination was requested successfully”, but the path “may not become available until after a few frames later”, and “While the path is being computed, pathPending will be true” (Unity Technologies, Scripting API: NavMeshAgent.SetDestination). remainingDistance is infinity while the distance is unknown, so it must be read only once pathPending is false (Unity Technologies, Scripting API: NavMeshAgent.remainingDistance). Unity’s own patrol how-to uses the same !agent.pathPending && agent.remainingDistance < 0.5f test before choosing the next point (Unity Technologies, AI Navigation manual: Make an agent patrol between a set of points). This is the NavMesh version of the Running status described in behaviour-trees: walking somewhere takes many ticks, so the leaf keeps returning Running until it is done.
Commit to the decision
If the decision layer changes its mind every frame, no amount of steering will look smooth: the destination flips between two candidates and the agent twitches between them. Separate two cases:
- The same decision with a moving goal (chasing a player). This is not a new decision, only an updated destination. Re-path when the goal has moved far enough, as in the snippet above.
- A genuinely different decision (cover point A versus cover point B, or “flee” versus “fight”). Here the decision layer needs inertia. Graham suggests adding weight to the action already in progress, a cooldown during which staying is heavily favoured, or not deciding again until the current action finishes (Graham, “An Introduction to Utility Theory”, Game AI Pro, see source-game-ai-pro-360-architecture); utility-ai implements this as a commitment bonus. In a behaviour tree, the guard in behaviour-trees uses two health thresholds for the same reason. For a chosen position, keep it until it is compromised (for cover: the enemy can now see it) and re-check on a timer, rather than re-running the whole query every frame.
Choosing where to go is a tactical query
“Seek cover” hides a question NavMesh cannot answer: which point is cover? That is a tactical query, usually structured as generate (candidate points), filter (remove exposed or unreachable points) and score (distance, safety, direction), as described in tactical-position-selection. Path length is a better distance score than straight-line distance, and NavMesh.CalculatePath provides it; tactical-position-selection shows how to sum the path’s corners. Danger and control maps from influence-maps make good scoring terms. The query returns one point; the move leaf above takes it from there.
Four ways to split the work
| Route | Who plans the path | Who moves the body | Choose it when |
|---|---|---|---|
NavMeshAgent alone | Agent | Agent | The default for 3D NPCs. Tune the agent’s settings (below) before writing any movement code |
| Agent + custom steering | Agent (path and avoidance) | Your script, root motion or physics, with updatePosition = false | You need your own acceleration, separation, formation offsets, strafing or animation-driven movement |
NavMesh.CalculatePath + your own movement | NavMesh.CalculatePath | Your script (for example a CharacterController) | You want the path’s corners for reasoning (path length, cover) or full control of motion, and do not need the agent’s crowd avoidance |
| Your own A* + your own movement | Your graph (pathfinding-algorithms) | Your script | 2D tilemaps, grid tactics, custom costs or graphs NavMesh cannot express |
The second and third routes are the “hybrid” options. Only agents take part in the agent-to-agent avoidance, so with the third and fourth routes any avoidance is yours to write (see steering-behaviours and context-steering).
Smooth movement and retargeting
Tune the agent first
Most stiff or stuttering NavMesh movement is a settings problem. The NavMesh Agent component’s settings, as the AI Navigation manual describes them (Unity Technologies, AI Navigation manual: NavMesh Agent component reference):
| Setting | What it does | Advice |
|---|---|---|
| Speed | Maximum speed along the path | Match the animation’s run speed to avoid foot sliding |
| Acceleration | Maximum acceleration, units/s² | Higher = snappier starts and turns; too low and agents overshoot corners |
| Angular Speed | Maximum turning speed, degrees/s | Too low makes agents swing wide at corners; 360–720 is a reasonable starting point for humanoids (a rule of thumb, not a Unity default) |
| Stopping Distance | The agent stops this close to the destination | Give chasers a value greater than 0 so they stop short of the target instead of pushing into it |
| Auto Braking | Slows down approaching the destination | The manual advises disabling it for a patrolling guard so it moves smoothly between points; turn it off for chasers too, and on for precise stops such as reaching cover |
| Radius / Height | The agent’s avoidance cylinder | Keep the radius close to the radius the NavMesh was baked for (the agent type), or agents will clip walls or refuse gaps |
| Quality (obstacle avoidance) | Quality of local avoidance | Lower it when there are many agents; at None, agents do not actively avoid each other |
| Priority | Agents avoid higher-priority agents and ignore lower ones (0 is highest) | Give important characters (a companion, a boss) a high priority so the crowd moves round them |
For tight corners, Unity’s how-to on cornering slows the agent by looking ahead at the next two corners of agent.path and lowering agent.speed in proportion to the turn angle (Unity Technologies, AI Navigation manual: Control agent speed for corners).
Retarget without stutter
When a behaviour tree switches from “patrol” to “chase”, or a chased target keeps moving, the agent should flow from one path into the next:
- Throttle
SetDestination. Re-path only when the goal has moved more than a threshold, and/or on a short timer (0.2–0.5 s is a reasonable starting range; tune it to how fast targets move). Re-pathing every frame wastes CPU and, with Auto Braking on, can produce stop-start movement as the agent brakes for each new end point. - Do not stop between targets.
ResetPath()“Clears the current path”, andisStoppedstops movement “along the current path” (Unity Technologies, Scripting API: NavMeshAgent). Calling either before setting the next destination makes the agent brake and restart. Just callSetDestinationwith the new goal. In practice the agent carries on along its current path whilepathPendingis true and switches when the new path is ready; the Scripting API states only thatpathPendingis true meanwhile, so treat this as observed behaviour rather than a documented guarantee. - Choose Auto Braking per action. Off for chasing and patrolling, on for “stop exactly here” (cover, a door, a conversation spot). It can be changed from code (
agent.autoBraking) as the action changes. - Use
ResetPath()only for a real stop: when the decision layer aborts movement altogether, not when it changes destination.
Custom steering on top of the agent
When the agent’s built-in movement is not enough (you want separation in a doorway, a formation offset, or physics or root motion to move the body), let the agent plan and avoid, but move the body yourself. Setting updatePosition to false means “the simulated position will not be applied to the transform position and vice-versa”, which “can be used to enable explicit control of the transform position via script” (Unity Technologies, Scripting API: NavMeshAgent.updatePosition). The agent then supplies:
agent.desiredVelocity: “The desired velocity of the agent including any potential contribution from avoidance”;agent.steeringTarget: the point the agent is currently steering towards, usually the next corner of the path or its end point (Unity Technologies, Scripting API: NavMeshAgent.desiredVelocity and steeringTarget).
private void Start()
{
agent.updatePosition = false; // this script moves the transform...
agent.updateRotation = false; // ...and turns it
}
private void Update()
{
// 1. Path-following intent from the agent, which already includes its RVO avoidance,
// blended with your own steering (separation, formation offset, strafing...).
Vector3 desired = agent.desiredVelocity + separationWeight * Separation();
desired = Vector3.ClampMagnitude(desired, agent.speed);
// 2. Integrate with your own acceleration limit and move the body.
velocity = Vector3.MoveTowards(velocity, desired, agent.acceleration * Time.deltaTime);
transform.position += velocity * Time.deltaTime;
// 3. Hand the real position back. The agent's position is constrained to the NavMesh,
// so copying it back stops the body drifting off the mesh or through walls.
agent.nextPosition = transform.position;
transform.position = agent.nextPosition;
}Step 3 follows the Scripting API’s example for objects moved by “another system”: copy the transform’s position into nextPosition every frame. Setting nextPosition does not teleport the agent; the move “is constrained by the navmesh connectivity and boundaries”, which is why it only makes sense when updated continuously, and Warp is the call for a real teleport (Unity Technologies, Scripting API: NavMeshAgent.nextPosition). Reading nextPosition straight back to clamp the body is common practice rather than something the documentation spells out. Separation() and turning are in the runnable example below; the separation rule itself is in steering-behaviours.
The same pattern couples navigation with animation. The AI Navigation manual’s rule is that “Information should always flow in one direction”: either the Animator follows the agent (feed agent.velocity to the Animator), or the agent follows the animation (disable updatePosition and updateRotation and drive the body with root motion) (Unity Technologies, AI Navigation manual: Using NavMesh Agent with other components).
The hybrid: NavMesh.CalculatePath and your own movement
You can use the NavMesh only as a pathfinder: NavMesh.CalculatePath(from, to, NavMesh.AllAreas, path) fills a NavMeshPath, and your code follows path.corners. The call is synchronous, it returns true for partial as well as complete paths (check path.status), and Unity recommends only a few path finds per frame (Unity Technologies, Scripting API: NavMesh.CalculatePath). Create the NavMeshPath once (in Awake, not in a field initialiser) and reuse it. You lose the agent’s crowd avoidance, so add your own if several characters share space.
Following a hand-written A* path
Whether the points come from path.corners or from your own grid A*, the follower is the same. These rules remove most jitter and corner snagging:
- Smooth the path first. On a grid, A* produces staircase paths. Remove every waypoint you can see past: from each kept point, jump to the furthest later point with a clear line (for a NavMesh,
!NavMesh.Raycast(a, b, out _, NavMesh.AllAreas); for a grid, walk the cells on the line). This is the “rubber-band” post-processing described in pathfinding-algorithms. NavMesh paths are already straightened: the agent “will reach the destination by always steering towards the next visible corner of the corridor” (Unity Technologies, AI Navigation manual: Inner workings of the navigation system), using the funnel idea described in navigation-mesh-construction. - Advance with a waypoint radius. Treat a corner as reached when you are within a radius of it (about the agent’s radius to its diameter), and also when you are already closer to the next segment than the current one. Never steer back towards a corner you have passed; that is what causes U-turns and orbiting.
- Seek a lookahead point. Instead of seeking the corner itself, project your position onto the current segment and seek a point a short distance further along the path, as in Reynolds-style path following (steering-behaviours). The agent cuts corners slightly and stops zig-zagging.
- Arrive only at the last waypoint. Use Seek (full speed) for intermediate corners and Arrive (slowing inside a radius) only on the final segment, so the agent does not brake at every turn.
- Blend, then clamp. Add separation or obstacle avoidance to the path-following velocity, clamp to maximum speed, and limit acceleration and turn rate when integrating, as in the custom steering snippet.
Avoiding clipping
- Match radii. The agent’s radius should match the agent type the NavMesh was baked for; the bake already keeps paths that distance from walls.
- Let the agent own position, or hand it back. With
updatePositionon, do not also move the transform from your own code. With it off, writenextPositionevery frame as above. - Physics. If a
NavMeshAgentand a non-kinematicRigidbodyare both active, the AI Navigation manual warns of a race condition and says to turn on Is Kinematic. Agents do not need colliders to avoid each other (Unity Technologies, AI Navigation manual: Using NavMesh Agent with other components). - Moving obstacles. Use
NavMeshObstaclefor things agents must path round; enable carving only for obstacles that stop and stay put (see Gotchas).
Runnable example
A runnable version of everything in this section and the previous one is in code/unity-csharp/game-ai/navmesh-steering/:
- NavMover.cs:
MoveTo()returning Running / Arrived / Failed, throttled retargeting, and optional custom steering with separation - WaypointPathFollower.cs: line-of-sight smoothing, progress tracking, lookahead and Arrive-on-last for any list of points
- CalculatePathMover.cs: the hybrid route with a
CharacterControllerand no agent - SoldierAI.cs: a behaviour tree (take cover > chase > patrol) with a committed cover query, using the framework from behaviour-trees
The scripts were compile-checked against the Unity 6 engine assemblies, and the path follower was tested in a headless simulation (it completes an L-shaped path and a 10-step staircase; with smoothing, the staircase becomes one straight line). They have not been play-tested in a scene.
Gotchas
- Wrong default for 2D tile games: NavMesh is strongest in 3D or freeform navigation. For strict 2D grids, custom A* over a grid may be cleaner.
- Carving is expensive if abused:
NavMeshObstaclewith carving enabled is not meant for constantly moving objects. - Bake settings are design settings: agent radius or slope values that are slightly wrong can silently remove narrow paths or generate awkward movement.
- NavMesh is not tactics: it gives traversable routes, not smart destination choice. That still belongs to AI logic.
Related
navigation-mesh-construction · pathfinding-algorithms · steering-behaviours · behaviour-trees · utility-ai · tactical-position-selection · context-steering · game-ai-agent-design · buddy-ai · npc-performance-at-scale · source-game-ai-pro-360-movement-pathfinding · environmental-storytelling