Unity RTS开发实战:从寻路避障到命令队列的框架设计

📅 发布时间:2026/10/14 16:07:07
Unity RTS开发实战:从寻路避障到命令队列的框架设计
做RTS游戏的人基本都懂一个反直觉的事实门槛最高的部分往往不是玩法设计而是那些玩家根本不会去注意的“底盘系统”。兵力多少、建筑怎么摆、技能怎么放这些都还好说但当你在地图上框选二十个农民让他们去采矿还要保证他们自动绕开挡路的巨石、不挤成一团、命令执行不串线——整套策略游戏基础设施才是团队最容易翻车的地方。最近我在一批Unity RTS项目里反复试用了一套叫RTS Starter Kit的工具包它把我印象中至少需要做两个月的基建部分直接变成了可配置项。这套包能做什么一句话概括它把RTS品类的通用系统比如框选、编队、命令队列、寻路避障、战争迷雾、资源采集、建筑放置、AI对手都提前封装好让开发者拿到手就能在Unity里搭出一个有完整“即时战略手感”的可玩场景。适合独立开发者快速出原型也适合中小团队在成熟底子上做自己的差异化玩法想系统学习RTS框架设计的人同样可以拿它当解剖样本。下面我结合自己实际跑过一遍的体验把这套包的内部逻辑和二次开发要点一次讲透。1. 一套RTS工具包到底解决什么问题1.1 RTS项目的“隐形基建”RTS玩法看起来热闹采集、建造、生产、战斗、科技全在玩家屏幕上不停发生。但是这些玩法全部依赖一套极其繁琐的基础层单位选择与框选、命令系统、寻路避障、战争迷雾、队形维持、UI绑定、AI行为调度。这块基建对玩家是透明的对开发者却是巨大的时间黑洞。我自己在模拟项目X里做过一次统计从零手写一个最小可玩的RTS框架不算美术资源、不算网络同步只把“框选单位、右键移动、A攻击、N个农民采矿、一个简单的建造逻辑”跑通大概需要六到八周。而且这六到八周产出的代码还非常脆弱换个场景布局就可能出寻路bug编队一多就卡。卡点通常在路径避障、命令队列、性能优化这三块——每块单独拎出来都能做一个月的专题。工具包的价值就在这里它不是替你写玩法而是把RTS品类里那些“大家都要做、做起来又很容易做歪”的通用系统一次性打包。你拿到的不是一串零散的脚本而是一套分工明确、模块之间已经接好口的框架。玩法团队可以从第一天就把精力放在真正能形成差异化的东西上比如单位技能、阵营特性、地图机制。1.2 工具包的模块划分与核心能力从使用角度拆RTS Starter Kit这类包通常覆盖以下几个核心模块模块解决什么问题选择系统点击选单位、矩形框选、Shift加选减选、编队调用命令系统移动、攻击、采集、建造、巡逻以及命令排队和插入寻路与避障单位从A点到B点自动找路多单位集群时避免互相卡死战争迷雾阵营视野管理、单位视野半径、地形遮挡效果资源与建筑矿点采集、建筑放置校验、建造进度与生产队列AI控制器电脑对手的资源调度、暴兵、进攻逻辑可扩展每个模块在设计上通常都留了接口。比如选择系统不会面向“某个具体兵种”写死而是面向“可以被选中的实体”抽象命令系统也是一样单位只要在框架里挂了对应的行为组件就能响应右键指令。这意味着你后续加一个新单位只要按照框架约定配置属性它自动就拥有框选、编队、寻路这些能力不需要重新造轮子。1.3 什么情况适合直接上手什么情况要想清楚做技术选型时最忌讳“因为别人说好就上”。我实际跑下来这几类场景特别适合直接用RTS Starter Kit独立游戏快速原型想验证一个策略玩法好不好玩用工具包一周内就能出可玩Demo。中小团队做Steam上的中体量RTS玩法创新点放在单位、技能或地图机制上底层用包做支撑。学习和研究把包当代码教材看成熟框架怎么组织选择、命令、寻路这些系统比看零散教程强得多。但也有不适合的情况。如果你要做的是电竞级别的强同步对抗RTS对帧同步、预测回滚、反作弊要求极高那么通用工具包的网络层往往不够用需要评估二次开发的成本或者把同步层整个替换掉。另外一个隐藏成本是美术资产和动画工具包自带的是占位模型已经能跑通游戏循环但真正上线版本的美术整合工作量并不会因为用了包而减少。2. 核心系统逐个拆从框选到寻路的实现逻辑2.1 框选与单位注册摆脱“遍历场景里所有单位”的坑框选听起来简单鼠标拉个矩形把矩形内的单位都选中。新手实现时最容易写出的方案是每帧获取屏幕上的矩形然后遍历场景里所有单位判断它们的屏幕坐标是否落在矩形内。单位少的时候没问题一局到几百个单位时这个遍历就很吃力而且还要处理相机旋转、单位个子大小差异等一堆问题。工具包里的选择系统一般会引入“单位注册表”的概念。每个可被选中的实体在生成的时候会向一个全局管理器注册自己管理器用空间划分结构比如网格或四叉树维护所有单位的位置索引。框选的时候先根据屏幕矩形算出世界空间里的一个区域然后只查区域附近的单位而不是全场景遍历。这个思路不只是性能优化更是RTS单位管理的地基。还有一个细节值得注意框选矩形什么时候生效如果鼠标只是点了一下矩形很小那应该按“点击选择”处理只有当鼠标拖动的距离超过某个阈值时才进入框选模式。这个阈值一般按屏幕像素算默认5到8像素就不会误触发。很多人第一次接这类系统觉得“框选怎么还带延迟”其实就是这个判定阈值没调好。配套的单位选择状态管理也很关键。选中一个单位时HUD要显示它的血量、当前命令、技能按钮框选多个单位时HUD要变成群体面板双击一个同类单位要能把屏幕上同类型的单位都选进来。工具包通常把这些交互逻辑也一并做好了你只需要替换UI预制体。2.2 命令系统与状态机右键不是“换个目的地”那么简单RTS右键下达命令看起来只是给单位设一个目标点。实际拆开看一个移动命令包含至少四层信息命令类型、目标位置或目标对象、执行优先级、附加参数比如攻击时要不要走位。而在一个成熟的RTS里玩家还会按着Shift键连续下达多个命令构成一条命令队列单位在执行完上一条之后才会自动接着执行下一条。这就是命令队列系统的核心价值。单位身上挂一个CommandQueue组件内部维护一个队列结构。任何输入来源都可以往队列里追加命令比如玩家右键、AI脚本调用、事件触发。每帧单位从队列头部取当前命令执行命令完成、被打断或者失效时再切换到下一条。这里最需要处理好的细节是“打断”和“优雅切换”如果单位正在采矿你右键让它去建房子它是立刻丢下矿就走还是先把手上的矿石送回基地框架会按命令优先级和策略来处理而不是简单覆盖。工具包在实现命令时通常还会配合一个单位状态机。状态机保证单位同一时刻有且只有一个主要行为比如空闲、移动、攻击、采集、建造。这个状态机不仅控制逻辑还会驱动动画层的切换。外部系统不直接改单位位置而是通过发命令间接驱动状态迁移这样做的好处是整个单位的生命周期是可控的出问题时可以沿着命令队列和状态记录回放排查。示意代码层面通常会长这样public class UnitEntity : MonoBehaviour { public int factionId; public UnitDefinition definition; public CommandQueue CommandQueue { get; private set; } public void Select(bool selected) { // 播放选圈、更新HUD } } public class CommandQueue { private QueueUnitCommand commands; public void AddCommand(UnitCommand cmd, bool insertFront false) { if (insertFront) // 插入到队首用于立即执行 else commands.Enqueue(cmd); } }这个抽象非常实用。AI系统要指挥一支军队只需要往每个单位身上追加攻击命令单位群就能按队列调度自己行动玩家按Shift也是一样的原理。命令系统跑顺之后RTS的“操控感”就有了八成。2.3 寻路与避障网格方案和群体行为的选择RTS里寻路是个又基础又容易翻车的环节。Unity自带的NavMesh是一套成熟的静态路径查找方案绝大多数RTS工具包都会在它基础上做封装。做法是先给地图地面烘焙一张导航网格然后单位请求路径时在网格上做A星搜索得到一条多段折线再由单位沿折线移动。选NavMesh而不是完全自研理由很实际Unity已经处理了网格生成、动态障碍、寻路多线程这些最麻烦的部分团队没必要重复造。但RTS场景下NavMesh直接用也会遇到两个问题。一是动态障碍更新比如单位建造了一堵墙导航网格需要实时重新烘焙或局部更新否则单位会试图穿过新建筑二是大量单位同时寻路时CPU开销呈指数上涨所以工具包一般会合并同类请求或者对近距离单位复用路径。群体避障是另一个分水岭。假设你选了三十个步兵点到一个目标点如果每个单位都各自按最短路径走它们会在狭窄通道口挤成一团像一群没头苍蝇。解决思路大致有两种一是局部避障算法比如RVO速度障碍区计算每个单位应该调整的速度向量避免与其他单位相撞二是流场寻路预先计算网格上每个格子指向目标的方向单位群沿着流场走整体效果好得多适合大量单位朝同一目标移动的场景。工具包通常会提供至少一种群体避障方案并在参数面板里暴露“单位半径”“停止距离”“避障权重”这几个关键字段。真正调优时你会发现很多“卡住不动”的bug其实不是寻路算法坏了而是停止距离设得太远、或者避障权重过大导致单位在原地抖动这些问题后面我会专门讲。3. 实操把示例工程改成自己的RTS Demo3.1 导入与目录结构梳理拿到RTS Starter Kit后第一步不是急着打开示例场景而是先花半小时把目录结构看明白。一般这类包的根目录会分成Core、UI、AI、Art、Scenes几个子目录。Core里面是运行时框架脚本UI里是HUD面板AI里是电脑玩家逻辑Art里是占位模型和特效Scenes里则是开箱即用的示例场景。我拿到的是某资源商店的RTS Starter Kit 1.x版本导入后项目里多了一个完整的示例场景。这个场景麻雀虽小五脏俱全地图上有矿点、几个农民单位、一个基地建筑还有一排用来当靶子的敌方单位。第一次运行你就可以直接框选农民、右键采矿、右键攻击敌方单位全流程是通的。这里有个很关键的习惯先跑通官方示例再动任何代码。不要在还没跑通示例的时候就往包上叠加自己的新功能否则下次示例场景跑不报错你自己都不知道是环境问题还是改动引入的问题。示例场景能跑说明依赖、版本、渲染管线都是对的。3.2 战场地图与导航网格配置跑通示例后我开始尝试把示例场景换成自己的地形。创建一个新场景放一块地面往地面上拖一个NavMesh Surface组件点击Bake按钮生成导航网格。这里有几个参数直接影响RTS手感需要认真调不是用默认值就行。参数推荐起始值说明Agent Radius0.5左右代表单位占地面太大会导致狭窄通道被认为不可走Agent Height2.0一般不需要改除非你有飞行单位Max Slope45地形坡度限制Step Height0.4台阶高度的通过能力Voxel Size自动或0.1越小烘焙精度越高但耗时和内存也越大改完烘焙参数后记得在NavMesh上设置一个可行走区域层。RTS地图除了地面还要区分“通行区”和“不可通行区”。水、悬崖、大型装饰物都要通过NavMesh Obstacle或行走层权限排除掉否则你会发现单位直直开进湖里。这一步是所有后续体验的基础前期多花一小时调网格后面能省一整周修寻路bug。战争迷雾层也是在这个环节一起做的。工具包一般会在场景里放一个FogOfWar管理器把地面网格的可见性交给它控制。你需要在设置里指定迷雾纹理的分辨率比如128x128、256x256分辨率越高视野边缘越锐利但对性能要求也更高。自己项目里建议先用64或128跑通最后再做画质优化。3.3 单位预制体与阵营配置RTS本身是阵营对抗游戏单位预制体上通常要挂几个核心组件UnitEntity单位身份、Health血量、CommandQueue命令队列、以及一个视觉表现部分模型、血条、选中圈。在框架里预制体不需要自己处理寻路和命令逻辑只要按约定挂好组件系统会自动接管。阵营配置是RTS里最容易做乱的环节。工具包一般会用FactionManager统一管理阵营数据每个阵营有一个ID、一个显示名称、一组单位颜色。单位生成时通过初始化接口把factionId传进去单位身上的材质颜色、小地图点色、战争迷雾归属都由系统按阵营ID自动切换。我这个Demo里建了红蓝两个阵营把农民和士兵预制体分别拖到阵营配置面板选中圈的显示颜色立刻区分开了这比到处找代码硬改颜色要舒服得多。资源点和建筑也是这步一起配的。矿点一般需要一个ResourceNode组件设置矿量、采集半径、单次采集量。建筑的建造逻辑通常依赖一个ICommandProvider接口单位靠近指定建筑后会把向建筑添加建造订单作为一个命令类型来执行。这个流程如果是在自己的框架里写至少要处理相邻判定、排队、进度条、取消命令、资源回退五种情况工具包已经把这一串状态机封装好了配置就能跑。3.4 输入绑定与玩家控制器RTS的操作入口是鼠标和键盘输入系统的配置直接决定操控手感。工具包大多同时支持旧的输入管理器和新版Input System。我推荐直接用新版Input System因为键位自定义比较方便而且不用担心Unity后续淘汰旧接口。在新的Input Action资产里需要配置几个基础ActionMousePosition鼠标位置、Select左键点击、Command右键点击、CameraMove镜头移动、GroupSelect编队按钮。配置的时候注意一点鼠标位置必须单独设置成Pointer类型的Action而不是把它当成普通Vector2按键否则在UI层级上会有很多鼠标穿透问题。然后把PlayerController组件挂到场景里的一个空物体上。这个控制器负责把输入事件翻译成框架的命令调用左键点击调选择系统右键点击调命令系统按数字键编队调GroupManager。如果你发现右键点了没反应先检查射线检测层Tag或Layer必须包含地面和可交互单位否则射线打不到东西。3.5 跑通第一个可玩闭环的检查项一个RTS Demo画面再好看核心还是游戏闭环。我把自己搭的模拟项目X列出过一份检查清单每项都能在几分钟内手测验证框选农民HUD能显示选中数量。右键空地农民全部移动到目标点不挤团。Shift右键多个地点农民按顺序走完所有路线。农民靠近矿点后自动开始采矿身上挂着资源时能送回基地。建筑放置时红色表示不可建造绿色表示可建造放置后资源正确扣除。选中战斗单位右键敌方单位单位能走过去并进入攻击状态。按1、2编队双击编队钮能跳到队伍位置。这份清单看起来基础但跑完它相当于把整个工具包的调用链都过了一遍。我自己的经验是新手卡关最多的地方不是脚本而是漏了配置要么Layer没设置要么NavMesh没烘焙要么预制体上少挂了一个组件。每一个报错都可以沿着刚才说的系统模块去定位用框架时养成“先查配置、再查代码、最后才怀疑bug”的排查顺序能省下大量时间。4. 二次开发与扩展在框架里加自己的玩法4.1 数据驱动从硬编码到ScriptableObject工具包再好也只是给你一个底盘真正的玩法还是要靠扩展实现。这里最值得优先学的是框架对“数据驱动”的支持也就是用ScriptableObject替代硬编码属性。把模板里硬编码的单位属性抽到配置里[CreateAssetMenu(menuName RTS/UnitDefinition)] public class UnitDefinition : ScriptableObject { public string displayName; public int maxHealth; public int moveSpeed; public int attackDamage; public float attackRange; public GameObject prefab; }这样做的最大好处是策划可以用Asset文件调单位数值而且可以随时增删单位类型不需要改逻辑代码。工具包的UnitEntity组件上通常会暴露一个UnitDefinition字段在Inspector里拖进去就能完成绑定。我在自己的Demo里加了近战步兵和远程弓箭手就是通过新建两个配置文件实现的十分钟就接好了。4.2 自定义命令与行为树另一个重要的扩展点是命令系统。从工具包的角度命令就是一个类或者结构体包含执行类型和参数。如果你想新增“驻守”命令先不要急着改框架代码先看它有没有提供命令注册接口。常见做法是新建一个类继承基础命令类然后重写执行逻辑。对于更复杂的单位AI比如英雄单位同时要会放技能、会拉扯、会优先攻击残血敌人简单命令队列就不够用了。这时要在工具包允许的范围内接入行为树或者在Update里写状态判断。我见过一种比较稳妥的做法把“命令队列”作为行为树的根节点单位先执行玩家命令玩家命令清空后再轮到AI行为树接管做巡逻、警戒、自动还击这类事情。这套“玩家优先级高于AI”的设计非常关键。RTS里玩家最不能忍的就是自己下达命令后单位“自作主张”去做别的。所以无论你的AI设计多聪明命令队列里只有在没有玩家指令时才允许AI行为自由发挥。4.3 阵营AI的接入方式电脑对手是RTS Demo最容易露怯的一环。工具包一般会提供最简单的AI框架按固定间隔给某个阵营发一笔资源然后让AI单位去采集、生产、按人口比例暴兵最后向敌方基地发起攻击。虽然是基础逻辑但它的架构值得借鉴。AI管理器通常维护一个决策循环每隔几秒运行一次检查当前资源、单位数量、敌方位置然后决定执行哪个“意图”。比如设定一个“穷则发育、富则进攻”的逻辑就是当兵力大于某个阈值时给所有战斗单位的命令队列追加一个移动到敌方基地的命令到达后追加攻击命令。把AI接入框架时不要直接操作单位的位置而是通过框架的命令接口下达指令。这套做法的好处是AI和玩家的操作都走同一条命令链路单位的表现一致不会出现“玩家单位会卡地形、AI单位却像幽灵一样穿墙”的尴尬。我在Demo里让蓝色阵营在开局三分钟后自动暴兵进攻整个流程只写了不到一百行逻辑因为底层寻路、队列、战斗全部由工具包托管。5. 实战避坑指南我遇到过的5个典型问题5.1 框选和鼠标拖拽冲突这是所有人第一次接入鼠标选择时几乎必踩的坑。框选矩形的触发条件没控制好玩家想拖动镜头时系统会不停地弹出选择框想框选单位时镜头又会被拖走。我调这个问题时先检查了PlayerController里对输入的分流逻辑确认左键按下后是否立刻进入框选模式还是等拖动距离超过阈值再切换。解决思路很简单按下鼠标时记录初始位置在鼠标移动距离超过阈值之前保持“未决定”状态一旦超过阈值锁定输入为框选不再响应镜头拖动。这个阈值建议按屏幕像素而不是世界单位计算因为世界单位会随相机高度变化导致远处和近处手感不一致。另外Shift键要多选逻辑要和框选做区分它的加减选优先级高于新建选择。5.2 单位挤成一团寻路抖动单位多起来之后最常见的视觉灾难是堵在墙角来回抖。一开始我以为是NavMesh网格问题结果重新烘焙了很多次都没用。后来才反应过来问题出在避障参数上单位半径设得太大互相避让的权重也高导致在狭窄区域里谁都不愿意先走来回试探。调参是有顺序的。先调Agent Radius到合适大小再调停止距离单位到达目的地后认为“到了”的距离。停止距离设成0.0单位会努力挤到目标正中心反而导致目标点周围堆成山我通常设为0.5到1.0让单位在目标点附近松散分布。如果你的框架支持避障权重的拉伸记住“权重不是越高越好”真正顺滑的感觉是单位群既不会重叠也不会因为互相让行而卡死。5.3 命令队列执行顺序错乱我在模拟项目X里最崩溃的一次是农民连续接到三个命令后中间的采集命令总是被跳过。排查了很久发现不是框架的问题我在添加命令时误用了“插到队首”接口每次都把新命令插到了当前命令之前于是后加的命令永远最先执行队列自然乱套。这里要理解命令队列的两种语义普通追加是“玩家预期的顺序执行”插队是“高优先级打断”。玩家右键和AI调度默认应该走追加只有取消现在行为、立刻要做某件事时才应该走插队。建议在代码里把这两种接口区分清楚命名上也直接体现比如AddCommand和InsertCommandToFront避免团队其他成员误用。5.4 生成大量单位后帧率骤降RTS一打起来单位数量很容易上几百甚至上千性能瓶颈立刻显现。工具包能帮你搞定框架但性能优化仍然躲不开。我实测下来首当其冲的是渲染batch数量几十个单位都用独立Material每帧多几百个Draw Call卡顿几乎是必然的。解决方向有四个一是用GPU Instancing把同种单位的材质替换成支持实例化的Shader二是用对象池单位死亡后不要销毁而是回收复用避免频繁Instantiate和Destroy三是分帧更新不需要每一帧都处理所有单位的寻路请求可以按检测到的间隔分散到不同帧四是利用Job System和Burst。这里工具包可能已经做了部分优化但你需要确认场景里有没有多余的材料实例和没关掉的实时阴影很多时候卡顿是美术资源设置不当引起的不是框架的问题。5.5 Input System版本适配很多新手导入工具包后打开示例场景发现鼠标点击毫无反应。问题通常有两种可能一是工程里面同时启用了新旧两套输入系统导致Input Action配置冲突二是示例场景是为旧输入管理器写的而工程设置里已经切到了新Input System。我的建议是统一到新版Input System。做法是在Player Settings里把Active Input Handling设为Input System Package然后重新生成Input Action资产把事件监听挂到新系统上。旧系统虽然还能用但官方已经明确不再扩展长期看迟早要迁移。迁移时注意鼠标光标锁定问题RTS不需要像FPS那样锁定光标要确保Input Action里没有把鼠标指针误设成锁定的类型否则你会碰到“鼠标只能在一个小范围内移动”的灵异现象。问题常见原因排查顺序框选与拖动冲突阈值未按像素设置检查PlayerController的拖拽阈值单位抖动避障权重或停止距离问题先调Agent Radius再调停止距离命令顺序错乱插队误用区分普通追加与高优先级插队群体卡顿Draw Call过多检查实例化、对象池、阴影与Batch鼠标无响应新旧Input System冲突统一输入管理重建Action资产我实际用下来的体会是RTS Starter Kit这类工具包的价值不在于“替代思考”而在于“把你从重复劳动里解放出来”。第一次跑通示例场景时你会觉得功能很全但等你真正接手一个自己的项目才会发现80%的定制工作恰好从这时候开始。多花时间读框架代码的调用链比在参数面板上瞎调一整天划算得多。前期的坑基本都是配置问题后期的问题基本集中在数值调优和框架理解这个节奏是每一个RTS开发者都会反复经历的。