RogueLike地牢生成算法Unity實現


最近幾日閑來無事,后來看到了RogueLike的游戲,就像來試一下地牢生成算法。

網上看到了一篇文章寫的挺好的。后面會有轉載,不急哈。

先看一下我實現的效果圖

生成過程:

地牢生成算法的思路是:

1.生成大量隨機位置隨機大小的房間
2.通過物理碰撞將房間分散開
3.通過閾值選取主要的房間
4.對主房間進行三角剖分運算,得到房間之間的可選路徑
5.使用最小生成樹選定房間之間的路徑,並且保留少量回圈
6.確定房間路路線
7.路上經過的房間划入次要房間中
8.繪制房間和路線.

隨機生成房間

需要隨機生成房間的初始位置,以及房間的長寬高

文章中提供了一種思路,在圓形中隨機確定一個點,作為房間的初始位置。

而如何在圓形中隨機生成一個點。

圓中生成隨機點

先從一個三角形中入手

假設三角形ABC 且|AB| =|AC|。

在AB中找一個點X,在AC中找一個點Y,並用AX和AY做一個平行四邊形XAYZ。那么這個隨機點Z就找到了。

 

那萬一Z超出了三角形呢

沒事,超出了三角形,就將點Z以BC翻折到三角形中。

那我們該怎么運用在圓中呢?

其實這里去了一個極限,將一個圓細分成無數的等腰三角形

 

這樣,我們只要在圓中選擇一個三角形,再從三角形中隨機選擇一個點。

這樣就能隨機生成一個點了。

 

現在輪到房間大小了

房間大小可以利用上面的方法,在圓內生成一個點,再用這個點生成一個矩形。

可以有兩種生成方法,如下圖。

分散房間

這里可以選擇使用Unity中自帶的Rigidbody分散,

因為Unity的碰撞不好控制,常常跑出來一堆小數,所以我選擇自己寫一個Rigidbody。

邏輯非常簡單,只需要判斷自己是否覆蓋別人的房間,如果覆蓋就移動到沒有覆蓋。

移動距離計算

假設房間1 和房間2發生碰撞,

對於房間1

Offset = pos1-pos2

如果選擇豎直方向的移動

需要判斷是向上還是向下

方向:dirY = offsetY>0?1:-1;

距離:dis = y1/2+y2/2 – abs(offsetY)

pos1 += vector2.up*dirY*dis

水平方向移動同理。

然后在FixUpdate中調用Simualeting。

篩選主房間

這里沒什么好說的了,就是這個篩選房間可以在碰撞之前進行。

還有就是 item.size.Sum();這個是自己寫的一個方法。

三角剖分

這里是第一個難點,花了不少時間理解和實現。

三角剖分就是將多個點連起來生成多個三角形,並且三角形之間沒有重疊的部分,或者說是線段沒有交叉。

就是類似下面的圖:

這里實現的是Delaunay三角剖分算法,

我先走一遍流程。

首先生成一個三角形(又稱超級三角形),這個三角形將所有的點都包含進去。

例如:

要實現將所有的點包圍起來,我的想法是:

找到x和y的最大值和最小值,生成一個矩形,利用這個矩形生成一個圓形。

最后利用這個圓形生成一個三角形,這個三角形內接剛才生成的圓形。

如圖:

將這個超級三角形放入待確認的三角形列表中。

開始遍歷三角形列表,

如果當前三角形的外接圓內有點,

選定一個點,將當前三角形拆分成三個邊,讓這個點和三個邊分別連接成三角形,

將這三個三角形放入列表中,並且將當前三角形刪除。

如果三角形的外接圓內沒有點,那么這個三角形就是Delaunay三角形。

將三角形放入Delaunay三角形列表中。

上面的流程看起來挺正確的,

但似乎忽略了一種情況

按照剛才的算法,會生成下面的情況:

 

這時候應該怎么辦?

不急,先執行另一個三角形看看

執行后多出來了三個三角形,這里用了藍色的線標記起來。

此時有兩個三角形是完全相同,(大小相同,位置相同)

如果將這兩個三角形刪除,就會得到下面的情況。

這可太美妙了。(當時理解的時候的想法)

下面展示一下偽代碼

輸入頂點列表
對頂點的x進行排序
生成一個超級三角形,並添加到待確定三角形列表中
遍歷頂點
    遍歷待確定三角形列表
        如果該點在三角形外接圓中
            將三角形拆分,分成三個邊,將三個邊添加到緩存區中,將本三角形刪除
            繼續當前循環
        如果該點在三角形外接圓的右側,即該點的x>三角形外接圓最右側
            這時候所有的點都不可能在圓內,
            此時將三角形添加到Delaunay三角形列表中
            將三角形從待確定三角形中移除。
        如果該點不在三角形外接圓右側,這時候不能確定其他點不在三角形內部
            繼續當前循環。
    查找緩存區中相同的邊,
    將相同的邊根除,即一條邊不止出現一次,就將這條邊在列表中全部刪除。
    將頂點和緩存中所有的邊組合成三角形。
添加剩下的三角形到delaunay三角形列表中。
算法完成
刪除與超級三角形相關的三角形。
輸出Delaunay三角形列表。

代碼中使用了排序,外接圓右側是個什么意思

其實是這樣的

如果沒有點1 和點2 ,這時只有點3和點4 ,遍歷順序是3 4,

判斷點3 在圓外時,點4一定也在圓外。

但是如果點1 2 都存在,那么遍歷順序是1 2 3 4

此時點1 在圓外,而點2在卻圓內。

這樣懂了吧。

至此三角剖分環節結束了,

代碼參考(代碼是真的僅供參考,因為代碼能力比較emmmm)

生成超級三角形

對頂點列表進行排序

遍歷頂點列表

遍歷三角形列表

如果頂點在三角形外接圓右側

如果頂點在三角形外部,而不是在右側

如果頂點在外接圓內部,拆分三角形,將三角形添加緩存區,

如果已經存在緩存區中,添加到待刪除的緩存區。

根除重復的邊(上面講的是根除三角形,其實同理)

生成新的三角形,添加到待確認的列表中

最后合並所有的三角形,然后刪除超級三角形相關的三角形。

要是覺得講的不好可以去看一些我當時參考的博客

https://www.cnblogs.com/zhiyishou/p/4430017.html

https://blog.csdn.net/keneyr/article/details/90316467

最小生成樹算法

從上面獲取的三角形列表,將三角形列表轉換成邊列表,對邊列表進行最小生成樹算法,

這里使用的是Kruskal的最小生成樹算法

我就直接把算法流程畫出來,看的方便

我們先從最小的邊開始連接,這時候看邊的兩端的父親是否為同一個節點。

如果沒有父親,那自己就是父親節點。

接下來是第二條邊,同樣因為父親節點不相同,所以連接起來

接下來邊是1-3這條邊,但是節點1和節點3 的父親是相同,所以不能連接起來。

然后選擇2-4這條邊,連接起來。

剩下的同理

最小生成樹有個性質,樹的邊數量等於節點數量-1

所以終止的條件是樹的邊數量等於節點數量-1;

下面是代碼:

首先對邊的權重進行排序

對邊進行遍歷

判斷邊的兩個節點的父親是否是同一個節點

至此最小生成樹就結束了

最后我們要隨機幾條邊到生成的路線中。

這里的方法比較簡陋,就是隨機找幾個,然后添加進去。

現在大部分工作已經准備好了,剩下的就是如何講我們的房間和道路展示出來

繪制房間和道路

這里采用了Unity中Tilemap工具來繪制房間和道路。

按照文章的設想,房間之間的通路是由水平和垂直的路組成的,並不是斜着的路。

如圖

但是兩個房間的路又存在兩條,就像moba類游戲中的上路和下路一樣,我們應該選擇哪一種

如圖:

雖然很不情願,但是我還是得使用隨機來指定路線,

接下來又面臨一個問題,房間的出口究竟應該設置在哪里?

如果是中間,這樣看起來就不夠多樣性。那只能選擇隨機了。沒錯又是隨機,畢竟是隨機生成地牢嘛。

我的想法是在圖中紅色的區域隨機生成一個點,作為兩個房間道路的拐角點或者是轉折點。

代碼:

為了方便以后放置門,我們還需要講房間的出口計算出來

看圖可以看出來房間A的出口坐標

DoorA.x = room.x+size.x/2

DoorA.y = Turnpoint.y

看上去很完美,但是如果出現了下面的情況門口就會出現問題。

假設拐角點在房間內,按照上面的計算公式,就會出現下面的情況

為了排除這種情況,有兩種做法,

一、禁止轉折點在房間內生成,這個也有問題,我不過多解釋。

二、刪除房間B的出口,將轉折點修改為真正的出口

我的選擇自然是刪除房間B的出口

看圖可以看出轉折點的位置是

Turnpoint.x = Turnpoint.x

Turnpoint.y = roomb.y+size.y/2;

再將兩點返回

最后調整一下每個方向應該的值,微調一下代碼(這個微調花了我最多時間)

代碼參考

這里稍微解釋一下,代碼中有很多這樣的語句

(dir.x > 0) ? offset - 1 : -offset)
或者
(dir > 0) ? (roomB.size.y / 2) - 1 : -(roomB.size.y / 2)

這些 - 1 都是為了能夠在Tilemap中繪制圖的時候不會出錯

 

 如果我不減1 就會出現

心目中的6*6

 

現實中畫出來的6*6 注意:這里指的是如果不 減 1 的情況。

接下來繼續上代碼

將主房間和次方間,以及道路繪制出來

繪制房間到Tilemap的代碼:

繪制道路到Tilemap代碼

最后生成效果展示

終於結束了,真的花費了太長的時間,感覺有些不務正業hhh,

當然問題還是有的,比如,大量的射線檢測導致的性能問題,還有道路的選擇也有寫小問題。

由於房間之間的碰撞使用的是組件,所以碰撞的順序常常不相同,即使設置隨機種子,房間的位置也會不相同。

剩下的以后有機會再寫,保證不咕咕咕。

最后,我寫了一個月才完成了這些代碼,不知道大佬們需要花多久。

參考文章:

隨機地牢作者的博客:

https://www.gamasutra.com/blogs/AAdonaac/20150903/252889/Procedural_Dungeon_Generation_Algorithm.php

隨機地牢作者的github:

https://github.com/adnzzzzZ/blog/issues/7

隨機地牢的國內翻譯:

http://www.gamelook.com.cn/2015/12/239245

圓內隨機點算法:

https://stackoverflow.com/questions/5837572/generate-a-random-point-within-a-circle-uniformly

三角剖分算法:

https://www.cnblogs.com/zhiyishou/p/4430017.html

https://blog.csdn.net/keneyr/article/details/90316467

最小生成樹算法:

https://blog.csdn.net/qq_41754350/article/details/81460643

其他生成算法(未參考):

https://indienova.com/indie-game-development/roguelike-dungeon-building-algorithm/

https://indienova.com/indie-game-development/rooms-and-mazes-a-procedural-dungeon-generator/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM