AOI服务作为网络游戏中的中的一个重要组件,用于为地图中的对象根据当前坐标更新关注列表.对于玩家而言,在A关注列表中的对象,其状态发生改变时,需要通知A,这样A才能看到在视野内
其它对象的移动,战斗等。对于NPC而且,关注列表中的对象表示在自己一定范围内的对象,可作为AI选择的攻击目标。
典型的AOI算法包括格子,十字链表等,关于十字链表法可参考:http://www.codedump.info/?p=388
本文介绍一种基于格子的算法.
本算法实现的目标是支持可变长视距,根据视距半径,计算出一个包围这个视野圆的最小正方形,然后计算出这个正方形包含在哪些格子中,这些格子中的对象都有可能是可见对象.
首先介绍基本的数据结构:
单元格:
struct map_block { struct double_link aoi_objs; uint32_t x; uint32_t y; };
地图中的所有对象都归属于一个单元格管理,所以单元格中有一个双向链表,方便对象的添加和删除.x和y表示单元格在地图中的行列坐标.
struct aoi_object { struct double_link_node block_node; //ͬһ¸ömap_blockÄڵĶÔÏóÐγÉÒ»¸öÁбí struct map_block *current_block; uint32_t aoi_object_id; struct bit_set self_view_objs; //×Ô¼º¹Û²ìµ½µÄ¶ÔÏó¼¯ºÏ struct point2D current_pos; //µ±Ç°×ø±ê uint32_t view_radius; //¿ÉÊӰ뾶 };
aoi_object代表地图中对象,view_radius表示其实际可视半径,self_view_objs记录了当前关注的对象集,用一个bit_set实现,目前我将位集的大小设置为65536,也就是地图中最多可容纳65536个对象,
这样,当对象进入视野时就根据aoi_object_id设置相应的位,离开时清除相应的位,还可以快速的判断一个对象是否在自己的视野中.
基本算法如下:
1)当对象A进入地图时,以标准视距作为半径,算出相关的单元格,遍历单元格中的对象,计算出两对象A和B的distance,如果distance < A->view_radius则B进入A的视野,如果distance < B->view_radius
则A也进入B的视野.
2) 对象A离开地图时,以标准视距作为半径,算出相关的单元格,遍历单元格中的对象,计算出两对象A和B的distance,如果distance < B->view_radius则A离开B的视野.
3)当对象A在地图中移动时,以标准视距作为半径,分别为老坐标和新坐标计算出相关的单元格,这里的单元格会被分成三类,1:新进入的,2:离开的,3:无变化的.对于1,2类分别按1),2)处理即可.
对于第3类,格子中的对象需要做进一步的判断(参看后面贴出的实现代码)
上面提出了一个标准视距的概念,主要用于处理以下问题,A视距为100,B为50,A静止B向A移动,如果以B的实际视距做计算,则只有当B在A的50范围内时A才能看到B,而实际上B在A的100范围
内时A就应该看到B了。如果将标准视距设置为100就不存在这样的问题了.
但是,标准视距离设置得过大,会导致计算更多的格子,所以标准视距一般设置为玩家的可视距离.这又出现了另一个问题,游戏设定中,可能会出现少数超远视距的对象。例如,标准视距是50
但某些对象的视距是100,超视距对象在地图中相对来说是稀少的,所以对超视距的对象可做特殊处理.首先,超视距对象有一个更新间隔,每次更新视野后,记录下变更时间.触发视野变更的条件之
一是执行move,但如果超视距对象长时间静止就需要主动调用一个函数,以触发其视野变更.处理代码如下:
static inline tick_super_object(struct map *m,struct aoi_object *o) { uint32_t now = GetCurrentMs(); if(now - o->last_update_tick >= UPDATE_INTERVAL) { //remove out of view object first uint32_t i = 0; for( ; i < MAX_BITS; ++i) { if(o->self_view_objs.bits[i] > 0) { uint32_t j = 0; for( ; j < sizeof(uint32_t); ++j) { if(o->self_view_objs.bits[i] & (1 << j)) { uint32_t aoi_object_id = i*sizeof(uint32_t) + j; struct aoi_object *other = m->all_aoi_objects[aoi_object_id]; uint64_t distance = cal_distance_2D(&o->current_pos,&other->current_pos); if(distance > o->view_radius) leave_me(m,o,other); } } } } //process enter view uint32_t x1,y1,x2,y2; cal_blocks(m,&o->current_pos,o->view_radius,&x1,&y1,&x2,&y2); uint32_t y = y1; uint32_t x; for( ; y <= y2; ++y) { for( x=x1; x <= x2; ++x) { struct map_block *bl = get_block(m,y,x); struct aoi_object *cur = (struct aoi_object*)bl->aoi_objs.head.next; while(cur != (struct aoi_object*)&bl->aoi_objs.tail) { if(is_set(&o->self_view_objs,cur->aoi_object_id) == 0) { uint64_t distance = cal_distance_2D(&o->current_pos,&cur->current_pos); if(o->view_radius >= distance) enter_me(m,o,cur); } cur = (struct aoi_object *)cur->block_node.next; } } } o->last_update_tick = now; } }
完整代码如下:https://github.com/sniperHW/kendylib/tree/master/aoi