AOI

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

 

posted @ 2012-09-29 08:28  sniperHW  阅读(6212)  评论(5编辑  收藏  举报