NavMesh and AI — Pathfinding and a Simple AI Enemy
Navigation Mesh, NavMeshAgent, a basic state machine for an enemy.
A 3D level rarely consists of a flat floor. An enemy has to go around a table, descend the stairs, find
the short path to the player, and not fall off a cliff. Computing all of this by hand with Physics.Raycast
is a nightmare. Unity has NavMesh — a built-in navigation system that solves 90% of AI movement tasks
for free.
NavMesh — the “walkability map”
A NavMesh is baked, simplified floor geometry that agents know how to walk on. Unity scans your scene and assembles a polygonal mesh of all surfaces you can stand on. From this mesh it builds a graph for the pathfinding algorithm (A* under the hood).
In Unity 6 the recommended path is the AI Navigation package (a.k.a. com.unity.ai.navigation). The old
built-in NavMesh (via Window → Navigation in Unity ≤ 2021) is kept for compatibility, but
the new package gives you a component-based workflow: NavMeshSurface, NavMeshModifier, NavMeshLink.
Basic baking workflow
- Assemble the static floor and wall geometry.
- Add a NavMeshSurface component to an empty GameObject.
- In Agent Type select a profile (default “Humanoid”: radius 0.5, height 2, max slope 45°, step height 0.4).
- Click Bake. A blue semi-transparent mesh appears in the Scene view — that is the NavMesh.
__________
/ \ ← NavMesh covers the floor
/ Comfy \
| Room |
\ [agent]_/
\__________/
Walls, pillars, pits → "holes" in the NavMesh
In a new project use NavMeshSurface — it lets you have multiple meshes in a scene,
bake at runtime (for procedural levels), and doesn’t depend on a global scene setting. The old
Window → Navigation menu still works, but it’s the deprecated path.
NavMeshAgent — moving across the mesh
NavMeshAgent is a component that turns a GameObject into a “pedestrian” on the NavMesh:
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Chaser : MonoBehaviour
{
[SerializeField] private Transform target;
private NavMeshAgent _agent;
private void Awake() => _agent = GetComponent<NavMeshAgent>();
private void Update() {
if (target != null) {
_agent.SetDestination(target.position);
}
}
}
The main NavMeshAgent fields:
- Speed — speed in m/s.
- Angular Speed — turn speed in degrees/s.
- Acceleration — m/s².
- Stopping Distance — the distance from the target at which the agent stops. For melee attacks people set something like 1.5 m.
- Auto Braking — smoothly decelerates as it approaches the target.
- Obstacle Avoidance Type — priority in local avoidance when several agents meet.
NavMeshObstacle — dynamic obstacles
Doors, crates, fallen trees — dynamic obstacles. NavMeshObstacle has two modes:
- Without carve — agents go around the obstacle via avoidance, but the NavMesh doesn’t change. Cheap.
- With carve = true — the obstacle “cuts” a hole into the NavMesh and agents build a new path. More expensive, but correct for closed doors and large obstacles.
NavMeshLink — bridges and jumps
A standard NavMesh can’t handle “teleports”: a staircase with a gap, a jump across a chasm, an elevator.
NavMeshLink connects two locations — the agent crosses it like a regular edge of the graph.
A simple AI state machine
Let’s move from “walks toward a target” to a minimally alive enemy: it patrols, spots the player and chases, catches up and attacks.
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyAI : MonoBehaviour
{
private enum State { Patrol, Chase, Attack }
[SerializeField] private Transform[] patrolPoints;
[SerializeField] private Transform player;
[SerializeField] private float sightRange = 12f;
[SerializeField] private float attackRange = 2f;
[SerializeField] private float attackCooldown = 1.2f;
private NavMeshAgent _agent;
private State _state = State.Patrol;
private int _patrolIndex;
private float _nextAttackTime;
private void Awake() => _agent = GetComponent<NavMeshAgent>();
private void Start() {
if (patrolPoints.Length > 0) {
_agent.SetDestination(patrolPoints[0].position);
}
}
private void Update() {
float distToPlayer = Vector3.Distance(transform.position, player.position);
switch (_state) {
case State.Patrol:
if (distToPlayer < sightRange && HasLineOfSight()) {
_state = State.Chase;
} else if (!_agent.pathPending && _agent.remainingDistance < 0.5f) {
_patrolIndex = (_patrolIndex + 1) % patrolPoints.Length;
_agent.SetDestination(patrolPoints[_patrolIndex].position);
}
break;
case State.Chase:
_agent.SetDestination(player.position);
if (distToPlayer < attackRange) _state = State.Attack;
else if (distToPlayer > sightRange * 1.5f) _state = State.Patrol;
break;
case State.Attack:
_agent.SetDestination(transform.position); // stand still
if (Time.time >= _nextAttackTime) {
Attack();
_nextAttackTime = Time.time + attackCooldown;
}
if (distToPlayer > attackRange + 0.5f) _state = State.Chase;
break;
}
}
private bool HasLineOfSight() {
Vector3 dir = player.position - transform.position;
if (Physics.Raycast(transform.position + Vector3.up, dir.normalized, out RaycastHit hit, sightRange)) {
return hit.transform == player;
}
return false;
}
private void Attack() {
// Here — the attack animation and dealing damage through your Health
if (player.TryGetComponent<Health>(out var hp)) hp.TakeDamage(10);
}
}
Note: the transition back from Chase to Patrol uses sightRange * 1.5f, the one from Chase to
Attack uses attackRange, whereas the one from Attack to Chase uses attackRange + 0.5f. This is hysteresis:
the enter and exit thresholds don’t coincide, so the enemy doesn’t “jitter” between states right at the boundary.
A simple trick that noticeably improves how coherent the AI feels.
What to improve
- Behavior Trees — for AI more complex than 3 states. Ready-made packages — Behavior Designer, or the new built-in Unity Behavior (Unity 6 brought an official Behavior Trees system).
- Memory & senses — “see/hear/remember” pairs, where the enemy remembers the player’s last position for a few more seconds.
- Group coordination — several enemies share attack zones via
NavMeshAgent.avoidancePriority. - Cover system — finding cover points via NavMesh.SamplePosition and Raycast.
In the next chapter — Particle System and VFX Graph, so that the enemy bursts spectacularly into shards on death.