問題引入: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