2016总结

这其实是我工作总结的删减版,只挑了一部分出来,分享一下吧。


·服务端架构

立项之后,我的首要工作是搭好服务端环境,包括新引擎的搭建部署,相关接口嵌入到客户端,写好基础模块,调通从客户端到服务端,再到数据库整块网络流程。

从M项目到现在新项目,一直都在做服务端,说一下感受。立项之前,我其实是在做旧引擎的准备工作。不是别的原因,只是被动的认为部门的项目只能用内部的引擎。并不是说保守就一定不好,服务器确实需要稳定,经过验证。但很多项目,已经不是回合制MMO,也不太适合用旧的框架和脚本语言去实现。

正如生产力决定生产关系一样,生产内容的发展决定生产工具的变革。旧引擎从最初单服承载两三千人,到现在能支撑接近两万玩家,确实是一件了不起的事情。但这里有一个非常重要的因素是不能忽略的,那就是摩尔定律。从2003年到2016年,CPU的性能翻了好几倍,存储设备也从读写较慢的机械硬盘,变成高速的SSD。内存容量也越来越大,价格却越来越便宜。硬件的提升和软件的优化,到底哪个占主导作用,谁都说不清楚。而在这个硬件的升级过程中,是什么变得越来越昂贵?是人。服务端的技术选型,更重要的考虑因素反而是人的开发效率,维护成本,还有水平扩展能力(能否利用到多核,利用到更多机器)。降低人的开发维护成本,增加机器的利用率,提高自动化程度,这才是最重要的。以人为本,这也是我一直以来对技术选型,认为最重要的一个衡量标准。


·独立战斗服架构

(1)独立战斗服。这块工作也是在刚立项不久就定下的,原因是我们是一个实时同步的游戏,网络那块需要有更高的性能。独立出来,让客户端直连,不经过网关转发,可以减少延时,并且有利于后面针对性的优化工作。

(2)RPC自动生成机制,最初来源是我们的策划希望战斗既能够联网,又能够单机。要实现这个需求,那些涉及到客户端和服务端的交互,都需要各自写一套接口,到时根据是联网还是单机而进行切换。这个事情是很费劲的,我写了一个就觉得不靠谱,但并没有急着把所有接口都手工写完,而是一直在思考是否有更加简单有效的方法。

后来终于想到了,利用模板语言,自动根据单机接口,生成对应的联网接口。这块工作,既极大的提高了开发效率,也极大的减少了维护接口的心智负担。可以想象如果不是用自动生成的方案,这块做起来会很累,也会经常出问题。

这个工作案例,我想说明一件事情,那就是思考的重要性。无论是做业务开发还是工具开发,我们的工作不是靠一个劲埋头苦干就行,我们更多的是需要思考。通过提高劳动强度(加班),增加劳动力投入(加人),也许可以在一定程度上提高产出。但那些对生产效率产生质的影响的东西,往往不是通过这样的形式堆出来,而是通过思考。通过对生产过程的每一个环节,每一个细节进行思考,找出最优的解决方案,从而提高整个产出效率。思考的作用永远无法被别的东西替代。

(3)RPC网络流量优化,这块主要分为全局性优化和针对性优化。全局性优化包括协议的切换,从bson转到msgpack;还有RPC参数的编码优化,把key最终减少到一个字节。后者也是得益于RPC自动化生成机制,对调用层透明,只改下两行模板就对所有RPC生效。

针对性优化主要是通过对RPC入口进行监控,排序得到消耗较高的RPC(比如单位坐标同步),精简参数,优化调用逻辑。

目前网络流量监控这块是手工打开,后面我打算做成程序开关,制定一些量化的标准,每周去跑。检查那些因逻辑写得不好,导致流量消耗较大的RPC接口,及时修改。我觉得CPU和内存的检查,也需要制定类似的性能基线和检测流程。这种流程可以及时看到,是哪些修改或者bug导致性能急剧下降,这个时候改正往往成本是最低的。如果积累一段时间的代码,再去跑检测,那么优化工作就需要额外安排人天去做了。


·关卡编辑器

关卡编辑器也算今年做的量比较大的一块工作,随着关卡的开发,已经成为策划不可缺少的一个工具。做编辑器这个想法,主要原因还是观察到策划在种怪这块工作,有很大的改进空间。

在没有编辑器之前,策划已经在用excel种怪,通过场景编辑器,拖一个替代模型到场景里,然后把坐标记录到excel表。这种方法是非常低效,非常难维护的,因为怪物坐标在excel表里根本不可读。所以这种情况下,做一个可视化的工具完成种怪任务,非常必要。

有了想法,但当时策划的人手并不足,没法安排人手输出需求文档,这是第二个问题。在一般人看来,程序只是实现策划想法的执行者,没有策划需求没法做。但我不这么认为,作为一名模拟城市资深玩家,一个好用的编辑器,它是一个什么样子,有什么功能,需要什么交互,在我脑子里已经非常清晰。所以编辑器的核心框架,从设计到实现,我只花了大概5天时间,就做出来了,并投入生产。

编辑器一开始只是策划的种怪工具,但我的想法并不是那么简单。我觉得一切东西都是可以种的,不仅仅功能性的单位,还包括装饰性的静态物件,如花草树木、地表植被等。最终美术输出的只是一个个场景“毛坯”,还有一堆模型组件。至于怎么摆放,完全由策划决定。编辑器可以做得跟一个沙盒游戏一样,最终开放给玩家,让玩家来创造出好玩的地图,就如同风靡10年的dota地图一样。


·战斗方面

(1)军团整体寻路

寻路是我们这种RTS类游戏最重要的一个功能,也是游戏AI里技术含量较高的一块。我们目前使用的寻路模块,有两个问题。一是它的A*所使用的格子图是4*4,对于前方阻挡较大的情况,算法会遍历整块区域,消耗较大;二是它只考虑单个单位,对于多个单位的寻路,它没有统筹规划。所以经常会出现本来站一起的多个单位,遇到障碍物后会各自绕另外一边行走,这样就不符合我们的要求了。

第一个问题其实最后没有解决,考虑过两个方案。方案一是多做一张粒度较大的格子图,比如64*64,但后来发现现有模块不支持,要改也不是那么好改。另一个方案则是我自己在脚本层写一个A*,用来跑大格子图,但后面考虑到维护成本,也没有这样做。

第二个问题,我的具体做法是,根据多个单位计算一个几何中心点,从这个点出发,分阶段调用A*,得出多个子目标点。然后每个单位沿着子目标点做steering,一直到目标点。用RTS的行话来讲,这就是shift+点地操作。子目标点的距离往往很接近,或者中间没有阻挡,这样所有单位的路线,就不再出现“分叉”的情况了。

(2)玩家AI

玩家AI这块,其实当初只是作为我做战斗断线重连的一个附属需求,策划只要求它完成一个基本的托管功能,不需要很智能。不过我对这块还是有点想法的,可能是以前自己在玩RTS时,很喜欢打电脑,而这些电脑大部分很蠢。我经常会想如果我来写,会怎样把它实现得更聪明一点,更像真人玩家一点。这算是我自己的一个痛点。

这块我并没有用行为树来做,而是直接写了一个状态机。之所以用状态机,而不用行为树,是因为我们现在的这个行为树工具还是挺复杂,挺难用的,包括策划也是这样反馈。初期在状态不多的情况下,状态机实现起来更快。

说起状态机和行为树的对比,很多人会觉得行为树会更加高大上。它们之间确实有一些不同点,比如从数据结构看,一个是图,另一个是树;从制作流程看,一个是程序纯代码实现,另一个是程序实现树节点,策划进行条件组合。但是,行为树跟状态机相比,其实并没有产生质的变化。这里的质,指的是它们两者,本质上都是规则驱动。而不像机器学习,本质上是数据驱动。

行为树可以通过构造子树,降低复杂度,增加模块重用率,从而实现更复杂的AI;但其实状态机也可以通过分层的方法,达到相同的效果。行为树可以把一部分工作分摊给策划,但也有增加策划学习成本,出问题时策划程序来回沟通的问题。所以行为树也并不是银弹,不同的问题,都需要多方面思考,才能找出最优方案。


·战斗外系统

今年最大的一块工作,是战斗外系统。从基础架构,到具体模块;从服务端,到客户端;从单服到跨服;从通用工具到运维部署,基本上能涉及到的方面,都做过一遍。这个过程当中,也把自己一直思考的一些提高效率,提高质量,降低维护成本的想法带到实践当中去。

这其中最重要的一件事情,是统一了数据结构。从客户端到服务端,到数据库,数据结构与变量名,都是统一的。这样做有非常多的好处,我通过写一个基类,里面包括服务端和客户端都使用的变量和方法,然后继承它,这样逻辑就省去了大量重复代码,维护起来不需要两边都改,降低了心智负担。

由于数据结构一致,使得我很方便的写出一个变量同步的框架sync_attrs(以及用类似原理实现的server_gm,方便添加各种测试接口)。它做的事情是,服务端某个变量的值改变了,可以通过mongo shell的写法调用该同步接口,使得客户端对应的变量实时更新。客户端界面则通过监听事件的方法,从客户端的类拿最新的数据,刷新自己。这种方法实现了view和data的解耦,可以说对开发和维护都是有极大的好处。

拿M项目的对比一下。以前的做法,我们每打开一个界面,都需要写一条额外的协议,还有组装数据的逻辑。这是一块非常大的开发维护成本,实际上是view和data双方耦合在一起了。而现在的方案,服务端通过变量同步机制,保证客户端的变量是最新的。view不管在什么时候打开,只需要读取客户端的数据即可,既不需要额外的协议,也不需要自己维护数据结构。后面哪怕UI迭代,核心的数据结构一般不会变,只需要改view的部分。这样就在最大程度上,减少了修改维护的工作量。

语言的统一,数据结构的统一,使得我们的开发工作不再需要严格划分客户端和服务端,每个程序员都可以是全栈开发者,这里节省下来的沟通成本,非常可观。以前做M项目,我主要是负责服务端,也试过单独做服务端+客户端的内容。但这两种工作方式,多少都有点不顺心的地方。要不就是两个人沟通、调试成本高;要不就是语言、数据结构不统一,心智负担高。现在新项目完成了这个工作,算是解决了一直以来的痛点,也为其他人的开发铺平了道路。

跨服方面,以前做M项目有比较丰富的经验,新项目这边也做了很多相关的工作,对比一下新旧跨服架构的一些差别。M项目所有的跨服逻辑都依赖于一个中心服,而且没有数据库。虽然架构简单,但也因为无法扩展,限制了思路。我更多需要考虑的是单点压力,还有数据一致性的问题。新引擎的中心服架构比较分散,可以按功能拆分到不同的进程,并且有数据库。当量达到一定程度,这样从功能上做垂直拆分,从架构上来说更加合理。所以我做功能的时候,设计上更多的是往模块化和扩展性方面考虑。


今年跟往常一样,精力也都放到工作上了。对于产出的东西,我自己是满意的。虽然目前游戏的样子,跟自己想象中的,有点不一样。但对产品,我还是抱着比较大的信心的,也期待着它成功上线的那一天。感谢这一年和我一起工作的同事,谢谢你们。



2017年1月