【著名博客搬運翻譯】無線過程式生成城市使用波函數坍縮算法

Infinite procedurally generated city with the Wave Function Collapse algorithm

英文原址:https://marian42.de/article/wfc/

這是一個遊戲,在這你可以走在一個無限的用程序生成的城市用程序生成,而且會邊走邊生成內容。它是由一組具有波函數坍縮算法的板塊組成的。

你可以下載遊戲的可玩版本 itch.io 。您可以在source code on github. 上獲得源代碼。以下是我在一個生成的城市中漫步的視頻:

算法

我將使用單詞“slot”來表示三維體素網格中可以包含塊(或爲空)的位置,並使用單詞“module”來表示可以位於這樣一個slot中的塊。slot是位置或者插口或者槽,module是模塊,之後以此翻譯。

坍縮

算法爲世界中的每個位置選擇模塊。一組位置被認爲是一個未被觀測的波函數。這意味着每個位置都有一組可能放在那裏的模塊。在量子力學的語言中,可以說“slot是所有module的疊加”。世界從一個完全不被察覺的狀態開始,在這個狀態下,每個模塊都可能出現在任何slot中。一個接一個,每個slot都被坍縮。也就是說,從一組可能的模塊中隨機選擇一個模塊。接下來是約束傳播步驟。對於每個模塊,只允許將模塊的一個子集放置在其附近。每當slot坍縮時,仍有可能放置在附近插槽中的模塊集需要更新。約束傳播步驟是算法中計算代價最高的部分。

熵減

算法的一個重要方面是決定要坍縮哪個slot。算法總是以最小熵entropy坍縮slot。算法總選擇(或混亂)最少的slot。1.假定每個模塊的出現概率相同,則擁有最少module的slot被視爲有最小的熵(可能出現的情況少,則不混亂,熵減)。一般來說,選擇模塊的概率不同。2.slot_A 擁有兩個概率相同的module,slot_B擁有兩個概率不同的module,則slot_A熵 > slot_B熵。

譯者補充:作者用量子力學的概念比喻自己的算法。量子物理中,未觀測的粒子處於疊加態,觀測時坍縮爲特定狀態。在此算法中,位置A在未觀測時處於多個模塊的疊加態,每個模塊都有一定概率出現。隨後作者定義了熵減規律,並規定兩個熵計算規則,1模塊少的slot熵少。2模塊數量相同的slot,模塊概率不一致的slot熵少。

你可以在Wave Function Collapse algorithm here. 找到更多的信息和波函數摺疊算法的一些漂亮的例子。提出了一種基於單個實例生成二維紋理的算法。在這種情況下,模塊概率和鄰接規則是根據它們在示例中的發生方式來確定的。在我的情況下,它們是手動供應的。算法的進行:https://marian42.de/article/wfc/wfc.mp4

 

 

關於街區,原型和模塊blocks, prototypes and modules

這個世界是由一組大約100個街區組成的,我用blender做的。我從幾個街區開始,每當我有空閒時間時,我就做更多。

算法需要知道哪些模塊可以放在一起。每個模塊有6個可能的鄰居列表,每個方向一個。但我想避免手工創建這個列表。我還想要一種方法來自動生成塊的旋轉變體。


兩者都可以通過使用我稱之爲模塊原型來實現。這是一個可以在Unity編輯器中方便編輯的MonoBehaviour。模塊以及被允許的相鄰模塊列表和旋轉變量都是從這些模塊原型中自動創建的。

 

一個困難的問題是如何建立鄰接信息的模型,使這個自動過程正常工作。以下是我想到的:


每個塊有6個連接器,每個面一個。連接器有一個號碼。此外,水平連接件不是翻轉的,就是對稱的。垂直連接器的旋轉索引介於0和3之間(屏幕截圖中爲b、c、d),或者它們被標記爲旋轉不變。


基於此,我可以自動檢查哪些模塊允許彼此相鄰。相鄰模塊必須具有相同的連接器號。並且它們的對稱性必須匹配(垂直旋轉索引相同,水平翻轉而非翻轉對),或者它們必須對稱/不變。

有些排除規則允許我禁止某些相鄰模塊。一些具有匹配連接器的塊在彼此旁邊看起來並不好看。下面是不使用排除規則生成的映射的示例:很難看

到達無限

原始的波函數摺疊算法生成有限個映射。我想有一個世界,當你走過它時,它會伸展越來越大。


我的第一個方法是生成有限大小的大塊chunks,並使用相鄰大塊的連接器作爲約束。如果生成了大塊,並且已經生成了相鄰大塊,則只允許生成與現有模塊匹配的模塊。這種方法的問題是,每當一個slot坍縮時,約束傳播將限制幾個slot之外的slot。在這張圖片中,您可以看到一個坍縮的slot影響的所有位置,影響太多是個問題。

當一次只生成一個大塊時,約束不會傳播到相鄰的大塊。這導致在考慮其他塊時不允許在塊中選擇模塊。當算法試圖生成下一個大塊時,它找不到任何解決方案。


我沒有使用大塊,而是將映射存儲在一個字典中,該字典將插槽位置映射到插槽。它只在需要時填充。算法的某些部分需要對此進行調整。在選擇要摺疊的插槽時,不能考慮所有無限插槽。取而代之的是,當玩家到達地圖時,地圖上只會同時生成一小塊區域。約束仍然傳播到此區域之外。


在某些情況下,這種方法不起作用。考慮一個模塊集,上面截圖中的隧道段是筆直的,但是沒有隧道入口。如果算法選擇這樣一個隧道模塊,這就預先確定了一個無限的隧道。約束傳播步驟將嘗試分配無限數量的插槽。我設計模塊集是爲了避免這個問題。

The original Wave Function Collapse algorithm generates finite maps. I wanted to have a world that expands further and further as you walk through it.

My first approach was to generate chunks of finite size and use the connectors of adjacent chunks as constraints. If a chunk is generated and an adjacent chunk was already generated, only modules are allowed that fit with the existing modules. The problem with this approach is, whenever a slot is collapsed, the constraint propagation will limit the posibilities even a few slots away. In this image you can see all the places affected from collapsing just one slot:

When just generating a single chunk at once, constraints were not propagated to adjacent chunks. This led to modules being selected within the chunk that would not be allowed when considering the other chunks. When the algorithm would then try to generate the next chunk, it could not find any solution.

Instead of using chunks, I store the map in a dictionary that maps a slot position to a slot. It is only populated when needed. Some parts of the algorithm needed to be adjusted to this. When selecting a slot to collapse, not all infinite slots can be considered. Instead, only a small area of the map is generated at once, when the player reaches it. Constraints are still propagated outside of this area.

In some cases this approach doesn’t work. Consider a module set with the straight tunnel piece from the screenshot above, but no tunnel entrance. If the algorithm selects such a tunnel module, this predetermines an infinite tunnel. The constraint propagation step would try to allocate an infinite amount of slots. I designed the module set to avoid this problem.這段沒整太明白大家自己看看

邊界約束

有兩個重要的邊界約束:位於地圖頂部的面必須具有“空氣air”連接器。地圖底部的面必須具有“實體solid”連接符。如果不滿足這些限制條件,地面上就會有洞,屋頂也會丟失。

在有限的映射中,這很容易做到:對於頂層和底層的所有slot,刪除所有帶有不需要的連接器的模塊。然後使用約束傳播移除不再有效的其他模塊。

在無限貼圖中,這不起作用,因爲在頂層和底層有無限多的slot。剛開始時,我只會在創建槽後刪除頂層和底層的這些模塊。但是,刪除頂層插槽中的模塊意味着對其相鄰插槽的約束。這將導致級聯效應cascading effect,再次無限地分配slot。

我通過創建一個1×n×1的地圖來解決這個問題,其中n是高度。此地圖使用連續平鋪world wrapping來傳播約束。這就像吃豆人,你離開右邊邊界,進入左邊邊界。現在在這個地圖上我可以應用所有的邊界約束。無論何時在無限映射中創建一個新的slot,都會使用該映射中相應位置的模塊集對其進行初始化。

錯誤狀態和回溯

有時WFC算法會達到一個slot沒有可能的模塊的狀態。在有限世界的應用程序中,您可以丟棄結果並重新開始。在無限的世界裏,這是行不通的,因爲世界的一部分已經展示給玩家了。我從一個解決方案開始,在出現錯誤的地方生成一個白色塊。

我現在的解決辦法是回溯。將坍縮的順序和有關約束傳播的信息被存儲爲歷史記錄以方便回溯。如果WFC算法失敗,一些歷史記錄將被撤消。這在大多數情況下都有效,但有時錯誤被識別得很晚,這會導致許多步驟被回溯。在極少數情況下,玩家所在的位置會重新生成。

在我看來,這種限制使得WFC方法不適合用於商業遊戲的無限世界。

 

見解

當我看到 talk by Oskar Stålberg who uses the WFC algorithm to generate levels in Bad North.,我就開始研究這個問題。大多數基礎都是在procjam實現的。

我對未來的改進有一些想法,但我不確定是否會增加遊戲性。如果我這麼做了,那可能就不是你想象中的吃雞戰場遊戲了。但如果你想看到你最喜歡的遊戲中添加到這個,就自己動手吧!源代碼畢竟是可用的,而且是麻省理工學院授權的

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