1. 项目概述当AI敌人开始“穿墙术”和“卡墙角”在Unity3D里做游戏尤其是带敌人AI的NavMesh导航网格绝对是绕不开的核心系统。它让开发者能快速实现寻路省去了自己写A*算法的麻烦。但用过的朋友都知道这东西用起来简单想用“好”却处处是坑。最典型的就是标题里提到的敌人走着走着就卡在墙角不动了或者像幽灵一样直接穿过了本应阻挡它的薄墙、家具。更头疼的是当场景里有会移动的障碍物比如被玩家推开的箱子、倒下的柱子AI要么傻站着等要么就一头撞上去逻辑混乱得一塌糊涂。这些问题本质上都不是NavMesh的“Bug”而是我们对这套系统的工作原理和参数配置理解不够深入。NavMesh Agent导航代理和Nav Mesh Obstacle导航网格障碍物这两个组件它们各自有一套行为逻辑如果设置不当或者组合使用有误就会产生各种诡异现象。我见过不少项目AI的“智障”表现直接拉低了整个游戏的体验而开发者往往花费大量时间在调整动画、状态机却忽略了最底层的导航问题。这篇指南就是把我这些年踩过的坑、调试出的经验系统地梳理一遍。我们会从NavMesh的基本工作原理讲起重点拆解“卡墙”和“穿模”这两个高频问题的根因最后深入动态障碍物的配置逻辑让你不仅能解决问题更能理解背后的“为什么”。无论你是刚接触Unity AI的新手还是被这些问题困扰已久的老手相信都能找到直接的解决方案和配置思路。2. NavMesh核心机制与问题根源剖析要解决问题必须先理解问题从何而来。Unity的NavMesh系统不是一个“魔法黑盒”它是一套有明确规则和计算周期的寻路方案。2.1 NavMesh是如何工作的烘焙、查询与移动首先你需要明白NavMesh是一个静态数据。你通过Window AI Navigation窗口设置好参数后点击“Bake”Unity就会根据场景中的静态碰撞体Static Collider生成一张覆盖可行走区域的三角网格图。这个过程叫“烘焙”。Agent比如你的敌人就在这张网格图上移动。Agent的移动分三步路径查询Pathfinding当你调用agent.SetDestination(target.position)时系统会在NavMesh网格上从Agent当前位置到目标位置计算出一条由一系列拐点Corners组成的路径。这个过程相对耗时所以不会每帧都做。转向与速度控制Steering VelocityAgent根据计算出的路径结合自身的Speed、Angular Speed角速度、Acceleration加速度等参数计算出一个期望的移动速度向量。避免碰撞Collision Avoidance这是防止Agent之间堆叠的关键。Unity使用了一种基于RVOReciprocal Velocity Obstacles的轻量级局部避障算法。Agent会感知周围一定半径由Radius和Obstacle Avoidance参数决定内的其他Agent和障碍物并微调自己的速度方向来避免相撞。“卡墙”和“穿模”问题就潜伏在这三个步骤的细节和交互中。2.2 “卡墙”问题的三大元凶敌人走到墙角或复杂地形边缘就卡住不动通常不是没路而是路“走不过去”。原因主要有三元凶一Agent的“身体”比想象中大这是最常见的原因。在Navigation烘焙窗口的Agents分页你可以定义不同“体型”的Agent。这里的Radius和Height决定了NavMesh系统认为这个Agent的物理尺寸。同时Agent组件上也有一个Radius参数。关键点Agent实际用于寻路和避障的半径取的是这两个地方设置的最大值。如果你在烘焙时定义了半径为0.5米但Agent组件上的半径是0.7米那么系统会按0.7米来算。 当路径经过一个狭窄的通道时系统会检查通道宽度是否大于Agent Radius * 2。如果通道是直角墙角可用空间更小。Agent走到那里计算发现自己的“身体”过不去即使视觉模型很小就会在原地不断尝试微调看起来就是“卡住”。元凶二路径拐点精度与采样距离NavMeshAgent有个参数叫Auto Traverse OffMesh Link通常我们关注的是Stopping Distance停止距离和Path End。但Path End的精度会影响拐弯。更关键的是agent.path.corners提供的路径拐点精度是有限的。当目标点非常靠近障碍物或墙角时最后一个拐点可能就在“卡住”的位置附近。同时NavMeshAgent.SamplePathPosition这个方法用于查询路径位置如果采样距离设置不当也可能导致代理在拐点处“犹豫”。元凶三局部避障Obstacle Avoidance的副作用局部避障本意是好的但在墙角这种空间受限的地方它可能帮倒忙。当Agent试图贴着墙走时避障系统为了不和墙被识别为静态障碍碰撞会施加一个远离墙的力。同时如果墙角另一边有另一个Agent或动态障碍物避障系统又会施加另一个方向的力。多个力的矢量叠加可能导致合速度接近于零Agent就“僵住”了。将Obstacle Avoidance Type设置为“None”或“Low Quality”有时反而能解决墙角卡顿但会失去群体避障效果需要权衡。2.3 “穿模”问题的本质碰撞体与导航体的分离穿模问题更让人恼火明明有墙敌人却视若无睹地穿了过去。这里99%的情况是配置错误墙体没有参与NavMesh烘焙这是最低级的错误。确保你的墙、柱子等障碍物的GameObject在Inspector右上角导航静态标志Navigation Static是勾选的。并且在Navigation窗口的Object分页该物体的“Navigation Area”被设置为“Not Walkable”。这样烘焙时这些区域就不会生成可行走网格Agent从根本上就无法计算穿过它的路径。Agent的碰撞体与导航体不匹配这是高级错误。你的敌人模型可能有一个Capsule Collider用于物理碰撞但NavMeshAgent组件有自己的“虚拟身体”。如果NavMeshAgent的Radius设置得远小于视觉模型或物理碰撞体那么系统认为的“可通行空间”就会大于实际视觉/物理空间。在复杂地形或高速移动下Agent可能计算出一条从视觉上看会穿墙但在导航网格上却合法的路径。解决方案是确保NavMeshAgent的Radius和Height与模型主碰撞体大致匹配。OffMesh Link的滥用OffMesh Link用于连接不相邻的导航网格比如跳下高台、跳过沟壑。如果你错误地创建了OffMesh Link连接了两个本应被墙隔开的区域Agent就会直接“传送”过去看起来像穿模。检查场景中是否有非预期的OffMesh Link。3. 静态场景避坑烘焙参数与Agent设置的黄金法则理解了原理我们就可以针对性地调整。对于静态场景地形、建筑优化配置可以杜绝大部分问题。3.1 导航烘焙参数精细化配置不要使用默认参数直接烘焙。打开Navigation窗口Window AI Navigation关注这几个面板Agents面板定义Agent类型在这里预设不同体型敌人的导航参数。我强烈建议至少创建两种一种用于普通小体型敌人如人类Radius 0.25-0.3m, Height 1.8m一种用于大型敌人或BossRadius 0.5-1m, Height 2.5-4m。Name: 如“Humanoid”“LargeMonster”。Radius: 这是最重要的参数。它必须大于或等于你模型中用于物理碰撞的主碰撞体半径。对于人形角色0.25-0.3是安全值。设置太小会穿模太大会卡门。Height: 角色身高。决定Agent能否通过低矮空间。Step Height: 可跨越的最大台阶高度。设置合理如0.3-0.4可以让角色自然走上楼梯而不是寻路绕开。Max Slope: 可爬行的最大坡度。45-60度是常见值。Areas面板定义区域成本你可以将不同的表面标记为不同区域如“道路”、“草地”、“沼泽”并设置不同的通行成本Cost。Agent寻路时会选择总成本最低的路径。这可以用来让AI优先走大路。确保所有不可行走的物体墙、家具都被分配到“Not Walkable”区域。Bake面板控制网格生成质量Agent Radius: 这里要和你上面定义的Agent类型半径一致。它决定了烘焙时网格距离障碍物边界的收缩距离。值越大生成的可行走区域离墙越远。Advanced Height Mesh:务必勾选。这个选项会为导航网格生成高度信息。对于有楼梯、斜坡、不平整地面的场景不勾选此选项会导致Agent在上下坡时严重抖动、卡住甚至从斜坡上“滑”下去。勾选后Agent会沿着网格表面高度移动行为自然得多。这是解决复杂地形卡顿的关键。Advanced Voxel Size: 体素大小影响烘焙精度和速度。值越小精度越高但烘焙越慢。对于大多数场景默认值即可。如果场景中有很多细小缝隙可以适当调小如0.05但会显著增加烘焙时间。3.2 NavMeshAgent组件关键参数详解为你的敌人GameObject添加NavMeshAgent组件后需要根据其类型精细调整Agent Type: 选择你在Agents面板中定义的预设如“Humanoid”。Base Offset: 代理中心相对于模型原点的Y轴偏移。如果你的模型原点在脚底这个值通常是Height / 2。Speed/ Angular Speed/ Acceleration: 移动参数。Angular Speed角速度过低会导致敌人在拐弯时转身缓慢显得“卡顿”。适当提高如120-360可以让转向更灵敏。Stopping Distance: 停止距离。在距离目标多远处开始减速停止。对于攻击敌人的AI这个值可以设为攻击范围半径这样敌人走到攻击距离就会停下而不是试图“贴脸”。Auto Braking: 是否自动刹车。如果关闭Agent到达目标点附近不会减速会冲过头然后折返产生“鬼畜”移动。通常保持开启。Obstacle Avoidance:Radius: 再次强调这个值应该与物理碰撞体半径、烘焙Agent半径相匹配。它是避障计算的依据。Quality: 避障质量。High质量效果好但耗能高在大量Agent时建议用Medium或Low。对于墙角卡顿问题可以尝试降低质量或设为None进行测试。Priority: 避障优先级0-99。值越低优先级越高。可以设置玩家控制的角色为高优先级低数值让敌人AI主动避让玩家。一个黄金检查清单模型物理碰撞体如CapsuleCollider的Radius是多少记为R_phy。Navigation烘焙面板中所用Agent类型的Radius是多少记为R_bake。NavMeshAgent组件上的Radius是多少记为R_agent。确保R_agent R_phy且R_agent 与 R_bake 相等或接近。最安全的做法是让这三个值保持一致。4. 动态障碍物的核心NavMeshObstacle深度解析静态问题解决了动态的才是真正的挑战。Unity提供了NavMeshObstacle组件来处理移动的障碍物但它的两种模式Carve和Non-Carve和行为逻辑很容易用错。4.1 两种模式障碍Obstacle与雕刻CarveNavMeshObstacle组件有两种影响NavMesh的方式通过Carve复选框切换1. 障碍模式Carve未勾选行为此模式下障碍物不会在NavMesh上挖洞。它仅作为一个“力场”或“排斥体”存在。工作原理NavMeshAgent的局部避障系统会感知到它并试图绕开。就像现实中你绕过一个个行走的路人。优点性能开销极低因为不需要动态修改导航网格。缺点避障效果是局部的、反应式的。如果障碍物完全堵死了狭窄的通道后面的Agent可能会因为无法规划出绕行路径而集体卡住。它只有简单的“排斥”逻辑。适用场景持续移动的物体如玩家角色、其他由AI控制的NPC、不断巡逻的敌人。对于这些物体你希望AI动态避让而不是在导航网格上永久挖个洞。2. 雕刻模式Carve勾选行为此模式下当障碍物静止时会在NavMesh上“雕刻”出一个不可行走的洞。当它移动时这个洞会跟着移动根据设置。工作原理它直接修改了导航网格的数据结构。寻路器Pathfinder在计算全局路径时会直接避开这个被挖掉的区域。优点提供全局的、预测性的避障。AI在很远的地方规划路径时就知道要绕开这个障碍物所在区域。缺点性能开销大。每次雕刻挖洞和更新洞的位置都需要重新计算局部导航网格是CPU密集型操作。适用场景会移动但最终会停下来的物体比如被玩家推开后静止的箱子、被炸倒的树干、可破坏的墙壁碎块。你需要AI长期将这个物体的位置视为不可通行区域。4.2 关键参数详解与配置策略勾选Carve后下面几个参数决定了雕刻行为的细节Move Threshold移动阈值这是判断障碍物“是否在移动”的阈值。如果障碍物在一帧内的移动距离小于此值则被视为“静止”大于此值则被视为“移动”。这个值不宜设置过小如0.001否则轻微的物理抖动都会触发昂贵的网格更新。对于大多数情况0.1到0.5是一个合理的范围。Time To Stationary静止时间障碍物需要停止移动多久才会被系统正式认定为“静止”状态并开始或重新雕刻。这避免了障碍物只是短暂停顿比如箱子被推了一下晃了晃就立刻挖洞造成性能浪费和路径抖动。通常设置为0.5秒到1秒。Carve Only Stationary仅雕刻静止物体这是最重要的一个选项也是性能优化的关键。勾选时推荐障碍物只在被认定为“静止”状态时才会雕刻NavMesh。在移动过程中它仅作为障碍物Obstacle模式工作由Agent的局部避障来处理。这是绝大多数场景的最佳实践兼顾了效果和性能。比如一个被推的箱子移动时AI会躲开它等它停稳了AI的寻路路径就会永久绕过它。取消勾选时障碍物在移动时也会雕刻NavMesh并且这个“洞”会随着障碍物移动而更新。这会产生非常精确的路径规避比如一大群士兵绕过一辆缓慢移动的坦克但性能代价极高只适用于少量、关键的大型移动障碍物。形状Shape选择Box适用于方形物体如箱子、柜子。Capsule适用于人形或圆柱形物体。Size/Center务必调整这些参数让形状尽可能紧密地包裹住你的障碍物模型。过大的形状会导致不必要的导航网格浪费过小则会导致AI规划出的路径过于贴近障碍物容易发生碰撞。4.3 动态障碍物配置实战案例假设我们有一个场景玩家可以推动场景中的木箱带有Rigidbody和Box Collider敌人AI需要避开这些箱子。正确的配置步骤为木箱预制体添加组件为木箱的GameObject添加NavMeshObstacle组件。设置形状与尺寸将Shape设为Box。点击组件上的Edit Collider按钮一个小圆点然后直接在Scene视图中拖动Gizmo使绿色线框的盒子与木箱的视觉模型和物理碰撞体基本重合。或者手动设置Center和Size使其匹配。启用雕刻并配置参数勾选Carve。勾选Carve Only Stationary。这是关键设置Move Threshold 0.3箱子移动超过0.3米才算动。设置Time To Stationary 0.8箱子停止移动0.8秒后才算真正静止开始挖洞。处理物理交互确保木箱的Rigidbody的Interpolate设置为Interpolate以减少物理抖动。这可以防止因微小抖动频繁触发NavMeshObstacle的状态判断。这样配置后的运行时行为玩家开始推箱子箱子移动距离 0.3米NavMeshObstacle进入“移动”状态不雕刻网格。附近的敌人AI通过局部避障系统实时躲开箱子。玩家松开箱子箱子因摩擦力停下计时开始。箱子静止时间超过0.8秒NavMeshObstacle被认定为“静止”开始在NavMesh上雕刻一个洞。所有敌人AI重新规划路径时都会永久绕开这个箱子所在的位置。玩家再次推动箱子一旦移动距离超过0.3米雕刻的洞立刻消失障碍物切回“移动”状态仅通过局部避障影响AI。这套配置完美平衡了效果与性能是处理动态障碍物的标准做法。5. 高级技巧与复合问题解决方案掌握了基础配置我们来看一些更复杂的情况和优化技巧。5.1 处理门、电梯等可开关的通道对于一扇可以打开和关闭的门你不能简单地将门本身设置为NavMeshObstacle因为当门打开时它不应该阻挡路径。解决方案使用两个导航网格区域在门的位置放置一个透明的GameObject比如一个Cube将其缩放调整为门洞大小。将这个Cube的Navigation Area设置为“Not Walkable”并标记为Navigation Static。烘焙导航网格。此时门洞是不可通行的。运行时当门打开时通过脚本动态修改这个Cube的Navigation Static状态并将其Navigation Area改为“Walkable”。调用NavMeshSurface组件如果你使用NavMesh Components包的BuildNavMesh()或UpdateNavMesh()方法局部更新这个区域的导航网格。对于大型场景局部更新比全局重建性能好得多。// 示例代码控制门的通行区域 public class DoorController : MonoBehaviour { public GameObject navBlocker; // 那个作为导航阻挡的Cube private NavMeshSurface navMeshSurface; // 引用你的NavMeshSurface组件 void Start() { navMeshSurface FindObjectOfTypeNavMeshSurface(); // 建议在Inspector中赋值 } public void OpenDoor() { // 1. 取消静态标记并改变区域为可行走 GameObjectUtility.SetStaticEditorFlags(navBlocker, StaticEditorFlags.NavigationStatic, false); // 注意运行时不能直接设置Area需要通过NavMeshModifier组件或NavMeshSurface的Modifiers // 更简单的方法直接禁用或销毁navBlocker物体 navBlocker.SetActive(false); // 2. 更新导航网格局部更新 // 如果你使用了NavMeshComponents可以获取navBlocker所在的NavMeshModifierVolume // 然后调用navMeshSurface.UpdateNavMesh(navMeshSurface.navMeshData, new ListNavMeshBuildSource(){...}); // 简化方案对于小型场景或门不多的情况可以每扇门关联一个小型NavMeshSurface只更新它。 // 或者如果性能允许在开门动画结束后简单调用 NavMesh.SamplePosition 让AI重新寻路。 // 最直接但较重的方法 // NavMesh.RemoveAllNavMeshData(); // 慎用清空所有 // NavMeshBuilder.BuildNavMeshAsync(); // 异步重建 } }注意动态更新NavMesh是相对昂贵的操作不要每帧进行。确保在状态改变如门开关后有策略地、低频次地更新。5.2 解决Agent在狭窄门口“排队”或“抖动”当多个Agent试图通过同一扇门时即使路径存在它们也可能在门口挤作一团不断调整位置导致集体卡顿。解决方案结合OffMesh Link与脚本控制在门的两侧创建一对OffMesh Link将其Cost Override设置得比正常行走略高比如2.0。这样AI在寻路时将“穿过门”视为一个需要“额外花费”的特殊链接而不是默认的最短路径。编写一个简单的“门交通管理”脚本。这个脚本管理一个队列每次只允许一个或固定数量的Agent“使用”这个OffMesh Link。其他Agent需要在队列中等待。可以通过禁用其他Agent的NavMeshAgent组件或临时设置一个等待目标点来实现排队逻辑。// 简化版门队列管理思路 public class DoorQueueManager : MonoBehaviour { public Transform waitPoint; // 门前的等待点 private QueueNavMeshAgent agentQueue new QueueNavMeshAgent(); private bool isDoorOccupied false; private void OnTriggerEnter(Collider other) { NavMeshAgent agent other.GetComponentNavMeshAgent(); if (agent ! null agent.gameObject.CompareTag(AI)) { agentQueue.Enqueue(agent); TryProcessQueue(); } } void TryProcessQueue() { if (!isDoorOccupied agentQueue.Count 0) { NavMeshAgent nextAgent agentQueue.Dequeue(); isDoorOccupied true; // 让这个Agent通过门 // 可以通过动画、传送或设置路径点实现 StartCoroutine(LetAgentPass(nextAgent)); } } IEnumerator LetAgentPass(NavMeshAgent agent) { // 示例让Agent移动到门内的一个点 agent.SetDestination(passThroughPoint.position); yield return new WaitUntil(() !agent.pathPending agent.remainingDistance 0.1f); // 假设通过需要1秒 yield return new WaitForSeconds(1.0f); isDoorOccupied false; TryProcessQueue(); // 处理下一个 } }5.3 性能优化减少NavMesh更新开销动态障碍物和实时更新的NavMesh是性能杀手。以下是一些优化准则严格使用Carve Only Stationary这是第一道也是最重要的防线。确保只有真正需要长期阻挡路径的静止物体才雕刻网格。增大Move Threshold和Time To Stationary根据你的游戏节奏适当调高这两个值。避免因物理引擎的微小抖动或物体的轻微晃动就触发昂贵的网格更新。合并更新如果有多个动态障碍物可能同时改变状态比如一场爆炸炸飞多个箱子不要在每个箱子静止时立即更新NavMesh。可以设置一个计时器在短时间如0.5秒内收集所有需要更新的障碍物然后一次性触发NavMesh更新。使用NavMesh代理层级Agent Type如果小型敌人和大型敌人共享同一个精细烘焙的NavMesh对大型敌人可能是一种浪费。可以考虑为大型敌人烘焙一个更粗糙Agent Radius更大的NavMesh它们使用自己的网格更新开销更小。限制同时活动的NavMeshAgent数量对于远离玩家或不在视野内的敌人可以禁用其NavMeshAgent组件并用一个更简单的脚本如朝玩家方向直线移动代替直到进入激活范围。6. 调试工具与问题排查实战理论再好也要能落地排查。Unity提供了一些强大的可视化调试工具。6.1 使用Navigation Debug可视化在Game视图左上角点击“Gizmos”下拉菜单你可以找到与Navigation相关的调试显示选项Show NavMesh显示当前场景中烘焙的导航网格。蓝色区域是可行走的。Show NavMesh Agents显示所有NavMeshAgent的Gizmo包括其半径、当前路径一条蓝线和下一个拐点。Show NavMesh Obstacles显示所有NavMeshObstacle的Gizmo。雕刻模式的障碍物会显示一个挖空的区域障碍模式的则显示为一个简单的形状轮廓。调试“卡墙”开启Show NavMesh和Show NavMesh Agents。控制敌人走到卡住的位置。观察敌人的蓝色路径线是否在墙角处中断或折返敌人的代理Gizmo一个圆柱体是否与墙壁的导航网格有重叠如果有说明Agent半径太大或者导航网格在墙角处收缩过度。将NavMeshAgent的Radius临时调小看是否解决问题。如果是就需要重新评估并统一你的半径设置。调试“穿模”确保Show NavMesh开启。走到穿模的墙边。观察墙体的位置在导航网格上是否是一片空白不可行走如果墙下有蓝色网格说明墙体没有正确设置为“Not Walkable”或未标记为Navigation Static需要重新烘焙。观察穿模的敌人其路径线是否是直线穿过空白区域如果是那可能是OffMesh Link错误连接或者Agent的路径查询 somehow 忽略了该区域。检查是否有意外的OffMesh Link。6.2 脚本调试与信息输出在代码中你可以实时获取并打印NavMeshAgent的状态信息这对于排查复杂问题非常有用。void DebugAgentInfo(NavMeshAgent agent) { if (agent null) return; Debug.Log($Agent: {agent.gameObject.name}); Debug.Log($ Path Status: {agent.pathStatus}); // 路径状态 (PathComplete, PathPartial, PathInvalid) Debug.Log($ Has Path: {agent.hasPath}); Debug.Log($ Path Pending: {agent.pathPending}); // 路径是否在计算中 Debug.Log($ Remaining Distance: {agent.remainingDistance}); Debug.Log($ Is Stalled: {agent.isStopped}); // 是否被手动停止 if (agent.hasPath) { Debug.Log($ Path Corners: {agent.path.corners.Length}); for (int i 0; i agent.path.corners.Length; i) { Debug.Log($ Corner {i}: {agent.path.corners[i]}); } } // 检查是否在NavMesh上 NavMeshHit hit; if (!NavMesh.SamplePosition(agent.transform.position, out hit, 0.1f, NavMesh.AllAreas)) { Debug.LogWarning( Agent is OFF NavMesh!); } }当敌人卡住时调用此函数。如果pathStatus是NavMeshPathStatus.PathPartial意味着路径计算只完成了一部分目标点可能不可达。如果remainingDistance很小但agent就是不动可能是遇到了局部避障的死锁。6.3 常见问题速查表问题现象可能原因排查步骤与解决方案敌人卡在墙角/门口1. Agent半径过大。2. 导航网格在拐角处生成不连续。3. 局部避障冲突。1. 检查并统一Agent Radius烘焙设置、组件设置、碰撞体。2. 开启Show NavMesh检查拐角处网格是否完整。可尝试减小烘焙的Agent Radius或增加Advanced Max Slope。3. 临时将Obstacle Avoidance设为None测试。敌人穿过薄墙/物体1. 墙体未标记为Navigation Static或Not Walkable。2. Agent半径远小于视觉/碰撞模型。3. 存在错误的OffMesh Link。1. 检查墙体属性重新烘焙。2. 确保NavMeshAgent.radius 模型碰撞体半径。3. 在Scene视图开启Show NavMesh和Show OffMeshLinks检查。动态障碍物箱子移动时AI不躲避或反应迟钝1. 未添加NavMeshObstacle组件。2.Carve Only Stationary被勾选且障碍物在移动。3. Agent的Obstacle Avoidance质量太低或半径太小。1. 为障碍物添加NavMeshObstacle。2. 确认需求若需移动时躲避应依赖局部避障。确保Agent的Obstacle Avoidance未禁用且Avoidance Priority合理。3. 提高Obstacle Avoidance Quality增大Agent的Radius。动态障碍物静止后AI仍朝其位置走1.NavMeshObstacle未开启Carve。2.Time To Stationary设置过长障碍物还未被认定为静止。3. Agent的路径未更新还在用旧路径。1. 勾选Carve。2. 适当减小Time To Stationary如0.5秒。3. 尝试让Agent重新设置目标点SetDestination或调用agent.ResetPath()后重设。多个AI在门口挤成一团都不动局部避障在狭窄空间形成死锁。1. 增加门的宽度如果设计允许。2. 使用交通管理脚本进行排队。3. 为其中一个AI临时提高避障优先级或禁用其避障让其先过。AI上下坡时抖动、滑步导航网格未烘焙高度信息。在Navigation Bake面板勾选Advanced Height Mesh然后重新烘焙场景。性能问题大量动态障碍物时帧率下降1. 过多障碍物开启了Carve且未勾选Carve Only Stationary。2. NavMesh更新过于频繁。1. 为所有非必须的移动障碍物勾选Carve Only Stationary。2. 增加Move Threshold和Time To Stationary。3. 合并NavMesh更新请求避免每帧更新。7. 实战构建一个健壮的敌人AI导航系统最后我们整合所有知识点为一个典型的第三人称动作游戏敌人配置一套健壮的导航系统。假设敌人有“巡逻”、“追击”、“攻击”三种状态。第一步场景烘焙准备将所有的地形、静态建筑、大型固定装饰物标记为Navigation Static。将墙壁、栏杆、大型岩石等不可通行物体的Navigation Area设为Not Walkable。在Agents面板创建“Humanoid”预设Radius0.3,Height2.0,Step Height0.4,Max Slope45。在Bake面板确认Agent Radius为0.3并勾选Height Mesh。点击Bake。第二步敌人预制体配置为敌人预制体添加NavMeshAgent组件。Agent Type选择 “Humanoid”。设置参数Speed3.5,Angular Speed360,Acceleration8,Stopping Distance1.5假设攻击距离为1.5米。Radius设置为0.3与烘焙设置一致。Height设置为2.0。Obstacle Avoidance的Quality设为MediumPriority设为50默认。第三步动态障碍物配置为场景中所有可移动的物体如木箱、油桶创建预制体。在预制体上添加NavMeshObstacle组件。设置Shape匹配物体调整Center和Size。勾选Carve和Carve Only Stationary。设置Move Threshold0.2,Time To Stationary0.7。第四步编写AI状态机简化示例public class EnemyAI : MonoBehaviour { public Transform[] patrolPoints; public float sightRange 10f; public float attackRange 1.5f; private NavMeshAgent agent; private Transform player; private int currentPatrolIndex 0; private enum State { Patrol, Chase, Attack } private State currentState State.Patrol; void Start() { agent GetComponentNavMeshAgent(); player GameObject.FindGameObjectWithTag(Player).transform; // 建议通过Manager获取 PatrolToNextPoint(); } void Update() { float distToPlayer Vector3.Distance(transform.position, player.position); switch (currentState) { case State.Patrol: if (distToPlayer sightRange) { currentState State.Chase; } // 检查是否到达巡逻点 if (!agent.pathPending agent.remainingDistance 0.5f) { PatrolToNextPoint(); } break; case State.Chase: agent.SetDestination(player.position); if (distToPlayer attackRange) { currentState State.Attack; agent.isStopped true; // 停止导航开始攻击 } else if (distToPlayer sightRange * 1.2f) // 加入一点滞后防止在边界抖动 { currentState State.Patrol; PatrolToNextPoint(); } break; case State.Attack: // 执行攻击逻辑比如播放动画、造成伤害 FaceTarget(player.position); if (distToPlayer attackRange) { agent.isStopped false; currentState State.Chase; } break; } } void PatrolToNextPoint() { if (patrolPoints.Length 0) return; agent.SetDestination(patrolPoints[currentPatrolIndex].position); currentPatrolIndex (currentPatrolIndex 1) % patrolPoints.Length; } void FaceTarget(Vector3 target) { Vector3 direction (target - transform.position).normalized; Quaternion lookRotation Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z)); transform.rotation Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5f); } // 在OnDrawGizmos中可视化侦查和攻击范围便于调试 void OnDrawGizmosSelected() { Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, sightRange); Gizmos.color Color.red; Gizmos.DrawWireSphere(transform.position, attackRange); } }第五步持续调试与优化在Game视图开启Show NavMesh和Show NavMesh Agents观察敌人行为是否与预期一致。使用OnDrawGizmosSelected绘制敌人的感知范围确保逻辑触发正确。在性能分析器Profiler中观察Navigation和NavMesh.Update的开销确保动态障碍物没有造成性能峰值。进行边界测试将玩家引到复杂角落、门边、多个动态障碍物中间观察AI行为是否出现卡顿、穿模或逻辑错误。这套组合拳下来你的敌人AI在绝大多数场景下都应该能表现稳定、自然。记住NavMesh是一个强大的工具但它不是“设置完就忘”的魔法。理解其原理善用调试工具根据你的游戏具体需求进行微调才是解决所有导航问题的根本之道。