mangos地图管理

来源:互联网 发布:淘宝怎么弄淘口令 编辑:程序博客网 时间:2024/04/30 12:37

在魔兽中,一组服务器为一个世界,一个世界内同时存在着多个游戏场景,

如东部王国,卡利姆多,监狱副本,战场等。每个游戏场景都对应着一幅地图,

但一幅地图却并不一定能确定一个场景,这是因为有副本的存在。在一个场景地图里,

还会分区域,比如东部王国里会有艾尔文森林区域、西部荒野区域等。

  

从wow客户端的adt及wdt数据里可以大致了解到地图相关数据。每张地图最多有64*64个格子,每个格式大小为533.33333*533.33333(即1600/3,为什么设定成这个数字?)除了东部王国和卡利姆多外,大部分的地图都比较小,不会真正占用这么多个格子。每个格子用一个adt文件来描述其详细信息,另外有一个wdt文件来指出该地图中哪些编号的格子存在。

 

再来看mangos中是如何表示这种结构的。一张地图就是一个Map对象,对于可以创建出多个场景的副本地图,多个副本也就对应着多个Map对象,里面记录了当前管理的是哪个副本,另外再有一个MapInstanced组件来维护副本地图列表。地图内分成的64*64的格子,每个格子的信息包括区域标志、地形类型、水位层级和高度数据,用GridMap结构体来表示,由Map来维护这个GridMap二维数组。另外Map内还维护了该地图内所有的WorldObject和GridObject列表,当然也是以Grid为单位来保存。由于一个Grid的实际大小为533*533,其范围还是非常大的,一般玩家也不需要关注这么大

范围内的所有对象,所以mangos又将一个Grid分为了8*8个Cell,每个对象都同时关注当前所在Cell及周围的八个Cell内的对象。这种划分格子来管理地图区域的方法在以前基于TILE的2D游戏中差不多为最通用的方法。

 

现在的服务器地图有些也引入了客户端场景管理的方法,使用四叉树、八叉树的方式,

http://blog.codingnow.com/2006/10/quadtree.html http://blog.codingnow.com/2006/12/collision.html



用四叉树管理散布在平面上的对象

周末一直在考虑怎样在游戏服务器中保存和管理那些有平面坐标的对象。希望能方便的查到每个对象附近的东西。以往用过的方法是给(平面)场景打上格子,然后再把每个对象放入相应的格子中。

这次又遇到这个问题,却不想用网格的方法来解决问题了。

原因主要是这么几点:一,用网格对于大场景特别消耗内存,而且不太适合做无限延展的场景。二,查询每个对象周围的东西时,不太方便。格子的粒度越小,速度越慢。

虽然这两个问题,都可以通过改进算法来回避。不过这次,我想尝试一下用四叉树解决这个问题。

用四叉树的话,内存占用量应该 O(n) 。如果定死了四叉树的扩展深度,可以推算出节点的最大可能消耗量。我想,如果只是为了解决 AOI 的计算,5 级深度已经足够。这样,大约预分配出 20 * n 个节点,几乎肯定够用了。

我希望场景是从坐标原点向四周无限延展开,没有什么具体的尺寸限制。所以,这次实现的时候对四叉树做了改造。第一级的划分是一个特例,它将平面分成四块,每一块都可以向某个方向无限延伸。

接下来,把各种可供切割的平面分为 9 类:

 2 6 1 7 5 9 3 8 4

我给这些类别编了号,5 号类型的平面有固定大小,可以被明确切割成四块,这四块依然是类型 5 。而类型 1 的块,被切割成四块后,四块的类型(按四个象限分布)分别是 1,6,5,9 。同理,类型 2,3,4 的平面,也会被切割成不同类型的四块。

类型 6,7,8,9 的平面由于他们只向一个方向延伸,所以只能被切割成 2 块,一块固定大小的正方型(类型 5),一块保留它原来的类型。

这样,我的数据结构不是一个完整的四叉树,有个支干上就只有两个分支了。如果一个对象被放到了离坐标原点过远的地方,树的深度可能很大(超过 5),但是宽度会减半。如果大多数对象都在原点附近的话,查找是很快的。

今天化了一通宵的时间来实现这个东西,写了四百多行 C 程序。因为要实际用到项目中去,效率上的考虑比较多,顾而时间用了近 8 个小时;而且篇幅也比我想象的长的多。

一般,我们会在四叉树的每个节点上保存父节点的指针。这样,遍历这棵树就不需要用递归实现。好久没有写纯算法的程序了,平时递归写惯了,今天为了实现个用回溯法遍历树的算法还颇费了点工夫。为了提高查找一个节点附近节点的效率,最后也做了特别的实现。

另外,因为树节点总数可以估算出来,我用了个预分配的大节点池,并内部实现了 gc 算法。这样,从四叉树上拿掉节点非常简单,分配新节点也很容易。事后想了一下,不用 gc 的话,用一个 freelist 来管理也慢不到哪去。

在四叉树的叶子上,存放一个对象链表即可。可以用一个备用链表,分配,释放,移动位置都不需要重新构造。估算了一下,对象在四叉树描述的场景中移动,处理过程应该是常数时间了。查询 AOI 区域的对象也是一个常数时间。总的来说,对今天的工作成果还是挺满意的 :)


隔日补充:

其实,通过动态增加层次来向边界延展的算法实现起来更简单,也没有增加太多储存和查询的负担。昨天晚上把问题想复杂了。睡一觉起来后,把程序改了。


 

 

碰撞检测

我始终认为,在 MMORPG 里采用多边形碰撞检测是件很傻的事情。当然傻不是坏事,基于多边形碰撞检测,一帧检查一次的这种做法,实现起来非常简单。很符合 kiss 原则。我们只需要一点点数学知识和一点点编程技能就能做出来了。反正 client 上,也就检查一个主角。加上可以使用简化的碰撞模型,进一步的减少运算量。

但是放在服务器上,这个运算量可不小。所以这几天我寻思着找个更好的方法出来。

据看过代码的朋友说,使用 unreal2 引擎的天堂2 当年用的打格子的方法来检测碰撞的。打格子同样是一个老土而且巨傻的方案。从 2d 的年代一直流传下来。非常非常的符合 kiss 原则 :D 同时我知道的还有我们公司已经上市的游戏产品,几乎都采用这个方案。

魔兽世界似乎不是这样做的,我想是因为 wow 的地图过大,如果把整个阻挡信息用位图保存起来,会吃掉大量的内存。即使大部分内存根本不需要访问,那被占用的地址空间也非常可观。早期 wow 的做法是,服务器不于检测。从先前的 wow 外挂那飞天遁地的能力来看是如此。

关于阻挡信息的压缩,这里就不想详述了。如果做连续地图如魔兽世界那么大,必须压缩的话。至少四叉树会比较有效。

这两天,我的想法是尝试用多层的平面来描述地图信息。每个平面上采用线段和园构成的矢量图形。(后来实际编码时,偷懒去掉了圆)

采用矢量信息,数据量比阻挡信息位图少了不只一个数量级。所以在单次运算量不增加很多的前提下,总的运算量会下降很多。具体思路是这样的:

当需检测物体有了一个速度后,从出发点发出一条射线,判断跟最近的障碍物(通常是一条线段或一个圆)相交的距离,并估算出时间。设置一个 timer 当时间时做出相应处理。

这个估算的距离不必完全精确,只需要小于等于实际距离即可。所以无论是判断线段相交还是圆相交都不必解方程精确计算。我们只需要取障碍物的外截矩形,做一个简单的碰撞检测。再可能发生碰撞时,以 x 方向和 y 方向的靠近速度来估算距离就够了。

采取这种逐步逼近的方法,障碍物比较稀疏的场合,会非常的有效。

利用这个矢量地图来做寻路,更是非常有效。这次就懒的写了 :D





0 0