# Unity 6 NavMesh Agent Patrol and Chase Setup Guide
This guide provides **start-to-finish instructions** for setting up a NavMesh Agent in Unity 6 that patrols between waypoints (with optional random variation) and chases a tagged "Player" when within detection range. We'll use a trigger collider for range detection.
Assumes basic Unity knowledge (e.g., navigating the Editor). Tested in Unity 6.x.
## Prerequisites
- **Unity Version**: Unity 6 (2023.3 LTS or later recommended).
- **New Project**: Create a new 3D project (File > New Project > 3D Core).
- **Packages**: Ensure the AI Navigation package is installed (Window > Package Manager > Search "AI Navigation" > Install if missing).
- **Scene**: Use the default SampleScene or create a new one (File > New Scene).
## Step 1: Set Up the Environment
1. **Create Ground/Floor**:
- In Hierarchy, right-click > 3D Object > Plane. Scale it up (e.g., Scale: 10, 1, 10) to create a walkable area.
- Position it at (0, 0, 0) for the NavMesh surface.
- For the Player to walk around and not fall through the floor, add a BoxCollider with Center at (0, -0.5, 0)
2. **Add Obstacles (Optional)**:
- Add cubes or walls (3D Object > Cube) to create a simple maze. Position them to block paths, ensuring gaps for navigation.
## Step 2: Bake the NavMesh
1. **Add NavMesh Surface**:
- Select the Plane (or a parent Empty GameObject containing the level).
- Add Component > AI > NavMesh Surface.
2. **Configure and Bake**:
- In Inspector: Set Agent Type to Humanoid (or custom), Radius ~0.5, Height ~2.
- You might need to go to Agent Type and select Open Agent Settings... to change settings:
- ![[OpenAgentSettings.png]]
- Click **Bake** to generate the blue NavMesh overlay (visible in Scene view via Navigation window: Window > AI > Navigation > Show NavMesh).
- ![[NavMeshSurfaceBake.png]]
*Tip*: If baking fails, ensure all static geometry (e.g., Plane) has Static flags (Batching Static).
## Step 3: Set Up the Player
1. **Create Player GameObject**:
- Right-click Hierarchy > 3D Object > Capsule. Name it "Player".
- Position at (0, 1, 0). Scale if needed (e.g., 1, 2, 1).
2. **Add Components**:
- **Rigidbody**: Add Component > Physics > Rigidbody. Set to Kinematic if player-controlled via script.
- **Collider**: Capsule already has one—ensure **Is Trigger** is unchecked.
- **Tag**: In Inspector, set Tag to "Player" (add via Tag dropdown if missing).
3. **Basic Movement (Optional)**:
- For testing, attach a simple WASD script (create `PlayerController.cs`):
```csharp
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
private Rigidbody rb;
void Start() { rb = GetComponent<Rigidbody>(); }
void Update()
{
float moveX = Input.GetAxis("Horizontal");
float moveZ = Input.GetAxis("Vertical");
rb.velocity = new Vector3(moveX * speed, rb.velocity.y, moveZ * speed);
}
}
```
- Attach to Player and play to move around.
## Step 4: Create Waypoints
1. **Add Waypoint Markers**:
- Right-click Hierarchy > Create Empty (repeat 4-6 times). Name them "Waypoint1", "Waypoint2", etc.
- Position them on/above the NavMesh (e.g., (5, 0.5, 5), (-5, 0.5, 5), etc.). Use spheres for visibility: 3D Object > Sphere, scale small, delete Mesh Renderer/Collider if just markers.
2. **Visualize (Optional)**:
- To see waypoints in Scene view, add a Gizmo script to each:
```csharp
using UnityEngine;
public class WaypointGizmo : MonoBehaviour
{
void OnDrawGizmos() { Gizmos.color = Color.yellow; Gizmos.DrawSphere(transform.position, 0.5f); }
}
```
## Step 5: Set Up the NavMesh Agent
1. **Create Agent GameObject**:
- Right-click Hierarchy > 3D Object > Capsule. Name it "Agent".
- Position at (0, 1, 0) away from player.
2. **Add Components**:
- **NavMesh Agent**: Add Component > AI > NavMesh Agent.
- Inspector: Speed ~3.5, Angular Speed ~120, Stopping Distance ~1, Radius ~0.5, Height ~2. Match NavMesh Surface settings.
- **Collider for Trigger**: Add Component > Physics > Sphere Collider.
- Set **Is Trigger** = true.
- Radius = 10 (detection range—adjust as needed).
- **Rigidbody**: Add if needed (Freeze Rotation X/Z for upright movement).
## Step 6: Create and Attach the Script
1. **Create Script**:
- In Project window, right-click > Create > C# Script. Name "WaypointPatrol".
- Double-click to open in your editor (e.g., VS Code).
2. **Paste the Full Script**:
- Replace contents with the code below. This includes patrol (random/sequential), positional offsets, and chase logic.
```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class WaypointPatrol : MonoBehaviour
{
[Header("Waypoint Setup")]
public Transform[] waypoints; // Assign your waypoint Transforms here in the Inspector
public float stoppingDistance = 1f; // How close to get before switching waypoints
public float waitTimeAtWaypoint = 2f; // Seconds to pause at each waypoint
public bool useRandomWaypoints = true; // Toggle: true for random, false for sequential patrol
[Range(0f, 5f)] public float randomOffsetRadius = 0f; // Random offset around waypoint (0 = no variation)
[Header("Chase Setup")]
public string playerTag = "Player"; // Tag for the player GameObject
public float chaseStoppingDistance = 2f; // Closer stop distance when chasing
public float updateChaseInterval = 0.5f; // How often to update chase destination (for performance)
private NavMeshAgent agent;
private int currentWaypointIndex = 0;
private bool isReversing = false;
private bool isWaiting = false;
private HashSet<int> visitedInCycle = new HashSet<int>(); // For random mode
private enum AgentState { Patrol, Chase }
private AgentState currentState = AgentState.Patrol;
private Transform playerTransform;
private float chaseTimer;
void Start()
{
agent = GetComponent<NavMeshAgent>();
if (waypoints.Length > 0)
{
SetNextDestination();
}
else
{
Debug.LogWarning("No waypoints assigned to " + gameObject.name);
}
}
void Update()
{
if (waypoints.Length == 0 || agent == null) return;
switch (currentState)
{
case AgentState.Patrol:
// Patrol logic: Check if reached waypoint
if (!isWaiting && !agent.pathPending && agent.remainingDistance <= stoppingDistance)
{
StartCoroutine(WaitThenMove());
}
break;
case AgentState.Chase:
// Continuous chase: Update destination periodically to follow moving player
chaseTimer += Time.deltaTime;
if (chaseTimer >= updateChaseInterval)
{
if (playerTransform != null)
{
agent.SetDestination(playerTransform.position);
}
chaseTimer = 0f;
}
// Optional: Stop chasing if too close (prevents orbiting)
if (!agent.pathPending && agent.remainingDistance <= chaseStoppingDistance)
{
agent.SetDestination(transform.position); // Stop in place
}
break;
}
}
void SetNextDestination()
{
if (waypoints.Length == 0) return;
Vector3 targetPosition = waypoints[currentWaypointIndex].position;
// Apply random offset if enabled
if (randomOffsetRadius > 0f)
{
Vector2 randomCircle = Random.insideUnitCircle * randomOffsetRadius;
targetPosition += new Vector3(randomCircle.x, 0, randomCircle.y); // Flat offset
}
agent.SetDestination(targetPosition);
// Choose next waypoint based on mode
if (!useRandomWaypoints)
{
// Sequential patrol
if (!isReversing)
{
currentWaypointIndex++;
if (currentWaypointIndex >= waypoints.Length)
{
currentWaypointIndex = waypoints.Length - 2;
isReversing = true;
}
}
else
{
currentWaypointIndex--;
if (currentWaypointIndex < 0)
{
currentWaypointIndex = 1;
isReversing = false;
}
}
}
else
{
// Random mode
List<int> availableIndices = new List<int>();
for (int i = 0; i < waypoints.Length; i++)
{
if (i != currentWaypointIndex)
{
availableIndices.Add(i);
}
}
if (availableIndices.Count > 0)
{
currentWaypointIndex = availableIndices[Random.Range(0, availableIndices.Count)];
}
}
}
IEnumerator WaitThenMove()
{
isWaiting = true;
agent.isStopped = true;
yield return new WaitForSeconds(waitTimeAtWaypoint);
agent.isStopped = false;
SetNextDestination();
isWaiting = false;
}
// Trigger events for player detection
void OnTriggerEnter(Collider other)
{
if (other.CompareTag(playerTag))
{
playerTransform = other.transform;
currentState = AgentState.Chase;
chaseTimer = updateChaseInterval; // Immediate first update
agent.stoppingDistance = chaseStoppingDistance; // Adjust stopping distance for chase
Debug.Log(gameObject.name + " starting to chase player!");
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag(playerTag))
{
currentState = AgentState.Patrol;
agent.stoppingDistance = stoppingDistance; // Revert stopping distance
playerTransform = null;
// Resume patrol from current index (or call SetNextDestination() if you want to move immediately)
Debug.Log(gameObject.name + " resuming patrol!");
}
}
}
```
3. **Save and Attach**:
- Save the script (Ctrl+S).
- Drag it onto the "Agent" GameObject in Hierarchy.
## Step 7: Configure the Inspector
1. **Select Agent**:
- In Inspector, expand **Waypoint Patrol (Script)**.
2. **Waypoint Setup**:
- **Waypoints**: Set Size to your waypoint count (e.g., 4). Drag each Waypoint Transform from Hierarchy into the slots.
- **Stopping Distance**: 1f (default).
- **Wait Time At Waypoint**: 2f (pause duration).
- **Use Random Waypoints**: Check for random jumps; uncheck for sequential patrol.
- **Random Offset Radius**: 0f (or >0 for jitter around points).
3. **Chase Setup**:
- **Player Tag**: "Player" (default).
- **Chase Stopping Distance**: 2f (how close before stopping).
- **Update Chase Interval**: 0.5f (balance smoothness/performance).
## Step 8: Test and Debug
1. **Play the Scene**:
- Click Play. Agent should patrol waypoints (wait, move, random/sequential).
- Move Player (WASD if scripted) into the agent's trigger sphere: Agent chases.
- Exit range: Agent resumes patrol.
2. **Debug Tips**:
- **Console Logs**: Watch for "chase" / "patrol" messages.
- **NavMesh Visualization**: Window > AI > Navigation > Toggle Show NavMesh/Links.
- **Gizmos**: Enable in Scene view (Gizmos button) to see waypoints.
- **Stuck Agent?**: Check NavMesh coverage (rebake), collider overlaps, or stopping distances.
- **No Movement?**: Ensure Agent's Auto Braking is off; velocity >0.
3. **Enhancements**:
- **Animations**: Use `agent.velocity.magnitude` to drive Animator speed.
- **Multiple Agents**: Duplicate Agent, assign same waypoints.
- **Line of Sight**: Add Raycast in Update() for smarter chasing.
## Troubleshooting
- **Trigger Not Firing**: Ensure layers match (Default) and player has non-trigger collider.
- **Path Errors**: Waypoints off NavMesh? Move them or use NavMesh.SamplePosition in script.
- **Unity 6 Changes**: NavMesh is stable, but update AI package for latest features.
This setup is modular—expand as needed (e.g., add attack on reach). If issues, share error logs!