[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
留言分享你的想法!
avatar-img
螞蟻兒的沙龍
21會員
46內容數
這裡是分享個人讀書心得或學習心得的沙龍,請大家多多指教~
螞蟻兒的沙龍的其他內容
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
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
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