Unity Term Book
进阶技术

NavMesh

NavMesh(Navigation Mesh)是从场景几何烘培出的可走网格,让 Unity AI Agent 能寻路、避障并在关卡中自动移动。配合 NavMeshAgent 与 NavMeshObstacle,适合敌人巡逻、NPC 跟随与塔防路径,帮助关卡设计与 AI 程序快速落地稳定可靠的导航系统。

想象一下...

NavMesh 是 3D 世界的路网地图。开玩前,Unity「画出」可走区域(蓝色)与阻挡(墙、障碍)。AI 要从 A 到 B 时,查阅该图并用 A-star 求最优路径——绕墙、上楼梯、远离悬崖。NavMeshAgent 就是在这张图上开的 GPS 司机。

概念详解

NavMesh Baking:Unity 分析静态场景几何 (标记为 “Navigation Static”)生成可走网格。关键参数:Agent Radius (与墙的最小间隙——缝隙小于 2×radius 时 agent 挤不过)、 Agent Height(头顶高度——低天花板下不能钻)、Max Slope (最陡爬坡)、Step Height(最大台阶)。烘培:Window → AI → Navigation → Bake。

NavMeshAgent 挂在 NPC/AI 上并查询 NavMesh。主要 API: agent.SetDestination(target) 开始寻路; agent.remainingDistance 看剩余距离; agent.pathStatus (Complete/Partial/Invalid)。speedaccelerationstoppingDistance 控制运动。

NavMeshObstacle 是动态阻挡——无需重新烘培。 开启 Carve 可在运行时切出 NavMesh 空洞(更耗 CPU);关闭 Carve 则仅做避让 (agent 绕行,网格不变)。适合开门与移动载具。

动态 NavMesh(运行时重烘)请用 NavMesh Components package (GitHub 上 Unity-Technologies)配合 NavMeshSurface ——烘培区域并增量更新。Unity 6 把多表面 NavMesh 收进内置包。

NavMesh 系统组成

🗺️

NavMesh

从场景几何 bake 出的“可行走”网格。静态,bake 后不再改变。

Navigation Static flag
🤖

NavMeshAgent

挂在 AI 上的组件 — 查询路径、自动移动、避开其他 agent(avoidance)。

SetDestination(target)
🚧

NavMeshObstacle

动态障碍 — 无需重新 bake。Carve 模式会在运行时在 NavMesh 上挖洞。

Carve = true/false

动手步骤

1

标记静态几何

选中静态网格(墙、地板、平台)→ Inspector → Static 下拉 → Navigation Static(需要光照贴图再勾 Contribute GI)。

2

烘培 NavMesh

Window → AI → Navigation → Bake 选项卡 → 调整 Agent Radius/Height/Slope → Bake。Scene 视图出现蓝色 NavMesh。

3

给 NPC 添加 NavMeshAgent

Add Component → NavMeshAgent。Agent Size 与烘培设置一致。若有 Rigidbody,关闭重力(由 agent 负责移动)。

4

用脚本设置 Destination

agent.SetDestination(target.position)。用 agent.remainingDistance <= agent.stoppingDistance 判断是否到达。

5

为移动阻挡添加 NavMeshObstacle

Add Component → NavMeshObstacle → Shape:Capsule/Box → 需要实时更新 NavMesh 时开启 Carve(门、停泊车辆)。

交互模拟器

点击地图设置目的地——Agent 用 A* 在 NavMesh 上寻路。

点击设置目标
NavMesh Agent Obstacle Path

代码示例

基础

简单 NavMeshAgent AI——追逐玩家并在停止距离处停下。

using UnityEngine;
using UnityEngine.AI;

public class EnemyAI : MonoBehaviour
{
  public Transform player;
  public float chaseRange   = 15f;
  public float attackRange  = 2f;
  public float updateRate   = 0.2f; // Recompute path every 0.2s

  NavMeshAgent agent;
  Animator anim;
  float nextUpdate;

  void Awake()
  {
      agent = GetComponent<NavMeshAgent>();
      anim  = GetComponent<Animator>();
  }

  void Update()
  {
      float dist = Vector3.Distance(transform.position, player.position);

      if (dist < chaseRange && Time.time >= nextUpdate)
      {
          nextUpdate = Time.time + updateRate;
          agent.SetDestination(player.position);
      }

      // Drive Animator from agent speed
      anim.SetFloat("Speed", agent.velocity.magnitude);

      if (dist < attackRange)
      {
          agent.ResetPath(); // Stop moving
          anim.SetTrigger("Attack");
      }
  }
}

代码示例

进阶

NavMesh 上的巡逻 AI——路点循环加玩家侦测。

using UnityEngine;
using UnityEngine.AI;

public class PatrolAI : MonoBehaviour
{
  public Transform[] waypoints;
  public float detectRange = 8f;
  public Transform player;

  NavMeshAgent agent;
  int current = 0;
  enum State { Patrol, Chase, Investigate }
  State state = State.Patrol;

  void Awake() => agent = GetComponent<NavMeshAgent>();

  void Start() => GotoNextWaypoint();

  void Update()
  {
      float dist = Vector3.Distance(transform.position, player.position);

      switch (state)
      {
          case State.Patrol:
              if (dist < detectRange) { state = State.Chase; break; }
              if (!agent.pathPending && agent.remainingDistance < 0.5f)
                  GotoNextWaypoint();
              break;

          case State.Chase:
              agent.SetDestination(player.position);
              if (dist > detectRange * 1.5f)
                  state = State.Investigate;
              break;

          case State.Investigate:
              if (!agent.pathPending && agent.remainingDistance < 0.5f)
              { state = State.Patrol; GotoNextWaypoint(); }
              break;
      }
  }

  void GotoNextWaypoint()
  {
      if (waypoints.Length == 0) return;
      agent.SetDestination(waypoints[current].position);
      current = (current + 1) % waypoints.Length;
  }
}

📌 快速记忆

  • NavMesh = 可走地图,从静态几何烘培
  • 每帧 SetDestination() 很贵——用协程节流
  • remainingDistance:剩余路径长度
  • NavMeshObstacle + Carve = 运行时挖洞
  • 使用 NavMeshAgent 时关闭 Rigidbody.gravity

⚠️ 常见错误

  • ❌ SetDestination 正确但 Agent 不动

    目标点不在 NavMesh 上,或从未烘培

    ✅ 用 NavMesh.SamplePosition() 找网格上最近点

  • ❌ Agent 穿墙或卡住

    NavMeshAgent 的 Agent Radius 小于烘培半径

    ✅ 让 NavMeshAgent Agent Radius 与烘培 Agent Radius 一致