文章目录
有利的部分
erlang是好的,吸引了很多人的兴趣。不少人甚至将erlang体系的影响带到了自己所在的其他语言项目中,构建一些类似erlang的基础设施。包括我。
- 随意挥霍的process。 端游从早期的单进程支持一个游戏服,发展到现在的多进程支持一个游戏服。其中涉及多个进程协作的逻辑越来越多,异步任务相互之间由于时间差带来的问题也越来越频繁。
通常我总会利用状态机或者类似的办法管理异步任务,但并不能顾及到每一处,而且状态机增加了理解代码的难度。
erlang轻量的process使我能为每个异步任务创建一个独立的执行过程,同时变量不可变的机制几乎消除了多线程中的资源共用的问题。 - 发送消息太简单。
用C++,发送消息从socket干起,还只能发给一个真正的系统进程,然后switch分发处理。
在异步任务逐渐增多的环境下,有时候很希望语法糖是:能将消息发送给某个异步任务。需要消息平台是一种逻辑上的抽象,而不是底层的socket。
这个可以C++包装一个,而erlang提供了一个极其完善的面向process的消息系统,process又可以映射到异步任务中。 - ETS,还有Mnesia,it’s great
在多进程支持一个游戏功能的情况下,有大把的数据需要在不同的进程之间同步共享。也用C++包装一个类似的系统能降低很多重复的数据同步工作。
erlang提供了ETS甚至Mnesia,几乎无条件的为我们提供了数据的分布、持久和恢复。
困难的部分
用erlang开发游戏最困难的地方就是实现场景逻辑。这里没有异步任务,这里是效率、指针和角色相互操作的天堂,但是对erlang来说可能是地狱。
常见的是player/NPC角色之间的立即相互修改,从地图区域查询一批player/NPC角色等。在指针环境下这些操作非常有效率。
一个实时战斗的MMORPG要求核心逻辑要达到25帧/S,区域地图在线1000人。
- 如果我们为每个player/NPC映射到process用于交互,那么他们必然使用消息去操作访问对方。 这很美,但是效率不行。因为player/NPC的操作访问实在太频繁了,这一来erlang就变成一个消息繁忙的系统,真正的游戏计算反而耽误了。
- 每个场景一个process,场景中所有的player/NPC集中保存到tuple,list中,通过参数传递。
这样具体操作player/NPC的数据确实快了,但是erlang的原生list类型不是利于搜索的类型,这一趟趟查下来也就将计算时间花光光了。
另外通过参数传递也会导致每个相关的子函数都要留一个口子。 - 每个场景一个process,场景中的所有player/NPC都保存到进程字典中。
进程字典的存取速度在erlang中算快了,插入100ns,查询40ns。ETS的速度是us。参见进程字典到底有多快
游戏的三种逻辑
如果要用erlang来做MMORPG,那规划一下游戏的基本逻辑模块,根据模块的特性选择不同的erlang模式,是有意义的。像C++那样一种模式就行得通的做法,并不适用于erlang。
- 自我修改逻辑
比如升级,强化之类,就是改改自己的属性,改改自己的技能。
这种逻辑erlang可以为每个player/NPC创建process负责,也可以一个process处理多个player/NPC的类似逻辑。客户端对于这种操作的延时总能容忍1,2秒,慢慢来,性能没关系。 - 低频角色交互逻辑
比如组队,领取任务,NPC思考等等,角色需要相互操作对方数据。同样也是延时容忍较高,可以尽情发挥erlang在这种交互逻辑中的异步任务支持能力。 - 高频角色交互逻辑
同一个场景中,角色交互频率最高的就是移动和战斗。
移动影响相互间的视野,战斗则直接读取修改彼此的数据。不要忘了,1000人25帧/S的计算。
移动和战斗
这一块比较纯粹,由一个process为一个场景的player/NPC提供服务,基本上有下面几样内容就搞定计算:
- pos,角色坐标
- atb,角色属性
- skill,技能列表
- state,状态列表
- 同屏表
- 地图数据
- 事件列表
前4条属于角色数据,简单操作即可。
同屏表通过四叉树管理角色的空间,为各种类型的群攻判定提供快目标的快速查询。
地图数据为角色检测技能释放的空间合法性,以及战斗寻路。
事件列表则每帧更新技能、状态等数值的计算。
效率优化
仅仅是纯粹的技能效果计算,erlang在虚拟机上的效率肯定不及C++。不过属性计算这块要求的性能并不高,猜测还是能扛下这个计算。
费时、难写的计算可能有两块,同屏表和地图数据。
erlang的数据类型很难高效的处理关系型结构,空间四叉树和地图三角关系用erlang实现起来相当凑合。那么,同屏表和地图的功能到底作为场景战斗process的一个功能,还是以独立process的形式服务呢?
process发送消息的性能大概是1us。
通常一个角色的一次战斗计算需要访问同屏表和地图功能各一次,4us。1000人的话是4ms。1帧40ms,那么每帧大概有30ms左右的时间用于完整的战斗计算,包括同屏计算和地图计算。
为了进一步提高同屏计算和地图计算的性能,可以用C++实现erlang的内置函数。此外,原先逐个角色串行计算同屏功能和地图功能,可以改并行:
- 先收集全部角色的同屏计算需求
- 将全部计算需求均匀发送到多个同屏表process进行计算
- 一边收集结果一边继续计算剩下的战斗部分。
地图计算也采用类似的并行处理,即可提高效率。