想象你第一次走进一座超大型商场:头顶挂着“前方电梯”的指示牌,但真正让你不迷路的,是脚下那一格格颜色略深的地砖——它们悄悄标记了所有可行走的区域,而货架、柜台、花坛的位置则被“画”成了不可跨越的禁区。商场管理员早在开门营业前,就拿着建筑蓝图把整层楼摸了个遍,把“哪块地砖能踩、哪块不能踩”整理成一张总图纸。之后每一位顾客(或者是会自己送货的机器人),只需要回答三个问题:我在哪、要去哪、下一脚踩哪块砖?这就是 Unity AI Navigation 的整套逻辑:它把一个复杂的三维世界,变成一张“踩不踩得上去”的图纸,再让角色像一个熟门熟路的老顾客一样,沿着地砖走出流畅的路线。 在 Unity 中,这张图纸叫 NavMesh(导航网格)。它不是实时扫描的,而是事先“烘焙”出来的多边形拼图,配合 Agent、Obstacle、Off-Mesh Link 等组件,构成了游戏世界中一切智能体移动的基础设施。无论你将来做开放世界 NPC、VR 虚拟导览,还是数字孪生工厂里的巡检机器人,理解这套“地砖系统”,就等于握住了让角色自己认路、绕路、抄近路的万能钥匙。
对 AI 从业者而言,NavMesh 是连接“算法世界”与“可交互 3D 场景”的桥梁。掌握它,你就能在虚拟仿真、机器人路径规划、游戏 AI 等方向,把抽象的寻路算法快速落地成用户看得见、摸得着的智能行为。
目录
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
地砖图纸:NavMesh 的烘焙原理
商场开业前,总经理不会让每位顾客自己拿尺子去量货架的缝隙。他会请来一位“地砖规划师”,拿着建筑蓝图把整层楼走一遍:哪块地砖平整能踩,哪片货架区必须避开,哪段台阶高得让人望而却步。规划师把这些信息画成一张“可踩地砖总图”,贴在每个入口处。之后,任何顾客都不必再思考“这里能不能走”,只需对照总图找路。Unity 里这张总图,就是 NavMesh(导航网格)——把世界地图提前扫描、计算、归档成一张多边形拼图。
不过,画这张图没那么简单,因为角色是有“体积”的。一个贴墙走的壮汉和一个小孩子,需要的过道宽度完全不同。所以 Unity 在烘焙(Baking)时,会先把所有障碍物的边缘向外“膨胀”一个 Agent Radius(代理半径),相当于在地砖图上给每堵墙圈出一圈“安全警戒线”。半径越大,能走的路就越窄,这就是为什么同一个场景,换一个更胖的角色就要重新烘焙。
在 Unity 中执行烘焙时,引擎会把标记为 Navigation Static 的物体视为障碍物,把地面、斜坡、台阶按照 Max Slope(最大坡度)和 Step Height(最大台阶高度)筛选出真正的“可踩地砖”:
烘焙完成之后,场景视图里会出现一层半透明网格,这就是商场门口贴出的“总图”。请注意,烘焙是离线预计算:营业期间货架不动,地砖图就不用重画;一旦货架挪了位,就得重新请规划师进场测绘一次。
- 表面集合 S表示场景中所有被当成“地面”的几何表面,也就是商场里所有铺了地砖的楼层表面。
- 距离函数 d(p,O)计算表面上的某个点 p 到最近障碍物 O 的直线距离,好比顾客站在这块地砖中心,量一量离货架还有多远。
- 代理半径 r_agent角色身体占用的空间近似半径(通常取胶囊体宽度的一半),对应商场比喻里“顾客肩膀宽度”。
- 严格大于号 >距离必须严格大于半径,否则角色走到那里会与障碍物发生擦碰,Unity 会直接放弃这块地砖。
- 可行走集合 A把所有满足条件的地砖点收集起来,最终拼成一张绿色导航网格,这正是我们可视化演示里那一格格的“绿色地砖”。
智能顾客:NavMesh Agent 组件
图纸贴好了,可顾客不会读图怎么办?没关系,商场给每位“特殊顾客”发了一个会自动认路的智能小推车——它内置了传感器、转向轮和刹车,你只要告诉它“去三楼收银台”,剩下的全自动。在 Unity 中,这个小推车就是 NavMesh Agent 组件。把一个角色挂上 Agent,它便拥有了“看懂地砖图”的能力:无论你把它丢到绿色网格的哪个角落,它都能沿着网格移动,绝不会走到灰色货架上去。
使用方式极其简单:先给角色 GameObject 添加 NavMeshAgent 组件,然后在代码里调用 SetDestination(Vector3),Agent 就会自动计算路径并移动。真正让“推车”手感像真人的,是它的运动属性:Speed 决定最快步行速度,Acceleration 决定起步和刹车的平顺感,Angular Speed 决定转弯是否灵活。你不需要自己写逐帧位移逻辑,引擎已帮你封装好。
Agent 的移动不是一个瞬移过程,而是一个速度随时间平滑变化的物理过程。每帧它先按加速度提速,再被最大速度限制住:
有了这套机制,角色从静止到匀速再到刹停,动作就自然得像真人逛街。你还会注意到 Agent 有一个 Stopping Distance(停止距离)参数:它代表当推车距离目标点小于这个值时开始减速停车,避免了“冲到目标脚下再来个急刹车”的尴尬。
- 旧速度 v_old上一帧结束时角色当前的移动速度,也就是推车此刻已经跑多快。
- 加速度 a角色每秒钟能增加的速度量,数值越大起步越迅猛,好比推车电机的功率。
- 帧间隔 Δt两帧之间的时间差,Unity 中通常取
Time.deltaTime,用于把加速度换算成这一帧的速度增量。 - 速度上限 v_maxSpeed 属性设定的最大移动速度,任何情况下都不能超越,相当于商场的限速线。
- 取最小值 min从“加速后的速度”和“最高限速”中选小者,确保推车只会在安全范围内越跑越快。
找路大脑:A* 与路径多边形
总图有了,车也发动了,可“怎么走”才是真正的智慧。想象你在商场里要去一家网红奶茶店,你心里其实在快速盘算:已经走过的路(不能回头),加上估计还要走多远,两条加起来最小的路线就是最优路线。A*(A-Star)算法干的正是这件事——它是 Unity 寻路的“大脑”,在 NavMesh 的多边形网格上寻找最短路径。
NavMesh 把地砖图抽象成一个个相邻的多边形,角色从一个多边形中心走到另一个多边形中心的代价是已知的,A* 在这个“多边形地图”上展开搜索。它维护一个优先级队列,每次取出“当前总代价最小”的节点向外探索,直到抵达目标。这里的核心是估价函数:
A* 搜索完成后,引擎会得到一条经过多个多边形走廊的路径,再经过拉直和拐角平滑处理,形成 Agent 实际跟随的折线轨迹。到达判定也很简单:当 Agent 的 remainingDistance(剩余距离)小于等于 stoppingDistance 时,它就知道“到了”,自动刹车停止。整个过程中,玩家甚至不需要知道算法细节,就像顾客不需要懂地图学也能逛完整个商场。
- 当前节点 n正在被考察的地砖多边形,可理解为推车当前停在哪个导航区域。
- 已走代价 g(n)从起点走到当前节点已经消耗的路径长度,这是确定值,每走一步都会累加。
- 启发代价 h(n)从当前节点到终点的“直线估计距离”,通常用欧氏距离,它未必精确但绝不能高估。
- 总估价 f(n)两者相加,作为优先级排序的依据,A* 每轮都从开放列表中挑 f 最小的节点扩展。
灵活绕行:动态障碍与避障
商场难免有意外:保洁阿姨推着清洁车堵住了过道,或者一辆补货小车临时停在电梯口。如果每次移动一个物体都要重新烘焙整张总图,那商场管理员得累死。所以,静态货架和动态障碍物必须分开处理。烘焙完成的 NavMesh 只对“永远不动”的物体负责;而那些会临时出现、移动的物体,Unity 提供了 NavMesh Obstacle 组件。
当一个动态物体挂上 Obstacle 组件并开启 Carve(雕刻)选项后,它就像一把“地砖切割刀”——运行时在导航网格上实时挖掉一块区域,迫使 Agent 绕行。等这个物体移开,被挖掉的地砖又会自动补回来,完全不需要重新烘焙。这正是我们在可视化里看到的情景:灰色货架挡住了路,蓝色主角沿着绿色地砖绕了一个弧线继续前进。
除了躲避静态挖洞,多个 Agent 之间还会互相让路,这叫局部避障(Local Avoidance)。每个 Agent 根据自己和邻居的速度、方向,计算一个不会相撞的安全速度:
需要注意的是,Carve 的实时计算有一定开销,所以不要把成百上千个 Obstacle 都开启 Carve。实际项目中,通常只对少数几个关键可移动物体开启雕刻,其余的交给物理碰撞或局部避障解决,这样商场的“地砖维护系统”才能既灵活又高效。
- 基础网格 A_base最初烘焙生成的完整可行走区域,也就是商场原始的绿色地砖总图。
- 雕刻区域 A_carve(t)在 t 时刻,所有激活了 Carve 的动态障碍物在网格上占据的区域,类似清洁车此刻挡住的那几块地砖。
- 集合差运算符 \setminus表示从基础网格中减去雕刻区域,得到此刻真正能走的路。
- 动态可走区域 A_walk(t)最终结果,它会随时间变化,Agent 每帧读取它调整路径,实现“现走现算”的灵活绕行。
空中桥梁:Off-Mesh Link 跳跃
商场里并不是所有区域都连着同一层地砖:二楼到三楼要坐扶梯,室内和室外天台之间还有一座天桥。对 NavMesh 来说,这些“断头路”天生无法连通,系统不可能自动知道扶梯在哪里。这时就需要人工搭建一座“空中桥梁”——Off-Mesh Link 组件。
Off-Mesh Link 的工作方式很直观:在场景中放置两个空物体,一个放在桥梁起点,一个放在终点,然后把它俩拖进 Off-Mesh Link 的 Start 和 End 字段。烘焙时,Unity 会在这两个点之间生成一条隐形的“传送走廊”。当 Agent 走到起点附近,它会自动被“吸”进这条走廊,从起点平滑移动到终点——就像顾客踏上自动扶梯,不用自己爬楼梯就完成了楼层切换。
Agent 跨越 Link 时的移动是一个标准的插值过渡:
默认情况下 Off-Mesh Link 是双向的:顾客既能从起点到终点,也能从终点返回起点。如果你只想让它支持单向跳跃,取消勾选 Bidirectional 即可。值得注意的是,Link 只负责“位置转移”,跳跃动画、音效还得自己写触发器来处理——桥梁搭好了,怎么走过去仍是角色自己的事。
- 起点坐标 P_startOff-Mesh Link 的 Start 物体在世界空间的位置,即扶梯的入口地砖处。
- 终点坐标 P_endLink 的 End 物体的位置,即扶梯出口对接的那块地砖。
- 位移向量 P_end-P_start从入口指向出口的方向和距离,告诉 Agent 要往哪个方向、移动多远。
- 插值进度 t0 到 1 之间的时间比例,由 Link 的移动耗时换算而来,t=0.5 表示已经走完一半扶梯。
- 当前坐标 P(t)t 时刻 Agent 所在的位置,当 t 从 0 增长到 1,角色就从入口平滑抵达出口。
多层商场:分层与性能优化
真正的大商场绝不只有一层,楼层之间通过扶梯相连。如果规划师把整栋楼画成一张巨大的总图,不仅烘焙耗时极长,而且只要三楼挪了一个货架,整栋楼的图纸都要重画。聪明的做法是按楼层分别烘焙,再通过 Off-Mesh Link 连接——这就是 Unity 中的分层导航(Area/Layer)思想。
NavMesh 的 Area 可以让不同区域拥有不同“通行代价”:例如“走廊”区域代价低,“泥地”区域代价高,寻路时自动优先走造价低的道路。而 NavMesh Surface 组件则把烘焙从“整场景一次性”变成“按需局部生成”。你可以给每一块室内地板挂一个 Surface,运行时只更新角色附近的小块网格,就像商场哪块地砖松了就只修哪块,不必砸掉整层楼的地面。
多块区域组合起来,就是整个商场的完整路网:
这种分层设计带来了立竿见影的渲染与计算效率提升:大世界不再需要一次性烘焙全部地形,而是随玩家移动流式加载;修改局部场景时,只需重建受影响的那一小块 Surface,其他区域的 Agent 路径完全不受干扰。在优化实践中,还可以通过减少动态 Carve 数量、控制 Agent 数量、使用多线程寻路等手段,让商场里上千名“顾客”同时逛店也不卡顿。
- 楼层网格 S_i第 i 层地砖(即某一块 NavMesh Surface 生成的局部网格),只覆盖商场的一部分。
- 并集运算符 ∪把所有局部地砖拼在一起,允许相邻边界无缝衔接,形成连贯的寻路空间。
- 楼层数量 n场景中划分的 Surface 或 Area 总数,分得越细,更新单个区域的开销就越小。
- 总可行走集合 S_walkable全商场最终可寻路的完整网格,Unity 在此之上执行全局 A* 路径搜索。
AI 应用场景
开放世界游戏 NPC 智能导航
阶段:导航网格生成与动态寻路
解决的问题:开放世界地图面积大、房屋树木多,传统手工 Waypoint(路径点)既繁琐又容易因场景微调而失效。NavMesh 自动烘焙出可行走区域,NPC 在追逐玩家或执行巡逻任务时,能实时算出绕过建筑和野怪的合理路线。
具体案例:在一款 RPG 中,村庄的守卫需要从村口巡逻到铁匠铺。场景里有多个房屋、水井和随机刷新的野怪。开发者将地面标记为 Navigation Static 并烘焙 NavMesh,再给守卫挂上 NavMesh Agent;玩家引怪堵路时,守卫通过动态避障自动绕路,无需设计师手动铺设每一条巡逻线。
数字孪生工厂 AGV 巡检
阶段:局部快速重规划与动态障碍避让
解决的问题:智能工厂中货架位置经常调整,AGV 小车若使用静态预设路线,一旦遇到临时堆放的物料就会死锁。NavMesh Surface 支持局部重烘焙,AGV 只需调用 SetDestination,即可在新地图上实时算出安全路径。
具体案例:某汽车工厂的 AGV 需要从仓库送零件到装配线,途中有移动的叉车和临时堆料区。工程师为每块厂房地面配置 NavMesh Surface,并在叉车上挂 NavMesh Obstacle 且开启 Carve。叉车一移动,导航网格动态挖洞,AGV 随即绕行,全程无需停机重刷地图。
Python 演示
import heapq # 1 表示可走地砖, 0 表示障碍物(货架)GRID = [ [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 1, 1], [1, 0, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1],] START = (0, 4) # 起点(左下角)GOAL = (7, 0) # 终点(右上角) def neighbors(node): r, c = node for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)): nr, nc = r + dr, c + dc if 0 <= nr < len(GRID) and 0 <= nc < len(GRID[0]) and GRID[nr][nc] == 1: yield nr, nc def manhattan(a, b): return abs(a[0] - b[0]) + abs(a[1] - b[1]) def astar(start, goal): frontier = [(0, start)] came_from = {} cost_so_far = {start: 0} while frontier: _, current = heapq.heappop(frontier) if current == goal: break for nxt in neighbors(current): new_cost = cost_so_far[current] + 1 if new_cost < cost_so_far.get(nxt, float("inf")): came_from[nxt] = current cost_so_far[nxt] = new_cost heapq.heappush(frontier, (new_cost + manhattan(nxt, goal), nxt)) path = [] node = goal while node in came_from: path.append(node) node = came_from[node] path.append(start) return path[::-1] def show_map(path): for r, row in enumerate(GRID): line = "" for c, cell in enumerate(row): if (r, c) == path[0]: line += "S" elif (r, c) == path[-1]: line += "G" elif (r, c) in path: line += "*" elif cell == 0: line += "#" else: line += "." print(line) print("path:", path) if __name__ == "__main__": show_map(astar(START, GOAL))
GRID迷你版“地砖图纸”,1 代表绿色可走地砖,0 代表灰色货架障碍物。neighbors(node)返回上下左右四个相邻地砖,相当于检查角色下一步能踩哪几块砖。manhattan(a, b)曼哈顿距离充当 A* 的启发函数 h,它永远不高于实际代价。frontier = [(0, start)]优先级队列,存放待考察的节点,A* 每次取出 f 值最小的节点扩展。new_cost + manhattan(nxt, goal)这就是 f=g+h,把已走代价和未来估计加在一起。came_from[node]记录每个节点是从哪里走来的,最后倒推还原整条路径。show_map(path)把地砖图纸画出来:S 是起点,G 是终点,*是选中的绿色路径,#是障碍物。
常见误区
误区我以为只要给角色挂上 NavMesh Agent,它就会像雷达一样实时扫描周围地形,自己生成可走区域。场景里随便放个箱子,Agent 都应该马上绕开它。
正解NavMesh 本身是离线烘焙的产物,就像商场地砖是开业前画好的;运行时 Agent 只是在这张已经画好的网格上查询路径。烘焙后新增的静态物体,Agent 根本看不见——除非它带有动态障碍组件,或者你重新烘焙/局部更新 Surface。
为何常见:因为 Agent 的实时避障表现太自然了,看起来像是在“思考”,很容易让人误以为网格也是实时生成的。
误区我挂上 NavMesh Agent 后,让它朝一个 Rigidbody 箱子走过去,以为 Agent 会自动把箱子推开继续前进,结果角色卡在箱子前面不停抖。
正解NavMesh Agent 只负责“走路”,不负责“推东西”。它沿网格移动时默认避开障碍,如果路径被动态障碍堵死,它会停下来等待或重试。想让角色推动箱子,需要给箱子加 Rigidbody 并编写推挤逻辑,或者使用物理材质和力来实现。
为何常见:在 RPG 里我们经常看到 NPC 推门、推箱子,容易以为这是导航组件自带的功能,实际是开发者特意写的物理交互。
误区我搭好 Off-Mesh Link,角色走到起点后确实瞬移到了终点,但没有任何跳跃动作,看起来像是被 bug 传送过去的。
正解Off-Mesh Link 只负责“路径转移”——把 Agent 从起点插值移动到终点。跳跃、攀爬、翻墙的动画需要你在 OnLinkTraversal 或动画状态机里自行触发。Unity 可以感知 Link 的开始和结束,但演出效果完全由你编排。
为何常见:因为 Link 在编辑器中表现为一条连接线,很多人把它当成“自动播放动作的传送门”,忽略了引擎的职责边界。
误区我在烘焙完成后移动了一面墙,发现 Agent 直接穿墙而过。我以为 NavMesh 会像 NavMesh Obstacle 一样自动适应变化。
正解被标记为 Navigation Static 的静态物体一变,原网格就“过期”了,必须重新烘焙,或改用 NavMesh Surface 做局部重建。只有当移动的物体挂的是 NavMesh Obstacle 时,运行时才会动态挖洞。穿墙说明你移动了静态物体而没重建网格。
为何常见:动态障碍的实时雕刻效果让人产生错觉,以为所有场景变化都能被导航系统自动感知,混淆了静态网格和动态 Obstacle 的分工。
面试题
Q1. NavMesh 在 Unity 中的本质是什么?
- A.实时计算的最短路径树
- B.预先生成的可行走网格← 正确答案
- C.深度学习训练的路径模型
- D.逐帧生成的碰撞检测框
答案:B。NavMesh 是离线烘焙得到的“可行走多边形网格”,像商场地砖图一样在运行前就画好。A 选项把寻路结果和网格混为一谈,C 是误用 AI 概念,D 是物理组件而非导航网格。
Q2. 烘焙 NavMesh 时设置 Agent Radius 的作用是什么?
- A.控制角色行走动画的快慢节奏
- B.判断障碍物外扩的通过半径← 正确答案
- C.调节相机裁剪范围的缩放半径
- D.设置光源照射半径调节明暗
答案:B。Agent Radius 决定障碍物边缘向外膨胀多少,防止角色擦墙或卡缝。它影响地砖“安全警戒线”的宽窄,与动画、相机、光照均无关。
Q3. NavMesh Agent 的 Stopping Distance 参数表示什么?
- A.角色可移动的最大总里程数
- B.抵达目标附近触发停下的距离← 正确答案
- C.从最高速刹到零的滑行路程
- D.允许脱离网格的偏离容差
答案:B。Stopping Distance 是角色离目标点多近时开始停车,相当于推车快到收银台前提前减速的距离。它不限制总里程、不描述刹停过程,也不允许角色偏离网格。
Q4. Off-Mesh Link 在游戏中最典型的用途是什么?
- A.连接高低差平台让角色跳跃← 正确答案
- B.渲染一座三维桥梁的实体模型
- C.压缩纹理贴图提高手机帧率
- D.同步在线玩家的动画播放状态
答案:A。Off-Mesh Link 用于连接两段不相连的 NavMesh,比如悬崖两端的跳跃点或楼层间的扶梯,本质是“空中桥梁”。它不负责渲染模型、压缩纹理或同步动画。
Q5. NavMesh Obstacle 开启 Carve 选项后会发生什么?
- A.让障碍物在网格上动态挖洞← 正确答案
- B.永久雕刻出一片新的战场地形
- C.把角色皮肤雕刻出肌肉纹理
- D.优化动态阴影的裁剪计算开销
答案:A。Carve 会在运行时从 NavMesh 上实时挖掉动态障碍物占据的区域,等效于“清洁车挡住几块地砖,Agent 自动绕行”。它是临时挖洞,不是永久改地形,也与渲染无关。
Q6. 在 A* 算法中,启发函数 h 满足什么条件才能保证找到最短路径?
- A.启发值大于真实剩余代价
- B.启发值小于等于真实剩余代价← 正确答案
- C.启发值取任意常数也能保证最优
- D.启发值必须严格等于零才可行
答案:B。只有 h(n) 不大于真实剩余代价(可采纳性)时,A* 才能保证最优解。若 h 高估实际代价,算法可能错过真正的最短路径;h 等于零会退化成 Dijkstra,仍可最优但搜索范围大、效率低。
Q7. 下列哪个是 NavMesh Agent 的移动控制属性?
- A.控制移动快慢的 Speed← 正确答案
- B.物理碰撞计算中的 Mass
- C.材质表面摩擦力的 Friction
- D.细节层次渲染的 LOD
答案:A。Speed 是 Agent 自身的最大移动速度,直接决定角色走多快。Mass、Friction 属于 Rigidbody 物理系统,LOD 属于渲染性能优化,均与 Agent 移动控制无关。
Q8. 下列哪些情况需要重新烘焙或局部更新 NavMesh?
- A.移动了静态货架障碍物的位置← 正确答案
- B.修改了地面坡度可行走角度← 正确答案
- C.换了一套角色皮肤和配音音效
- D.把相机分辨率从 720 提升到 4K
答案:AB。静态障碍移动和坡度调整都会改变“地砖图纸”,必须重烘焙或使用 Surface 局部更新。换皮肤、调分辨率只影响渲染表现,不改变场景的可行走几何。
Q9. 关于动态障碍物和 Agent,下列说法正确的是?
- A.移动角色应挂载 Agent 组件← 正确答案
- B.可推拉的箱子用 Obstacle 组件← 正确答案
- C.Agent 寻路中能实时修正路线← 正确答案
- D.动态障碍物不会影响导航网格
答案:ABC。角色用 Agent 让“小推车”自动移动;可移动道具用 Obstacle 并开启 Carve 让网格动态挖洞。Agent 在遭遇障碍时会实时修正路径。D 错误——开启 Carve 的障碍物确实会临时修改导航网格。
Q10. 下列哪些方法能提升大世界 NavMesh 的运行性能?
- A.分块烘焙降低单帧计算量← 正确答案
- B.关闭所有 Agent 的碰撞检测
- C.用 Surface 做局部增量更新← 正确答案
- D.无限提高网格精度换取美观
答案:AC。分块烘焙和 Surface 局部增量更新都能避免一次性重建整张地图,是官方推荐方案。关闭碰撞检测会让角色穿模,无限提高网格精度则会大幅增加烘焙和寻路开销,均不可取。