| # Unity Navigation (NavMesh, Pathfinding, AI) |
|
|
| ## Overview |
|
|
| Unity's AI Navigation package (`com.unity.ai.navigation`) bakes a |
| **NavMesh** -- a simplified walkable surface -- and provides |
| `NavMeshAgent` to move characters along it. This is the standard solution |
| for AI NPCs in the generated project. |
|
|
| ## Baking a NavMesh |
|
|
| 1. Mark static colliders as **Navigation Static** (Window > AI > |
| Navigation > Object). |
| 2. Bake the NavMesh (Window > AI > Navigation > Bake). |
| 3. At runtime, every `NavMeshAgent` on the scene automatically walks on |
| the baked NavMesh. |
|
|
| > For a procedurally generated city, baking at design time is impossible. |
| > Either: |
| > * Bake at runtime via `NavMeshSurface.BuildNavMesh()` (AI Navigation |
| > package), or |
| > * Use the simpler approach in `NPCController.cs`: pick random points via |
| > `NavMesh.SamplePosition` so the agent always lands on a valid walkable |
| > spot. |
|
|
| ## NavMeshAgent |
|
|
| ```csharp |
| [RequireComponent(typeof(NavMeshAgent))] |
| public class NPCController : MonoBehaviour |
| { |
| private NavMeshAgent _agent; |
| |
| private void Awake() => _agent = GetComponent<NavMeshAgent>(); |
| |
| private void Start() |
| { |
| PickNewDestination(); |
| } |
| |
| private void PickNewDestination() |
| { |
| Vector3 random = transform.position + Random.insideUnitSphere * 20f; |
| if (NavMesh.SamplePosition(random, out NavMeshHit hit, 20f, NavMesh.AllAreas)) |
| _agent.SetDestination(hit.position); |
| } |
| } |
| ``` |
|
|
| Key properties: |
|
|
| | Property | Meaning | |
| |---------------------|------------------------------------------------------------| |
| | `speed` | Max movement speed (m/s) | |
| | `angularSpeed` | Max turning speed (deg/s) | |
| | `acceleration` | How quickly the agent reaches `speed` | |
| | `stoppingDistance` | Distance at which the agent considers the destination reached | |
| | `autoBraking` | Whether to slow down near the destination | |
| | `isStopped` | Pause the agent without clearing the path | |
| | `remainingDistance` | Distance to the end of the current path | |
| | `pathPending` | True while a new path is still being computed | |
|
|
| ## State machines for AI |
|
|
| A common pattern is a small finite state machine: |
|
|
| ``` |
| Idle --(timer)--> Wander --(player in range)--> Chase |
| ^ | | |
| | (lost sight) +---------------------------------+ |
| +--------------------+ |
| ``` |
|
|
| The generated `NPCController.cs` implements exactly this pattern with three |
| states (`Idle`, `Wander`, `Chase`) and switch-based dispatch in `Update()`. |
|
|
| ## Pathfinding details |
|
|
| * `NavMeshAgent.SetDestination(target)` is async -- the path may not be |
| ready on the same frame. Check `!agent.pathPending` before reading |
| `remainingDistance`. |
| * `agent.pathStatus` tells you whether a path is `PathComplete`, |
| `PathPartial` or `PathInvalid`. |
| * `NavMesh.CalculatePath(source, target, mask, out path)` lets you compute a |
| path without an agent (useful for "can the NPC reach this point?" checks). |
|
|
| ## NavMeshObstacle |
|
|
| For dynamic obstacles (a car parked across a road, a door that closes), add |
| a `NavMeshObstacle` component. Set `carve = true` to actually cut a hole in |
| the NavMesh; otherwise the obstacle only pushes agents around. |
|
|
| ## Off-mesh links |
|
|
| When the walkable surface is disconnected (e.g. a gap between rooftops), |
| drop an `OffMeshLink` to let agents jump or teleport across. You can also |
| use them for ladders, climbing walls, and jumping down from ledges. |
|
|
| ## Performance |
|
|
| * Keep the agent count under ~100 simultaneous active agents on desktop. |
| * For larger crowds use the **ECS/DOTS navigation** solutions or batch |
| pathfinding across frames. |
| * Set `agent.autoRepath = false` if you do not need the agent to follow |
| moving targets, and call `SetDestination` less often (e.g. every 0.5 s). |
|
|