问题引入:aoi(area of interest).在大地图中,玩家只需要关心自己周围的对象变化,而不需要关心距离较远的对象的变化。所以大地图中的数据不需要全部广播,只要同步玩家自己视野范围的消息即可。

解决方案:

1:灯塔法。

所谓灯塔法,即将大地图划分成有限的小格子,在每个小格子中间放一个灯塔,这个灯塔管理两个队列:一个是本格子内所有的对象集合,另一个是对本灯塔感兴趣的对象集合(简称观察者)。

而地图上的每个对象,维护一个视野队列:该队列为其视野范围内的所有对象,即自身感兴趣的所有对象。

一个对象在地图上面运动:分为三个操作:enter,move,leave.

enter:当对象进入地图的时候,根据对象的当前位置和对象的感知距离,可以获取到该对象能观察到的所有灯塔,遍历这些灯塔,将该对象添加为其观察者。同时将这些对象添加到自己的视野队列中。

move:当对象开始移动的时候,对象从一个点到另一个店,那么视野范围必然发生变化。此刻需要将对象从老的灯塔的观察者列表移除,同时将对象添加进新的灯塔的观察者列表。此外,还需要跟新玩家的视野队列,因为视野范围变化,视野内的对象也相应变化。

leave:当对象离开的时候,将自身从附近灯塔的观察者队列中移除。

通过灯塔法,每当物体发生变化,我们能马上根据其当前位置,定位到他的所在的灯塔,同时找到它视野范围内相关联的物体。这样避免了遍历地图上所有玩家进行处理的方式。

当然灯塔的格子大小划分要因地制宜,格子越小,消耗内存越大,同时计算量变大。

2: 九宫格

九宫格也是打格子的方式之一,把地图划分为很多小格子,每个格子记录格子内的玩家,每个玩家的aoi范围是以自己为中心范围内的九个格子,九个格子的大小略大于屏幕大小,同样的有三个主要的操作:enter,move,leave

enter:根据玩家坐标,加入到所属的格子中,通过计算以这个格子的为中心的九个格子,这九个格子内的玩家就要被通知有新玩家初始化,同时这个新玩家初始化九个格子内的所有玩家。

move:根据移动前位置的格子,计算出移动前的oldaoi集合,根据当前位置的格子,计算出当前的curaoi集合,如果oldaoi, curaoi为同一个格子,则通知格子内的所有玩家该玩家在移动。如果oldaoi,curaoi不是同一个格子,即发生了跨格子的操作,那么要将该玩家从旧格子移除,同时加入新格子。同时分别遍历oldaoi,curaoi,计算出需要通知玩家消失的格子集合,通知玩家出生的格子集合,以及通知玩家移动的格子集合。

leave:玩家离开地图,将玩家从对应的格子里面删除,同时通知aoi集合有玩家离开。

3:十字链表法

这里以2d游戏为例,3d游戏顺势扩展即可。

所谓十字链表法,即维护两天链表,一条根据地图上所有物体的x坐标从小到大依次插入链表,一条根据地图上所有物体的y坐标从小到大依次插入链表,可以想象成一个十字架。这样便把地图上的所有对象按序分配到了x,y链表上。

这里的链表为双向链表,双向链表的好处是,获取到链表中的一个节点,便可以向前和向后遍历。这样,当我们拿到一个对象时,要获取该对象的视野范围就变得非常简单。避免了从头到尾遍历所有对象。

首先根据x坐标,在x链表上找到该节点,然后从该节点向前和向后遍历,根据x方向的视野范围找出需要识别的对象。

然后根据y坐标,在y链表上找到该节点,然后从该节点向前和向后遍历,根据y方向的视野范围找出需要识别的对象。

拿到x,y链表上需要关注的对象,然后取他们的交集,这便是玩家视野范围内的对象。

对于对象在地图上的enter,move,leave 。根据前面的思路就变得非常简单

对应的golang 九宫格实现:https://github.com/yyhero/gridview

06-27 23:33