公司动态

Unity NavMesh动态障碍物避障实战:从原理到性能优化

📅 2026/7/23 5:25:02
Unity NavMesh动态障碍物避障实战:从原理到性能优化
1. 项目概述为什么NavMesh动态障碍物是游戏AI的“刚需”如果你做过Unity里的寻路大概率用过NavMesh。传统的NavMesh Agent确实好用点个目标AI角色就能自己绕开静态的墙壁和沟壑一路跑过去。但现实游戏场景里哪有那么多“静态”的东西队友在你面前晃来晃去敌人会移动封堵你的路线甚至一个被炸飞的木箱都可能成为你前进路上的绊脚石。这时候如果你的AI还只会对着预先烘焙好的静态网格寻路那场面就尴尬了——它会像个瞎子一样直挺挺地撞向移动中的障碍物或者被卡在一个动态物体旁边不知所措。这就是Unity 2023.2版本中NavMesh动态障碍物Dynamic Obstacle Avoidance功能要解决的核心痛点。它不再是那个“笨拙”的静态寻路系统而是让NavMesh Agent具备了实时感知和规避场景中移动物体的能力。简单来说它让游戏里的NPC、怪物、队友的移动逻辑从“沿着固定铁轨走”升级成了“在车流中自如穿梭的老司机”。这个功能对于任何需要智能移动的游戏类型都是革命性的。无论是RTS游戏中单位之间的穿插走位ARPG里怪物对玩家走位的封堵与反制还是开放世界游戏中NPC在熙攘街道上的自然穿行动态避障都是提升沉浸感和策略深度的关键。以前要实现类似效果开发者要么自己写一套复杂的局部避障算法比如RVO、流场要么用物理碰撞加力推的“土办法”不仅性能开销大效果还难以控制。现在Unity引擎层直接提供了原生、高效的支持。我最近在一个战术小队模拟项目中深度使用了这个功能实测下来它极大地简化了群体移动逻辑的开发。以前十几个单位挤在一起会乱成一团现在它们能像训练有素的士兵一样在保持队形的同时优雅地互相避让整个移动过程流畅且符合直觉。接下来我就结合实战代码带你彻底搞懂这个“黑科技”怎么用以及如何避开那些官方文档里没写的“坑”。2. 核心机制拆解动态障碍物是如何“思考”的在深入代码之前我们必须先弄明白Unity这套新机制底层是怎么工作的。如果你把它当成一个魔法黑盒出了问题就只能干瞪眼。理解其原理是高效使用和调试的基础。2.1 从静态网格到动态体素避障数据的进化传统的NavMesh是一个预先计算好的、覆盖在可行走区域上的多边形网格。Agent寻路时本质上是在这个静态网格上搜索从A点到B点的最短路径。障碍物信息在烘焙Bake时就被“固化”进了网格运行时无法改变。这就是为什么移动的物体无法被规避。动态障碍物系统引入了一个关键概念NavMesh障碍物NavMesh Obstacle。你可以把它理解为一个在运行时能实时“污染”NavMesh特定区域的组件。当你在一个GameObject上添加了NavMeshObstacle组件后这个物体就不再仅仅是一个普通的碰撞体它成为了NavMesh系统中的一个“禁区”信号源。系统内部维护着一个动态更新的“可行走区域”图。NavMeshObstacle会根据自己的形状立方体、胶囊体等和当前位置实时地在内部地图上标记出一块不可行走的区域。所有正在寻路的NavMeshAgent在计算每一步的移动时都会查询这张动态地图如果发现前方即将进入被标记的禁区就会提前重新规划路径或调整速度方向来规避。2.2 避障逻辑的两种模式Carve与AvoidNavMeshObstacle组件有两个核心属性直接决定了障碍物的行为模式这也是最容易混淆的地方Carve雕刻模式作用此模式下的障碍物会永久性地从NavMesh中“挖掉”一块区域。就像用雕刻刀从一块木头上切掉一部分。这块区域在障碍物存在期间对于所有Agent来说就是“不存在”的Agent的全局路径规划会直接绕过这个坑。适用场景场景中突然出现的、长期存在的、需要被全局路径绕开的障碍物。例如战斗中倒塌的桥梁、玩家建造的一堵墙、一个被推过来堵住路口的巨大箱子。Agent在很远的地方规划路径时就会避开这个区域。重要参数CarveOnlyStationary仅当静止时雕刻。通常需要勾选。这意味着物体只有在停止移动后才会执行雕刻操作。否则一个移动的物体到处“挖洞”会导致NavMesh千疮百孔性能低下且逻辑混乱。Avoid避让模式作用此模式下的障碍物不会改变全局的NavMesh网格。它更像一个“局部力场”或“斥力源”。Agent的全局路径可能仍然会穿过这个障碍物所在的位置但当Agent靠近时它会感知到这个斥力并在局部进行微调比如减速、转向从旁边滑过去。适用场景其他移动的Agent、玩家角色、短暂停留的物体。例如你的队友从你面前跑过一个敌人正在移动封堵你一个即将消失的魔法效果。Agent的最终目标是到达路径点但中途需要灵活地避开这些临时障碍。性能Avoid模式的计算开销通常比Carve要小因为它不修改底层导航数据只影响局部行为。如何选择一个简单的判断原则这个障碍物是否需要被“永远”绕开如果是用Carve并确保它最终会静止。如果它只是移动过程中的临时干扰用Avoid。在我的项目中固定的防御工事用Carve而所有移动的士兵单位都用Avoid。2.3 Agent的“感知”与“决策”半径、质量与预测光有障碍物发出信号还不够Agent得能接收并处理这些信号。避障半径Avoidance Radius这是NavMeshAgent组件上的一个参数。它定义了Agent在进行避障计算时所考虑的自身“体积”半径。这个值通常略大于Agent的物理碰撞体半径。如果设得太小Agent会贴得太近甚至穿过其他单位设得太大又会显得过于“胆小”早早地就开始绕大圈。需要根据角色模型大小和游戏感觉来微调。避障质量Avoidance Quality与优先级Priority当多个Agent挤在一起时谁让谁Avoidance Quality高、中、低决定了Agent进行避障计算的精度和频率高质量意味着更平滑的避让但开销也更大。Priority0-99则是一个简单的规则数字小的优先级高。优先级高的Agent会“理直气壮”地要求优先级低的Agent让路。你可以设置精英怪物优先级高杂兵优先级低或者玩家控制的单位优先级最高。预测时间Time Horizon这是一个高级但非常重要的参数。Agent不是等到撞上了才避让而是会预测障碍物未来的位置。Time Horizon可以理解为“向前看多少秒”。对于高速移动的物体如冲锋的骑兵、飞驰的汽车需要设置较大的预测时间让Agent能提前做出反应。对于慢速移动的物体可以设置小一些以优化性能。注意动态避障是一个持续进行的局部行为修正它不会触发一次完整的、昂贵的全局路径重新计算CalculatePath。Agent的目标终点destination没有变它只是在走向终点的过程中实时地微调每一步的速度向量以避开沿途的动态障碍。这保证了性能和高响应速度。3. 实战配置从零搭建一个动态避障Demo理论说再多不如动手做一遍。我们创建一个简单的场景包含一个静态的地面NavMesh几个作为动态障碍物的移动方块和一个受玩家点击控制的Agent。3.1 场景与静态NavMesh搭建创建地形在场景中创建一个Plane或使用Terrain作为地面。烘焙静态NavMesh窗口菜单中选择Window AI Navigation。在Navigation窗口的Bake面板保持默认参数点击Bake按钮。你会看到地面变成蓝色的网格这表示可行走区域。设置导航区域可选但重要在Objects面板你可以选中场景中的物体如墙壁、陡坡在Navigation Area中为其分配一个类型例如“Not Walkable”。这样在烘焙时这些物体会被排除在NavMesh之外。这是管理复杂场景静态障碍的基础。3.2 创建动态障碍物Moving Obstacle创建一个Cube命名为DynamicObstacle。为其添加NavMeshObstacle组件。关键配置如下Shape: Cube 根据你的模型选择也可以是CapsuleCarve:不勾选因为我们演示移动避让用Avoid模式Carve Only Stationary: 不关心因为Carve没开Time To Stationary: 0.5 物体停止移动后多久被视为“静止”这个参数在Avoid模式下也影响避障行为的消退速度为了让它动起来我们附上一个简单的来回移动脚本using UnityEngine; public class MovingObstacle : MonoBehaviour { public float moveRange 5.0f; // 移动范围 public float speed 2.0f; // 移动速度 private Vector3 startPos; private bool movingRight true; void Start() { startPos transform.position; } void Update() { // 简单的左右移动逻辑 float movement speed * Time.deltaTime; if (movingRight) { transform.Translate(Vector3.right * movement); if (transform.position.x startPos.x moveRange) movingRight false; } else { transform.Translate(Vector3.left * movement); if (transform.position.x startPos.x - moveRange) movingRight true; } // 关键步骤告诉NavMeshObstacle组件它的位置/形状可能改变了 // 对于移动的障碍物这一步是必须的否则避障更新会有延迟。 NavMeshObstacle obstacle GetComponentNavMeshObstacle(); if (obstacle ! null) { // 强制更新障碍物数据。对于频繁移动的物体可以在FixedUpdate中调用。 obstacle.Carve(); // 注意即使Carve属性为false调用此方法也能更新Avoid所需的数据 } } }代码解释脚本让障碍物左右移动。最关键的是obstacle.Carve()这一行。对于位置或形状发生变化的NavMeshObstacle必须手动调用Carve()方法来通知NavMesh系统更新障碍物信息。否则Agent感知到的障碍物位置会有延迟导致避障不准确。这是一个非常容易遗漏的步骤3.3 创建具备避障能力的AgentPlayer Agent创建一个Capsule命名为PlayerAgent。为其添加NavMeshAgent组件。关键配置如下Speed: 5Angular Speed: 360Acceleration: 20Stopping Distance: 0.5Auto Braking: 勾选 快到达时减速行为更自然Radius: 0.5 Agent的物理半径影响路径拐角Height: 2.0避障相关参数Avoidance Priority: 50 默认值数值越小优先级越高Auto Traverse OffMesh Link: 视情况勾选如果用了跳跃、传送点等Auto Repath: 勾选 目标点被阻时尝试重新寻路重点Obstacle Avoidance部分Radius: 0.6 避障半径应略大于上面的Radius比如1.2倍Quality: High 为了演示效果设为高。实际项目可根据单位数量调整Priority: 50添加点击移动的脚本using UnityEngine; using UnityEngine.AI; public class ClickToMove : MonoBehaviour { private NavMeshAgent agent; private Camera mainCamera; void Start() { agent GetComponentNavMeshAgent(); mainCamera Camera.main; } void Update() { if (Input.GetMouseButtonDown(0)) // 左键点击 { Ray ray mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; // 注意这里射线检测的是NavMesh而不是任何碰撞体。 // 确保地面有NavMesh并且代理可以行走。 if (Physics.Raycast(ray, out hit)) { // 更推荐使用NavMesh.SamplePosition来确保目标点在可行走区域 NavMeshHit navHit; if (NavMesh.SamplePosition(hit.point, out navHit, 1.0f, NavMesh.AllAreas)) { agent.SetDestination(navHit.position); } else { // 如果点击位置不在NavMesh上可以设置一个默认行为比如移动到射线击中的世界坐标点但Agent可能无法到达 agent.SetDestination(hit.point); } } } } }3.4 创建静态雕刻障碍物Static Carved Obstacle为了对比Carve和Avoid的区别我们再创建一个静态障碍物。创建一个Cube缩放一下做成一个类似路障的形状放在Agent的必经之路上。添加NavMeshObstacle组件。关键配置Shape: CubeCarve:勾选Carve Only Stationary:勾选重要Time To Stationary: 0.5Carve Stationary: 勾选不需要任何移动脚本。运行测试运行游戏用鼠标点击让PlayerAgent移动。当Agent走向目标时它会提前绕开那个静态的Carve障碍物因为它的全局路径在计算时就已经避开了被“雕刻”掉的区域。同时Agent会实时地、平滑地避让那个来回移动的DynamicObstacleAvoid模式不会撞上去也不会因为全局路径穿过其移动轨迹而卡住。4. 高级技巧与性能优化实战基础功能跑通后我们会遇到更实际的问题几十上百个单位同时避障会不会卡不同大小的单位如何设置参数如何让避障行为更符合游戏性4.1 分层管理与参数差异化你不能让所有单位都用High质量的避障。我的项目里是这么做的精英单位/玩家Avoidance Quality High,Priority 10-30。他们移动最智能享有路权。普通小兵Avoidance Quality Medium,Priority 50。平衡效果和性能。背景NPC/小动物Avoidance Quality Low或甚至关闭避障(Quality None)Priority 70-99。他们纯粹是场景点缀移动简单并且必须为重要单位让路。如何批量设置我写了一个编辑器脚本或者更简单的方法创建不同的Prefab预制体为每一类单位配置好不同的NavMeshAgent参数。4.2 动态调整避障行为避障行为不应该是一成不变的。例如冲锋状态当单位发动冲锋技能时可以临时提高其优先级比如从50调到10并略微增大避障半径让其他单位纷纷“退避三舍”营造出势不可挡的感觉。恐慌/逃跑状态可以降低避障质量High - Medium甚至允许一定程度的碰撞表现出慌不择路的效果。编队移动当多个单位以密集队形移动时可以临时调小它们之间的避障半径或者降低它们互相之间的避障优先级让队形保持得更紧密而不是互相推搡散开。// 示例冲锋技能时调整Agent参数 public class ChargeSkill : MonoBehaviour { public NavMeshAgent agent; public float originalPriority; public float chargePriority 10f; public float chargeRadiusMultiplier 1.5f; private float originalRadius; void Start() { if (agent null) agent GetComponentNavMeshAgent(); originalPriority agent.avoidancePriority; originalRadius agent.radius; } public void StartCharge() { agent.avoidancePriority (int)chargePriority; agent.radius originalRadius * chargeRadiusMultiplier; // 可能还需要增加速度、改变动画等 } public void EndCharge() { agent.avoidancePriority (int)originalPriority; agent.radius originalRadius; } }4.3 性能监控与瓶颈分析动态避障的计算主要发生在NavMeshAgent系统内部Unity每帧会处理所有激活的Agent。你可以通过Profiler窗口的Navigation类别来监控性能。主要开销ProcessAvoidance。这是执行避障计算的核心函数。当屏幕上单位很多时这个数字会上升。优化策略距离裁剪Distance Culling对于距离玩家很远、或者不在摄像机视野内的单位禁用它们的NavMeshAgent组件agent.enabled false。这是最有效的优化。你可以根据距离或通过OnBecameVisible/OnBecameInvisible回调来管理。更新频率降低Update Rate对于不重要的单位可以不每帧都更新Agent。你可以写一个管理器将这些单位分到不同的更新组里每隔2-3帧更新一次位置和寻路。注意这会影响移动的流畅度。简化碰撞形状NavMeshObstacle使用Cube或Capsule比使用复杂的Mesh Collider性能要好得多。尽量用简单的几何体来近似表示障碍物。控制总数这是最根本的。同时进行高质量避障的单位数量需要有一个上限。根据目标平台性能来设定。4.4 与动画系统的协调避障是逻辑层的移动最终要体现在视觉层的动画上。这里有个常见的“滑步”问题Agent的移动速度可能因为避障而忽快忽慢、方向突变如果动画的移动速度是固定的就会产生脚底打滑。解决方案使用Root Motion或速度同步。如果使用Animator的Root Motion那么角色的实际位移由动画驱动。你需要将NavMeshAgent的updatePosition和updateRotation设置为false然后在OnAnimatorMove()回调中用agent.nextPosition和agent.desiredVelocity来同步Animator的状态并让Agent知道角色实际移动了多少agent.velocity animator.deltaPosition / Time.deltaTime。这套流程稍复杂但动画融合效果最好。更简单的方法是不使用Root Motion让动画系统适配逻辑速度。在Animator Controller中使用一个基于agent.velocity.magnitude速度大小的参数来混合“待机”、“行走”、“奔跑”动画。同时将角色的旋转transform.rotation平滑地插值到agent.desiredVelocity的方向上。这样当Agent因避障减速或转向时动画也能平滑过渡。// 简化的速度同步示例挂在有NavMeshAgent和Animator的对象上 public class AgentAnimationSync : MonoBehaviour { private NavMeshAgent agent; private Animator animator; public float rotationSmoothTime 0.1f; private float turnSmoothVelocity; void Start() { agent GetComponentNavMeshAgent(); animator GetComponentInChildrenAnimator(); } void Update() { // 1. 将速度传递给Animator float speed agent.velocity.magnitude / agent.speed; // 归一化速度 animator.SetFloat(Speed, speed, 0.1f, Time.deltaTime); // 使用阻尼平滑数值 // 2. 平滑旋转到移动方向 if (agent.velocity.magnitude 0.1f) // 有移动输入时 { float targetAngle Mathf.Atan2(agent.velocity.x, agent.velocity.z) * Mathf.Rad2Deg; float angle Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, rotationSmoothTime); transform.rotation Quaternion.Euler(0f, angle, 0f); } } }5. 避坑指南那些我踩过的“坑”和解决方案在实际项目中我把能踩的坑几乎都踩了一遍。下面这个表格总结了我遇到的最典型问题、原因和解决办法希望能帮你节省大量调试时间。问题现象可能原因解决方案与排查步骤障碍物完全被忽略Agent直接穿过去1.NavMeshObstacle组件未启用。2. 障碍物没有Collider组件某些形状需要。3. Agent的Obstacle Avoidance的Quality设置为None。4. 障碍物和Agent的Layer被导航系统忽略。1. 检查NavMeshObstacle组件的勾选框。2. 确保有合适的ColliderBox, Capsule等。3. 将Agent的Avoidance Quality至少设为Low。4. 在Navigation窗口的Object面板检查该Layer是否被设置为Navigation Static或影响了生成。避障反应迟钝Agent快撞上了才转向1. Agent的Avoidance Radius设置过小。2. 障碍物的Time To Stationary设置过长影响Avoid模式感知。3.移动的障碍物没有调用Carve()方法更新数据。1. 适当增大Agent的Avoidance Radius。2. 对于移动障碍将Time To Stationary设小如0.1-0.3。3.在障碍物移动的脚本中每帧或定时调用obstacle.Carve()。Agent在静止的Carve障碍物前“抖动”或“抽搐”1. 障碍物的Carve和Carve Only Stationary设置矛盾。2. 障碍物的物理碰撞体与NavMeshObstacle形状/位置不匹配。3. Agent的路径终点在障碍物“雕刻”出的空洞边缘导致它不断尝试靠近又被打断。1. 确保勾选Carve时也勾选Carve Only Stationary。2. 检查两者是否对齐。可以用Gizmos可视化。3. 检查目标点设置或为Agent设置合理的Stopping Distance。多个Agent挤在一起时“卡死”谁也不动1. 所有Agent优先级相同且避障半径过大陷入“死锁”。2. 空间过于狭窄没有物理上的可行走路径。3. Agent的Auto Braking和避障逻辑冲突。1.差异化优先级这是最重要的技巧。2. 检查场景NavMesh是否连通或考虑使用NavMeshLink连接断开区域。3. 尝试在拥挤时临时关闭部分Agent的避障或使用脚本控制它们“侧身”通过。性能急剧下降大量单位时1. 大量单位同时进行High质量避障计算。2. 大量移动的障碍物每帧调用Carve()。3. 没有对不可见/远距离单位做裁剪。1. 采用4.1和4.3的分层与裁剪策略。2. 对于非关键移动障碍降低其Carve()的调用频率如每3帧一次。3. 使用距离或视锥裁剪禁用远处单位的NavMeshAgent。Agent避障时“画圈”或绕远路Agent的Avoidance Radius设置过大。适当调小Avoidance Radius使其更接近模型的真实物理尺寸。在场景中放置一个与避障半径等大的球形Gizmo进行可视化调试非常有用。动态障碍物移除后Agent仍绕路走使用了Carve模式的障碍物在销毁或禁用后其“雕刻”出的空洞没有自动恢复。这是Unity旧版本的一个已知问题新版本已改善。确保销毁障碍物GameObject或禁用NavMeshObstacle组件。你也可以尝试在销毁前手动设置obstacle.carving false并等待一帧。最重要的心得一定要在场景中可视化调试信息。在Navigation窗口的Debug面板勾选Show Navigation Meshes和Show NavMesh Obstacles。这样你就能在Scene视图中清晰地看到蓝色的静态NavMesh。被Carve障碍物“挖掉”的红色空洞。Avoid模式障碍物产生的黄色或其它颜色的避让区域。Agent的当前路径绿色线和下一步的移动方向。眼见为实很多逻辑问题通过可视化工具一看就明白了。6. 完整代码示例与扩展思路最后我将提供一个整合了上述所有要点的、更健壮的Agent控制脚本。这个脚本包含了点击移动、动态参数调整、简单的状态管理以及与动画系统的接口。using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent), typeof(Animator))] public class AdvancedAgentController : MonoBehaviour { // 导航相关 private NavMeshAgent agent; private Camera mainCam; // 动画相关 private Animator animator; private int speedParamHash; public float animSmoothTime 0.1f; private float currentSpeed; // 避障参数可动态调整 [Header(避障设置)] public float baseAvoidRadius 0.6f; public int basePriority 50; public ObstacleAvoidanceType baseAvoidQuality ObstacleAvoidanceType.HighQuality; // 状态 public enum AgentState { Normal, Charging, Fleeing } private AgentState currentState AgentState.Normal; void Start() { agent GetComponentNavMeshAgent(); animator GetComponentInChildrenAnimator(); mainCam Camera.main; speedParamHash Animator.StringToHash(Speed); // 初始化避障参数 ApplyNavMeshSettings(); } void Update() { HandleInput(); UpdateAnimation(); } void HandleInput() { if (Input.GetMouseButtonDown(0)) { Ray ray mainCam.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { // 更精确的目标点采样 NavMeshHit navHit; float sampleDistance 1.0f; if (NavMesh.SamplePosition(hit.point, out navHit, sampleDistance, NavMesh.AllAreas)) { agent.SetDestination(navHit.position); } else { Debug.LogWarning(点击位置附近没有可行的导航网格。); } } } // 示例按键切换状态 if (Input.GetKeyDown(KeyCode.Alpha1)) SetState(AgentState.Normal); if (Input.GetKeyDown(KeyCode.Alpha2)) SetState(AgentState.Charging); if (Input.GetKeyDown(KeyCode.Alpha3)) SetState(AgentState.Fleeing); } void UpdateAnimation() { // 计算归一化速度0到1之间 float targetSpeed agent.velocity.magnitude / agent.speed; // 使用平滑阻尼让动画过渡更自然 currentSpeed Mathf.SmoothDamp(currentSpeed, targetSpeed, ref animSmoothTime, 0.1f); animator.SetFloat(speedParamHash, currentSpeed); // 平滑转向如果速度足够 if (agent.velocity.magnitude 0.1f) { Vector3 direction agent.velocity.normalized; direction.y 0; // 保持水平旋转 if (direction.magnitude 0.01f) { Quaternion targetRotation Quaternion.LookRotation(direction); transform.rotation Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * agent.angularSpeed / 360f); } } } public void SetState(AgentState newState) { if (currentState newState) return; currentState newState; ApplyNavMeshSettings(); Debug.Log($Agent状态切换为: {newState}); } void ApplyNavMeshSettings() { switch (currentState) { case AgentState.Normal: agent.radius 0.5f; agent.avoidancePriority basePriority; agent.obstacleAvoidanceType baseAvoidQuality; agent.speed 5f; break; case AgentState.Charging: agent.radius 0.5f * 1.3f; // 冲锋时感觉体积更大 agent.avoidancePriority 10; // 高优先级别人让路 agent.obstacleAvoidanceType ObstacleAvoidanceType.HighQuality; agent.speed 8f; break; case AgentState.Fleeing: agent.radius 0.5f; agent.avoidancePriority 80; // 低优先级主动避让 agent.obstacleAvoidanceType ObstacleAvoidanceType.MediumQuality; // 降低质量允许一些碰撞 agent.speed 7f; break; } // 避障半径通常略大于物理半径 agent.avoidanceRadius agent.radius * 1.2f; } // 在Scene视图中绘制Gizmos方便调试 void OnDrawGizmosSelected() { if (agent ! null) { // 绘制避障半径 Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, agent.avoidanceRadius); // 绘制物理半径 Gizmos.color Color.green; Gizmos.DrawWireSphere(transform.position, agent.radius); } } }扩展思路群体移动与队形结合动态避障你可以实现更智能的群体移动。让一个“队长”单位负责寻路其他队员以相对队长的偏移位置作为目标点。同时队员之间启用避障这样既能保持队形又能灵活应对路上的动态障碍。与行为树/状态机集成将不同的避障参数配置如Normal, Combat, Patrol作为状态机或行为树节点的一部分。当AI进入“战斗”状态时自动切换到更激进、优先级更高的避障模式。动态生成障碍物比如爆炸效果、临时生成的魔法墙。你可以写一个脚本在运行时实例化一个带有NavMeshObstacle的GameObject并根据技能范围设置其形状和大小并在一段时间后销毁它。这能创造出非常动态的战场环境。混合使用Carve和Avoid想象一个可破坏的场景。一堵墙被炸毁掉落的碎石块。大块的、静止的碎石可以用Carve模式永久改变地形。而一些还在滚动的小石块则用Avoid模式作为临时障碍物直到它们停止滚动后再切换为Carve模式通过检测其速度当速度接近0时修改NavMeshObstacle的carve属性。Unity 2023.2的动态避障功能把之前需要第三方插件或复杂自研算法才能实现的效果变成了开箱即用的引擎基础能力。它可能不是最顶尖、最定制化的解决方案但对于绝大多数游戏项目来说其性能、效果和易用性已经达到了一个非常理想的平衡点。关键在于理解其运作机制根据你的游戏需求精细地调配参数并做好性能管理。希望这篇结合了实战代码和踩坑经验的分享能让你在开发中少走弯路更快地创造出那些灵动而富有生气的游戏世界。