Unity 記錄一種基於深搜和圖的迷宮算法

假設一個5*5的迷宮地圖,按下圖分割
在這裏插入圖片描述
可見,問題變成了Unit和Unit之間哪些紅色方塊應該被打通。則,使用深搜算法遍歷Unit,隨機往一個相鄰的、沒有被走過(因爲只希望有一條正確通道)的其他Unit走,再把路打通即可。如果無路可走則回溯。

效果如圖
在這裏插入圖片描述
完整代碼

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System.Threading;

public class MazeBuilder : MonoBehaviour
{
    public int height;
    public int width;
    public Vector2Int entrance;
    public Transform wallPrefab;
    public Stuff[] stuffs;

    [System.Serializable]
    public struct Stuff
    {
        public int number;
        public Transform prefab;
    }

    List<Vector2Int> directions = new List<Vector2Int>
    {
        new Vector2Int(0, -1),
        new Vector2Int(1, 0),
        new Vector2Int(0, 1),
        new Vector2Int(-1, 0),
    };

    public void Build()
    {
        // 記錄unit
        bool[,] units = new bool[height * 2, width * 2];
        // 記錄pass
        bool[,] passs = new bool[height * 4 - 1, width * 4 - 1];

        // 記錄路徑
        var stack = new Stack<Vector2Int>();
        // 設置入口
        stack.Push(entrance);

        while (stack.Count > 0)
        {
            // 打通這個unit
            var now = stack.Peek();
            units[now.x, now.y] = true;
            passs[now.x * 2, now.y * 2] = true;
            foreach (var item in RandomDirections())
            {
                // 新通向的unit座標
                var point = now + item;
                
                // 如果這個unit沒被打通
                if (!IsOutRange(point) && !units[point.x, point.y])
                {
                    // 打通當前unit和這個unit的通道
                    passs[point.x + now.x, point.y + now.y] = true;
                    // 將這個unit設爲當前unit
                    stack.Push(point);
                    break;
                }
            }
            // 無路可走,回溯到上一個unit
            if (stack.Peek() == now)
            {
                stack.Pop();
            }
        }

        List<Vector2> passPoints = new List<Vector2>();

        // 牆根節點
        GameObject wallParent = new GameObject("walls");
        wallParent.transform.SetParent(transform);
        wallParent.transform.localPosition = Vector3.zero;
        wallParent.transform.localScale = Vector3.one;

        for (int i = 0; i < height * 4 - 1; i++)
        {
            for (int j = 0; j < width * 4 - 1; j++)
            {
                if (!passs[i, j])
                {
                    // 生成牆
                    var wall = Instantiate(wallPrefab, wallParent.transform);
                    wall.localPosition = new Vector3(i - width * 2 + 1f, 0, j - width * 2 + 1f);
                    wall.gameObject.name = $"({i},{j})";
                    wall.Rotate(0, Random.Range(0, 4) * 90, 0);
                }
                else
                {
                    // 記錄通道
                    passPoints.Add(new Vector2(i, j));
                }
            }
        }

        // 物體根節點
        GameObject stuffParent = new GameObject("Maze Stuffs");
        stuffParent.transform.SetParent(transform);
        stuffParent.transform.localPosition = Vector3.zero;
        stuffParent.transform.localScale = Vector3.one;

        foreach (var item in stuffs)
        {
            for (int i = 0; i < item.number && passPoints.Count > 0; i++)
            {
                // 生成物體
                int r = Random.Range(0, passPoints.Count);
                var stuff = Instantiate(item.prefab, stuffParent.transform);
                stuff.localPosition = new Vector3(passPoints[r].x - width * 2 + 1f, 0, passPoints[r].y - width * 2 + 1f);
                stuff.localScale = new Vector3()
                {
                    x = 1 / transform.lossyScale.x * item.prefab.localScale.x,
                    y = 1 / transform.lossyScale.y * item.prefab.localScale.y,
                    z = 1 / transform.lossyScale.z * item.prefab.localScale.z
                };
                stuff.gameObject.name = $"{item.prefab.gameObject.name} {i}";
                passPoints.RemoveAt(r);
            }
            if (passPoints.Count == 0)
            {
                break;
            }
        }

        // 判斷越界
        bool IsOutRange(Vector2Int point)
        {
            return point.x < 0 || point.x >= height * 2 || point.y < 0 || point.y >= width * 2;
        }

    }

    // 洗牌算法
    private List<Vector2Int> RandomDirections()
    {
        for (int i = directions.Count; i >= 0; i--)
        {
            int r = Random.Range(0, i);
            directions.Add(directions[r]);
            directions.RemoveAt(r);
        }
        return directions;
    }
}


參考文章

  1. jollysoul-三大迷宮生成算法 (Maze generation algorithm) – 深度優先,隨機Prim,遞歸分割
  2. 小牛-圖的遍歷迷宮生成算法淺析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章