freeeyes 发表于 2011-8-25 15:38:59

一个MMO游戏服务逻辑系统(三)

如果你看了上两篇文章,你一定会跃跃欲试,想看看服务器如何把这些信息串联起来。这一讲的技术将会非常精彩,如果你详细阅读了地图和NPC的制作,在这里我将告诉你,一个优秀的游戏服务器是如何做到数据处理的。
首先,看看到目前为止,我们有什么。
一个场景
一个地图
一个NPC管理器
那么,我们可以这么试想,我把场景可以想象成一个生命体,它有自己的心跳,每次心跳的时候,我都可以借此机会运算我场景内所有需要运算的对象。
那么;怎么做呢?
bool CScene::Update()
{
        m_objNPCObjectManager.Update();
        return true;
}


这里其实非常简单,我更新NPC中的所有信息,所有的NPC都会调用Update(),而这个实际Update是AI的Update()实现AI逻辑的处理。这里其实可以非常方便的展开,比如我上次说的白天黑夜,我可以给map一个Update()方法,在这里处理白天和黑夜的问题,亦或更进一步,我在这里可以定义某些事件在某一时刻的出现。比如,每天的10:00-12:00场景中的某一区域开放,过了这个时间就会自动关闭。呵呵,我想聪明的你一定已经领会了其中的奥妙,去自己实现一个自己想要的扩展吧。
其实这里还有更深的一个技巧,整个地图上所有的NPC我都要计算吗?仔细想想我的话?如果是只有在玩家身边的NPC,我需要计算,而在玩家视野外的NPC,我不需要计算。那么我的服务器就能提供更宏大的场景是不是?呵呵,是不是有了什么灵感?对,我们只需要一个必要计算NPC的队列,和玩家的队列,就可以了。这样,很多NPC在"静默"的时候,是不需要消耗服务器计算资源的,对不?那么,你看现在流行的MMO网游,单台服务器差不多3000-5000,那么需要对照运算的NPC有多少,估计不会超过15000-20000。更多的时候绝对小于这个数字,因为很多情况下玩家并不在野外而在城内,服务器的运算量会大大减低,把资源留给我们的IO(这里我下面会讲到)。
这里就有一个有意思的现象了,我有一个玩家,移动的时候需要通知周围的玩家,当NPC移动的时候,也要通知在此玩家视野范围内的玩家。那么这不是成了一个N*N的巨大数据量,我们真的需要如此吗?
呵呵,如果真是用这种方法,我敢说再强的服务器也禁不起你的这番折腾。其实,这里就有了一个服务器设计的小技巧。比如,我的场景心跳是100毫秒一次,那么100毫秒内,我必然会运算完当前场景的"静态图片"(也就是说那一时刻的场景状态)。我们只要通知指定区域的周围的玩家当前的改变即可。
那么,怎么通知呢?通知什么呢?
还记得我上一讲说到的("区域"管理器)吗?对,现在这个东东就是解决我数据发送的问题,我的地图是一个网格,在上面,我再用一层透明的网格,记录当前NPC的位置,这样,我只需要通知周围8个网格内的玩家,我的改变,就可以了。具体来说,我的改变可以分为几种,第一是状态的改变,比如死亡,重生,战斗,防御。第二是特效的改变,比如我身上冒光,冒血,大招特技等等,第三是我位置的改变,比如我的移动路线。
在这样的网格里,我只需要做到发送当前最新的状态即可,而路径特殊一些,我可以一次性把要移动的路径发送给客户端去处理,这样下次除非路径发生改变,否则我是不会再次重发的。
那么我们可以预计一下,如果3000个玩家,就算所有人每时每刻都在动,1秒钟服务器发送的数据包上限是30000个。不会出现无序增长的情况,更多的时候,我们发送的数据包远远小于这个数值。所以好的游戏服务器,并不会让你感觉到太卡,同时服务器的资源也是可控的,达到最大的效益平衡点。在做压测时,再大的压力也不会让服务器不堪重负,这里还要说明,服务器IO需要一些技巧来处理客户端请求过于频繁或者一些垃圾数据对服务器资源的占用,在这里IO模块应该能很好的挡住这些垃圾数据。
好了,在讲一个小技巧,那就是脚本怎么和游戏服务器结合。魔兽世界用lua估计很多朋友都知道,不过,脚本语言可不能滥用,滥用的结果往往是给自己的服务器造成比想象更大的压力。那么,怎么区分什么事情应该用脚本,什么事情不能用呢?最简单的规则就是,以调用频度区分,比如,或许有些朋友会问我,游戏的AI你为什么不用脚本?这里我得说,对于MMO而言,100毫秒调用一次的需求,比如战斗,寻路和移动。如果我再经过脚本解析器去运行一下,本身就会增大服务器的负担,得不偿失(脚本一般是基于虚拟机的,虚拟机对语言的翻译绝对慢于直接的机器码)。反而,如果我有很多任务描述,NPC对话,副本创建这种事情,触发概率相对AI较少,则完全可以使用脚本。因为从实际效果来看,AI策划变化往往不会经常修改,反而是任务,副本策划会经常添加修改。这部分设计程序员完全可以抛离出来给策划去做,从系统架构上看更加清晰。
那么,以lua为例,我们怎么把脚本引擎融合到当前的场景线程中去呢?
        CLuaFn::GetInstance()->InitClass();

        int nNPCCount         = IniFile.ReadInteger("NPC", "NPCCount");
        int nBegin            = 1001;
        char szIniNPCName = {'\0'};
        for(int i = 0; i < nNPCCount; i++)
        {
                sprintf(szIniNPCName, "%d", nBegin);
                string strNPCFileName = IniFile.ReadString("NPC", szIniNPCName);
                CLuaFn::GetInstance()->LoadLuaFile(strNPCFileName.c_str());
                //printf("IniNPCFile=%s.\n", strNPCFileName.c_str());
                nBegin++;
        }

InitClass是初始化lua需要用到的和C++的接口。
我可以通过一个ini文件,读取所有需要加载的lua脚本,比如,按照NPC的不同,加载的不同的lua脚本。
具体lua的使用基础,请读我以前的http://www.acejoy.com/bbs/viewthread.php?tid=1931&extra=page%3D1%26amp%3Bfilter%3Ddigest
在这个系统中,我进一步完善了这个引擎,大家可以参考着读一下。
那么,说了这么多,还是把代码放上来看看吧。
我的代码在windowsXp Vs2005下编译通过,同时1500个NPC移动和寻路,耗时基本在10-16ms左右(Release版),符合我的预期,当然,我当前的机器也不咋样,你的机器或许能跑出更好的性能。
写了这么多,兑现了在群里说的,如何做一个MMO服务器的承诺,如果有大家有兴趣,可以多多交流。

wesom 发表于 2011-8-26 11:37:03

讲得好,比较着重原理性的东西

melz 发表于 2011-12-12 15:40:04

讲的很好,俺要好好学习一下

ttgcn2008 发表于 2012-6-5 21:06:14

不错,学习了。谢谢!

Zerak 发表于 2012-9-4 17:53:31

好文,理论知识很重要啊。剩下的就是编码了。

霸气使者 发表于 2013-1-7 13:28:35

版主,讲的很好:)啊
页: [1]
查看完整版本: 一个MMO游戏服务逻辑系统(三)