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