最近在學習過程生成技術,在這里寫一些心得。
對於元胞自動機,我們這里只討論輸入是一副二維bool數組的情況,即大多數游戲中的情況。
一個元胞自動機,對於一個輸入,給出一個同樣格式的輸出。輸出中的每個點都是按照自動機中的規則從輸入中演化而來的。大部分情況下,一個輸出上的點,是根據輸入中該點周圍的點的狀態來決定輸出中的狀態。
我們輸入一個二維bool數組,並且規定它的演化規則為,某個點周圍的true較多,就演化為true,否則演化為false。這種規則下,我們輸入一個隨機生成的噪點圖,經過一定次數的演化,就可以獲得相當自然的“洞穴”地圖。
根據噪點圖中噪點的數量,最終生成的洞穴被填充的面積也不同。
unity官方給出了一個用元胞自動機隨機生成洞穴地形的教程 https://unity3d.com/cn/learn/tutorials/projects/procedural-cave-generation-tutorial
其中的元胞自動機生成的部分代碼有一點錯誤,它並沒有把輸入的數組進行備份,直接對每個點進行遍歷,因此每個點取到的之前一行的點都是新生成的點。
不過負負得正的是因此生成了看起來更像洞穴的結果。事實上如果按照它的規則,寫一個周圍有4個true,則下一次變為true,否則變為false的自動機的話,最終的圖像會出現非常明顯的鋸齒抖動。如下圖。
上圖中的鋸齒不管進行幾次迭代都會一直存在
我做出的修正是當周圍的格子為true時剛好有4個時,則自身保持不變。最終獲得了看起來像是洞穴的效果。
類似這樣的小調整可以做很多,比如邊界上的點的處理等。通過不斷調整可以獲得各種不同效果。
貼一下代碼
using UnityEngine;
using System.Collections;
namespace CS.MapGeneration {
public class CelluarAutomata {
private static int[,] offset = {
{ -1, -1 }, { -1, 0 }, { -1, 1 },
{0,-1 }, {0,1 },
{1,-1 }, {1,0 }, {1,1 }
};
private static int GetNeighbourCount(bool[,] map,int x,int y) {
int res = 0;
for (int i = 0; i < offset.GetLength(0); i++) {
int nx = x + offset[i, 0];
int ny = y + offset[i, 1];
if (nx<=0 || ny <=0 || nx>=map.GetLength(0) || ny >= map.GetLength(1) ||map[nx,ny])
res++;
}
return res;
}
public void Iterate(bool[,] map) {
bool[,] copy = map.Clone() as bool[,];
for(int i = 0; i < copy.GetLength(0); i++) {
for(int j = 0; j < copy.GetLength(1); j++) {
map[i, j] = Rule(copy, i, j);
}
}
}
protected static bool Rule(bool[,] map, int x,int y) {
if (!map[x, y] && GetNeighbourCount(map, x, y) > 4)
return true;
else if (map[x, y] && GetNeighbourCount(map, x, y) < 4)
return false;
return map[x, y];
}
}
}