[Unity] 迷宮產生器

更新 發佈閱讀 17 分鐘

想要在Unity快速產生迷宮,提升製作關卡的效率,可以自己寫一個簡單的迷宮產生器腳本,產生迷宮物件後,再自行進行修改。

[Step1:新增Script腳本]

新增一個空的物件,命名為"MazeGenerator",並新增Script"MazeGenerator":

raw-image
raw-image

[Step2:編輯Script腳本]

編輯MazeGenerator類別,將ChatGPT建議的程式碼覆蓋上去。主要在函數前面標註[ContextMenu("Generate Maze")],便能夠在Unity編輯執行該函數。

using UnityEngine;

public class MazeGenerator : MonoBehaviour
{
public int width = 10; // 迷宮寬度(以格為單位)
public int height = 10; // 迷宮高度(以格為單位)
public float cellSize = 2f; // 每格大小 (2x2)

public GameObject wallPrefab;

private bool[,] maze; // true = 空地, false = 牆

// 清除舊的物件,並且做程式初始化
private void ClearObject()
{
// 先清空舊的
for (int i = transform.childCount - 1; i >= 0; i--)
{
DestroyImmediate(transform.GetChild(i).gameObject);
}

// 建立迷宮資料(這裡用簡單演算法,可以換成 DFS/Prim/Kruskal)
maze = new bool[width, height];

// 簡單做法:全部先設為牆
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
maze[x, y] = false;
}
}
}

// 產生新的物件
private void GenerateObject()
{
// 產生 GameObject
int mid_width = width >> 1;
int mid_height = height >> 1;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Vector3 pos = new Vector3((x - mid_width) * cellSize + 1, 0, (y - mid_height) * cellSize + 1);

if (!maze[x, y])
{
// 牆
if (wallPrefab != null)
Instantiate(wallPrefab, pos, Quaternion.identity, transform);
}
}
}
}

[ContextMenu("Generate Maze")]
public void GenerateMaze()
{
// 清除舊的物件,並且做程式初始化
ClearObject();

// 用 DFS carve 出路徑
Carve(Random.Range(0, 2), Random.Range(0, 2));

// 產生新的物件
GenerateObject();
}

// 用 DFS carve 出路徑
void Carve(int x, int y)
{
maze[x, y] = true; // 打通

// 隨機方向
var dirs = new Vector2Int[]
{
Vector2Int.up, Vector2Int.down,
Vector2Int.left, Vector2Int.right
};
dirs = Shuffle(dirs);

foreach (var dir in dirs)
{
int nx = x + dir.x * 2;
int ny = y + dir.y * 2;

if (nx >= 0 && ny >= 0 && nx <= width - 1 && ny <= height - 1 && !maze[nx, ny])
{
maze[x + dir.x, y + dir.y] = true; // 中間打通
Carve(nx, ny);
}
}
}

Vector2Int[] Shuffle(Vector2Int[] arr)
{
for (int i = 0; i < arr.Length; i++)
{
int rand = Random.Range(i, arr.Length);
var tmp = arr[i];
arr[i] = arr[rand];
arr[rand] = tmp;
}
return arr;
}
}

這段程式的大意是從起點開始,利用DFS隨機走四個方向其中一個,每次走兩格然後中間打通,直到無路可走。地圖maze一開始都設成牆壁,走到某格再設成空格,兩格之間的牆壁也設成空格。

[Step3:測試程式]

a.設定好參數後,點選MazeGenerator腳本的右上"…",選擇執行GenerateMaze函數:

raw-image
raw-image

b. 測試執行效果,每次點選GenerateMaze函數,都會重新產生迷宮物件,這些物件在Hierarchy視窗會顯示出來,所以可以進行編輯的動作。

raw-image
raw-image

[Step4:修改Script腳本]

可以看到該程式產生的迷宮比較單調,而且有兩邊都是牆壁,所以後來我自己又改了一下程式。

	public int MAX_EXCEPTION_COUNT = 3; // 最多例外的數量
private int exceptionCount = 0; // 目前例外的數量

[ContextMenu("Generate Maze 3")]
public void GenerateMaze3()
{
ClearObject();

// 用 DFS carve 出路徑
Carve3(Random.Range(0, 2), Random.Range(0, 2));

GenerateObject();
}

/// 回傳該點是否為合法位置
private bool isValidPosition(int x, int y)
{
return x >= 0 && x < width && y >= 0 && y < height;
}

/// 該點是否為空格? true是空格,false是牆壁
/// <returns>true是空格,false表示牆壁或超出邊界</returns>
private bool isEmptyGrid(int x, int y)
{
return isValidPosition(x, y) && maze[x, y];
}

/// 該點是否為牆壁? true是空格,false是牆壁
/// <returns>true是牆壁,false表示空格或超出邊界</returns>

private bool isWallGrid(int x, int y)
{
return isValidPosition(x, y) && !maze[x, y];
}

/// <summary>
/// 取得鄰居有幾個牆壁的點
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private int GetNeighborWallCount(int x, int y)
{
int count = 0;
if (isWallGrid(x, y - 1)) ++count;
if (isWallGrid(x - 1, y)) ++count;
if (isWallGrid(x, y + 1)) ++count;
if (isWallGrid(x + 1, y)) ++count;
return count;
}

/// <summary>
/// 這邊不允許出現2*2空格
/// 牆:false 空格:true
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
bool IsValid(int x, int y)
{
// 左上角
if (isEmptyGrid(x - 1, y - 1) && isEmptyGrid(x, y - 1) && isEmptyGrid(x - 1, y)) return false;
// 右上角
if (isEmptyGrid(x, y - 1) && isEmptyGrid(x + 1, y - 1) && isEmptyGrid(x + 1, y)) return false;
// 左下角
if (isEmptyGrid(x - 1, y) && isEmptyGrid(x - 1, y + 1) && isEmptyGrid(x, y + 1)) return false;
// 右上角
if (isEmptyGrid(x + 1, y) && isEmptyGrid(x, y + 1) && isEmptyGrid(x + 1, y + 1)) return false;

return true;
}

/// <summary>
/// 檢查(x,y)四周的牆壁是否被孤立
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
bool CheckWallIsValid(int x, int y)
{
if (isWallGrid(x, y - 1) && 0 == GetNeighborWallCount(x, y - 1)) return false;
if (isWallGrid(x - 1, y) && 0 == GetNeighborWallCount(x - 1, y)) return false;
if (isWallGrid(x, y + 1) && 0 == GetNeighborWallCount(x, y + 1)) return false;
if (isWallGrid(x + 1, y) && 0 == GetNeighborWallCount(x + 1, y)) return false;
return true;

}

void Carve3(int x, int y)
{
maze[x, y] = true; // 打通

if(!CheckWallIsValid(x, y)) // 旁邊有牆壁被孤立
{
if (exceptionCount >= MAX_EXCEPTION_COUNT) // 而且超出例外
{
maze[x, y] = false;
return;
}
++exceptionCount;
}

// 隨機方向
var dirs = new Vector2Int[]
{
Vector2Int.up, Vector2Int.down,
Vector2Int.left, Vector2Int.right
};
dirs = Shuffle(dirs);

foreach (var dir in dirs)
{
int nx = x + dir.x;
int ny = y + dir.y;

if (nx >= 0 && ny >= 0 && nx <= width - 1 && ny <= height - 1 && !maze[nx, ny] && IsValid(nx, ny))
{
Carve3(nx, ny);
}
}
}

這版的DFS走訪就不再是走兩格而是走隔壁,而且不允許有2*2空格的存在。此外根據新增變數exceptionCount來判斷有幾個牆壁被孤立(旁邊四個方向都是空格),到達MAX_EXCEPTION_COUNT界限就不允許牆壁被孤立,這是為了避免地圖太破碎,不過看來效果不大,所以通常我會再修改一下地圖。

[Step5:測試修改的結果]

測試新函數的結果如下:

raw-image
raw-image

舊版的GenerateMaze函數也會保留,所以可以根據需要選用適合的函數。

【結語】

這兩個版本目前只能夠產生牆壁和空格產生的迷宮,不過我們的迷宮還有許多元素,例如金幣、門、鑰匙、陷阱和機關等等,目前看來也無法隨便用程式產生這些元素。不過利用程式幫助自己製作關卡,也算是提升一些工作效率了吧。也許以後除了迷宮產生器,也可以寫各式各樣的Script幫助自己能夠更快速開發遊戲或工具。

留言
avatar-img
螞蟻兒的沙龍
36會員
85內容數
這裡是分享個人讀書心得或學習心得的沙龍,請大家多多指教~
螞蟻兒的沙龍的其他內容
2025/08/25
[說明] (1) 這次主要新增火、水、冰三種地形,還有火盾和水盾。 (2) 這次新增兩種坦克,一種會自動追蹤玩家,一種固定間隔會發射砲彈。 (3) 之後會將專案轉成Android專案,做個幾十關發佈出來。
Thumbnail
2025/08/25
[說明] (1) 這次主要新增火、水、冰三種地形,還有火盾和水盾。 (2) 這次新增兩種坦克,一種會自動追蹤玩家,一種固定間隔會發射砲彈。 (3) 之後會將專案轉成Android專案,做個幾十關發佈出來。
Thumbnail
2025/08/08
使用Unity-Chan套件做遊戲開發,如果要將專案build成WebGL,會發現在遊戲載入時出現閃黑畫面然後就當掉的現象,儘管遊戲場景內容很單調也會出現這樣的錯誤。這可能是Unity娘使用【Unity Toon Shader】套件時導致的問題。
Thumbnail
2025/08/08
使用Unity-Chan套件做遊戲開發,如果要將專案build成WebGL,會發現在遊戲載入時出現閃黑畫面然後就當掉的現象,儘管遊戲場景內容很單調也會出現這樣的錯誤。這可能是Unity娘使用【Unity Toon Shader】套件時導致的問題。
Thumbnail
2025/07/17
[Unity chan!](俗稱Unity娘)是Assets Store上廣受歡迎的3D模型,因為它是既免費又是動漫美少女風格的模型。但她在Unity 6.0會有材質渲染失敗的問題,這是因為她使用較舊版本Unity的自訂Shader,這些Shader在Unit 6.0中並不相容。
Thumbnail
2025/07/17
[Unity chan!](俗稱Unity娘)是Assets Store上廣受歡迎的3D模型,因為它是既免費又是動漫美少女風格的模型。但她在Unity 6.0會有材質渲染失敗的問題,這是因為她使用較舊版本Unity的自訂Shader,這些Shader在Unit 6.0中並不相容。
Thumbnail
看更多
你可能也想看
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
Thumbnail
這篇內容,將會講解什麼是函式,以及與函式相關的知識。包括函式的簡介、Runtime Function、自訂函式、Script Function 腳本函式、Method 方法。
Thumbnail
這篇內容,將會用一個簡單的範例,來解釋物件(Object)和實體(Instance)的差別。包括Instance的簡介、ID、物件改變會影響實體。
Thumbnail
這篇內容,將會用一個簡單的範例,來解釋物件(Object)和實體(Instance)的差別。包括Instance的簡介、ID、物件改變會影響實體。
Thumbnail
這篇內容,將簡單介紹Asset Browser、Workspace、Inspector、Code Browser,作為入門的介面導覽。
Thumbnail
這篇內容,將簡單介紹Asset Browser、Workspace、Inspector、Code Browser,作為入門的介面導覽。
Thumbnail
這篇內容,將教你如何開啟新的GameMaker專案,並調整畫面佈局。也會講解,為何建議用英文語系,來進行遊戲開發。
Thumbnail
這篇內容,將教你如何開啟新的GameMaker專案,並調整畫面佈局。也會講解,為何建議用英文語系,來進行遊戲開發。
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
Thumbnail
Steam上的遊戲製作工具那麼多,到底哪個比較好用呢? 我只能說,每個人的能力和想製作的遊戲類型都不同,適合的工具當然也不會一樣,只能你自己去選擇最適合的。 雖然沒有辦法推薦最適合你的,但可以告訴你避開地雷工具的訣竅!
Thumbnail
Steam上的遊戲製作工具那麼多,到底哪個比較好用呢? 我只能說,每個人的能力和想製作的遊戲類型都不同,適合的工具當然也不會一樣,只能你自己去選擇最適合的。 雖然沒有辦法推薦最適合你的,但可以告訴你避開地雷工具的訣竅!
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News