对于MMORPG游戏来说,每个场景的人数可能会以千计,为了服务器性能考量.每个玩家的视野必然要有所限制。广播算法主要是用来解决如何高效让每个玩家感知周围物件.本人重点讨论各种广播算法和优化
一.常用算法说明
概念说明:
实体: 需要服务器管理状态的各种object.比如玩家/NPC/宠物等
广播算法最原始的做法,每个实体保存一个周围实体列表,再定时检测整个场景内实体之间的距离.据此派发出现/消失,算法复杂度是0(n^2).对于稍大些的场景,实体个数可能会上万,这部分的消耗会非常可观,采用的人也很少.
现有游戏的广播一般会基于格子来计算,常用的有3*3, 5*5广播.通常的流程是:
玩家进入场景时,根据坐标添加到对应的格子,每次移动时检测是否跨格子,如果跨越,计算出消失/出现的格子.对其中的实体派发事件.玩家退出场景,再对周围格子内实体发送消失事件.
这个算法最大的好处是把距离检测的消耗均摊.总体的消耗也非常少.缺点是视野的不对称,玩家不可能总是居于格子的中心点,大部分的情况是玩家某个方向上能看到50M,另外一个方向可能看到100M.这些冗余的视野会显著的增加服务器的流量.一个理想的状态是玩家无论在那个方向上,视野都是一致的.即圆形视野.
假定策划要求的最小视野是R,我们可以计算出各种算法的视野面积
3*3广播,玩家的视野面积是 (3*R)^2 = 9*R^2
5*5广播,视野面积是 (5*R/2)^2 = 6.25*R^2
圆形广播, PI*R^2
视野面积与广播流量是成正比的,从这里的计算可以看出,圆形算法能显著的减少广播流量.
二.圆形广播算法
上节总结了圆形算法所能带来的好处.这节重点说下实现.
考虑实现,首先我们针对问题做更细的分析
1.既然是优化广播,实际中我们做的处理只需要针对 玩家到 玩家/NPC/宠物等可能产生大流量的实体类型 即可。其他所有出现/消失逻辑都用格子算法
2.在副本内,广播量不大,可以不用做圆形广播.
我们综合原始算法和格子算法的优缺点.保证场景的格子大小为最小视野R的前提下,确定如下圆形广播算法流程(假定玩家/NPC/宠物等需要检测的类型是实体A):
1.玩家进入场景,取九宫格范围内实体A.判断距离,视野内的派发出现事件
2.玩家退出场景,对于之前保存的广播实体派发消失事件.
3.玩家在场景中移动产生的出现/消失通过定时判断距离来触发.
这里步骤3是重点.又分成以下三步:
1.玩家的周围实体列表保存一个标记位.每个定时轮回开始,置为0
2.场景内的每个玩家, 以他所在格子C为中心的九宫格内.总是检索 格子索引不 比C小的格子内的实体A.视野内的标记置为1.如果是周围列表内是第一次出现的,派发 出现事件.
3.检查每个玩家周围实体的标记位.依然为0的派发消失事件.
出于效率的考虑,还需要注意一些细节:
1.为了更方便快捷的取格子内的指定实体,格子内实体按照类型存储
2.定时检查的步骤1,可以合并到步骤3中.
3.周围实体也按照类型存储.减少定时检查步骤3的消耗.
4.周围实体列表的数据结构选取很关键.经过测试,经过内存优化的hash_map在这里具有更好的适用性.
总体实现下来,1600实体的检测消耗依然会达到6%以上.是很大一头.需要考虑更多的优化
三.更多优化
圆形算法的定时检测是个计算密集的地方,更多的优化,需要考虑进一步挖掘硬件的潜力.可供选择的有 矢量化(SSE),并行化以及利用GPU的通用计算能力加速(OPENCL).根据算法的特点以及服务器的硬件条件(核多利用不充分,无GPU),选择并行化进行下一步的优化重点。
并行化的底层实现方案有很多, 比如openMP,tbb, vc2010的自带并行库, 自实现等.
为了更好的适用性以及方便,可以选openMP.
现在服务器逻辑结构一般都是单线程/多进程的.引入并行化后,为了防止多线程的扩散,以及尽量多的计算放入到并行循环中. 需要我们把检测期 派发出现/消失事件的逻辑剥离.等并行计算结束后再触发.
定时检测的主循环并行后,大概如下:
#pragma omp parallel for
for(iy = 0; iy < m_cntRow; ++iy)
{
curCellID = iy*m_cntCol;
for( ix = 0; ix < m_cntCol; ++ix, ++curCellID)
{
检测curCellID内玩家到其他格子实体的距离
检测本格子内 玩家到其他实体的距离,并审查标记位
}
}
触发缓存的事件
并行后的效果非常明显,根据核的个数,一般能提升N*0.9倍.8000实体相互检测只占5%左右
四.广播算法的复用及其他
鉴于广播在服务器的重要性,为达到最大化复用/优化的效果.我们可以在服务器另外启一个进程专门来做这个事情,约定跟主逻辑服的协议,可以让所有游戏通用
定义主逻辑服为M,广播服是N.协议定义如下:
M-->N 创建场景
M-->N 某个位置创建实体
M-->N 实体移动
M-->N 实体删除
N-->M 实体之间出现
N-->M 实体之间消失
如果出现/消失事件本身的处理消耗比较大的话,可以对圆形视野广播算法适当进行改进,小视野进,大视野出.减少玩家频繁在边界来回移动产生的事件
五.引申应用
基于如上的算法思想,也可以把它应用于无限大世界的客户端资源加载上.
客户端的资源加载也有服务器广播的缺点:资源加载不均衡,边界地带还会有频繁的创建和销毁.
我们可以在九宫格基础上,把每个格子继续细化成更小的格子.通过小格子距离的判断来近似达到圆形加载的效果.
再引入大小视野,减少边界地带的创建/销毁.