同樣使用Kruskal's algorithm來做為迷宮產生的判斷。
這次不用做太多的修改,只需要改幾個部分而已,也就是Component中使用到Random.Range()的部分,這個亂數目前是無法自己控制的,所以做個小小的調整改為使用Seed即可。
先建立一個類別來裝需要的東西,使用Static也可以,就可以直接呼叫使用,不過如果還有其他地方也會用到的話,就要小心先後呼叫的順序不同會造成同個Seed卻不同結果,所以這邊還是用需要建立instance的方法。
public class MyRandom
{
private System.Random random;
public MyRandom(int seed)
{
random = new System.Random(seed); //使用System的Random來丟入Seed
}
//回傳整數
public int Range(int min, int max) //使用跟Random.Range()相同的方法名稱,之後把原本的Random取代
{
return random.Next(min, max);
}
//回傳浮點數
public float Range(float min, float max)
{
double range = (double)max - (double)min;
double sample = random.NextDouble();
double scaled = (sample * range) + min;
return (float)scaled;
}
}
之後跟原本的Component差不多,不過多加Seed參數可以在編輯器輸入數值,以及把原本的Random.Range()改為MyRandom.Range()而已,這樣就可以亂數產生迷宮的同時,使用Seed來複製某一個想要重來的迷宮。
參考Component。
public class KruskalMaze : MonoBehaviour
{
private class Cell
{
//使用一個陣列來設定四個牆的屬性,0還沒檢查,1是有牆面,2則是打通的通道
public int[] wall = new int[4]; //陣列四個依序為N, E, S, W
public Cell[] nextCell = new Cell[4]; //紀錄通道通往的下一個格子
public int x, y;
}
public int width = 10; //地圖的寬
public int height = 10; //地圖的高
private Cell[][] cellArr; //地圖陣列
public int seed; //輸入Seed數值
private MyRandom myRandom; //使用自己的類別
void Start ()
{
myRandom = new MyRandom(seed); //初始化
StartCoroutine(Maze (width, height));
}
//這邊用一個簡單的Draw Gizmos在Unity編輯室窗上顯示地圖形成的過程
void OnDrawGizmos()
{
float size = 1.5f;
Gizmos.color = Color.yellow;
if(cellArr != null)
{
for(int i = 0; i < height; ++i)
{
for(int j = 0; j < width; ++j)
{
Gizmos.DrawCube(new Vector3(j*size, i*size, 0), new Vector3(0.5f, 0.5f, 0.5f));
if(cellArr[i][j].wall[0] == 2) Gizmos.DrawCube(new Vector3(j*size, i*size + 0.5f, 0), new Vector3(0.5f, 0.5f, 0.5f));
if(cellArr[i][j].wall[1] == 2) Gizmos.DrawCube(new Vector3(j*size + 0.5f, i*size, 0), new Vector3(0.5f, 0.5f, 0.5f));
if(cellArr[i][j].wall[2] == 2) Gizmos.DrawCube(new Vector3(j*size, i*size - 0.5f, 0), new Vector3(0.5f, 0.5f, 0.5f));
if(cellArr[i][j].wall[3] == 2) Gizmos.DrawCube(new Vector3(j*size - 0.5f, i*size, 0), new Vector3(0.5f, 0.5f, 0.5f));
}
}
}
}
//建立地圖
IEnumerator Maze(int width, int height)
{
//初始化地圖陣列跟所有點的列表
List<Cell> cellList = new List<Cell>();
cellArr = new Cell[height][];
for(int i = 0; i < height; ++i)
{
cellArr[i] = new Cell[width];
for(int j = 0; j < width; ++j)
{
cellArr[i][j] = new Cell();
//順便設定邊界的牆
if(j == 0) cellArr[i][j].wall[3] = 1;
if(j == width-1) cellArr[i][j].wall[1] = 1;
if(i == 0) cellArr[i][j].wall[2] = 1;
if(i == height-1) cellArr[i][j].wall[0] = 1;
cellArr[i][j].y = i;
cellArr[i][j].x = j;
//加入列表
cellList.Add(cellArr[i][j]);
}
}
//只要列表的格子還沒檢查完就一直檢查
while(cellList.Count > 0)
{
//從列表亂數選一個格子
Cell cell = cellList[myRandom.Range(0, cellList.Count)]; //改為myRandom
//先判斷這個格子四個方向是否都檢查完,如果檢查完就移除列表並且重新再選一個格子
if(IsCellFinished(cell))
{
cellList.Remove(cell);
continue; //Re-Pick cell
}
//亂數選一個方向並確定該方向是還沒檢查過的
int dir = myRandom.Range(0, 4); //改為myRandom
while(true)
{
if(cell.wall[dir] == 0)
break;
dir = myRandom.Range(0, 4); //改為myRandom
}
//取得該方向的下一個格子資料
int dirY = (dir == 0) ? 1 : (dir == 2) ? -1 : 0;
int dirX = (dir == 1) ? 1 : (dir == 3) ? -1 : 0;
int nextY = cell.y + dirY;
int nextX = cell.x + dirX;
Cell nextCell = cellArr[nextY][nextX];
//從這個格子開始檢查是否連回最初的格子
if(IsClosedCircuit(cell, nextCell, new List<Cell>()))
{
//起始格跟下一個方向的格子連線會形成封閉迴圈,把這個方向設定為牆壁
cell.wall[dir] = 1;
nextCell.wall[(dir+2)%4] = 1;
//順便檢查這兩個格子是否都檢查完
if(IsCellFinished(cell)) cellList.Remove(cell);
if(IsCellFinished(nextCell)) cellList.Remove(nextCell);
continue; //從頭步驟在開始
}
//都檢查完,把起始格跟下一個方向的格子連線形成通道
cell.wall[dir] = 2;
cell.nextCell[dir] = nextCell;
nextCell.wall[(dir+2)%4] = 2;
nextCell.nextCell[(dir+2)%4] = cell;
//順便檢查這兩個格子是否都檢查完
if(IsCellFinished(cell)) cellList.Remove(cell);
if(IsCellFinished(nextCell)) cellList.Remove(nextCell);
yield return null;
}
}
//檢查封閉迴圈
private bool IsClosedCircuit(Cell checkCell, Cell start, List<Cell> visitedList)
{
visitedList.Add(start);
for(int i = 0; i < 4; ++i)
{
if(start.wall[i] == 2)
{
if(visitedList.Contains(start.nextCell[i]))
continue;
if(start.nextCell[i] == checkCell)
return true;
if(IsClosedCircuit(checkCell, start.nextCell[i], visitedList))
return true;
}
}
return false;
}
//格子是否全部方向都檢查完
private bool IsCellFinished(Cell cell)
{
bool isFinished = true;
for(int i = 0; i < 4; ++i)
{
if(cell.wall[i] == 0) isFinished = false;
}
return isFinished;
}
}

No comments:
Post a Comment