蒐集整理了一些遊戲迷宮生成的演算法與實現
前段時間學校遊戲開發課大作業,做了乙個roguelike的恐怖遊戲。蒐集整理了一些迷宮生成的演算法。
當初也受了indienova上一些文章的啟發。現在在此把學到的一些東西理一理分享出來。
第一次寫這種東西,感覺有點囉嗦,還請大家不要介意,也可以直接看專案位址
**寫在unity環境下,應該可以直接使用。
先上一張圖
這是我最早拍腦袋憑著感覺寫的乙個演算法結果,給定區域長寬和分支概率,可以生成一張迷宮圖。
這完全就是隨機挖洞**,其步驟如下:
計算當前掃瞄點周圍可以挖的方塊
隨機選乙個方塊挖開
若周圍還有可挖方塊,按分支概率隨機挖開另一方塊,設為新掃瞄點
所有掃瞄點執行1
操作
若周圍無方塊可挖,中止此掃瞄點工作。
可以看出,這個演算法有相當的缺陷,生成的迷宮總面積不可控,在運氣不好的極端情況下,會產生比預期面積小很多的迷宮。
即使我們將分支概率調到100%,依舊會有黑色的空洞存在:
而且生成的迷宮非常扭曲怪誕,這很克蘇魯。或許我們可以風格化一下……此時的迷宮已經勉強可以使用,但是與傳統迷宮的差別依舊非常大。
它的斜線非常多。這會使得遊戲過程中包含八個方向,對玩家的方向感是極大的考驗,很難再記住地圖,容易暈頭轉向。
對於這個演算法,相比室內環境,更適合生成自然環境下迷宮。也可以作為無主線、弱主線沙盤遊戲的大地圖生成的一環。
接下來這個演算法與第乙個就是兩個極端——生成完全沒有斜線的迷宮。
話不多說,先上圖:
在介紹本演算法前,需要提出乙個概念
完美迷宮perfect maze:沒有迴路,也沒有孤立區域的迷宮。用圖論來解釋,就是可以用生成樹表示的迷宮,迷宮中兩點有且僅有一條路徑。
這個演算法是乙個分治演算法,即將一塊大的生成區域分成4塊小區域分別生成迷宮並保證聯通,以此類推,直到不可細分。
分塊很簡單,長寬上各取乙個隨機數即可。如何保證迷宮完美呢?
我們看極端情況,對於乙個田字形區域,生成完美迷宮的方法是敲開三堵牆。
利用分治演算法的特性,每一層遞迴都是完美迷宮,直到全圖生成完美迷宮。
演算法不難,注意遞迴狀態的邊界情況就行。
這種分治遞迴的痕跡在生成的地圖俯檢視上很明顯,但對於置身其中的玩家或許就不是了。
它生成的迷宮完全沒有斜線,橫平豎直,同時會生成4*4的小房間。
用作城市地圖、或建築環境的迷宮非常合適。
當然在遊戲中地圖沒有迴路是非常致命的,乙個完美迷宮會讓玩家疲於奔命,並不方便設計玩法。
對於迴路,我們只需要消除死路就行了,也就是那些三面臨牆的格仔,在地圖生成完後遍歷死路,按一定概率打通即可。
絕大多數的程式設計問題都可以用數學工具解決,當然我們的迷宮生成演算法也不例外。
數學中最適合表達迷宮的符號莫過於圖
,下面兩個演算法是迷宮生成中應用最普遍的理論之二。
首先我們需要將地圖轉換為便於數學表達的形式。
之前兩個演算法在處理地圖時都是以方塊
為單位的,即每乙個方塊不是牆就是路。
而圖
的基本組成是點
與邊
,對於乙個待處理的迷宮,我們做如下轉換。
迷宮大小10*10,其中白塊代表點
,紅塊代表邊
,而黑塊代表虛無
,只是填充物質罷了。
如果乙個圖
中,任意兩點
都能通過邊
組成的路徑聯通,稱之為連通圖
。
而如果乙個連通圖
上沒有迴路,則我們可以稱之為樹
,因為沒有迴路,所以每對點之間有且僅有一條路徑聯通。
可以看到,樹
與我們完美迷宮的概念不謀而合,所以現在我們的任務是找到包含所有點的一棵樹
。
生成樹,顧名思義,就是從給定的點
,邊
集合中生成一棵符合要求的樹。
下面介紹的兩種最小生成樹演算法都可以勝任。
雖然寫作最小生成樹,但這兩個演算法其實可以做到「按一定條件生成樹」。
「最小」是演算法的典型描述,即在有權邊的集合中找出權值最小的樹。原演算法使用貪心演算法求解。
而在這裡,我們的條件就是:隨機。
下面簡單介紹一下這兩個演算法的步驟:
兩個演算法都需要點
的集合e
,與邊
的集合v
。對於上圖,e
代表所有白塊,v
代表所有紅塊
kruskal:
一開始每個點將自己作為單獨的一棵樹。
從v
中隨機選出一條邊v
判斷v
兩端的e1
,e2
是否屬於一棵生成樹
從v
中刪除v
當v
不為空,則返回 1. ,v
為空則完成
ps:判斷與合併兩點所在樹可以使用並查集相關演算法,在專案**中ufset類就是並查集的c#實現之一
這裡簡單講下並查集,並查集是指一些不相交集合的 合併 與 查詢 問題,
對應到我們迷宮問題中就是:合併不相連的生成樹、查詢兩點是否屬於同乙個生成樹。
並查集使用了一種稱為 路徑壓縮 的演算法,使得所有子節點的父節點均指向跟根節點。
雖然壓縮演算法是為了提高合併效率,但壓縮演算法本身時間複雜度也是o(n),所以我們只在查詢乙個節點時,才對此節點所在路徑進行壓縮,並且在合併時,將小樹併入大樹,以平衡效率。
經過優化的並查集合並演算法時間複雜度可達神奇的常數級,比起之前的全圖標記不知道高到**去了,證明就在此略過,有興趣的同學可以去深入學習一下。
prim:初始v
為空,所有e
∈e
標記為0
隨機選乙個點e
將與e
相連的邊的集合併入入
v
,e標記為1
從v
中隨機選一條邊v
判斷v兩端情況
從v
中刪除v
當所有e
∈e
均被標記為1,結束,否則返回 3. 。
ps:可以維護乙個包含所有v
∈v
的標記表,防止被重複併入v
,提高效率
以上為演算法步驟,建議配合**食用更佳。
下為執行結果
前者為kruskal,後者為prim。白路黑牆。
可以看到兩個演算法的生成迷宮風格幾乎一樣,並且都較為接近傳統迷宮。可以用於各類需要迷宮生成的遊戲。
值得一提的是這兩個演算法都可以加入房間,只需將房間預先生成,在將剩餘可生成的點與邊的集合放入演算法中執行即可。
關於這個詳細可以參考房間和迷宮:乙個地牢生成演算法
到這裡關於遊戲中迷宮生成最常用的幾個演算法已經寫完了。除了上述幾種以外,迷宮的生成方法還深度廣度優先搜尋之類很多。
此外還有諸多適用於特定遊戲系統的地圖生成演算法,如mc中的噪音,unexplored中的環狀地圖等
**:
隨機系列生成演算法
給定乙個正整數n,需要輸出乙個長度為n的陣列,陣列元素是隨機數,範圍為0 n 1,且元素不能重複。比如 n 3 時,需要獲取乙個長度為3的陣列,元素範圍為0 2。首先,讓我們先構造如下函式 cpp view plain copy 隨機數生成函式,生成 a,b 之間的乙個隨機數字 其中0 aint r...
演算法 隨機生成迷宮
演算法原理 從起點開始,隨機選擇乙個方向移動,一直移動到終點,則移動的路徑便是迷宮的路徑。移動過程中要保證路徑不要相交,不要超出邊界,生成效果 public partial class mainform form void btncreateclick object sender,eventargs...
隨機數生成演算法
看到一些介紹隨機數生成演算法的文章,收集下來,有空深入了解下。c語言中偽隨機數生成演算法實際上是採用了 線性同餘法 具體的計算如下 xi xi 1 a c mod m 其中a,c,m都是常數 一般會取質數 當c 0時,叫做乘同餘法。srand 函式置的seed實際上會作為x0被代入上式中,然後每次呼...