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