Skip to main content

๐Ÿšถ Lesson 6.2: Moving Agents โ€” Destinations & Patrol Routes

Last lesson you baked a NavMesh and stood an agent on it. Now you'll make it go. It turns out moving a NavMeshAgent is almost embarrassingly simple โ€” one line โ€” but doing it well (knowing when it arrived, looping a patrol, handling moving obstacles) is where the craft lives. That's this lesson.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Send an agent anywhere with agent.SetDestination(...)
  • Detect arrival reliably using pathPending and remainingDistance
  • Build a looping patrol between an array of waypoints
  • Understand hasPath, velocity, and why you never move the Transform yourself
  • Choose between a NavMeshObstacle (carving) and re-baking for obstacles that move

Estimated Time: 45 minutes  ยท  Prerequisite: Lesson 6.1 (The NavMesh: Baking & Agents)

In This Lesson

SetDestination: One Line to Move

To move an agent, you give it a world position and it does the rest โ€” finds the path, steers around obstacles, and walks there:

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class GoToTarget : MonoBehaviour
{
    [SerializeField] Transform target;
    NavMeshAgent agent;

    void Awake() => agent = GetComponent<NavMeshAgent>();

    void Start()
    {
        if (target != null)
            agent.SetDestination(target.position);   // that's the whole thing
    }
}

That single call kicks off asynchronous pathfinding. Unity computes a corner list across the NavMesh from the agent to the destination, then the agent follows it, updating its own Transform position and rotation every frame. You never touch transform.position yourself โ€” if you do, you'll fight the agent and get jitter.

๐Ÿ“– Definition

Path: the sequence of straight segments (corners) the agent walks to reach its destination. SetDestination requests one; it may not be ready the same frame you ask, which is why the next section matters.

โš ๏ธ Don't call SetDestination every frame with the same value

Recomputing an identical path each frame is wasted work. Set the destination when it actually changes (a new waypoint, the player moved far enough). For a moving target, throttle it โ€” recompute a few times a second, not 60. You'll do exactly this in the chase state next lesson.

Knowing When You've Arrived

The trickiest part of agent movement isn't starting โ€” it's knowing when the agent has finished. The naive check "is my distance to the target small?" fails, because the agent walks the NavMesh path, which is usually longer than the straight line. Unity gives you the right tools:

  • pathPending โ€” true while Unity is still computing the path. You must wait for this to be false before trusting the distance.
  • remainingDistance โ€” the distance left along the path. This is the number to compare against stoppingDistance.
  • hasPath and velocity โ€” a fully arrived agent has no path and (near) zero velocity.

The idiomatic "have I arrived?" check combines them:

bool ReachedDestination()
{
    // 1. path still being calculated? not arrived yet
    if (agent.pathPending) return false;

    // 2. still farther than our stopping distance? still walking
    if (agent.remainingDistance > agent.stoppingDistance) return false;

    // 3. either no path left, or we've slowed to a stop
    return !agent.hasPath || agent.velocity.sqrMagnitude < 0.01f;
}

All three checks matter. Skip pathPending and you'll get a false "arrived" on the very first frame (when remainingDistance is still 0 because no path exists yet). The velocity check handles the case where the agent has a path but is idling at the destination.

โœ… Pro Tip

Compare squared magnitudes (velocity.sqrMagnitude) instead of velocity.magnitude when you just need "is it basically zero?". Skipping the square root is a tiny, free optimization โ€” and a habit worth building for any per-frame distance/speed test.

A Waypoint Patrol

A patrol is the classic enemy behaviour: walk to waypoint 0, then 1, then 2, then loop back to 0 forever. With SetDestination and an arrival check, it's short:

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class Patrol : MonoBehaviour
{
    [SerializeField] Transform[] waypoints;
    [SerializeField] float waitAtPoint = 1.5f;

    NavMeshAgent agent;
    int index = 0;
    float waitTimer = 0f;

    void Awake() => agent = GetComponent<NavMeshAgent>();

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

    void Update()
    {
        if (waypoints.Length == 0) return;

        if (ReachedDestination())
        {
            // pause a beat at the waypoint, then advance to the next
            waitTimer += Time.deltaTime;
            if (waitTimer >= waitAtPoint)
            {
                waitTimer = 0f;
                index = (index + 1) % waypoints.Length;   // loop
                agent.SetDestination(waypoints[index].position);
            }
        }
    }

    bool ReachedDestination()
    {
        if (agent.pathPending) return false;
        if (agent.remainingDistance > agent.stoppingDistance) return false;
        return !agent.hasPath || agent.velocity.sqrMagnitude < 0.01f;
    }
}

The modulo (% waypoints.Length) is what makes it loop: after the last waypoint, index wraps back to 0. Drop empty GameObjects around your level, drag them into the waypoints array in order, and the agent walks the circuit. Here's a top-down view of what that circuit looks like:

Top-down view of a patrol route through waypoints A top-down room with four numbered waypoints connected by a looping dashed path. Arrows show the agent walking from waypoint 0 to 1 to 2 to 3 and back to 0. Two obstacles sit inside the loop and the path bends around them. A guard icon sits partway along the first segment. crates barrel 0 1 2 3 guard loop: 0 โ†’ 1 โ†’ 2 โ†’ 3 โ†’ 0 โ€ฆ
Figure 1: A four-point patrol loop (recreated top-down). The agent visits waypoints in order and wraps back to 0; the NavMesh bends the actual path around the crates and barrel automatically.

Reading the Route

The Update loop above hides a small state machine: the agent is either travelling to a waypoint or waiting at one, and arrival is what flips it. Seeing that logic as a flow makes the wrap-around click:

flowchart TD A["Start: SetDestination(waypoints[0])"] --> B["Walking to waypoints[index]"] B --> C{"ReachedDestination()?
(not pending AND
remaining โ‰ค stopping)"} C -- "No" --> B C -- "Yes" --> D["Wait at point
(waitTimer += deltaTime)"] D --> E{"waited long enough?"} E -- "No" --> D E -- "Yes" --> F["index = (index + 1) % length"] F --> G["SetDestination(waypoints[index])"] G --> B

Figure 2: The waypoint-advance logic. Arrival triggers a wait; the wait expiring advances the index (wrapping with modulo) and sets the next destination.

A few properties are worth knowing for debugging and for the states you'll build next lesson:

  • agent.destination โ€” the current target position (read it back to confirm what you set).
  • agent.isStopped โ€” set true to freeze the agent in place without losing its path; set false to resume. Great for "pause while attacking."
  • agent.pathStatus โ€” Complete, Partial (couldn't fully reach โ€” target is off the mesh or blocked), or Invalid. Check for Partial when a destination might be unreachable.
๐Ÿ’ก Warp, don't teleport. If you ever need to move an agent instantly (respawn, cutscene), use agent.Warp(position), not transform.position = position. Warp re-seats the agent on the NavMesh correctly; a raw Transform assignment can leave it confused about where it is on the mesh.

Obstacles: Carving vs. Re-baking

Your baked NavMesh is a snapshot. So what happens when something blocks the path after the bake โ€” a door slams shut, a crate gets pushed into the corridor, a car parks across the road? You have two tools, and picking the right one matters.

  • NavMeshObstacle (with Carving) โ€” add a NavMeshObstacle component to the moving thing. With Carve ticked, it cuts a hole in the NavMesh at runtime wherever it sits, and agents path around it live. This is the right choice for things that move: doors, crates, other characters, destructible cover.
  • Re-baking โ€” regenerate the whole surface. Expensive and causes a hitch, so it's for author-time or rare, deliberate level changes (a wall collapses once in a scripted event), not per-frame movement.

The rule of thumb:

๐Ÿ“– Definition

Carving: a NavMeshObstacle dynamically subtracts its volume from the baked mesh while it's in place. Static level geometry gets baked into the mesh; things that move should carve instead. Never try to re-bake every frame to handle movement.

There's one important subtlety. An obstacle with carving off still pushes agents away via avoidance, but it does not alter the path โ€” agents will keep trying to walk their original route and just bump against it. Turn carving on when you need agents to actually re-route around the blockage. Carving is more expensive (it edits the mesh), so use it only where re-routing is required.

โš ๏ธ Don't put a NavMeshObstacle and a NavMeshAgent on the same object

They're opposites: an Agent walks the mesh, an Obstacle blocks it. Other characters avoid each other through the agents' own avoidance (Radius / Priority), not by giving each one an obstacle. Reserve NavMeshObstacle for non-agent movers like doors and physics crates.

Hands-on Challenge

๐Ÿ‹๏ธ Exercise 1: Walk the loop

Objective: A capsule that patrols four waypoints forever.

  1. In the room you baked last lesson, create four empty GameObjects as waypoints, spread around the floor, and name them WP0โ€“WP3.
  2. Add the Patrol script to your agent capsule and drag the four waypoints into the array in the order you want them visited.
  3. Press Play. Confirm the capsule walks 0 โ†’ 1 โ†’ 2 โ†’ 3 โ†’ 0, pausing briefly at each.
  4. Reorder the array and confirm the route changes accordingly.
๐Ÿ’ก Hint: it reaches WP0 then never moves again

Your arrival check is probably firing on frame one before the path is ready. Make sure ReachedDestination() returns false while agent.pathPending is true โ€” that's the guard that stops a phantom "arrived" at startup.

โœ… Success check

The capsule cleanly visits every waypoint in order, pauses ~1.5s at each, and loops indefinitely without jitter or stopping short.

๐Ÿ‹๏ธ Exercise 2: Block the path live

Put a cube in the middle of one patrol segment and give it a NavMeshObstacle with Carve on. Press Play and watch the agent re-route around it. Now toggle Carve off and observe the difference: the agent no longer paths around it and instead pushes against it. This is the carving-vs-avoidance distinction made visible.

๐ŸŽฏ Quick Quiz

Question 1: Why must you check pathPending before trusting remainingDistance?

Question 2: A door that opens and closes during play needs agents to re-route around it while shut. What do you add to the door?

Question 3: What makes the patrol in the Patrol script loop back to the first waypoint?

Summary

๐ŸŽ‰ Key Takeaways

  • agent.SetDestination(position) is the whole move โ€” it requests an async path and the agent drives its own Transform there.
  • Check arrival with all three: !pathPending, remainingDistance <= stoppingDistance, and no path / near-zero velocity.
  • A patrol is an array of waypoints plus an index that advances with (index + 1) % length to loop.
  • Useful members: isStopped (freeze/resume), pathStatus (Complete / Partial / Invalid), and Warp() for instant repositioning.
  • For things that move, add a NavMeshObstacle with carving โ€” never re-bake per frame. Bake static geometry; carve dynamic blockers.

๐Ÿš€ What's Next?

Right now the agent only ever patrols โ€” it can't react to anything. In Lesson 6.3: An Enemy State Machine you'll wrap this patrol in a code-driven FSM with Patrol, Chase, and Attack states, so the enemy switches behaviour based on where the player is, and feeds the Animator from Module 1 as it does.

๐Ÿšถ Your agent gets around now

Set a destination, know when it's reached, loop a patrol, re-route around movers. These four moves are the entire vocabulary of NavMesh movement โ€” the enemy AI you build next is just deciding which destination to set, and when.