# 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!