Unity AI Navigation:使用NavMeshSurface实现坦克与摩托差异化寻路

发布时间:2026/7/9 21:44:27
Unity AI Navigation:使用NavMeshSurface实现坦克与摩托差异化寻路 1. 项目概述当AI角色需要“看懂”世界在Unity里做游戏尤其是涉及多个AI角色的项目最头疼的问题之一就是“寻路”。你肯定不想让游戏里的坦克、士兵或者怪物像个没头苍蝇一样到处乱撞或者卡在墙角、桌子腿旁边动弹不得。一个智能、流畅的寻路系统是游戏沉浸感和策略性的基石。过去我们可能需要手写A*算法或者用NavMesh Agent配合烘焙好的静态NavMesh但一旦场景里有动态障碍物或者不同角色对“路”的理解不同比如坦克能压过灌木丛但步兵不行事情就变得复杂起来。这就是NavMeshSurface组件大显身手的地方。它属于Unity的AI Navigation系统Package Manager里可以找到AI Navigation包但和传统的静态NavMesh烘焙有本质区别。简单说NavMeshSurface让寻路网格NavMesh从一个“静态场景数据”变成了一个“动态的游戏对象”。你可以为不同的AI角色类型创建多个Surface每个Surface可以独立定义哪些物体是可行走的、哪些是障碍物并且能在运行时动态更新。这意味着你的重型坦克可以拥有一个忽略矮墙和灌木的寻路网格而你的轻型摩托则需要一个避开所有障碍的精细网格它们在同一片战场里却能“看到”完全不同的地形。这个项目我们就来实战演练如何利用NavMeshSurface为坦克和摩托这两种移动特性迥异的AI角色构建两套独立且智能的寻路系统。你会学到从环境搭建、角色配置到动态避障的完整流程以及如何避开那些官方文档里没写的“坑”。2. 核心思路与组件选型解析2.1 为什么是NavMeshSurface而不是传统NavMesh传统的Unity NavMesh工作流是在编辑器里选中场景中的静态物体Static然后执行Window AI Navigation烘焙。这会生成一个全局的、单一的导航网格。所有使用NavMeshAgent组件的角色都共享这个网格。这种方式的局限性非常明显静态性烘焙好的网格是静态数据。如果游戏运行时移除了一个箱子障碍物NavMesh不会自动更新AI仍然会认为那里有障碍而绕路。单一性所有Agent共享同一套“可行走”定义。你无法让某些角色能走上斜坡而另一些不能也无法让坦克能碾压某些障碍而步兵需要绕行。管理不便对于大型或动态场景频繁重新烘焙整个场景的NavMesh是不现实的。NavMeshSurface的出现正是为了解决这些问题。它的核心思想是**“按需烘焙动态管理”**。它的核心优势动态对象NavMeshSurface本身是一个GameObject上的组件。你可以像管理其他游戏对象一样管理它启用/禁用、实例化、销毁。局部烘焙每个Surface只关心它管理的NavMeshModifier和NavMeshModifierVolume范围内的几何体。你可以为一个房间、一个区域甚至一种特定类型的“路”创建一个Surface。多网格共存你可以在一个场景中创建多个NavMeshSurface实例每个实例可以关联不同的NavMeshAgent类型通过Agent Type ID。这样坦克和摩托就能拥有各自独立的导航数据。运行时更新通过调用NavMeshSurface.BuildNavMesh()或UpdateNavMesh()可以在游戏运行时动态重建或更新导航网格以响应场景变化如建筑物被摧毁。2.2 项目架构设计坦克与摩托的差异化寻路我们的目标是实现坦克可以无视低矮的障碍如沙袋、灌木丛但需要避开坚固的墙壁和深沟摩托则需要灵活地避开所有障碍物并且对道路的“宽度”要求更高。为了实现这个目标我们需要规划以下核心组件和流程场景准备创建包含不同属性障碍物可碾压的、不可逾越的的测试场景。定义Agent类型在Navigation设置中创建两种不同的Agent Type分别命名为“Tank”和“Motorcycle”并设置不同的半径、高度、坡度、跳跃高度等参数。创建NavMeshSurface在场景中创建两个空的GameObject分别添加NavMeshSurface组件。一个Surface的Agent Type设置为“Tank”另一个设置为“Motorcycle”。使用Modifier标记地形为场景中的物体添加NavMeshModifier组件。例如给灌木丛物体添加Modifier并勾选“Override Area”将其区域类型设置为“Not Walkable”对摩托或“Walkable”对坦克。更精细的控制可以使用NavMeshModifierVolume盒子体积来标记一片区域。关联Surface与Modifier确保每个NavMeshModifier组件都勾选了“Affected Agents”并只选择它想要影响的Agent类型如灌木丛的Modifier只影响“Motorcycle”类型。烘焙导航网格分别选中两个NavMeshSurface组件点击“Bake”按钮。Unity会为每种Agent类型生成独立的导航网格。你可以在Scene视图中通过Gizmos AI Navigation来切换查看不同的网格。配置AI角色为坦克和摩托的Prefab添加NavMeshAgent组件并将Agent Type属性分别设置为对应的“Tank”和“Motorcycle”。编写寻路逻辑编写脚本让NavMeshAgent根据玩家的输入或AI逻辑调用SetDestination()方法前往目标点。注意NavMeshModifier和NavMeshModifierVolume是控制导航网格生成的关键。Modifier作用于单个游戏对象而ModifierVolume是一个3D体积可以影响该体积内所有导航网格的生成。合理使用它们可以精确控制不同AI的“世界观”。3. 环境搭建与核心组件配置详解3.1 初始化项目与导入AI Navigation包首先确保你使用的是较新版本的Unity如2021 LTS或2022 LTS。AI Navigation是一个官方维护的包但需要手动导入。打开Unity新建一个3D项目Core或URP/HDRP根据需求选择。打开Window Package Manager。在Package Manager窗口左上角确保查看的是Unity Registry。在搜索框中输入“Navigation”找到AI Navigation包并点击安装。这个包包含了NavMeshSurface、NavMeshModifier等所有我们需要的组件。安装完成后你会在Component菜单下看到Navigation子菜单里面包含了NavMesh Surface、NavMesh Modifier等选项。3.2 定义坦克与摩托的Agent参数这是差异化寻路的基石。不同的物理参数决定了导航网格如何生成以及Agent如何移动。打开Window AI Navigation窗口。切换到Agents标签页。这里默认有一个“Humanoid”类型。我们需要新建两个。点击号新建一个类型命名为“Tank”。再新建一个命名为“Motorcycle”。分别配置它们的参数下面是一个参考配置体现了两种载具的物理特性差异参数Tank (坦克)Motorcycle (摩托)配置逻辑解析NameTankMotorcycle标识符方便在组件下拉框中选择。Radius1.20.5坦克车体宽大需要更大的半径来避免擦碰摩托灵活半径可以较小。Height2.51.8坦克较高摩托相对低矮。这决定了能通过的门洞或桥洞高度。Step Height0.80.3坦克越野能力强可以爬上较高的台阶或缓坡摩托通过性差。Max Slope45°30°坦克能爬更陡的坡摩托的爬坡能力有限。Drop Height5.01.0坦克可以从较高的平台落下而无损摩托则不行。Jump Distance00本例中两者均不涉及跳跃设为0。实操心得Radius参数尤其重要且容易被低估。如果设置过小Agent在拐弯或贴近障碍物时可能会“卡住”或产生不自然的抖动因为它的碰撞体与导航网格边缘太近。通常这个值应该略小于角色碰撞体半径为路径计算留出一点余量。3.3 构建测试场景与障碍物分类我们需要一个能清晰展示差异的场景。创建一个简单的平面作为地面然后摆放几种典型的障碍物高墙 (Wall)使用Cube拉长拉高制作。对于坦克和摩托这都应是不可逾越的障碍。为其添加NavMeshModifier组件Area Type设置为“Not Walkable”并勾选影响“Tank”和“Motorcycle”两种Agent。灌木丛/沙袋 (Bush)使用低矮的Cube或Sphere制作。对于坦克这应该是可碾压的Walkable对于摩托这应该是需要避开的Not Walkable。这是展示差异化的关键。为灌木丛物体添加NavMeshModifier。Area Type选择“Not Walkable”。在Affected Agents列表中只勾选“Motorcycle”。这意味着这个“不可行走”的标记只对摩托生效坦克会忽略它。狭窄通道 (Alley)用两个长条形的Cube摆出一个狭窄的过道。宽度设置为略大于摩托的直径如1.2米但小于坦克的直径如2.0米。这个通道本身的地面Plane是Walkable的。两边的墙壁Cube按照高墙的方式处理对两者都是Not Walkable。这样在烘焙后只有摩托的导航网格会穿过这个通道坦克的导航网格在这里会是断开的。可破坏箱子 (Breakable Crate)一个Cube用于演示运行时导航更新。初始时它对两者都是障碍Not Walkable影响两者。我们后续写脚本当它被“摧毁”时从NavMesh中移除。提示为了在Scene视图中清晰区分建议给不同类型的障碍物赋予不同的材质颜色。例如墙壁用灰色灌木丛用绿色箱子用棕色。3.4 创建并配置NavMeshSurface现在我们来创建两个导航网格的“管理者”。在场景层级中创建两个空GameObject分别命名为“NavMeshSurface_Tank”和“NavMeshSurface_Motorcycle”。分别为它们添加NavMeshSurface组件 (Component Navigation NavMesh Surface)。配置NavMeshSurface_TankAgent Type: 选择我们之前创建的“Tank”。Collect Objects: 默认为“All”它会收集场景中所有活动且启用的、带有NavMeshModifier或属于特定层的物体。为了更精确的控制也可以选择“Volume”并配合NavMeshModifierVolume但本例中“All”更方便。Use Geometry: 选择“Render Meshes”。这表示使用物体的渲染网格来生成导航网格精度较高。Default Area: 设置为“Walkable”。这是没有Modifier标记的物体的默认区域类型。同样配置NavMeshSurface_Motorcycle将其Agent Type设置为“Motorcycle”。关键步骤——分层烘焙为了避免干扰我们先只启用一个Surface进行烘焙。禁用NavMeshSurface_Motorcycle游戏对象选中NavMeshSurface_Tank在Inspector中点击Bake按钮。等待烘焙完成你会在Scene视图看到蓝色的导航网格并且这个网格会穿过灌木丛但在狭窄通道和墙壁处断开。然后禁用NavMeshSurface_Tank启用NavMeshSurface_Motorcycle选中它并点击Bake。现在你看到的是摩托的导航网格它会绕开灌木丛但能穿过狭窄通道。踩坑记录初次使用时常犯的错误是两个Surface都启用并烘焙后在Scene视图里只能看到一个网格通常是最后烘焙的那个。这是因为Scene视图的导航网格Gizmo默认只显示当前选中Agent类型的网格。你需要点击Scene视图右上角的Gizmos下拉菜单找到AI部分展开Navigation然后勾选你想查看的Agent类型如Tank, Motorcycle。这样就能同时看到多层半透明的导航网格叠加了非常直观。4. 坦克与摩托AI角色的实现与差异化控制4.1 创建角色Prefab与NavMeshAgent配置有了导航网格接下来需要创建能在上面移动的AI角色。创建坦克模型可以用多个Cube拼接一个简单的坦克或者导入一个Asset Store的免费模型。将其做成Prefab命名为“Tank_Prefab”。添加NavMeshAgent给坦克Prefab的根节点添加NavMeshAgent组件 (Component Navigation NavMesh Agent)。配置坦克Agent参数这些参数应与之前定义的“Tank” Agent类型协同工作但这里是实例级别的微调。Agent Type: 选择“Tank”。这是链接到对应NavMeshSurface的关键Speed: 设置为3.0。坦克移动较慢。Angular Speed: 设置为120。坦克转向也较慢。Acceleration: 设置为8。模拟坦克的惯性。Stopping Distance: 设置为0.5。在距离目标点多远时开始减速。Auto Braking: 勾选。到达目的地后自动停止。特别注意Radius、Height、Base Offset等参数不需要在这里设置因为它们由选择的“Tank” Agent类型定义。这里的参数主要控制移动行为。创建摩托Prefab同样方式创建一个简单的摩托模型或Prefab命名为“Motorcycle_Prefab”。配置摩托Agent参数Agent Type: 选择“Motorcycle”。Speed: 设置为8.0。摩托更快。Angular Speed: 设置为300。摩托转向灵活。Acceleration: 设置为12。加速更快。Stopping Distance: 设置为0.2。可以停得更精确。Auto Braking: 勾选。4.2 编写通用的智能寻路控制器我们将编写一个脚本同时适用于坦克和摩托它负责接收移动指令并驱动NavMeshAgent。为了增加一点趣味性我们让坦克移动时带有“转向延迟”的模拟而摩托则更灵敏。using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] public class AIController : MonoBehaviour { private NavMeshAgent agent; private Vector3 targetPosition; private bool hasDestination false; // 用于坦克的转向平滑参数 [Header(Movement Settings)] [SerializeField] private float rotationSmoothTime 0.3f; // 转向平滑时间坦克用 private float turnSmoothVelocity; // 转向平滑速度缓存 [Header(Debug)] [SerializeField] private bool drawPath true; void Start() { agent GetComponentNavMeshAgent(); // 确保Agent类型已在Inspector中设置正确 if (agent null) { Debug.LogError(NavMeshAgent component not found on gameObject.name); } } void Update() { // 示例点击鼠标右键设置目的地 if (Input.GetMouseButtonDown(1)) // 右键点击 { SetDestinationToMouseClick(); } // 如果正在寻路更新转向针对坦克的模拟 if (hasDestination agent.remainingDistance agent.stoppingDistance) { // 对于坦克我们可能希望有更平滑的转向而不是Agent的瞬间转向 // 摩托可以依赖Agent自身的转向这里做个差异化处理 if (agent.agentTypeID NavMesh.GetSettingsByIndex(0).agentTypeID) // 这里需要根据你的Agent类型ID判断简化示例 { SmoothFaceMovementDirection(); } } // 调试绘制路径 if (drawPath agent.hasPath) { Debug.DrawLine(transform.position, agent.steeringTarget, Color.yellow); for (int i 0; i agent.path.corners.Length - 1; i) { Debug.DrawLine(agent.path.corners[i], agent.path.corners[i 1], Color.blue); } } } /// summary /// 设置AI的目的地 /// /summary /// param namedestination世界坐标下的目标点/param public void SetDestination(Vector3 destination) { if (agent ! null agent.isActiveAndEnabled) { // 先检查目标点是否在导航网格上 if (NavMesh.SamplePosition(destination, out NavMeshHit hit, 1.0f, agent.areaMask)) { agent.SetDestination(hit.position); targetPosition hit.position; hasDestination true; } else { Debug.LogWarning(Destination is not reachable on NavMesh: destination); hasDestination false; } } } /// summary /// 清除当前目的地停止移动 /// /summary public void StopMovement() { if (agent ! null) { agent.isStopped true; hasDestination false; } } /// summary /// 示例通过鼠标点击设置目的地 /// /summary private void SetDestinationToMouseClick() { Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hitInfo)) { SetDestination(hitInfo.point); } } /// summary /// 平滑地面向移动方向模拟坦克转向惯性 /// /summary private void SmoothFaceMovementDirection() { if (agent.velocity.sqrMagnitude Mathf.Epsilon) { Vector3 direction agent.velocity.normalized; direction.y 0; // 保持水平旋转 if (direction.sqrMagnitude Mathf.Epsilon) { float targetAngle Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg; float angle Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, rotationSmoothTime); transform.rotation Quaternion.Euler(0f, angle, 0f); } } } // 在Inspector中显示一些只读信息便于调试 void OnGUI() { GUILayout.BeginArea(new Rect(10, 10, 300, 100)); GUILayout.Label(AI Controller: gameObject.name); GUILayout.Label(Has Destination: hasDestination); if (agent ! null) { GUILayout.Label(Remaining Distance: agent.remainingDistance.ToString(F2)); GUILayout.Label(Path Status: agent.pathStatus); } GUILayout.EndArea(); } }将这个脚本分别挂载到坦克和摩托的Prefab上。对于坦克Prefab可以适当调高rotationSmoothTime例如0.5以增加转向的沉重感对于摩托可以将其设为0或一个很小的值甚至注释掉平滑转向的代码让其完全由NavMeshAgent控制显得更灵活。4.3 实现动态障碍物与运行时导航更新静态差异化寻路已经实现现在我们来增加动态元素让寻路系统“活”起来。我们将让之前放置的“可破坏箱子”在受到攻击例如鼠标点击后消失并更新导航网格。为可破坏箱子编写脚本using UnityEngine; public class BreakableObstacle : MonoBehaviour { public NavMeshSurface[] affectedNavMeshSurfaces; // 需要更新的NavMeshSurface数组 public GameObject intactVisual; // 完整的箱子模型 public GameObject brokenVisual; // 破碎的箱子模型可选 public float health 100f; void Start() { if (intactVisual ! null) intactVisual.SetActive(true); if (brokenVisual ! null) brokenVisual.SetActive(false); } // 示例被攻击时调用 public void TakeDamage(float damage) { health - damage; if (health 0) { DestroyObstacle(); } } void DestroyObstacle() { // 1. 切换视觉效果如果有破碎模型 if (intactVisual ! null) intactVisual.SetActive(false); if (brokenVisual ! null) { brokenVisual.SetActive(true); // 可以添加一个延迟销毁破碎模型的操作 Destroy(brokenVisual, 5f); } else { // 如果没有破碎模型直接禁用或销毁碰撞体/渲染器 GetComponentCollider().enabled false; MeshRenderer mr GetComponentMeshRenderer(); if (mr ! null) mr.enabled false; } // 2. **关键步骤更新受影响的导航网格** if (affectedNavMeshSurfaces ! null affectedNavMeshSurfaces.Length 0) { foreach (var surface in affectedNavMeshSurfaces) { if (surface ! null surface.isActiveAndEnabled) { // 方法一完全重建。适用于障碍物变化大的情况但开销较大。 // surface.BuildNavMesh(); // 方法二异步更新推荐。Unity会收集修改并增量更新性能更好。 // 我们需要先告诉NavMesh这个物体发生了变化 // 通常直接销毁或禁用物体NavMesh系统会自动标记相关区域为脏数据。 // 但为了确保立即更新我们可以手动触发。 surface.UpdateNavMesh(surface.navMeshData); } } } // 3. 延迟销毁这个游戏对象如果不需要保留破碎模型 Destroy(gameObject, 0.1f); // 稍等一帧确保NavMesh更新指令发出 } // 示例鼠标点击摧毁 void OnMouseDown() { TakeDamage(100f); } }配置箱子Prefab将上述脚本挂载到箱子上。在Inspector中将affectedNavMeshSurfaces数组大小设为2然后分别将场景中的“NavMeshSurface_Tank”和“NavMeshSurface_Motorcycle”对象拖拽赋值。这意味着箱子被摧毁会影响两个导航网格。确保箱子物体上勾选了Static复选框或在Navigation窗口的Object标签页中其Navigation Static被勾选并且其NavMeshModifier组件标记了对两种Agent都是“Not Walkable”。这样它在初始烘焙时才会被识别为障碍物。运行测试运行游戏用坦克或摩托尝试走向箱子它们会绕行。然后用鼠标点击箱子。箱子“消失”后稍等片刻NavMesh更新需要一两帧再指挥AI走向箱子原来的位置。你会发现AI现在会直接穿过那个区域因为障碍已经不存在导航网格被更新了。重要提示NavMeshSurface.UpdateNavMesh()是部分更新性能优于BuildNavMesh()。但对于大规模动态变化频繁更新仍可能有性能开销。最佳实践是“批量更新”例如在摧毁一批障碍物后统一调用一次更新或者使用NavMeshLink来处理门、桥等特定动态连接点。5. 高级技巧、性能优化与问题排查5.1 多Surface管理技巧与性能考量当场景中有多个NavMeshSurface时管理和性能变得重要。按需加载与烘焙对于开放世界游戏不要一开始就烘焙整个世界的所有Surface。可以采用流式加载当玩家进入一个区域时再动态实例化和烘焙该区域的NavMeshSurface。Surface的粒度是每个房间一个Surface还是每种地形一个Surface这取决于需求。更细的粒度更新更快但管理更复杂更粗的粒度管理简单但更新开销大。一个折中的方案是为频繁变化的区域如战场中心使用独立的Surface。代理与Surface的匹配确保你的NavMeshAgent的Agent TypeID与其要使用的NavMeshSurface的Agent Type完全一致。不一致会导致Agent找不到路径。使用NavMeshQuery进行高级查询对于需要复杂路径判断的AI如RTS游戏中选择多个单位的最佳集结点可以使用NavMeshQuery类进行更底层的导航查询它不依赖于NavMeshAgent。5.2 常见问题与解决方案速查表在实际开发中你肯定会遇到各种奇怪的问题。下面这个表格整理了最常见的一些坑及其解决方法问题现象可能原因解决方案AI角色原地抖动或打转1.Stopping Distance设置过小Agent在接近目标时不断微调。2. 目标点不在NavMesh上Agent在寻找最近点。3. 导航网格在目标点附近有碎片或狭窄区域。1. 适当增加Stopping Distance如0.3-0.5。2. 使用NavMesh.SamplePosition确保目标点有效。3. 检查烘焙设置确保网格连接性避免在目标点放置微小障碍。Agent无视障碍物直接穿模1. 障碍物未被标记为Navigation Static或没有NavMeshModifier。2. Agent的Agent Type对应的NavMeshSurface没有正确烘焙包含该障碍物。3. 障碍物的碰撞体Collider缺失或不是Mesh Collider。1. 检查障碍物在Navigation窗口或Inspector中的静态标记和Modifier设置。2. 确认烘焙了正确的Surface并在Scene视图Gizmo中查看该Surface的网格是否绕开了障碍。3. 确保障碍物有合适的碰撞体NavMesh烘焙主要依赖Mesh信息。烘焙后导航网格缺失或异常1. 用于烘焙的物体如地面Scale异常如X0。2.NavMeshSurface的Collect Objects范围设置错误。3. 场景中物体太多或太复杂烘焙设置如Voxel Size不合适。1. 检查关键物体的Transform Scale确保不为零。2. 尝试将Collect Objects改为“Volume”并调整Size或检查物体Layer是否被Surface包含。3. 在Navigation窗口的Bake标签页尝试增大Agent Radius或Voxel Size来简化网格。运行时更新NavMesh后Agent卡住1. 更新导航网格时Agent正好位于被更新的区域其内部路径数据与新网格冲突。2. 更新是异步的Agent在更新完成前尝试寻路。1. 在更新导航网格前先停止所有受影响区域内的Agent (agent.isStopped true)更新完成后再恢复。2. 使用NavMeshSurface.UpdateNavMeshAsync并等待其完成回调再通知Agent重新计算路径。不同Surface的网格重叠导致AI行为怪异多个Surface的烘焙区域有重叠且Agent可能错误地使用了另一个Surface的网格。确保每个NavMeshAgent的Agent Type与其对应的NavMeshSurface严格匹配。通过NavMesh.GetSettingsByID可以检查ID。在复杂场景中可以使用NavMeshQuery并指定agentTypeID来精确查询路径。移动不平滑有“跳格子”感1. Agent的Speed或Angular Speed设置过高与固定帧率下的物理更新不匹配。2. 导航网格不够平滑存在很多小三角形。1. 适当降低速度或确保在FixedUpdate中处理Agent移动相关逻辑以保持物理步长稳定。2. 在Navigation Bake设置中降低Voxel Size会增加烘焙时间或启用Height Mesh以获得更精确的高度信息。5.3 调试与可视化技巧高效的调试能节省大量时间。Scene视图Gizmo如前所述充分利用Gizmos AI Navigation来分层查看不同Agent类型的导航网格。蓝色代表可行走其他颜色代表不同的Area如红色为不可行走可在Navigation窗口的Areas标签页定义和着色。绘制路径像我们示例脚本中那样使用Debug.DrawLine在Game视图或Scene视图中实时绘制Agent的当前路径agent.path.corners。这能直观看到AI“脑子”里想的路线。NavMeshAgent可视化组件Unity官方有一个隐藏的调试工具NavMeshAgentVisualizer。你可以通过代码[RequireComponent(typeof(NavMeshAgentVisualizer))]来尝试添加或者在Package Manager中搜索社区提供的调试工具。检查IsPathStale和PathStatus在代码中监控NavMeshAgent.pathStatusPathComplete,PathPartial,PathInvalid和isPathStale属性。当目标点变得不可达或路径因动态障碍失效时这些状态会改变你需要让AI重新寻路或执行备用逻辑如寻找掩体。5.4 超越基础NavMeshLink连接特殊路径有时你需要AI进行一些非标准移动比如跳过一个断崖、通过一个传送门、或者爬梯子。这时NavMeshLink组件就派上用场了。NavMeshLink可以在两个导航网格位置之间创建一个“链接”AI在寻路时会将其视为一种特殊的可穿越连接。你可以设置链接的宽度、成本、是否双向通行以及穿越动画需要通过脚本控制。例如为坦克创建一个“跳跃坡道”的链接在两个分离的、但坦克应该能跳过去的平台边缘各放一个空对象。在其中一个对象上添加NavMeshLink组件。设置Start Point和End Point为这两个对象的位置。在NavMeshLink组件上设置Agent Type为“Tank”Cost可以设置高一些表示这不是首选路径Width设置为坦克的宽度。在坦克的寻路逻辑中当它使用这个链接时你可以触发一个跳跃动画。NavMeshLink是连接由NavMeshSurface生成的、可能不连续的导航网格区域的强大工具它能极大地扩展AI移动的可能性。通过这个从理论到实践从静态到动态从基础到进阶的完整流程你应该已经掌握了使用NavMeshSurface为不同AI角色构建智能、差异化寻路系统的核心技能。记住好的寻路系统是隐形的玩家不会注意到它但一旦它出问题游戏体验会立刻崩塌。多测试多调试根据你的游戏需求灵活组合这些工具你的AI角色就能在复杂的世界中游刃有余地行动了。