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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM