把 AOI 的部分独立出来
应该是在 blog 上第 2 次讨论 AOI 的问题了,另外还要算上一篇写完没有公开的。
昨天刚出差回来,晚上跟前大唐的服务器主程聊了一下。扯到了 AOI 的问题,我提了个分离这个模块的方案,在此记录一下。
AOI 主要有两个作用,一个是在服务器上的角色(玩家或 NPC )做出动作时,把消息广播到游戏地理上附近的玩家。当进程中负责的玩家不是很多的时候,可以直接对整个进程内的连接广播。这样的处理最简单,随着硬件的提高,可能是未来的主流方法。但是目前,为了减少处理的数据量,尤其是带宽,我们通常需要 AOI 模块裁减一些数据。
第二,当玩家接近 NPC 时,一旦进入 NPC 的警戒区域,AOI 模块将给 NPC 发送消息通知,以适合做一些 AI 的反应。
AOI 的实现算法很多,这里不展开来谈。我目前关心的是,如果 AOI 在游戏中非常重要(尤其对于即时战斗模式的大场景 MMO )能否把这个模块抽离到独立进程中。
首先我们来看第一个需求。其实在游戏中,需要广播的消息大多次序不敏感。比如移动,是 A 先移动还是 B 先移动并不重要;又比如攻击,是先攻击再移动,还是先移动再攻击也不重要。我指的先后是指 client 接收次序。攻击是否启动虽然跟距离有关,但是发出攻击指令前,服务器逻辑已经校验过合法性。也就是说,服务器发过来的信息都是合法的,client 就不用太在意位置改变在攻击前还是攻击后了。
那么,我们把 AOI 广播的这个职责拆分到独立进程做,就不需要跟逻辑进程协调发包的次序。这是个好消息。
设计一个 AOI 进程,让逻辑进程在任何一个对象改变位置时发一个包通知 AOI 进程,这样等于复制一份对象位置状态在 AOI 进程中。以后,当对象需要广播它的状态变化,只需要把需要广播的消息发到 AOI 进程里,然后由 AOI 进程过滤一下(筛出附近的对象,做一个组播)。
需要注意的是,由于消息都是异步发生。我们在 client 接收对象 id 时,应该过滤掉一些过期的 id 号。因为有可能逻辑服务器已经删除了某对象,但是 AOI 服务器迟一些发送了跟这个对象相关的消息。(把对象删除的消息交给 AOI 服务器转发是一个糟糕的想法,因为这增加了 AOI 服务器的职责,增加了复杂度)
对于 AOI 消息通知的问题,可以让 AOI 服务器在检测到对象侵入别人的警戒区时,向逻辑服务器发会一条消息。这个消息不需要实时反应。比如,策划要求一个 NPC 有 20 米的警戒区,其实不必在玩家刚好 20 米的边界上触发,稍微延迟半秒是无所谓的。而且警戒区越大,允许的延迟时间就越长。
单独的 AOI 服务,可以根据警戒区的半径,进行优先级排序。然后有条不紊的通知消息一个个发回。
对比单独的 AOI 模块,独立 AOI 进程有更低的耦合度。通过协议耦合而不是接口耦合。更容易维护和日后的优化。没有和逻辑部分的共享状态,减少了 bug 滋生的可能。
ps. 最近跟新同事讲了一节课。其实没讲太多技术上的东西。只是介绍了游戏行业,以及计算机行业的硬件发展历程,以及游戏的进化,还有一点点软件开发上的进步。
我想,一切的历史都在见证,只有把东西设计的足够简洁,才能够顺利的向前发展。如果不够简洁,有一天我们会抛弃它们,或者为之付出代价。希望可以给刚从学习毕业的同学们一些启示。
前天开会前抽空读了一小时新运营的大唐二的服务器代码。追查出一个隐藏很深的 bug 。据说这个项目刚对外开放的这几天情况不太好,老是当机。程序员应该明白,其实也不一定是很多 bug ,但是一个就够你受。
大唐 2 的服务器写的挺“精巧”,C++ 和 lua 之间绕来绕去的,差点没把我绕晕。太复杂了……