【Unity】Unity AI Navigation: Building Smart Enemy Characters with NavMesh

Created: 2025-12-07Last updated: 2026-07-13

Your enemy walks straight at the player and gets stuck on a wall—the fix is NavMesh (pathfinding). From baking with NavMeshSurface to configuring the NavMesh Agent, chase AI with SetDestination, and the practical patrol-detect-chase-search pattern, this guide covers how to build smart enemies.

The first implementation that sends an enemy after the player is usually code that moves transform.position straight toward them. And the first bug arrives just as fast— the enemy gets stuck on a wall, staring at you from the other side.

Finding the shortest route to a destination while avoiding walls and obstacles on its own—this is pathfinding, and Unity's standard feature for it is NavMesh (navigation mesh). This article walks through everything from baking a NavMesh to chase AI and the practical patrol-detect-chase-search pattern.

Conceptual image of NavMesh. A blue mesh covering the floor of a white miniature room shows the walkable area, and a blue figure follows a dotted path around obstacles toward the player figure

What You'll Learn

  • The three players of the NavMesh system: NavMesh / NavMesh Agent / NavMesh Obstacle
  • Baking with NavMeshSurface (the current Unity 6 workflow)
  • Chase AI that works with just SetDestination()
  • Patrol, detect, chase, search—assembling an enemy AI that doesn't give up the moment it loses you
  • Arrival checks with remainingDistance, and the three gauges to read when nothing moves

Sponsored

The NavMesh system has three cast members. The quickest way to remember them is "the map," "the traveler," and "the roadblock."

Diagram of the three NavMesh elements. The NavMesh is drawn as a blue map spread across the floor, the NavMesh Agent as a traveler figure walking along a path, and the NavMesh Obstacle as a barrier blocking the way and cutting a hole in the map
  1. NavMesh (the map): A polygonal mesh marking the areas the AI can walk on. It is generated (baked) automatically from your scene geometry.
  2. NavMesh Agent (the traveler): The component that gives a character the ability to move on the NavMesh. Hand it a destination and it handles path calculation, movement, and even collision avoidance with other agents.
  3. NavMesh Obstacle (the roadblock): A component for dynamic obstacles. Attach it to things like closed doors or fallen pillars, and agents will route around them.

Step 1: Bake the NavMesh (NavMeshSurface)

In current Unity versions (2022.3 and later / Unity 6), the standard workflow uses the NavMeshSurface component from the AI Navigation package.

tips: The steps you'll find in older tutorials—"open Window > AI > Navigation, mark objects as Navigation Static, and Bake"—are the legacy workflow. The concepts are the same, but for new projects, use NavMeshSurface.

  1. Check the package: In Window > Package Manager, confirm that AI Navigation (com.unity.ai.navigation) is installed (in Unity 6 it usually comes preinstalled).
  2. Add NavMeshSurface: Add a NavMeshSurface component to your ground object (or an empty GameObject).
  3. Choose what to bake: Use Collect Objects to select the scope (such as All Game Objects). Walls and obstacles are collected as geometry too, and get reflected as "impassable areas."
  4. Run the bake: Click the Bake button in the Inspector, and blue polygons appear in the Scene view. This is where your AI can walk.
Before-and-after comparison of a NavMesh bake. Before baking it is plain white terrain; after baking a translucent blue mesh covers the entire floor, with holes only around walls and obstacles

Bake Settings Are the Agent's "Body Type"

What the bake settings really describe is "what body type of character this map is built for."

  • Agent Radius: The agent's radius. Gaps narrower than this become impassable.
  • Agent Height: The agent's height. Areas under ceilings lower than this become impassable.
  • Max Slope: The steepest slope the agent can climb.
  • Step Height: The tallest step the agent can climb over.

When "the enemy won't go through a narrow corridor," the culprit is usually an Agent Radius that is too large for the corridor width.

Sponsored

Step 2: Configure the NavMesh Agent

Add Add Component > Navigation > NavMesh Agent to your AI character, then shape its movement personality in the Inspector.

PropertyRoleGuideline
SpeedMaximum movement speedAround 2 for a strolling villager, 4-6 for a chasing enemy
Angular SpeedTurning speedHigher for faster enemies (so they don't stall at corners)
AccelerationAccelerationLower values make starts and direction changes more sluggish
Stopping DistanceHow far before the destination to stop0 causes jitter. Use 0.5+, or the attack range for attacking AI
Radius / HeightThis individual agent's sizeAffects collision avoidance between agents

Step 3: Control the Agent from a Script

Issuing a move order takes one line. Pass a destination to SetDestination(), and the agent takes care of pathfinding, movement, and avoidance.

using UnityEngine;
using UnityEngine.AI; // Required for NavMesh-related classes

[RequireComponent(typeof(NavMeshAgent))]
public class EnemyChase : MonoBehaviour
{
    private NavMeshAgent agent;
    private Transform playerTarget;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();

        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player != null)
        {
            playerTarget = player.transform;
        }
    }

    void Update()
    {
        if (playerTarget != null)
        {
            // Keep setting the player's current position as the destination
            agent.SetDestination(playerTarget.position);
        }
    }
}

That's all it takes—the enemy starts chasing the player while avoiding obstacles. The enemy that used to get stuck on walls now swings around corners as if cutting you off.

tips: To sync with a walking animation, the standard approach is to feed agent.velocity.magnitude into the Animator's Speed parameter (see the Animator Controller article).

In Practice: Building a Patrol-Detect-Chase-Search Enemy AI

Enemies in real games don't "chase forever." They patrol by default, chase when they spot the player, and attack when close—a set of switching states. Guards in stealth games, field monsters in RPGs, invading units in tower defense—this skeleton is shared across genres.

State diagram of patrol, spotted, and chase. A single diorama shows a blue guard figure patrolling a dotted loop between flags, spotting the hiding player who enters its vision cone, then switching to a chase—three stages in one image

An enemy that snaps straight back to patrol the instant it loses sight of you feels a little fake. So we'll go one step further: walk to the last place the player was seen, linger there a few seconds, then return — a search behavior that makes the guard feel human.

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class GuardAI : MonoBehaviour
{
    [SerializeField] private Transform[] patrolPoints; // Patrol waypoints
    [SerializeField] private float sightRange = 8f;    // Detection distance
    [SerializeField] private float attackRange = 1.5f; // Attack distance
    [SerializeField] private float searchTime = 3f;    // Seconds to linger where the player vanished

    private enum Mode { Patrol, Chase, Search }
    private Mode mode = Mode.Patrol;

    private NavMeshAgent agent;
    private Transform player;
    private int patrolIndex = 0;
    private Vector3 lastKnownPosition;
    private float searchTimer;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();

        // Don't crash when no Player is placed (tag search can return null)
        GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
        if (playerObj != null) player = playerObj.transform;

        if (patrolPoints.Length > 0)
            agent.SetDestination(patrolPoints[patrolIndex].position);
    }

    void Update()
    {
        if (player == null || patrolPoints.Length == 0) return;

        bool canSee = Vector3.Distance(transform.position, player.position) <= sightRange;
        bool arrived = !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance;

        switch (mode)
        {
            case Mode.Patrol:
                if (canSee) { mode = Mode.Chase; break; }
                if (arrived)
                {
                    patrolIndex = (patrolIndex + 1) % patrolPoints.Length;
                    agent.SetDestination(patrolPoints[patrolIndex].position);
                }
                break;

            case Mode.Chase:
                if (canSee)
                {
                    // Keep updating "last known position" while we can still see them
                    lastKnownPosition = player.position;
                    agent.stoppingDistance = attackRange;
                    agent.SetDestination(player.position);
                }
                else
                {
                    // Lost them -> head to the last known position and start searching
                    agent.stoppingDistance = 0.5f;
                    agent.SetDestination(lastKnownPosition);
                    searchTimer = searchTime;
                    mode = Mode.Search;
                }
                break;

            case Mode.Search:
                if (canSee) { mode = Mode.Chase; break; }
                if (arrived) searchTimer -= Time.deltaTime;  // Count down only after reaching the spot
                if (searchTimer <= 0f)
                {
                    agent.SetDestination(patrolPoints[patrolIndex].position);
                    mode = Mode.Patrol;
                }
                break;
        }
    }
}

Three points matter here.

  • Check arrival with remainingDistance: !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance is the standard "we've arrived" idiom. Check pathPending so you don't evaluate before the path calculation finishes.
  • Build the attack range with stoppingDistance: Set the chase-time Stopping Distance to your attack distance, and "stop at sword's reach instead of hugging the player, then attack" falls out naturally.
  • Update lastKnownPosition the whole time the player is visible: whatever it held at the moment of losing sight becomes the search destination. From the player's perspective, the guard "comes to check where you just were" — a move that instantly reads as intelligence.

Press Play and duck behind a wall mid-chase. The guard walks to where you last stood, lingers about three seconds, and — finding nothing — drifts back to its rounds. If you want detection based on vision (a forward-facing cone) rather than distance alone, combine an angle check with occlusion checks using Raycast (the finished version lives in the enemy vision article). And once the states start piling up, it's time for the State pattern article.

When a NavMesh AI won't move, there are three gauges to read before suspecting your code.

SymptomGaugeCommon culprit and fix
Won't take a single stepagent.isOnNavMeshIf false, the Agent is off the NavMesh (spawned in midair, bake didn't cover the spot). Put it back on the mesh with agent.Warp(position)
Stops short of the goal / patrol stallsagent.pathStatusPathPartial (can only get partway) or PathInvalid (unreachable). A partial path to an isolated waypoint keeps remainingDistance from shrinking, so the arrival check never fires. Move the waypoint back onto the NavMesh
Feels janky and heavyHow often you call SetDestinationPath recalculations are piling up. Every frame is fine for chasing a moving player, but a fixed patrol point needs it only once, on arrival

Bonus: Good to Know for Later

  • NavMesh Obstacle's Carve: Attach an Obstacle to a dynamic object and enable Carve to cut a temporary hole in the NavMesh, prompting agents to recalculate a detour. Great for closing doors and destructible fences.
  • Ledges and jumps use NavMesh Link: Connections across non-contiguous ground—dropping off a cliff, hopping over a ditch—are defined with NavMesh Link.
  • Agents with different body types: When a "human-sized" agent and a "giant boss" can fit through different gaps, define multiple Agent Types and bake a NavMeshSurface for each.
  • When you need to move an agent directly: For physics-driven movement like knockback, use agent.Warp() or agent.isStopped. Moving the Transform or Rigidbody directly desyncs the agent from its position on the NavMesh and causes erratic behavior.

Summary

With NavMesh, you can build "smart-moving enemies" without writing a pathfinding algorithm yourself.

  • The three players: NavMesh (the map) + NavMesh Agent (the traveler) + NavMesh Obstacle (the roadblock).
  • Bake with NavMeshSurface (the current workflow). Bake settings are the agent's "body type."
  • Move orders are one line: SetDestination(). Avoidance and pathing are automatic.
  • Never set Stopping Distance to 0. It also doubles as an attack range.
  • Real enemy AI is "patrol, detect, chase, search". Check arrival with the remainingDistance idiom; when nothing moves, read the isOnNavMesh and pathStatus gauges.

Beyond enemy AI, it powers ally NPCs following you, townsfolk wandering the streets, RTS unit movement—any situation where "a character walks on its own."