ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Unity 2D游戏智能寻路实战:NavMeshPlus配置与避坑指南

Unity 2D游戏智能寻路实战:NavMeshPlus配置与避坑指南 1. 项目概述为什么2D游戏也需要智能寻路在Unity里做2D游戏尤其是俯视角的RPG、塔防或者策略游戏角色移动是个绕不开的坎。很多新手朋友一开始可能会用Transform.Translate或者Vector2.MoveTowards写个简单的直线移动对付固定路径还行。但一旦场景复杂起来有障碍物、有动态变化的通道角色要是还像个没头苍蝇一样撞墙那游戏体验可就大打折扣了。这就是智能寻路要解决的问题。它能让你的游戏角色像真人一样自己找到从A点到B点的最优或可行路径自动绕开障碍。在3D游戏里Unity自带的NavMesh导航网格系统是标配功能强大。但到了2D世界官方并没有提供原生的2D NavMesh解决方案。直接拿3D的NavMesh来用也不是不行但你需要把2D的Sprite强行放到3D空间里处理Z轴、碰撞体朝向等问题非常别扭而且性能开销和配置复杂度都上去了。所以社区里的大神们就开发了NavMeshPlus。这是一个专门为Unity 2D项目打造的导航网格系统它继承了Unity NavMesh的核心思想但完全在2D的坐标系下工作。你可以把它理解为“2D版的NavMesh”。它的目标很明确让你能用类似3D NavMesh的流程和工作流轻松为你的2D精灵实现智能寻路。我最近在一个2D俯视角的生存建造项目里深度用了一把NavMeshPlus从踩坑到跑通感触颇深。这篇文章我就把自己从零配置、到让怪物智能追击玩家、再到处理动态障碍物的完整过程以及那些官方文档里没写的“坑”和技巧毫无保留地分享出来。无论你是刚接触2D寻路的新手还是正在寻找更优雅2D导航方案的老手这篇教程都能给你一套可直接“抄作业”的配置流程和实战心法。2. 核心思路与工具选型为什么是NavMeshPlus在决定使用NavMeshPlus之前我们得先理清2D寻路的几种常见方案以及它们各自的优劣。这样你才能明白为什么在特定场景下NavMeshPlus是更优解。2.1 常见2D寻路方案对比A寻路算法*原理基于网格Grid或点阵Waypoint的经典算法通过评估每个节点的代价来寻找最短路径。优点极度灵活路径精确社区资源丰富如A* Pathfinding Project插件。缺点需要预先生成网格或布置路点对于大型、开放或地形复杂的2D场景网格数据量可能很大。动态更新障碍如玩家建造的墙需要实时更新网格有一定性能开销。配置和调优相对复杂。Raycast 或 Physics2D.Raycast 探测原理朝目标方向发射射线检测碰撞遇到障碍则尝试调整方向。优点实现简单适合行为非常简单的AI如只朝玩家直线移动碰壁后随机转向。缺点无法处理复杂地形极易卡在角落或U型障碍里寻路结果不智能更像是“碰壁反应”而非“路径规划”。Unity 3D NavMesh 降维使用原理将2D平面视为3D的XZ平面或XY平面为3D物体即使它只是个薄片生成导航网格。优点可以直接使用Unity成熟、高效的NavMesh系统包括动态障碍物NavMeshObstacle。缺点工作流不原生需要处理3D碰撞体、3D代理NavMeshAgent在纯2D项目中显得格格不入容易引发坐标系混乱和渲染层级问题。NavMeshPlus原理专为2D设计在2D碰撞体如BoxCollider2D,PolygonCollider2D的基础上生成2D空间的导航网格。使用NavMeshAgent2D组件进行移动。优点工作流原生完全在2D坐标系下工作与SpriteRenderer、Collider2D等组件无缝衔接概念清晰。性能与功能平衡继承了NavMesh的快速路径查询和本地规避Local Avoidance潜力对于大多数2D游戏来说性能足够。动态更新支持通过NavMeshModifier等组件可以相对高效地处理场景中动态变化的可行走区域。社区驱动持续维护作为Unity官方“Experimental”仓库下的项目有一定的质量保证和社区支持。注意NavMeshPlus目前撰写本文时仍处于“实验性Experimental”阶段这意味着它的API在未来的版本中可能会有变动。但对于大多数稳定开发的2D项目来说其核心功能已经非常可靠。建议从GitHub仓库下载发布版Release而非直接克隆开发分支以获得更高稳定性。2.2 NavMeshPlus 核心组件一览在开始配置前我们先快速认识一下NavMeshPlus里的几个关键组件这有助于理解后续的配置流程NavMeshSurface2D这是大脑。你需要把它挂载在场景中的一个空物体上通常命名为“Navigation”或“NavMeshSurface”。它的职责是收集场景中标记了特定层的2D碰撞体并根据这些碰撞体的形状烘焙Bake生成整个场景的导航网格数据。NavMeshAgent2D这是执行者。你需要把它挂载在需要智能移动的游戏对象上比如你的玩家角色或敌人。它包含了速度、加速度、转向速度、停止距离等参数负责根据NavMeshSurface2D生成的网格来计算路径并控制对象移动。NavMeshModifier/NavMeshModifierVolume2D这是规则制定者。它们可以附加在场景中的物体上用来局部修改导航网格的生成规则。比如你可以用一个NavMeshModifier让某个平台只能被“飞行”类型的Agent行走或者用NavMeshModifierVolume2D定义一个区域禁止任何Agent进入。理清了这些我们的配置目标就很明确了首先用NavMeshSurface2D根据场景地形烘焙出“可行走的地图”然后为角色添加NavMeshAgent2D并编写脚本让它去目标点最后利用NavMeshModifier来处理一些特殊规则。3. 环境准备与项目导入工欲善其事必先利其器。在开始烘焙导航网格之前我们需要先把NavMeshPlus正确地导入到你的Unity项目中。3.1 获取NavMeshPlus官方推荐的方式是通过Unity的Package Manager从Git仓库添加。这是最规范、便于后续更新的方法。打开你的Unity项目建议使用2020.3 LTS或更新版本兼容性更好。在顶部菜单栏选择Window Package Manager。在Package Manager窗口左上角点击“”按钮选择“Add package from git URL...”。在弹出的输入框中粘贴NavMeshPlus的Git仓库地址https://github.com/h8man/NavMeshPlus.git点击“Add”按钮。Unity会开始下载并导入这个包。这个过程可能需要一点时间取决于你的网络。实操心得有时直接从Git URL添加可能会因为网络问题失败。备选方案是去它的GitHub发布页面https://github.com/h8man/NavMeshPlus/releases下载最新的.unitypackage文件然后像导入普通资源包一样双击导入。我个人更推荐Git URL方式因为它能更好地与Package Manager集成未来检查更新更方便。导入成功后你可以在Package Manager的列表里看到“NavMesh Plus (Experimental)”。同时在你的项目窗口中应该能看到一个“Packages”目录里面包含了NavMeshPlus的代码和示例。3.2 基础场景搭建为了演示我们创建一个最简单的测试场景。你可以跟着做也可以在你的项目场景中直接操作。创建地形在场景中创建一些2D精灵Sprite作为地面和障碍物。例如创建一个名为“Ground”的Sprite缩放成一个大的矩形作为主地面。创建几个名为“Wall”或“Obstacle”的Sprite缩放成矩形或任意形状散落在“Ground”上作为障碍物。添加碰撞体这是最关键的一步NavMeshPlus是依靠2D碰撞体来识别边界和生成导航网格的。为“Ground”添加一个BoxCollider2D组件。这定义了“可行走区域”的范围。为每一个“Obstacle”添加BoxCollider2D或PolygonCollider2D组件。这定义了“障碍物”的范围。创建角色创建一个简单的圆形或方形Sprite命名为“Player”或“Agent”。暂时不用为它添加任何移动脚本。现在你的场景应该包含一个有碰撞体的地面几个有碰撞体的障碍物和一个没有脚本的角色。层级视图Hierarchy大概类似这样- Ground (带有Sprite Renderer和BoxCollider2D) - Obstacle_1 (带有Sprite Renderer和BoxCollider2D) - Obstacle_2 - Player (带有Sprite Renderer暂无其他组件)4. 核心配置烘焙你的第一张2D导航网格有了场景和导入的包接下来就是最核心的步骤——烘焙导航网格。这个过程和在3D里用NavMesh Surface几乎一样直观。4.1 创建并配置NavMeshSurface2D在层级视图Hierarchy中右键选择Create Empty将新建的空物体命名为“Navigation”或“NavMeshSurface”。选中这个空物体在检查器Inspector中点击Add Component。在组件搜索框中输入“NavMeshSurface2D”并添加该组件。现在我们来配置这个NavMeshSurface2D组件。你会看到如下几个关键参数Agent Type代理类型。这定义了一组导航属性如高度、半径、坡度等。对于纯2D我们通常使用默认的“Humanoid”即可或者点击“...”新建一个类型主要关注“Agent Radius”代理半径和“Height”高度在2D中意义不大但需要大于0。Collect Objects收集对象。决定从哪些物体收集几何体。通常选择“Volume”或“All”。对于2D场景我强烈推荐使用“Volume”因为它允许你通过一个2D碰撞体来精确划定需要烘焙的区域性能更好控制更精准。Size当“Collect Objects”为“Volume”时这个参数出现。它定义了收集区域的矩形大小。你可以手动输入数值或者更方便的是点击组件上的“Add Component”按钮。添加一个BoxCollider2D组件到“Navigation”这个游戏对象上。回到NavMeshSurface2D组件你会发现“Size”参数旁边出现了一个小圆圈图标。点击它然后选择我们刚添加的BoxCollider2D组件。这样NavMeshSurface2D就会自动使用这个BoxCollider2D的形状和大小作为烘焙区域。调整这个BoxCollider2D的大小让它完全覆盖你的“Ground”地面但不要过大以免包含无用的空白区域。Layer Mask层掩码。这是极其重要的过滤器它决定了NavMeshSurface2D会收集哪些层Layer上的物体的碰撞体来生成导航网格。Use Geometry使用几何体。选择使用物体的“Physics Colliders”物理碰撞体来生成网格。对于2D就选这个。Default Area默认区域。表示生成网格的区域类型。通常就是“Walkable”可行走。4.2 设置图层Layer与烘焙NavMeshPlus通过图层来区分“可行走地面”和“障碍物”。这是一种清晰高效的管理方式。创建图层打开Unity的图层设置Edit Project Settings Tags and Layers。在“Layers”列表里找到用户层User Layers 8-31定义两个新图层例如Walkable(用于地面)Obstacle(用于障碍物)分配图层将“Ground”对象的图层设置为“Walkable”。将所有“Obstacle”对象的图层设置为“Obstacle”。配置Layer Mask回到“Navigation”对象上的NavMeshSurface2D组件。找到“Layer Mask”参数。这里需要仔细设置我们的目标是收集“Walkable”层上的碰撞体作为“可行走区域”同时收集“Obstacle”层上的碰撞体作为“需要被挖空障碍的区域”。因此在“Layer Mask”中你需要同时勾选“Walkable”和“Obstacle”。是的两个都选。NavMeshSurface2D会收集这两个层上的所有碰撞体几何信息但在生成网格时它会将“Walkable”层上的区域标记为可行走而“Obstacle”层上的区域则会被排除挖空。首次烘焙确保“Navigation”对象被选中在NavMeshSurface2D组件底部找到并点击“Bake”按钮。如果一切配置正确你将在场景视图中看到蓝色的导航网格覆盖了“Ground”区域并且在“Obstacle”物体所在的位置出现了蓝色的“空洞”。这表示导航网格已经成功生成角色可以在蓝色区域行走并自动避开那些空洞障碍物。踩坑记录最常见的烘焙失败就是一片空白看不到蓝色网格。请按以下顺序排查检查Layer Mask确保勾选了包含地面碰撞体的图层如“Walkable”。检查Collect Objects模式如果用了“Volume”确保附加的BoxCollider2D大小和位置正确覆盖了地面。检查碰撞体确保“Ground”和“Obstacle”对象上确实有Collider2D组件并且没有勾选“Is Trigger”除非你希望触发器也被视为障碍这通常不是默认行为。检查代理半径如果“Agent Radius”设置得太大而你的通道很窄可能会导致整个区域都无法行走从而不生成网格。可以尝试调小代理半径如从0.5调到0.1再重新烘焙。5. 让角色动起来配置NavMeshAgent2D与基础移动脚本导航网格烘焙好了相当于地图已经绘制完成。接下来我们需要给角色配备一个“导航仪”和“司机”也就是NavMeshAgent2D组件和移动脚本。5.1 添加并配置NavMeshAgent2D选中你的“Player”角色对象。在检查器中点击Add Component搜索并添加“NavMeshAgent2D”组件。配置NavMeshAgent2D的关键参数。这些参数直接影响角色的移动行为Agent Type必须和NavMeshSurface2D烘焙时使用的Agent Type一致通常是默认的“Humanoid”。Speed最大移动速度。Angular Speed转向速度度/秒。在2D中这控制角色转向目标方向时的旋转速度。对于即时战略RTS风格的单位可以设高一些如720对于需要平滑转身的角色可以设低一些如180。Acceleration加速度。值越大达到最大速度越快。Stopping Distance停止距离。当角色距离目标点多远时开始减速并停止。设为0会尝试精确走到目标点中心有时可能导致在目标点附近轻微抖动。根据角色大小设置一个小的正值如0.05通常更平滑。Auto Braking自动制动。勾选后当角色接近目的地时会自动减速。通常建议勾选。Radius代理半径。这个参数至关重要它必须小于或等于NavMeshSurface2D烘焙时所用Agent Type中定义的“Agent Radius”。如果角色的半径大于烘焙网格时允许的半径它将无法在狭窄的通道中寻路。通常保持默认值0.5即可但需要与烘焙设置匹配。Priority优先级。当多个Agent试图通过同一狭窄通道时优先级高的会优先通过。用于群体移动时的避让逻辑。5.2 编写基础移动脚本NavMeshAgent2D组件提供了寻路能力但它需要你通过代码来下达“去哪里”的指令。我们创建一个简单的脚本来测试。在“Player”对象上创建一个新的C#脚本命名为PlayerController2D或SimpleAgentController。打开脚本编写如下基础代码using UnityEngine; using NavMeshPlus.Components; // 注意命名空间 public class SimpleAgentController : MonoBehaviour { private NavMeshAgent2D agent; private Camera mainCamera; void Start() { // 获取组件引用 agent GetComponentNavMeshAgent2D(); if (agent null) { Debug.LogError(NavMeshAgent2D component not found on gameObject.name); } mainCamera Camera.main; } void Update() { // 示例1点击移动常用于RTS、ARPG if (Input.GetMouseButtonDown(0)) // 左键点击 { // 将鼠标屏幕坐标转换为世界坐标 Vector2 mouseWorldPos mainCamera.ScreenToWorldPoint(Input.mousePosition); // 设置代理的目标位置 agent.SetDestination(mouseWorldPos); } // 示例2持续追踪目标常用于敌人AI // 如果你有一个公开的Transform变量target可以这样写 // if (target ! null) // { // agent.SetDestination(target.position); // } } // 一个公共方法方便其他脚本调用让角色移动到指定位置 public void MoveToPosition(Vector2 targetPosition) { if (agent ! null agent.isActiveAndEnabled) { agent.SetDestination(targetPosition); } } }将脚本挂载到“Player”对象上。运行游戏。现在在Game视图中点击鼠标左键你的角色就应该能自动寻路绕过障碍物移动到点击的位置了实操心得agent.SetDestination()是核心方法。调用它后NavMeshAgent2D会在后台自动计算路径并控制角色移动。你不需要在Update中手动修改Transform.position。角色的移动、转向、加减速都由Agent组件根据你设置的参数Speed, Angular Speed等自动处理这极大地简化了移动逻辑代码。6. 高级功能与实战技巧基础寻路跑通后我们来看看NavMeshPlus一些更高级的用法和实战中必然会遇到的问题。6.1 处理动态障碍物NavMeshModifier与实时更新静态障碍物在烘焙时就已经处理好了。但如果你的游戏里有玩家可以建造/拆除的墙、可以开关的门、或者可以移动的箱子这些动态障碍物怎么办方案一使用NavMeshModifier标记动态物体这是处理简单动态障碍的推荐方法。你不需要每次障碍物变化都重新烘焙整个网格那很耗性能而是让Agent在寻路时动态避开这些物体。创建一个会动态出现/消失的障碍物比如一个“Box_Dynamic”。为其添加BoxCollider2D。再添加一个NavMeshModifier组件注意不是NavMeshModifierVolume2D。在NavMeshModifier组件中勾选“Override Area”。在“Area”下拉菜单中选择“Not Walkable”。这告诉导航系统这个物体占据的区域是不可行走的。确保“Affects Agent Type”包含了你的Agent类型如Humanoid。现在无论这个物体是开始时禁用后来启用还是在游戏过程中移动只要它处于激活状态且碰撞体开启NavMeshAgent2D在计算路径时就会尝试绕开它。方案二使用NavMeshObstacle2D如果未来版本支持在3D NavMesh中NavMeshObstacle组件是处理动态障碍物的标准方式它能创建更精确的规避形状。NavMeshPlus的实验版本可能包含或不完全支持类似的NavMeshObstacle2D。如果可用它的用法类似添加到动态物体上设置形状和大小Agent会自动避开。方案三手动更新NavMeshSurface适用于大面积地形变化如果是一大片区域的地形发生了永久性改变比如桥梁被炸毁那么可能需要部分或全部重新烘焙导航网格。获取你的NavMeshSurface2D实例。调用其BuildNavMesh()或UpdateNavMesh()方法具体方法名需查看当前API。这个方法可能会比较耗时不建议每帧调用。避坑指南对于频繁移动的障碍物比如来回巡逻的敌人不建议将其直接设为导航障碍因为这会导致所有其他Agent的路径频繁重新计算性能开销大。更常见的做法是移动的敌人本身也是一个NavMeshAgent2D它们之间通过Agent的“避让”功能基于半径和优先级来相互避开而不是把彼此当作静态障碍物纳入网格计算。6.2 多区域与区域代价实现复杂地形想象一下你的游戏里有草地、沼泽、公路。在草地上走正常速度在沼泽里走得很慢在公路上走得很快。这可以通过NavMesh的“区域Area”和“代价Cost”来实现。定义区域在Unity菜单栏打开Window AI Navigation这是Unity标准导航窗口NavMeshPlus的部分设置与之共享。在“Areas”标签页你可以看到默认的区域列表Walkable, Not Walkable, Jump等。你可以点击“”创建自定义区域例如“Grass”, “Swamp”, “Road”。为区域设置代价每个区域都有一个“Cost”值。默认Walkable的Cost是1。你可以将“Swamp”的Cost设为3意味着走过沼泽的距离相当于走3倍长的平地将“Road”的Cost设为0.5走得更快。在场景中标记区域对于使用“Volume”模式烘焙的NavMeshSurface2D你可以在场景中放置带有NavMeshModifierVolume2D组件的物体。创建一个空物体添加NavMeshModifierVolume2D组件。设置其BoxCollider2D的大小和位置覆盖你想要标记的区域比如一片沼泽地。在组件中勾选“Override Area”并从下拉列表中选择你创建的“Swamp”区域。重新烘焙修改区域后需要重新点击NavMeshSurface2D上的“Bake”按钮。烘焙后你可以在场景视图中通过导航窗口的“Area”颜色覆盖图来查看不同区域需在Navigation窗口的“Bake”页签下勾选“Show NavMesh”并在下拉框选择“Area”。Agent感知代价NavMeshAgent2D在寻路时会自动计算路径的总代价并倾向于选择总代价最低的路径。所以即使从A到B穿过沼泽是直线距离更短但如果绕远走公路的总代价距离*成本更低Agent可能会选择绕路。6.3 常见问题排查与性能优化问题1角色在目标点附近抖动或打转原因通常是因为“Stopping Distance”设置过小为0而Agent的“Radius”和“Speed”较高导致它无法精确停在一点不断微调位置。解决适当增加“Stopping Distance”如0.1到0.3。或者在脚本中当Agent非常接近目标时agent.remainingDistance 0.1f手动将agent.isStopped设为true来停止Agent。问题2角色卡在角落或两个障碍物之间原因可能是烘焙时“Agent Radius”设置过小导致生成的网格通道比Agent的实际物理半径还窄。也可能是障碍物的碰撞体之间有微小的缝隙。解决检查烘焙用的“Agent Radius”是否小于或等于角色的NavMeshAgent2D.radius。在烘焙前确保障碍物碰撞体之间没有可能导致Agent卡住的、比Agent直径更窄的缝隙。可以适当让障碍物碰撞体稍微重叠或者使用NavMeshModifierVolume2D来定义一个明确的不可行走区域覆盖缝隙。考虑启用或调整Agent的“Auto Traverse Off Mesh Link”和“Obstacle Avoidance”相关属性如果NavMeshPlus版本支持但2D中简单的本地避障可能不如3D成熟。问题3烘焙后某些应该能走的地方没有蓝色网格原因除了之前提到的Layer Mask和碰撞体问题还可能是“烘焙区域”没有完全覆盖该地方。解决检查NavMeshSurface2D上用于定义“Volume”的BoxCollider2D是否足够大。确保“Use Geometry”选项正确。性能优化建议分层烘焙如果你的场景非常大可以考虑使用多个NavMeshSurface2D每个负责一个区域如一个房间、一个楼层然后通过脚本来激活/停用它们。这比烘焙一个巨大的网格更高效。简化碰撞体导航网格的复杂度直接取决于用于烘焙的碰撞体网格的复杂度。对于不规则形状的障碍物使用PolygonCollider2D时尽量用最少的点来勾勒形状。避免使用高精度的碰撞体来烘焙导航。控制动态更新尽量避免在运行时频繁调用完整的BuildNavMesh()。对于动态障碍优先使用NavMeshModifier。7. 实战案例构建一个简单的2D塔防敌人寻路让我们把上面的知识串联起来实现一个塔防游戏中敌人从出生点走向终点的经典寻路。场景设置创建一个有曲折路径的2D地图例如一条用Sprite铺成的路两边是障碍。路径的起点放一个空物体命名为“SpawnPoint”。路径的终点放一个空物体命名为“EndPoint”可以附加一个带有Collider2D的触发器来检测敌人到达。为整个可行走的“路径”地面设置“Walkable”层并为两旁的障碍物设置“Obstacle”层。使用NavMeshSurface2DVolume模式烘焙出路径的导航网格。敌人预制体EnemyPrefab创建一个敌人精灵。添加NavMeshAgent2D组件配置速度、加速度等。添加一个C#脚本EnemyAI。敌人AI脚本using UnityEngine; using NavMeshPlus.Components; public class EnemyAI : MonoBehaviour { private NavMeshAgent2D agent; public Transform target; // 在Inspector中拖入EndPoint void Start() { agent GetComponentNavMeshAgent2D(); if (target ! null) { agent.SetDestination(target.position); } else { // 如果没有指定目标可以尝试按标签查找 GameObject endPoint GameObject.FindGameObjectWithTag(EndPoint); if (endPoint ! null) target endPoint.transform; } } void Update() { // 可选实时更新目标位置如果目标是移动的 // if (target ! null) agent.SetDestination(target.position); // 检测是否到达终点通过距离判断 if (target ! null agent.remainingDistance agent.stoppingDistance) { // 敌人到达终点触发扣血等逻辑 Debug.Log(gameObject.name reached the end!); Destroy(gameObject); // 或触发其他事件 } } // 提供一个方法可以在生成敌人时动态设置目标 public void SetTarget(Transform newTarget) { target newTarget; if (agent ! null) agent.SetDestination(target.position); } }生成敌人在“SpawnPoint”位置写一个生成器脚本定时实例化EnemyPrefab并调用其SetTarget方法将“EndPoint”的Transform传递过去。运行游戏敌人就会自动沿着你烘焙好的导航网格路径从出生点智能地走向终点完美绕过所有障碍。你可以通过修改烘焙区域、调整Agent参数让某些敌人走得更慢但血量厚来创造不同的敌人类型。通过这个完整的流程你应该已经掌握了使用NavMeshPlus为Unity 2D游戏添加智能寻路的核心技能。从工具导入、场景配置、网格烘焙到Agent控制、动态障碍处理和高级区域功能这套工作流能够应对绝大多数2D游戏的寻路需求。记住关键是多动手尝试根据自己游戏的具体情况调整参数遇到问题时回头检查图层、碰撞体和烘焙设置这三个最常出错的环节。
返回列表