上回写了写服务端的分层结构,分层是比较宏观上的东西,至于层次间具体的交互方式还得通过各个模块的交互方式来体现,姑且把这种模块划分以及其间的交互关系称之为架构吧,下面就来谈谈MMORPG的服务端架构,
对于制作一款游戏而言,首先要考虑的是做什么样的游戏?要实现哪些功能?而为了进一步简化问题,我们先考虑一款游戏的核心玩法——即少了这一部分便会使得整个游戏失去骨骼,乃至于产生一种“皮之不存毛将焉附”的感觉的部分。
首先,对于一款MMORPG而言,角色和战斗是RPG游戏中比较强调的一个部分,其他功能会丰富整个游戏使它增添乐趣,但不会取而代之。所以我们的系统至少应当包含有NPC模块、玩家角色模块,需要有活动的场所因此——场景模块,需要有活动的内容因此——战斗模块。
其次,为了使玩家间能够相互通信,我们还需要网络模块;为了记录玩家数据,我们需要数据库模块;为了监控和记录游戏和程序状态,我们需要一个日志模块。好了,再说缺少什么的话,我觉得是有了更好,但若没有我们也能好歹应付下来。
最后,罗列一下:NPC、玩家、场景、战斗、网络、数据库、日志——完整MMORPG的最小系统版图~
接下来考虑这些逻辑如何在程序中体现的问题——
在《游戏系统开发笔记(四)——游戏程序简介》中曾简单介绍过服务端的程序结构,那么这里再来细化一下,先考虑最简单的设计:
直接上贾代码:
int main(int argc, char* argv[])
{
Init(); //初始化各种资源、环境、数据
while(g_State != GAME_EXIT)
{
CheckToSleep(); // 定一个循环间隔,没跑满的时间咱们让CPU放松下
//GameLoop
NetMessLoop(); // 收发网络消息,并将收到的消息分派给各个模块(比如加到各自的消息队列中)
NpcAILoop(); // Npc的AI
PlayerLoop(); // 处理玩家行为
//OtherLoop // 比如帮会、副本、任务等等等
DBLoop(); // 数据库操作
TimeEvent(); // 定时任务
}
Release(); // 清理和记录工作
return 0;
}
这里我们把预期游戏的功能拆分成网络消息、AI、玩家行为、定时任务几块,通过一个循环不断的去刷新各种状态、执行各种请求,从而达到使游戏运转起来的目的,循环的间隔时间主要影响了游戏的最小响应时间。其中日志模块、数据库一般会设计成一个全局范围的功能,各个模块直接对其进行操作。
这个例子把各个模块功能完成理论上就可以完成任务了,而且看上去是十分清晰简洁的。按这种方式写下去,通常就是对每个在线玩家和NPC设置一个唯一ID,放到各个模块的表中,各个Loop维护一个消息队列,消息格式可能设计成消息ID+参数的形式。接下来实现各种消息对应的处理函数,然后处理消息的过程中从参数解析到对象ID,通过ID和一组操作角色行为的函数来进行控制。
——这是比较典型的C风格的游戏服务端做法,过程式的风格使得结构看起来比较扁平化,相对容易快速把握住整体结构。
下面是这个简单例子的逻辑结构:
--------------------------------------------------------------------------------------------------
可以从中看出,我们把大部分任务都归到了逻辑模块,这会带来两个显而易见的问题:1、过于复杂 2、项目代码难以重用
另外,在这个例子中我们也没有考虑多线程,所有操作都在主线程内完成,好处当然就是简单清晰。带来的问题,首要的倒谈不上硬件资源浪费,因为多核的服务器其实大不了多开几个游戏服务端,一个核跑一个服也就没有浪费不浪费了。更加重要的问题是现在单核的主频因为技术制约已经很难再有所提高了,依靠单核的计算能力很可能会使得一个游戏只能承载寥寥几百人同时在线——多么杯具!
所以总的来说,对于现在的商业游戏项目来说,项目代码难以重用和承载人数太少是太难以接受的,一般来说也不愿意投入许多精力去做这样的东西吧。
所以我们需要首先针对这两个问题对项目进行优化——
代码重用问题一般而言首先会考虑根据“一般性”和“特殊性”对功能进行分离,设计一组划分更加合理的模块。而承载人数的问题,虽然优化程序是很重要一方面,但既然结构上具有不合理性,当然先考虑从结构上解决问题——我们需要使程序能够更加充分的利用硬件资源。具体的措施我会先从第一个问题着手,因为模块设计好了通常也就为多线程做好了必要准备,剩下的可能也就是把它们分别装配到其他线程的事情而已了。
因为网络、数据库和日志三个模块功能上非常独立,为它们各自设计一组逻辑无关的操作接口后就已经具备了重用的条件,这个例子中主要的问题在逻辑模块。
经过考虑,对于MMORPG来说逻辑模块的只有移动、场景相关的功能是具有比较广泛的一般性的,其它功能都或多或少的和具体游戏产生不可分割的联系。但因为移动相关的功能比较复杂、执行频率也非常高,所以可以单独抽出来做一个模块。说它复杂因为移动不只是简单的从一点到另一点,还会涉及碰撞、移动路径、同步等问题。我们把可视、可移动的这种能力——很基本的能力设计成一个独立对象(实体对象),组合到需要这种能力的其它对象中。
实体对象的设计包含两个部分,其一是执行具体移动相关业务的部分,其二是逻辑模块和该模块交互的接口层,上面说的组合对象实际上是指把这里的作为接口的对象组合进来,这样我们才能把实体对象需要做的工作给完整的抽取出来。给这个模块赋予一个好听点的名字,就叫引擎模块吧。
我们的逻辑模块剩余的还有角色、NPC、战斗、场景以及其它游戏功能,它们相互之间会有比较频繁和复杂的相互操作,最多只能做到把各个子模块划分的再清晰些,但若是把任何一个部分放其它线程去就麻烦啦!复杂的游戏逻辑里面还要夹杂各种异步操作简直要让人发疯。
所以最后关于线程划分的设计也差不多出来啦:
-------------------------------
这种结构说起来算是一个拆分多线程的雏形吧,具体操作过程中可能还要有许多调整。根据性能热点和功能类型可以考虑进一步拆分细项,但拆分的过程中要考虑好多线程带来的利弊得失,对于游戏服务器来说,多线程间过多的交互会带来较大的消息处理延迟,使得一些细节的游戏表现很难做到位。另外也要考虑到开发的难度,像逻辑这边只能说还是更多的要为开发难度着想,异步的游戏逻辑下实在是很容易出各种BUG,有的不好查,有的查出来又不好解决 等等等~~
总体来说,负载问题能得到或多或少的缓解,而代码复用的问题,一般来说引擎层的东西写稳定了就不需要再改了。逻辑上的东西虽然大多都很有个性,但考虑到MMORPG类游戏总体上都长的差不多,直接拿到一个项目来修改和扩展也无妨,而且可以相对节约学习使用引擎的成本。考虑到扩展和节约代码的问题,我觉得可能(仅仅是可能,这方面毫无经验)用C++来实现会更适合些。
结合上一章的分层结构来看,系统层实际上只提供了一些底层操作供上层调用,相当于工具库,没有结构上的意义,所以它对于这套架构来说是不可见的。而引擎层在上面第二张图里也已经有雏形了——有个引擎线程,但要注意的是引擎线程和引擎层是不等价的,上面也说了,这里只是“比较随便的”称之为引擎模块而已,其实你爱叫什么“移动层”的也完全随你。引擎层所指的范围要大些,包括这里的“引擎线程”在内,还有网络、数据库、日志模块都可以归入引擎线程。而作为大头的逻辑线程(主线程),同时也可以认为是分层意义上的逻辑层。
很晚了,先写到这里。