隨機迷宮生成算法整理分析

蒐集整理了一些遊戲迷宮生成的算法與實現


前言

前段時間學校遊戲開發課大作業,做了一個Roguelike的恐怖遊戲。蒐集整理了一些迷宮生成的算法。

當初也受了indienova上一些文章的啓發。現在在此把學到的一些東西理一理分享出來。

第一次寫這種東西,感覺有點囉嗦,還請大家不要介意,也可以直接看項目地址

代碼寫在Unity環境下,應該可以直接使用。

第一種算法

先上一張圖

Image title


這是我最早拍腦袋憑着感覺寫的一個算法結果,給定區域長寬和分支概率,可以生成一張迷宮圖。

這完全就是隨機挖洞大法,其步驟如下:

  1. 計算當前掃描點周圍可以挖的方塊
  2. 隨機選一個方塊挖開
  3. 若周圍還有可挖方塊,按分支概率隨機挖開另一方塊,設爲新掃描點
  4. 所有掃描點執行 1 操作
  5. 若周圍無方塊可挖,中止此掃描點工作。

可以看出,這個算法有相當的缺陷,生成的迷宮總面積不可控,在運氣不好的極端情況下,會產生比預期面積小很多的迷宮。
即使我們將分支概率調到100%,依舊會有黑色的空洞存在:

而且生成的迷宮非常扭曲怪誕,這很克蘇魯。或許我們可以風格化一下……

而且生成的迷宮非常扭曲怪誕,這很克蘇魯。或許我們可以風格化一下……


此時的迷宮已經勉強可以使用,但是與傳統迷宮的差別依舊非常大。
它的斜線非常多。這會使得遊戲過程中包含八個方向,對玩家的方向感是極大的考驗,很難再記住地圖,容易暈頭轉向。

對於這個算法,相比室內環境,更適合生成自然環境下迷宮。也可以作爲無主線、弱主線沙盤遊戲的大地圖生成的一環。

遞歸分割

接下來這個算法與第一個就是兩個極端——生成完全沒有斜線的迷宮。
話不多說,先上圖:

迷人的大迷宮

在介紹本算法前,需要提出一個概念

完美迷宮Perfect maze:沒有迴路,也沒有孤立區域的迷宮。用圖論來解釋,就是可以用生成樹表示的迷宮,迷宮中兩點有且僅有一條路徑。

這個算法是一個分治算法,即將一塊大的生成區域分成4塊小區域分別生成迷宮並保證聯通,以此類推,直到不可細分。

分塊很簡單,長寬上各取一個隨機數即可。如何保證迷宮完美呢?
我們看極端情況,對於一個田字形區域,生成完美迷宮的方法是敲開三堵牆。

利用分治算法的特性,每一層遞歸都是完美迷宮,直到全圖生成完美迷宮。
算法不難,注意遞歸狀態的邊界情況就行。

這種分治遞歸的痕跡在生成的地圖俯視圖上很明顯,但對於置身其中的玩家或許就不是了。

它生成的迷宮完全沒有斜線,橫平豎直,同時會生成4*4的小房間。
用作城市地圖、或建築環境的迷宮非常合適。

當然在遊戲中地圖沒有迴路是非常致命的,一個完美迷宮會讓玩家疲於奔命,並不方便設計玩法。
對於迴路,我們只需要消除死路就行了,也就是那些三面臨牆的格子,在地圖生成完後遍歷死路,按一定概率打通即可。

生成樹算法 Kruskal & Prim

絕大多數的編程問題都可以用數學工具解決,當然我們的迷宮生成算法也不例外。
數學中最適合表達迷宮的符號莫過於 ,下面兩個算法是迷宮生成中應用最普遍的理論之二。

首先我們需要將地圖轉換爲便於數學表達的形式。
之前兩個算法在處理地圖時都是以 方塊 爲單位的,即每一個方塊不是牆就是路。
而  的基本組成是  與  ,對於一個待處理的迷宮,我們做如下轉換。

Image title

迷宮大小10*10,其中白塊代表 ,紅塊代表 ,而黑塊代表 虛無,只是填充物質罷了。

如果一個  中,任意兩  都能通過  組成的路徑聯通,稱之爲 連通圖

而如果一個 連通圖 上沒有迴路,則我們可以稱之爲 ,因爲沒有迴路,所以每對點之間有且僅有一條路徑聯通。

可以看到, 與我們完美迷宮的概念不謀而合,所以現在我們的任務是找到包含所有點的一棵 

最小生成樹

生成樹,顧名思義,就是從給定的 , 集合中生成一棵符合要求的樹。
下面介紹的兩種最小生成樹算法都可以勝任。

雖然寫作最小生成樹,但這兩個算法其實可以做到“按一定條件生成樹”。
“最小”是算法的典型描述,即在有權邊的集合中找出權值最小的樹。原算法使用貪心算法求解。

而在這裏,我們的條件就是:隨機。

下面簡單介紹一下這兩個算法的步驟:

兩個算法都需要  的集合E,與  的集合V。對於上圖,E代表所有白塊,V代表所有紅塊

Kruskal:
一開始每個點將自己作爲單獨的一棵樹。

  1. V中隨機選出一條邊v
  2. 判斷v兩端的e1e2是否屬於一棵生成樹
    • 是,無動作
    • 否,繪製e1,v,e2併合並樹
  3. V中刪除v
  4. V不爲空,則返回 1. ,V爲空則完成

ps:判斷與合併兩點所在樹可以使用並查集相關算法,在項目代碼中UFset類就是並查集的c#實現之一

這裏簡單講下並查集,並查集是指一些不相交集合的 合併 與 查詢 問題,

對應到我們迷宮問題中就是:合併不相連的生成樹、查找兩點是否屬於同一個生成樹。

並查集使用了一種稱爲 路徑壓縮 的算法,使得所有子節點的父節點均指向跟根節點。

雖然壓縮算法是爲了提高合併效率,但壓縮算法本身時間複雜度也是O(n),所以我們只在查詢一個節點時,纔對此節點所在路徑進行壓縮,並且在合併時,將小樹併入大樹,以平衡效率。

經過優化的並查集合並算法時間複雜度可達神奇的常數級,比起之前的全圖標記不知道高到哪裏去了,證明就在此略過,有興趣的同學可以去深入學習一下。

===============

Prim:初始V爲空,所有eE標記爲0

  1. 隨機選一個點e
  2. 將與e相連的邊的集合{Ve}併入入V,e標記爲1
  3. V中隨機選一條邊v
  4. 判斷v兩端情況
    • 均爲1:無動作
    • 一個0一個1:將爲0的點e標記爲1,繪製v,e,將e連接的邊併入V
    • 均爲0:不可能
  5. V中刪除v
  6. 當所有eE均被標記爲1,結束,否則返回 3. 。

ps:可以維護一個包含所有vV的標記表,防止被重複併入V,提高效率

以上爲算法步驟,建議配合代碼食用更佳。

下爲運行結果

Image title

前者爲Kruskal,後者爲Prim。白路黑牆。

可以看到兩個算法的生成迷宮風格幾乎一樣,並且都較爲接近傳統迷宮。可以用於各類需要迷宮生成的遊戲。

值得一提的是這兩個算法都可以加入房間,只需將房間預先生成,在將剩餘可生成的點與邊的集合放入算法中運行即可。
關於這個詳細可以參考房間和迷宮:一個地牢生成算法

到這裏關於遊戲中迷宮生成最常用的幾個算法已經寫完了。除了上述幾種以外,迷宮的生成方法還深度廣度優先搜索之類很多。
此外還有諸多適用於特定遊戲系統的地圖生成算法,如MC中的噪音,Unexplored中的環狀地圖等


轉自:https://www.indienova.com/u/cocolate/blogread/1493

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章