Unity RTS单位移动实战:基于NavMesh的寻路与群体避障方案

发布时间:2026/10/17 23:24:29
Unity RTS单位移动实战:基于NavMesh的寻路与群体避障方案 想做类似红警那种RTS单位移动的Unity开发者通常第一反应是打开搜索引擎找“A*寻路算法”“Flow Field”再一头扎进各种第三方插件的评测里。我在手边这个项目中把它彻底做完一遍后最大的感触是如果你的目标是先跑通“框选一队坦克右键一点整队绕开障碍物开到目标点”这个经典手感Unity官方那个NavMesh方案就是效率最高的起点。它内置了导航网格烘焙、路径查询和局部避障大部分底层逻辑已经替你写好了。这篇内容就从官方案例和官方组件的角度出发把类红警移动从场景准备到单位寻路、群体避障的完整链路拆开讲。适合不想从零手写寻路算法、想尽快在项目里获得可落地效果的Unity开发者不管是PC端还是移动端这套思路都通用。1. 动手前先拆一拆红警式移动到底在移动什么1.1 移动手感的三层结构很多人照着教程把NavMeshAgent拖到单位身上设置好目的地跑起来发现“能走但根本不是红警那种感觉”。原因在于“红警式移动”不只是一个单纯的状态机切换它其实由三层东西共同构成。第一层是寻路。红警的单位被命令移动时会绕开建筑、残骸、水域等障碍物而不是傻乎乎地直直撞上去。这背后是导航网格或寻路算法在实时计算路径这也是NavMeshAgent最擅长的部分。第二层是群体行为。框选几十个坦克点击地图某个点红警的做法并不是让所有坦克直接冲向同一个坐标而是每个单位独立计算一条路径在行进过程中互相让路、错开位置最终停成一个不太规则的扇形或矩形阵型。这个细节直接影响手感单纯把所有单位SetDestination到同一个点你会发现它们全都叠在一起这绝对不是RTS该有的观感。第三层是操作反馈。点选目标后单位立刻转向、起步、加减速镜头跟随指示圈在目标点出现。这套即时反馈虽然不完全由移动逻辑实现但移动逻辑必须做到不拖沓、不犹豫。1.2 方案选型为什么直接吃官方NavMesh这套在做技术选型时我当时列过一张表对比了手写A*、第三方插件和官方NavMesh。结论其实很清晰如果你的项目不是千人同屏的大型战略游戏官方NavMesh完全够用而且省掉大量时间成本。方案优势劣势适合场景自研A*寻路完全可控、可匹配特殊业务逻辑需要自己处理网格体素化、平滑路径、局部避障调优成本很高有专职算法团队、玩法强依赖寻路细节自研A* 流场大规模群体移动性能优化潜力大实现复杂度高移动端内存压力大千人以上同屏的战争游戏官方NavMesh NavMeshAgent开箱即用、烘焙可视化、自带避障动态地形支持弱、极端大规模群体需要额外优化中小规模RTS类游戏、类红警移动、大部分实时战斗游戏关键点在于红警这类游戏的核心乐趣是“指挥少量但多编队的单位”不是“控制一千只蚂蚁每个都精准避开其他人”。官方NavMesh的寻路性能和表现力在这个量级下完全够用。官方案例之所以能成为学习的标杆也正是因为它把多单位寻路、烘焙、动态障碍物这几个最容易出问题的地方都做了常规化处理照着它走一遍就能避开许多坑。1.3 官方素材里最值得抠的三个细节我不是让你直接把官方示例里的代码拖到项目里用而是建议你做三件事先读官方文档里关于NavMeshAgent属性那一页搞清楚每个参数的含义再看官方示例中场景是怎么布置Navigation Layers和烘焙尺寸的最后仔细研究示例脚本里处理“单位卡住”和“目标不是有效位置”时的优雅降级方式。其中第三件事最容易被忽略。红警式的移动要求单位在目标位置被占据、目标点在NavMesh外面、路径被动态障碍物切断时仍然以合理的方式表现。官方示例通常会用NavMesh.SamplePosition对目标点做一次有效性校正把“玩家点的那个点”修正到“离这个点最近的可行走位置”。这个单行代码就是你的单位不会把目标点停在悬崖外或障碍物体内的关键。2. 把地图变成可走的“路”导航网格烘焙2.1 静态场景和基础参数设置导航网格不是自动生成的它依赖场景里物体的几何数据。在旧版本的Unity里可以通过Window AI Navigation面板操作Unity 2022及之后更推荐先安装AI Navigation包在场景里挂NavMeshSurface组件来实际操作同样的功能工作流更清晰一些。我建议你直接走AI Navigation这条新方案因为官方未来重点维护的是这个包。安装方式是通过Package Manager搜索AI Navigation并导入然后在场景中创建一个空物体挂上NavMeshSurface组件。接下来把所有静态地形、建筑、墙体等物体的层级中加上Navigation Static标记。这一步等同于告诉Unity“烘焙时请考虑这些物体”。地面本身也做同样的标记但你要确保它是一个封闭的Mesh而不是残缺的平面。很多开发者第一次烘焙出来单位全往地下钻就是地形对象没有正确标记或烘焙区域没有覆盖完整。2.2 Agent Radius背后的通道逻辑点击NavMeshSurface的Bake按钮后Unity会按照Agent相关参数生成网格。这里有几个参数我需要展开讲因为它们直接决定了红警式移动的体验。Agent Radius是最容易被低估的参数。它代表单位占用的横向半径。红警里的步兵和坦克需要的通道宽度完全不同。如果你做的是坦克单位Agent Radius设0.5或更大烘焙时小于该半径的通路就会被忽略单位不会试图挤进一条坦克不可能通过的缝隙。如果你的游戏里同时有步兵和坦克两种单位最合理的做法不是用同一个NavMesh走天下而是分别用不同Agent Radius烘焙多个NavMeshData给不同单位分配对应的NavMeshAgent。这在官方文档里叫“多个代理半径”是类红警游戏的标准姿势。Agent Height决定单位能否从低矮障碍物下方通过。Max Slope则是最大爬坡角度45度是常规值但红警里的单位往往更“乖”建议设30度左右避免坦克爬上非常陡的斜坡。Step Height用于处理台阶和路沿一个0.5的台阶高度意味着单位能自动“走上”一级台阶而不需要真正跳跃。烘焙完成后进入Scene视图的NavMesh显示模式你会看到蓝色的可行走区域。检查这三件事建筑周围是否留有合理的通道宽度单位不能走进去的区域是否覆盖正确地形边缘是否出现不合理的尖角或空洞。如果空洞太多把Agent Radius稍微调小再重新烘焙即可。2.3 动态障碍与跳跃连接红警地图里最常见的动态障碍就是建筑被摧毁后的残骸。你没法把残骸做成静态物体所以需要在可被摧毁的物体上挂NavMeshObstacle组件。这个组件有两种模式默认模式是位移模式适合移动的障碍物勾选Carve模式后障碍物会实时在NavMesh上“挖洞”把阻挡区域从可行走区域中扣除。这里有个实际经验Carve模式是运行时计算开销不小不要给几十个障碍物全开Carve。更好的做法是把“会影响寻路的动态物体”数量控制在一个合理范围通常不超过20个否则每帧的网格更新会让你明显感觉到卡顿。如果红警式的战斗场景里爆炸产生的残骸很多我的做法是延迟一段时间后再启用Carve短时间内让它以普通碰撞体的方式存在。跨越断桥或沟壑的场景要使用NavMeshLink。NavMeshLink就是在一个网络节点和另一个节点之间搭一条虚线的“桥”你可以设置终点、方向、宽度。红警里不会飞的角色过桥、过传送带基本都靠这个缝合寻路断点。使用时注意将Link的方向设置成双向否则单位只能单向通行。3. 点击地面单位就动核心移动逻辑3.1 右键拾取世界坐标的两种常见写法在Unity里获取鼠标点击的世界坐标标准做法是Camera.main.ScreenPointToRay加Physics.Raycast。官方示例通常直接射线到地面然后取hit.point作为目标点。问题在于地面如果是一个大Plane而场景里还有桌椅、箱子等障碍物射线可能打在这些障碍物上。你当然可以把地面单独放在一个Layer里然后用LayerMask过滤射线检测这是RTS项目最常见的做法。我的建议是干脆把所有可移动单位也放在Unit层地面放在Ground层射线检测时只检测Ground层后续做单位选中时再检测Unit层互不干扰。第二种情况是玩家点击的位置偏离了NavMesh比如点到了地图边界外的景观区域。这时hit.point虽然在世界坐标上是某个地面位置但它并不在导航网格上。直接把该点传给SetDestinationAgent会停在原地或者表现出寻找最近可达点的“抽搐”行为。正确的做法是用NavMesh.SamplePosition做一次矫正把目标点吸附到NavMesh上。这是最值得抄的一份代码if (NavMesh.SamplePosition(hit.point, out NavMeshHit navHit, 2f, NavMesh.AllAreas)) { unitAgent.SetDestination(navHit.position); }第二个参数是矫正的原始命中点第三个参数是最大吸附距离2米通常合适。如果你期望的单位是坦克这种重装甲可以放宽到3米步兵则建议1.5到2米。3.2 NavMeshAgent的调参才是手感发动机把一个NavMeshAgent组件加到单位预设体上后真正跟手感相关的属性主要有这五个。Speed就是移动速度红警里不同单位速度差异很大坦克慢、侦察车快你可以按单位类型配置。Angular Speed是单位旋转的角速度步兵可以快一些坦克适当降一点不然坦克转身就像陀螺。Acceleration是加速度这个很多人不调但它决定单位从静止到全速的过渡时长。过大则单位起步非常“贼”过小则看起来很迟钝。我一般步兵给40坦克给20。Stopping Distance是单位距离目标点多远时停下红警式移动中这个值不是单纯的一个点它是一个小圆的半径后续做编队阵型时会用到。最后是Update Rotation。默认开启Agent会让单位朝向路径方向。想要红警那种底盘先转向再前进的坦克效果需要关闭它自己用旋转插值处理。这块我放在后面专门说。实例化单位后要把目标点通过SetDestination传给它。只有这一个入口Unity内部会完成路径生成和避障计算。千万不要自己每帧去改单位位置那等于绕过了整个NavMesh系统。3.3 转向、停靠与移动动画的衔接红警的单位移动有一个明显特征转向动作是独立于位移的。如果你只是用默认的UpdateRotation true单位会一边旋转一边前进手感上虽然能接受但做步兵这种小单位还行做大型载具就“飘”了。关闭UpdateRotation后需要自己在移动过程中计算转向目标方向并快速旋转。我常用这种写法if (agent.velocity.sqrMagnitude 0.1f) { Quaternion targetRotation Quaternion.LookRotation(agent.velocity.normalized); transform.rotation Quaternion.RotateTowards(transform.rotation, targetRotation, 720f * Time.deltaTime); }720度每秒意味着单位能在0.25秒内完成180度掉头接近红警里装甲车辆快速调头的感觉。如果你想让坦克更“重”可以降到360度甚至240度每秒让玩家明显感知到转向延迟。注意当速度接近零时不要强行计算朝向否则单位会在原地疯狂转圈。停靠逻辑方面红警单位到达目的地后通常不会再移动。这里要让StoppingDistance大于0并且在一段延时后把agent.velocity清零或者直接用agent.ResetPath()切断路径。否则单位会在目标点附近反复寻找更优路径看起来像在碎步抖动这个细节很影响观感。3.4 框选一队兵一起移动RTS的框选通常涉及鼠标拖拽生成屏幕矩形然后挑选投影在矩形内的单位。做选中不是这里的重点重点是把“选中结果”和“移动指令”连起来。选中单位后右键点击地面事件发生最朴素的代码如下public void CommandMoveTo(Vector3 destination) { foreach (var unit in selectedUnits) { NavMeshAgent agent unit.GetComponentNavMeshAgent(); agent.SetDestination(destination); } }这样确实能让一整队单位全部开始移动但效果是全部挤到同一个点非常难看。更接近红警的常见做法是不直接使用相同的目标点而是给每个单位在目标点周围分配一个偏移位置。最浅层的实现是保存单位的原始队形偏移量移动时将目标点加上偏移量后再传给Agent。具体实现思路后面会展开这里先记住一个原则每个单位的目标点应该是“目标区域内的一个差异化坐标”而不是全局坐标本身。4. 一群人一起走避障与编队行进4.1 NavMeshAgent的“你让让我、我让让你”单位的群体移动Unity内部用的是局部避障算法它在每一帧根据周围Agent的位置和速度计算出一个不会碰撞的临时移动方向。你不需要写任何额外代码前提是确保所有单位都挂在同一个NavMesh上并且Radius属性设置合理。NavMeshAgent上的Radius属性是运行时避障半径。注意它和烘焙时的Agent Radius是两个概念。烘焙半径决定路径能不能通过缝隙运行时Radius决定单位之间身体的间距。比如步兵的运行时Radius设0.4坦克设1.2站在一起时坦克会推开步兵而不是强行穿插。Priority是避障优先级值越小优先级越高。红警里高级单位往往更容易推开低级单位。想要哪种单位穿行时拥有更高的通行权就把它的Priority设成0或1。不过要小心相同优先级的Agent相互避障时表现最自然不会出现某一边频繁停止等待的情况。有一个容易被忽略的问题Agent的避障计算只对“正在使用Agent移动部件移动”的单位生效。如果你关闭了Agent的updateRotation但不关闭updatePosition那没问题。可是如果你图方便直接操作transform.position避障就会彻底失效。实战中遇到单位穿模先检查是否有一处代码直接改位置。4.2 集结点与停止距离不让一队兵叠成一个人要让一队兵像红警那样队伍散开、队形保持、最终铺开最实用的技巧是“队形偏移量 停止距离”。在单位被框选时记录每个单位相对于队伍中心的偏移量。当玩家右键点击目的地时以目标点为中心加上各自的偏移量得到每个单位独特的目标点。这就是红警式编队移动的基础不需要任何高级算法。Vector3 formationOffset unit.localFormationOffset; Vector3 destination targetPoint formationOffset; agent.SetDestination(destination);这里还需要配合Stopping Distance。如果想把队伍停成一个大圆圈把StoppingDistance设置为2米那么每个单位在接近自己目标点2米半径内就算到达队伍自然形成一个多个单位交错的松散圈形而不是士兵扎堆。更进阶的做法是为纵深部队设置第二层偏移让后排单位的目标点在更远处而不是所有偏移量都在目标点附近随机分布。否则移动长距离后队伍很容易被压缩成一条长龙速度快的先到速度慢的后到队形散架。我的土办法是先按单位在屏幕里框选时的行列顺序分配偏移用行距和列距控制编队纵深实战效果非常接近红警的坦克阵型前推。4.3 大规模单位移动的上限把几十个单位同时SetDestination后NavMeshAgent的局部避障会在每一帧计算每个Agent与他人的碰撞规避。如果有50个单位它还能流畅处理如果一下子框选500个每个Agent都在做邻居搜索CPU会出现肉眼可见的卡顿。这不是说官方NavMesh方案不行相反红警这类游戏的角色数量通常在几十到一两百的范畴。超过这个量优化的优先级就不是避障算法而是单位模型、碰撞体数量以及Agent的更新频率。我常做的优化是对距离玩家镜头较远的单位把Agent更新频率降低到每3帧一次。具体来说就是设置一个nextUpdateTime每隔0.1秒调用一次SetDestination。由于路径本身在变化这种低频更新对远距离单位观感影响极小但性能提升立竿见影。另外一个技巧是当单位到达目的地并停稳后调用agent.isStopped true减少继续计算路径的开销。等玩家再次下达移动指令时再置为false。5. 实跑过程中的坑问题排查与避坑心得5.1 点击了没反应八成不是代码问题我在做这个功能时遇到过最经典的“点击无反应”案例问题出在EventSystem上。如果你的鼠标射线被UI遮挡点击事件根本不会传给相机右键移动自然没有任何反馈。排查方式是临时把Canvas关闭如果点击立刻恢复说明是UI在吞射线。实际项目里通常给UI根Canvas加一个GraphicRaycaster普通UI事件不会有问题但全屏透明的Image会挡住一切所以UI面板最外层的透明图不要开Raycast Target。更隐蔽的原因是没有正确配置LayerMask。如果地面没有单独放在Ground层而代码里只检测Ground层点击必然没反应。检查方式是把Physics.Raycast的输出打印出来确认射线确实命中了地面再逐层检查Layer。5.2 Agent在半空飘着、走上房顶、卡台阶单位在NavMesh上“飘”着走通常是烘焙时地形对象与地面重叠或者烘焙生成的NavMesh边缘因Agent Radius过大造成了悬空边。解决方式是检查地面模型有没有单位量级的法线翻转把地面单独拆出来烘焙避免建筑等复杂模型的底面参与网格生成。走上房顶的问题基本是房顶可站立区域被错误地算成了可行走区域。红警里坦克不能上屋顶但Unity默认没有任何规则知道它是“房顶”。检查方法是把房顶对象的Navigation Static取消再重新烘焙。如果有些房顶是动态可破坏的就用NavMeshObstacle动态挖洞。卡台阶虽然是Bug但表现形式很微妙单位抵达台阶底部后原地来回挪动像在找路又找不到。我把Agent的Step Height调大到能覆盖台阶高度就解决了。顺便说一句在烘焙后改Step Height不会生效必须重新Bake。5.3 一群单位互相堵死框选中多队单位同时移动时最容易出现“堵车死锁”现象。两个单位迎面相遇互相避让却在狭窄通道里互相卡住。NavMeshAgent自带避障能处理大多数情况但它不是万能的。我的处理办法是给“AI控制下的单位”加一个检测如果单位在数秒内速度一直很低就往正后方位移一小段距离强制打破僵局。这个方法比较粗暴但很有效。有时堵死也和烘焙有关。通道宽度如果刚好是Agent Radius的两倍两个单位会互相碰撞。解决办法是从模板上调整把通道拓宽或者调小单位烘焙半径。这类问题在运行时很难完美解决所以我建议在地图设计阶段就保证通道宽度至少是单位Width的三倍。5.4 手动接管转向的注意点前文提到了写代码手动控制单位旋转。这里要提醒你关闭updateRotation后寻路并没有失效但这会让Agent在路径拐角处晕头转向。因为路径是由多个WayPoint组成的Agent到达拐角前可能尚未完全转向自动移动方向与当前朝向不一致。我的经验是开启AutoBraking并通过StoppingDistance控制单位在拐角前减速同时把手动转向的角速度调高让单位能快速跟随路径方向变化。如果你发现单位在路上画圈那多半就是转向速度跟不上路径更新把RotateTowards的角速度从300提高到600或800就好。最后聊几句在实跑这个方案之前我一直纠结“手写寻路是不是更有掌控感”。做过一轮后我的结论是非必要不手写官方案的成熟度和稳定性远超想象把精力省下来去做单位和地图的交互表现更值得。如果你做的是类红警的RTS移动NavMesh NavMeshAgent这套组合可以稳定覆盖从几台坦克到上百单位的移动需求。要注意的是RTS移动手感不是靠单一技术决胜避障参数、编队偏移、停止距离、转向速度这些参数都拧在一起每次调整都建议在真实地图上多次试跑别光看Ignore。小技巧是把常用Agent配置保存成预设体不同单位类型直接引用能省下大量重复调整的时间。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询