在Unity中程式化生成的地牢環境

遊資網發表於2020-01-06
本文由開發者 vazgriz撰寫,將與各位分享如何在Unity中程式化生成2D和3D的地牢環境。

我非常喜歡Roguelike類遊戲,而且Roguelike類遊戲的關卡往往由許多地牢環境組成,所以我萌生出嘗試自己編寫程式化地牢生成器的想法。

地牢生成器有很多不同的實現方法,但我最後決定使用《TinyKeep》遊戲的演算法。通過擴充套件該演算法,我使它可以在3D環境中使用,建立出帶有多個樓層的地牢。

示例程式碼

下載示例的程式碼:

https://github.com/vazgriz/DungeonGenerator

瞭解《TinyKeep》遊戲的演算法:

https://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/

2D環境

首先,我必須編寫適用於2D環境的演算法。我的演算法和《TinyKeep》遊戲的演算法大致相同,但進行了一些調整,使演算法可以實現更加有趣的關卡。

本文中示例場景的名稱為Dungeon2D,程式碼儲存在Scripts2D資料夾。

演算法詳解

在專案中,遊戲世界被劃分為矩形網格。假設1個單位的寬度足以表示一個通道。例如:在一個遊戲中,1個單位可能對應著5米的距離。因此,我最後決定把遊戲世界的大小設為30×30。

1、首先隨意放置地牢區域,確保它們不會互相重疊。這些區域的佈置方式並不重要,在本文示例中,我只是隨機放置並調整了它們的大小。

我還在每個面新增了1個單位寬度的緩衝區,從而確保各個區域不會互相接觸,但這對演算法來說並不必要。

在Unity中程式化生成的地牢環境
紅色方框是各個地牢區域

2、使用所有地牢區域建立Delaunay三角剖分圖。我使用了Bowyer-Watson演算法,該演算法有不同語言的多種實現方法。我選擇了最容易轉化為C#程式碼的實現方法。

在Unity中程式化生成的地牢環境
Delaunay三角剖分效果

3、 使用三角剖分圖建立最小生成樹,我在此使用了Prim演算法。

在Unity中程式化生成的地牢環境
通道的最小生成樹

4、使用第3步中的每個邊緣,建立通道的列表。生成樹包含每個區域,因此它可以保證有通向每個區域的通道。

通過三角剖分圖隨機新增邊緣到列表中。這樣會把通道連成一圈,在這裡,我把每個邊緣的新增機率設為12.5%。

在Unity中程式化生成的地牢環境
給生成樹新增邊緣之後的通道

5、對於列表中的每個通道,使用了A*演算法來尋找從通道開始到結束的路徑。在找到一條路徑後,它會修改遊戲世界的狀態,從而讓之後的通道可以使用現有通道的路徑。

通過使用特別的代價函式,我讓演算法可以使用之前迭代的結果,該方法的開銷比建立新通道的開銷更低。這樣可以讓尋路程式使用組合通道穿過相同的區域。

雖然尋路程式可以穿過區域,但是開銷較大。因此在大多數情況下,尋路程式更傾向於繞著區域走。

在Unity中程式化生成的地牢環境
藍色方塊是通道

下圖是使用了美術資源實現的示例場景,美術資源和放置資源的程式碼不包含在示例程式碼中。

在Unity中程式化生成的地牢環境

在Unity中程式化生成的地牢環境

3D環境

有了2D環境的地牢生成器後,我開始把生成器轉變為適用於3D環境。我們使用的所有演算法都有3D版本,因此轉換過程不會很困難。

演算法詳解

現在,我們的網格大小設定為30x5x30。

1、首先要修改的是:讓所有地牢區域在3D環境中生成。

在Unity中程式化生成的地牢環境
請注意地牢區域有可能有多個樓層

2、計算地牢區域的3D Delaunay三角剖分結果,或改為計算Delaunay四面體剖分結構。

然而,我在網上雖然可以搜尋到很多研究論文,但沒有找到任何實際的程式碼。最接近專案情況的結果是CGAL的3D三角剖分實現方法,但是該方法存在兩個問題:

該功能模組只可以在GPL許可下使用。

程式碼有大量模板部分,而且非常難以理解,導致我無法找到實現演算法的具體位置。

最後,我不得不學習Bowyer-Watson演算法的工作原理,以便自己修改程式碼。

雖然,我並不理解外接圓對該演算法的重要性,但至少我可以參考Wolfram MathWorld網站相關頁面的資訊,在編寫的程式碼中使用了外接球體。由於大部分運算是4×4矩陣運算,我使用了Unity的Matrix4x4結構來處理運算過程。

MathWorld網站的相關頁面:

http://mathworld.wolfram.com/Circumsphere.html

新的程式碼實現版本在Scripts3D/Delaunay3D.cs指令碼中,如果需要基於MIT許可且易於理解的版本,可以參考這個指令碼。

在Unity中程式化生成的地牢環境

這種方法不會生成帶有3個頂點的三角形,而是會生成帶有4個頂點的四面體。其中至少有一個頂點會位於不同的樓層,否則該四面體會被銷燬。這為尋路過程提供了很多在不同樓層之間移動的機會。

3、現在到了2D版本的對應第3步和第4步。我簡單修改第2步的邊緣資訊,然後把它傳入Prim演算法。

在Unity中程式化生成的地牢環境
通道的3D生成樹

在Unity中程式化生成的地牢環境
重新新增了帶有邊緣的通道

4、3D版本的A*演算法是情況變得複雜的地方。2D版本A*演算法非常簡單,只要使用A*演算法的標準實現方法即可。但為了實現3D版本,我必須給尋路程式新增向上和向下移動的功能,讓它可以連線不同樓層的區域。

我決定使用樓梯臺階來連線樓層,而不是垂直的梯子。但這也是問題出現的地方。相對於垂直上下移動的梯子來說,臺階更加複雜。臺階必須通過水平移動,來實現垂直移動,也就是說,要結合上升和前進的過程。我們可以通過下圖來理解這個過程。

中心網格是藍色實心正方形。附近的網格都是空心的正方形。尋路程式不能直接移動到當前網格正上方的網格,而是必須同時在水平方向和垂直方向移動。

如下圖所示,這是從側面檢視的檢視。我們可以直接移動到水平方向的相鄰位置,但是無法直接去到頂部位置。

在Unity中程式化生成的地牢環境

為了針對臺階構建尋路程式,我必須選好臺階的形狀。如果把垂直移動和水平移動的距離比設為1:1,樓梯可能會太陡,因此我把比值設為1:2。每當在垂直方向移動1個單位時,尋路程式都要在水平方向移動2個單位。

此外,我們也要有合適的天花板,讓角色可以站在臺階上,因此臺階上也必須有2個網格的高度。總的來說,每個臺階會使用4個網格。

在Unity中程式化生成的地牢環境
樓梯臺階和上方淨空間

此外,樓梯的頂部和底部必須有合適的通道。尋路程式不可以從側面或相反方向進入樓梯。如果讓樓梯直接穿過通道,則會產生非常奇怪的情況,如下圖所示。

在Unity中程式化生成的地牢環境

在Unity中程式化生成的地牢環境

因此,樓梯形狀必須如下圖所示,尋路程式必須確保通道位於兩個藍色正方形的位置。如下圖所示,樓梯會從藍色實心正方形開始,向上移動一個樓層。

在Unity中程式化生成的地牢環境

尋路程式必須在一步中從開始位置移動到結束位置。這意味著它必須在水平方向移動3個單位,在垂直方向移動1個單位。

A*演算法的原理是每一步都會從一個節點移動到鄰近節點。為了製作樓梯,我需要“跳過”樓梯的四個網格。

這裡的難點在於:我需要讓尋路程式圍繞著它建立的樓梯工作。我無法把樓梯新增到A*演算法的封閉集合中,因為那樣會阻礙不同的路徑從其它方向穿過這些網格。

但我也不可以把它們排除在外,因為這樣會使尋路系統可以在最初建立的樓梯移動,出現上圖片中不合理的情況。

解決方法是:對於每個節點,我們要跟蹤該路徑上的之前節點,然後在評估相鄰節點時,如果遇到當前節點的路徑,尋路系統會拒絕生成路徑。

樓梯末端的通道會包含樓梯佔用的所有網格、樓梯起點的節點和之前路徑的所有節點。

尋路程式可以建立另一條路徑穿過樓梯,因為第二條路徑不瞭解樓梯的情況。

以上行為只需呼叫一次尋路函式,就可以處理多條潛在的路徑。我們可以多次呼叫尋路函式,生成所有通道。之後的迭代會圍繞已有的樓梯進行處理。

現在使用的演算法不完全是A*演算法。為了處理好樓梯,我們要應對許多特別情況。但如果每一步都檢查之前的整條路徑,這樣會費時費力。

最初的實現方法是一路跟隨節點到起點位置,像連結串列一樣讀取節點。因此,對於每個相鄰節點來說,檢查路徑的時間複雜度為O(N)。

我使用的實現方法是:在每個節點中儲存一個雜湊表,該雜湊表使用之前的節點作為鍵值。因此,檢查路徑的時間複雜度為O(1),但是在節點新增到路徑時,雜湊表必須進行拷貝,使用的時間複雜度為O(N)。

我選擇這個方法的原因是:我發現該演算法讀取路徑的頻率比修改路徑的頻率更高。

雖然我不知道如何對它的時間複雜度進行分析,但我認為總體時間複雜度應該在O(N^2)左右。這項改動是地牢生成演算法的主要瓶頸。

在完成所有改動後,實現的結果如下圖所示。

在Unity中程式化生成的地牢環境
綠色方框表示樓梯

在Unity中程式化生成的地牢環境
生成器建立的路徑可以很簡單

在Unity中程式化生成的地牢環境
也可以很複雜

下面是使用美術資源實現的3D地牢效果。

在Unity中程式化生成的地牢環境
有多個樓層的地牢


在Unity中程式化生成的地牢環境
兩個樓梯組成兩倍寬度的樓梯

在Unity中程式化生成的地牢環境
三倍寬度的樓梯

在Unity中程式化生成的地牢環境
下降兩個樓層的路徑可以建立互相銜接的樓梯

在Unity中程式化生成的地牢環境
出現多個互相靠近的路徑時,通道可能會變得很大

在Unity中程式化生成的地牢環境
兩個樓梯連線相同的門

結語

本文分享了在Unity中程式化生成2D和3D的地牢環境的方法。專案最難的部分是實現適用於3D環境的演算法以及編寫編寫尋路程式。

建立的地牢環境非常有趣,可以作為一款遊戲的基礎,來動手嘗試一下吧。

作者:vazgriz  
來源:Unity官方平臺
原地址:https://mp.weixin.qq.com/s/3yM-mAAXq_fX5tcy82s0uQ

相關文章