在Unity中為即時戰略遊戲實現戰爭迷霧(下)
新的戰爭迷霧及視野系統目標是實現下列功能:
- 能夠隨時渲染每個玩家的戰爭迷霧,用於進行回放和除錯。
- 能夠結合多個玩家的視野,用於提供友方視野、實現觀眾模式和觀看回放時使用。
- 使用不同地形高度和其它元素來阻擋視野。
- 優化開發,使視野在移動裝置上支援同時顯示50多個單位,並在60fps的狀態下執行。
- 該效果應該類似《星際爭霸2》和《英雄聯盟》等遊戲。
下面是《星際爭霸2》的戰爭迷霧,是我希望實現的效果。
請注意:為了讓本文的內容更簡潔,在提到“單位”時,所指的是角色、建築等影響遊戲內戰爭迷霧的結構。
邏輯
首先,我們有一個概念叫UnitVision,即單位視野,用於表示可揭開迷霧的任何物件。UnitVision在程式碼中是一種資料結構。
- struct UnitVision
- {
- // 表示視野系統中玩家分組的位掩碼。
- int players;
- // 使用世界座標的視野範圍。
- float range;
- // 使用世界座標的位置。
- vector2 position;
- // 用於阻擋視線。
- short terrainHeight;
- }
通常在遊戲中,每個單位都有一個單位視野,有的單位會有更多視野,例如:大型單位,而有的單位甚至沒有視野。
位掩碼用於指定玩家分組,例如:如果玩家0是0001,而玩家1是0010,那麼0011表示由玩家0和玩家1組成的分組。由於這是一個int型別數值,因此它最多支援sizeof(int)大小的玩家數量。
大多數時候,該分組僅包含一個玩家,但在部分情況下,例如:在通用特效或影片效果中,該分組需要被所有玩家看到,其中一種實現方法是使用帶有多個玩家的unitVision。
欄位terrainHeight儲存當前單位的高度,我們會使用該欄位來檢測單位是否會阻擋視野。如果該單位是地面單位,該值通常是該位置世界地形的高度,但也有一些特別情況。
例如:飛行單位或可改變單位高度的特殊技能,在計算被阻擋的視野時,要考慮到這些因素。遊戲要對該欄位進行相應的更新。
我們還有一個概念名叫VisionGrid,即視野網格,表示所有玩家的視野。下面是VisionGrid的資料結構。
- struct VisionGrid
- {
- // 下列變數表示網格的寬度和高度,它們需要訪問陣列。
- int width, height;
- // 儲存寬度乘高度得到的大小結果,每個部分都有int型別數值和位碼,表示哪個玩家的視野中有該部分。
- int[] values;
- // 和values陣列類似,但它只儲存玩家是否在某段時間訪問該部分。
- int[] visited;
- void SetVisible(i, j, players) {
- values[i + j * width] |= players;
- visited[i + j * width] |= players;
- }
- void Clear() {
- values.clear(0);
- }
- bool IsVisible(i, j, players) {
- return (values[i + j * width] & players) > 0;
- }
- bool WasVisible(i, j, players) {
- return (visited[i + j * width] & players) > 0;
- }
- }
請注意:陣列的大小為寬度乘以高度的結果。
網格越大,計算視野的速度越慢,但它會有更多資訊用於實現單位的行為或渲染更好的迷霧效果。網格越小,效果則會相反。我們必須在一開始就確定好二者的平衡,由此來構建遊戲。
下面是遊戲世界中網格的示例。
在獲得世界位置的網格部分後,該結構會在values陣列儲存int類數值,提供哪個玩家視野內有該位置的資料資訊。例如:如果該部分儲存了0001,那麼它表示只有玩家0能夠看到該部分,如果該部分儲存了0011,那麼玩家0和玩家1都會看到該部分。
該結構也會把玩家之前探索迷霧部分的時間儲存到visited陣列中,主要用於渲染功能,即渲染灰色迷霧,但該資料也可以用到遊戲邏輯中,例如:用於檢查玩家是否瞭解相關資訊。
如果位掩碼中的玩家能夠看到該位置,IsVisible(i, j, players)方法會返回True。
WasVisible(i, j, players)方法有類似的功能,但它會檢查visited陣列。
例如:如果玩家1和玩家2,即位碼中的0010和0100屬於同一陣營,如果玩家2希望知道敵人是否可見,以便展開進攻,則可以使用兩個玩家的位掩碼0110來呼叫isVisible方法。
計算視野
每次更新視野網格時,values陣列都會清空,然後重新計算。
下面是該演算法的虛擬碼。
- void CalculateVision()
- {
- visionGrid.Clear()
- for each unitVision in world {
- for each gridEntry inside unitVision.range {
- if (not IsBlocked(gridEntry)) {
- // 設為可見時,會更新values陣列和visited陣列。
- grid.SetVisible(gridEntry.i, gridEntry.j,
- unitVision.players)
- }
- }
- }
- }
為了在範圍內迭代網格部分,該指令碼首先會使用網格座標來計算視野的位置和範圍,相應的變數分別是gridPosition和gridRange,然後指令碼會圍繞gridPosition並以gridRange為半徑來繪製實心圓形。
被阻擋的視野
為了檢測被阻擋的視野,我們有相同大小的另一個網格,它帶有地形的高度資訊。
下面是該資訊的資料結構。
- struct Terrain {
- // 下列變數表示網格的寬度和高度,它們需要訪問陣列。
- int width, height;
- // 儲存寬度乘高度得到的大小結果,該陣列擁有網格部分的地形層級。
- short[] height;
- int GetHeight(i, j) {
- return height[i + j * width];
- }
下面是網格在遊戲中的示例效果。
在迭代unitVision範圍中視野的網格部分時,為了檢測該部分是否可見,我們的系統會檢查視野中心是否有障礙物。為此,它會從該部分的位置繪製一條直線,連線到中心的位置。
如果線條上的所有網格部分都在相同高度或較低高度,那麼該部分是可見的。下面是相應的示例,藍點表示進行計算的網格部分,白點表示連線中心的線條。
如果線條上至少有一個部分的高度較大,那麼視線會被阻擋。
在下面的例子中,藍點表示我們希望瞭解是否可見的部分,白點表示線條上相同高度的部分,紅點表示地形較高的部分。
在指令碼檢測到某個部分高於視野後,它不必繼續繪製連線視野中心的線條。下面是相應的虛擬碼。
- bool IsBlocked()
- {
- for each entry in line to unitVision.position {
- height = terrain.GetHeight(entry.position)
- if (height > unitVision.height) {
- return true;
- }
- }
- return false;
- }
優化
如果某個部分已經在迭代所有單位視野時標記為可見,則不必重新計算該部分。
減小網格的大小。
減少更新迷霧的頻率,我最近在玩《星際爭霸1》時發現,更新迷霧會有約1秒的延遲。
渲染
渲染戰爭迷霧時,我使用和網格相同大小的小型紋理FogTexture,通過使用Texture2D.SetPixels()方法,在該紋理上寫入相同大小的Color陣列。
在每一幀中,我會在每個VisionGrid部分進行迭代,通過使用values陣列和visited陣列,對陣列設定對應的顏色。
下面是這部分的虛擬碼。
- void Update()
- {
- for i, j in grid {
- colors[i + j * width] = black
- if (visionGrid.IsVisible(i, j, activePlayers))
- colors[pixel] = white
- else if (visionGrid.WasVisible(i, j, activePlayers))
- colors[pixel] = grey // 這裡用於處理之前的視野。
- }
- texture.SetPixels(colors)
- }
欄位activePlayers包含玩家的位掩碼,用於渲染玩家的當前迷霧。通常,它只包含遊戲中主要玩家的迷霧,但是在部分情況下,例如:回放模式中,該欄位可以隨時改變,渲染不同玩家的視野。
如果有兩個玩家處於同一陣營,兩個玩家的位掩碼可用於渲染二者的共享視野。
填補FogTexture紋理後,該紋理會使用帶有後期處理濾鏡的攝像機,在RenderTexture渲染紋理中進行渲染,濾鏡會給紋理應用模糊效果,讓迷霧的外觀效果更好。
為了實現更好的結果,在應用後期處理特效時,這裡使用的RenderTexture渲染紋理比FogTexture紋理大四倍。
獲得RenderTexture渲染紋理後,我會使用自定義著色器在遊戲中渲染它,該著色器會把影象看作Alpha遮罩,白色表示透明部分,黑色表示不透明部分,由於我在此不需要其它顏色通道,因此使用紅色作為補充,這部分處理類似《鋼鐵戰隊》的相應過程。
畫面效果如下圖所示。
下圖是該方法在Unity場景檢視中的效果。
渲染流程如下圖所示。
平緩過渡
在部分情況下,迷霧紋理會在不同幀數間大幅變化,例如:在新單位出現時,或是某個單位移動到高地時。
對於這類情況,我會給colors陣列新增平緩過渡過程,這樣陣列中每個部分會及時從原有狀態過渡到新狀態,從而最小化變化幅度。
該過程非常簡單,雖然該過程在處理紋理畫素時會增加少量效能開銷,但最終效果比我想象的更好,而且該過程也可以隨時禁用。
最初我不確定是否要直接把畫素寫入紋理,因為我擔心這項操作的速度很慢,但在移動裝置上測試後,我發現速度很快,因此這並不是一個問題。
單位可見性
為了瞭解某個單位是否可見,該系統要檢查包含單位的所有部分,大型單位會佔用多個部分,如果至少有一個部分是可見的,那麼該單位就是可見的。這個檢查能夠幫助我們瞭解某個單位是否可以被攻擊。
下面是相應的虛擬碼。
- bool IsVisible(players, unit)
- {
- // 這是某個玩家的單位。
- if ((unit.players & players) > 0)
- return true;
- // 返回包含該單位的所有部分
- entries = visionGrid.GetEntries(unit.position, unit.size)
- for (entry in entries) {
- if (visionGrid.IsVisible(entry, players))
- return true;
- }
- return false;
- }
哪些單位可見和渲染的迷霧有關,因此我們使用了相同的activePlayers欄位來檢查是否顯示單位。
為了避免渲染單位,我使用了類似《鋼鐵戰隊》的方法,也就是使用了遊戲物件的圖層。
如果單位是可見的,那麼我們會給該物件設定預設圖層,如果單位不可見,那麼我們會給它設定從遊戲攝像機剔除的圖層。
- void UpdateVisibles() {
- for (unit in units) {
- unit.gameObject.layer = IsVisible(activePlayers, unit) : default ? hidden;
- }
- }
小結
將整個遊戲世界簡化為網格,並開始對紋理進行思考後,我們可以輕鬆應用不同的影象演算法,例如:繪製填充圓圈或線條,它們在進行優化時非常實用。此外,還有更多影象操作可以用於遊戲邏輯和渲染。
《星際爭霸2》有很多紋理方面的資訊,不只是玩家的視野,還提供了API來訪問紋理,該API也被用到了機器學習的實驗中。我仍在開發更多相關功能,同時還計劃嘗試一些優化功能,例如:C# Job System。
給迷霧紋理使用模糊效果也存在缺點,例如:這會在不合適的時候展示一部分高地。我仍然會研究其它影象效果,尋找合適的方法。
相關閱讀:在Unity中為即時戰略遊戲實現戰爭迷霧(上)
作者:Ariel Coppes
來源:Unity官方平臺
原地址:https://mp.weixin.qq.com/s/HfalFXTpknC-YYxuKalFwQ
相關文章
- 在Unity中為即時戰略遊戲實現戰爭迷霧(上)Unity遊戲
- 戰爭迷霧
- 即時戰略遊戲(RTS)是否已經沒落?遊戲
- 太平洋雄風Victory At Sea Pacific for mac(即時戰略遊戲)Mac遊戲
- 團隊即時戰略遊戲《A YEAR OF RAIN》封測版今日開啟遊戲AI
- 網易宣佈與Creative Assembly戰略合作 將《全面戰爭》系列遊戲引入中國遊戲
- 《戰爭與征服》8月7日全平臺上線,開啟即時戰略新時代!
- 開發“全面戰爭”遊戲20年,CA為何從不染指現代戰爭題材?遊戲
- RTT二十四年:即時戰術遊戲興衰(下)遊戲
- 騰訊戰略「遊戲新經濟」全面入侵現實遊戲
- 在Unity實現遊戲命令模式Unity遊戲模式
- 戰爭遊戲:紅龍遊戲
- 遊戲的戰略(五):為什麼做遊戲難【完】遊戲
- 在 Unity 多人遊戲中實現語音對話Unity遊戲
- 軍事迷看過來!近現代戰爭RTS遊戲《烈火戰馬》今日開啟Steam搶先體驗遊戲
- 冰霜巨人工作室釋出首款真正的社交即時戰略遊戲——Stormgate™遊戲ORM
- 遊戲的戰略(二)——選擇性的戰略與落地的挑戰遊戲
- Mac經典戰略策略遊戲:Mac遊戲
- <題解>幻想鄉戰略遊戲遊戲
- 雲遊無界·一觸即發 中青寶雲遊戲戰略釋出會成功舉辦!遊戲
- 為什麼說雲遊戲是未來戰略要塞?遊戲
- 何為戰略?
- 將即時戰略玩法與戰棋巧妙結合!呵呵魚與它的《戰律》系列
- 保衛東部前線,即時戰略遊戲《游擊隊1941》將於10月14日在PC上釋出遊戲
- 戰棋遊戲《尼羅河勇士》將於8月7日發售 《迷霧偵探》主角亂入遊戲
- 【Unity3D開發小遊戲】《戰棋小遊戲》Unity開發教程Unity3D遊戲
- 網遊中的貨幣戰爭
- RTT二十四年:即時戰術遊戲興衰(上)遊戲
- 深度合作資源共享,中手遊與華為達成遊戲業務戰略合作遊戲
- 在遊戲與未成年人的迷霧中,尋找“隱形的家長”遊戲
- 史上最勵志的遊戲公司,在戰爭中誕生的3A大作遊戲
- Java實現飛機大戰遊戲Java遊戲
- 《全面戰爭模擬器》:詼諧幽默的沙盒戰爭模擬遊戲遊戲
- 在索尼未來戰略中,遊戲業務關鍵詞是「沉浸」與「無縫」遊戲
- 1.6億潛在玩家!遊戲界的中年男人爭奪戰遊戲
- 《迷霧偵探》國產賽博朋克偵探遊戲遊戲
- 張一鳴打響遊戲新戰爭遊戲
- 雲遊無界·一觸即發 預祝中青寶雲遊戲戰略釋出會圓滿召開!遊戲