首先我們來討論下游戲開發中的幾個坐標系,為了方便解釋,我截取了燈塔AOI DEMO當NPC數目為0時候的樣子(代碼地址覺得有幫助的童鞋記得給我代碼點個星_)
先對這張圖簡單說明下:
- 藍色的坐標軸表示是燈塔AOI坐標系,綠色的坐標軸表示的是游戲坐標系,向左為X軸正方向,向上為Y軸正方向(這個坐標是我自己后面畫上去的)
- 深藍色的點表示燈塔AOI坐標,左下的表示(0,0),右上表示(1,1)
- 深綠色的點表示游戲坐標,左下表示(0,0),右上表示(1,1)
- 每個灰色的小格子代表一個游戲坐標(邊長為15像素)
- 每個黃灰的大格子代表一個燈塔AOI坐標(邊長為11個灰色小格子)
- 瓦片地圖大小為寬:100個灰色小格子 高:70個灰色小格子
- 深藍色的方塊為玩家,大紅色方框為玩家在游戲坐標系中的視野(視野半徑為10個灰色小格子),蘭色方框表示玩家在燈塔坐標系中的視野
像素
這個是客戶端圖形實際顯示要用到的坐標(和游戲邏輯無關),每個圖片長寬各多少像素,描點在什么位置,但是在游戲中直接使用像素作為計量單位(坐標點)十分不方便,比如物體的移動,況且移動也不會一個像素一個像素移動,所以我們做了一層抽象(格子),游戲相關的貼圖(地圖/人物/道具)必須是格子的整數倍,這樣游戲中所有的物體才能被正常顯示,而不會產生偏差。
格子/瓦片(游戲坐標系)
什么是格子?格子是游戲邏輯使用的最小單位,代表游戲坐標系的點,它的單位為像素(DEMO中15*15像素為一格),游戲中所有設定都必須是格子的整數倍,所以不會出現有物體在兩個格子之間,導致坐標不確定的情況,物體的移動的步長單位也變成多少格,而不是像素了,即如果使用像素作為坐標點,從(0,0)移動到(0,1),則移動了1個像素,現在用格子作為坐標點,從(0,0)移動到(0,1),則移動了15個像素。
什么是瓦片?一張大的世界地圖或者背景圖可以由幾種地形來表示,每種地形對應一張小的的圖片,我們稱這些小的地形圖片為瓦片。把這些瓦片拼接在一起,一個完整的地圖就組合出來了,這就是瓦片地圖。
在DEMO中,為了簡化邏輯,我將瓦片設置為一倍格子大小,玩家和NPC的大小也設置為一倍格子大小。
燈塔AOI坐標系
為什么需要燈塔AOI?假設我們想知道某點周圍10格內有哪些對象,在沒有燈塔AOI的情況下,我們需要遍歷所有的對象計算其是否在范圍內,隨着地圖內的對象越來越多,查找的效率也會越來越差,所以我們需要一種方法來過濾那些明顯不需要參與計算的對象,所以我們將地圖分割成一個個區域,在其中心放置一個假想的"燈塔",每個"燈塔"都會保存區域內的對象,這樣當我們需要知道某點周圍10格內有哪些對象時,我們只需要計算出范圍內有哪些"燈塔",然后獲取這些"燈塔"保存的對象列表,針對這些對象進行計算就能節省大量計算。為了方便表示和管理這些"燈塔",我們為其分配了新坐標(左下為(0,0)),這個新的坐標系即燈塔AOI坐標系(這個坐標系是用來做碰撞檢測的)
燈塔視野和玩家視野
"燈塔"的視野越小,在碰撞檢測時能過濾的無效對象就越多,但是整張地圖"燈塔"的數目也就越多,消耗的內存就越大,而且對象進出燈塔的計算量就越多
"燈塔"的視野越大,在碰撞檢測時能過濾的無效對象就越少,碰撞檢測的計算量就越大,想象下只有一個"燈塔"的情況,即退回了沒有燈塔AOI系統的情況
由於玩家的視野一般是固定的(屏幕顯示區域大小一般固定),所以燈塔視野大小一般是玩家視野的1/2或1/3比較合適
燈塔坐標的計算
假設要計算游戲中某點所處的燈塔坐標(詳細邏輯見代碼注釋):
### 將游戲坐標系和燈塔AOI坐標系對齊,然后除以燈塔邊長(兩倍視野內包含的格子數 + 自身坐標格子)
static_cast<int>(std::floor(static_cast<float>(游戲X坐標 - 游戲地圖左下角原點X坐標) / (2 * "燈塔"視野 + 1))
static_cast<int>(std::floor(static_cast<float>(游戲Y坐標 - 游戲地圖左下角原點Y坐標) / (2 * "燈塔"視野 + 1))
燈塔AOI邏輯
對象進入(角色登入、生成怪物):
- 根據對象坐標計算對象所屬燈塔,將對象添加到燈塔的對象列表,如果燈塔上綁定了觀察者,則通知觀察者有對象進入
- 找出對象視野范圍的燈塔,將自身綁定為其觀察者,綁定燈塔會將自身現有對象列表發送給對象
對象離開(角色登出、怪物被殺死):
- 根據對象坐標計算對象所屬燈塔,將對象從燈塔的對象列表中移除,如果燈塔上綁定了觀察者,則通知觀察者有對象離開
- 找出對象視野范圍的燈塔,解除其觀察者綁定,解除綁定燈塔會將自身現有對象列表發送給對象
對象移動
- 如果對象所屬燈塔沒變,則不做任何操作
- 如果對象所屬燈塔改變,則對舊燈塔執行對象離開邏輯,對新燈塔執行對象進入邏輯,但是要注意的是對視野的處理,前后視野交集內的燈塔不需要執行解綁和綁定操作