☰
Unity RTS开发实战:从寻路避障到命令队列的框架设计
2026/10/14 16:07:06 网站建设 项目流程

做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 Queue<UnitCommand> 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 Demo

3.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资产里,需要配置几个基础Action:MousePosition(鼠标位置)、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开发者都会反复经历的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询