早在03年第一次玩魔兽争霸的时候就赞叹它强大地图编辑器,并且也自己试着去改一些地图自娱自乐,可以这么说,这个编辑器可以实现非3A游戏的绝大部分游戏。DOTA就是用这个编辑器做出来的一张地图,才有了后面的LOL与王者荣耀,塔防类也是如此。后从事游戏开发,就一直在想如果可以复刻一个那该多有成就感啊!这个想法也随着后面的忙碌与不遇,一直等到了年初终于有这么一个契机可以做这个事情,项目的整体需求基本类似 rts 游戏。2D 45度角 偏单机,游戏引擎用了 unity3d + lua 所以我开始了这个工作。这篇文章主要是讲述思路(我要回上家公司做服务端,给小伙伴讲框架的思想),适合有基础对相同项目没有头绪的小伙伴提供思路,后续如果有时间我会把和项目无关的代码放到GitHub上。
首先是 AI 部分,要求是敌我双方都可以在玩家不进行干预的情况下按照设定自己去做各种事情,诸如:休息、闲逛(巡逻)、追逐、攻击、释放技能等等行为,在玩家进行干预的时候可以按照玩家的意愿做出相应的回应,如:点选、群选、组队、攻击某一个目标、走到某一个地方等等。这块涉及的内容说多也不算太多。主要是在代码设计上需要做到几点
1.使用行为都需要可以复用,就像编辑器做的可视化编辑一样,可以给某个单位添加上,它就拥有这个行为的能力。
这块最终使用了决策树+行为树+行为池去实现
2.高效的寻路机制,毕竟在地图比较大的情况下同时寻路还是有比较大压力的。
寻路后面用了 JPS (Jump Point Search,跳点搜索)来解决,当然如果是纯 3D 可以直接使用 NavMesh,地图如果太大,可能要做局部和全局寻路。目前寻路还是一个简单的常规寻路,事实上魔兽争霸冰封王座3还有群体寻路,需要用到 RVO 之类算法。
3.合理的驱动机制,因为单位自身去感知世界,本质就是在一堆条件中去找特定的条件然后进行决策执行某种行为。
这里就涉及到一个平衡的问题,如果驱动的 tick 很快,则性能消耗大,太慢则单位反映迟钝,后面想到了某种空调的变频原理,对某些状态下,我调低了它的驱动频率,只再战斗状态下用最高的频率去执行 AI ,的确获得了很好的效率
4.感知和速度,单位需要感知周围,还要能精准的控制攻击速度、移动速度等。
感知范围就是用的类似九宫格的思想,以减少对身边单位遍历的代价,攻击距离就是两个单位之间距离,移动和攻击速度这些都没什么好说的。
再有就是空间部分,这里就涉及到逻辑坐标和屏幕坐标,2D和3D空间。因为在服务器中是不会去关注摄像头的角度的。一般 2D 45度的显示大部分是使用菱形地图,只要把逻辑坐标(正方形地图)转换为屏幕坐标(菱形地图)就好了,所以需要对这块进行设计,最终是把空间这块做成可以是2D/3D 通用的,譬如:获取以自身为中心一定半径内的单位等等接口。如果换 3D 就是把接口和地图模块换成适合3D的就可以了。
图中单位周围格子就是感知范围,左边2个单位相互感知对方的存在,右边则没有发现其他单位,其他单位也没发现他的存在。每个单位一定时间内遍历自己周围格子就可以了,那么理论上来说性能消耗就和地图大小没关系了(这是逻辑位置,45度角在显示的时候是要做空间转换为菱形地图的)
接下来就是各种 技能、buff、被动、光环、等功能了,这块的设计,我把他们定义为“特性”,因为单位类的设计无非就是抽取各种共有属性的聚合,譬如:阵营、类型、血量、力量、移动速度、攻击速度等等的集合,这块的设计遵循的就是常规的 OOP 思想,但是”特性“依然是要面向编辑器,事实上也是 OOP 思想中的“面向接口编程”,那么最后无非就是抽出就是各种可以脱离单位的逻辑单元,它并不会关心它附着在哪个单位身上,你给一个单位加一个扣血的debuff,那么它就应该持续扣血,你给它加一个提升攻击速度的光环,那么它和符合条件的单位,就应该可以提升攻击速度,你赋予它什么能力它就可以做什么事情,因为这些“特性” 如光环是随时会得到的和失去的,理论上来说所有单位都可以被加上所有的“特性”,如果在 c++ 等编译型语言中实现,则需要写很多的接口,如:加血的接口、扣血的接口、光环接口。。。需要加上已有“特性“全部接口,而在 lua 这种脚本中不需要设计一堆的接口就可以快捷实现。(当然这里不是说动态脚本多好,只是开发起来便捷很多,但是同时会存在定义模糊,代码不好阅读的代价) 而且随着项目的推进和不断迭代,抽出通用的“特性”,最终它就可以成为一个通用的,类魔兽争霸的 rts 框架。这就是我常常在思考的问题,我们做了那么多的项目,最终我们的技术得到提升和积累了吗?再开一个新项目,可以在之前的基础上做吗?
还有就是数据的恢复和数据合法性的校验,这块内容了,因为是偏单机,逻辑是放在了客户端进行推演,势必需要做准确性的校验,所以在开始写这个项目的时候,我就把逻辑和渲染分开去做,上面所述的功能都是没有涉及到渲染的,最后加了一个叫UnitGameObject 的对象,它用于把逻辑算出来的结果通过事件派发给显示层去处理,例如在某个坐标上创建单位、播放休息动画、闲逛到某个坐标、追逐、战斗等等。这样设计好处有几点:
1.代码可以跨平台,也可以直接在服务器上跑,因为只要随机种子是相同的话,那么不管是在客户端中执行,还是服务器中执行,结果都是一样的。
2.UnitGameObject 的事件改为 TCP 或者 UDP 传输,就可以在服务器上跑,客户端就只是按消息去做动画就好了。
3.并且因为是和渲染没有耦合,在做数据恢复和一段时间后的现场恢复也是很容易的,无非就是加快 tick 的频率就好了(后面采用类似cpu中断的计数方式替换掉正常的定时器,而且推演时长不能太长,否则运算需要很长时间,比较适合掉线重连,或者退出游戏后一天内重开游戏的推演,超过这个时间就需要通过设计数值规则来重现了)。当然也不是每一个用户的每一次数据都需要进行校验,原则上是发现数据异常的情况下,把他的存档拿出来跑一遍。如果最后的数值和跑出来的数值相差太大,则加入到监控中重点观察,如果多次和理论值相差太多,则大概率就是作弊,这样既可以保证服务器没有消耗过多性能,也可以相对防作弊。
最后就是其他一些常规的惯例和手法了,诸如:对象池、事件机制、测试模式、配置管理、地图遮挡配置、透明区域配置、和这个项目独有的需求。这些和地图编辑器没有太大的关联,就不一一枚举了。
这个框架可以用于各类基于地图世界的游戏,如:塔防类、策略游戏、即时战略游戏、回合制、等等。当然目前只是一个雏形,离编辑器还非常远,后面需要完善的东西还非常多。
感恩遇到一帮有理想有能力的小伙伴,特献上亲自创作Q版画像一副,你们未来前途无限。以后你们会成为我口中认识的大佬,未来还要仰仗你们的光芒,只因每个人都使命不一样,选择不一样,我们后会有期!