1. 程式人生 > >unity的迷宮生成演算法

unity的迷宮生成演算法

思路:

定義一個二維陣列,二維陣列中奇數行列視為圍牆,偶數為路徑。 從起始點開始,隨機從上下左右四個位置尋找周圍沒有被找到過的位置,找到後此點標記為1,並把此點與前一點之間的位置設定為1。 如果全部位置已經找到過,則退回到上一個點重複次邏輯,直到所有點都記錄到或 退回到起始點且沒有可用點。

文章最後我會附上完整程式碼。

下面我們來實現一下

 新建一個迷宮生成類:MazeCreate

定一個二維列表,用來儲存我們的資料,因為迷宮的尺寸需要是可配置的,所有我們用list來進行儲存:

public List<List<int>> mapList = new List<List<int>>();
為了區分每個點代表的不同內容,我們定義一個列舉:
    public enum PointType{
        wall = 0,//牆
        way = 1,//路
        startpoint = 2,//起始點
        endpoint = 3,//結束點
        nullpoint = 4,//空位置,不進行任何操作
    }

在建構函式中對迷宮的尺寸進行設定:

private MazeCeator(int row, int col){
        this.row = row;
        this.col = col;
}
然後定義Start方法開始迷宮資料的生成
初始化陣列,按照行數和列數對資料進行初始化:
for (int i = 0; i < row; i++)
        {
            mapList.Add(new List<int>());
            for (int j = 0; j < col; j++)
            {
                mapList[i].Add((int)PointType.wall);
            }
        }
然後隨機找一個點作為起始點,因為我們前面說過,奇數行為牆,偶數行為路線,所以要對隨機的結果進行判斷,避免起始點在牆上,像這樣:
        int _row = Random.Range(1, row - 1);
        int _col = Random.Range(1, col - 1);
        if (_row % 2 == 0) {  _row += 1; }
        if (_col % 2 == 0) { _col += 1; }
然後給起始點的資料賦值:
mapList[_row][_col] = (int)PointType.startpoint;
開始生成迷宮


首先實現一下尋找周圍可用點的方法:
    void FindNearPoint(List<int> nearpoint,int index){
        nearpoint.Clear();
        int _row = index / col;
        int _col = index % col;
        //up
        if (_row >= 2)
        {
            AddNearPoint(nearpoint, (_row - 2) * col + _col);
        }
        //down
        if (_row < row - 2)
        {
            AddNearPoint(nearpoint, (_row + 2) * col + _col);
        }
        //left
        if (_col >= 2)
        {
            AddNearPoint(nearpoint, _row * col + _col - 2);
        }
        //up
        if (_col < col - 2)
        {
            AddNearPoint(nearpoint, _row * col + _col + 2);
        }

    }
nearpoint引數用來記錄所有的可用點,index為要尋找的點,這裡為了傳遞引數更加方便,把二維索引轉換成一維來進行傳遞。

AddNearPoint方法用來判斷這個點是否滿足可一個被找到的條件:

    void AddNearPoint(List<int> nearpoint, int p)
    {
        int _row = p / col;
        int _col = p % col;

        if (p >= 0 && p < maxcount && mapList[_row][_col] == (int)PointType.wall)
        {
            nearpoint.Add(p);
        }
    }



接下來進行生成邏輯,定義一個方法void FindPoint(int nowindex);nowindex為當前的點。

然後在Start呼叫FindPoint並傳入起始點:

        int nowindex = _row * col + _col;
        FindPoint(nowindex);

FindPoint裡面我們使用遞迴的方式來找到所有的點。

首先判斷是否找到所有的點:

        if(findList.Count >= maxcount){
            return;
        }

其中maxcount=row * col,為了方便使用,把它定義成全域性變數並在Start中賦值。

然後我們定義一個列表用來儲存附近可用的點,即值為0的點並開始尋找:

        List<int> nearpoint = new List<int>();
        FindNearPoint(nearpoint,nowindex);
如果找到了附近可用的點,這遍歷這些點並對其進行處理:

        while (nearpoint.Count > 0)
        {
            int rand = Random.Range(0, nearpoint.Count);

            //中間的格子
            int midindex = nowindex + (nearpoint[rand] - nowindex) / 2;
            SetPoint(midindex);

            //新的格子
            int newindex = nearpoint[rand];
            SetPoint(newindex);
            nearpoint.RemoveAt(rand);
            //遞迴
            FindPoint(newindex);

            FindNearPoint(nearpoint, nowindex);
        } 

還需要一個設定目標點的方法:

    void SetPoint(int index)
    {
        int _row = index / col;
        int _col = index % col;
        mapList[_row][_col] = (int)PointType.way;

        findList.Add(index);
    }


這裡先是在所有可用點中隨機找出一個即rand,然後把當前點與rand之間的點打通,即設定為1,把目標點從nearpoint中移除,

然後遞迴呼叫FindPoint,並傳入新的目標點。

這裡因為在每一次迴圈後,之前找到的nearpoint中的點的狀態有可能會改變,所以最後需要重新尋找一次附近可用的點。

這樣我們的迷宮就可以正常生成了,我們來測試一下。

建立一個cube並做成預製體,放在Resources/Prefabs中取名為maze。

建立一個測試指令碼MazeTest:

定義地圖的寬高已經迷宮生成類:

    const int row = 41, col = 41;
    MazeCreate mazeCreate;
Start方法中初始化迷宮類
mazeCreate = MazeCreate.GetMaze(row, col);
然後遍歷迷宮資料並生成物體,這裡我們判斷是在路徑上生成物體:
for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {

                if (mazeCreate.mapList[i][j] == (int)MazeCreate.PointType.startpoint ||
                    mazeCreate.mapList[i][j] == (int)MazeCreate.PointType.way)
                {
                    GameObject column = (GameObject)Resources.Load("Prefabs/maze");
                    column = MonoBehaviour.Instantiate(column);
                    column.transform.position = new Vector3(i, 0, j); 
                }

            }
        }
MazeTest掛到攝像機上並執行場景,我們可以看到:


這樣我們的迷宮就生成成功了。

為了看到迷宮的生成過程,我們使用findList,這裡記錄了路徑點的先後順序,我們來試一下:

還是上邊的測試類,我們把for迴圈的部分註釋調,然後在Update中加入如下程式碼

    float addTime = 0;
    int addindex = 0;
	// Update is called once per frame
	void Update () {
        if (addindex >= mazeCreate.findList.Count)
        {
            return;
        }

        addTime += Time.deltaTime;

        if (addTime > 0.05)
        {
            addTime = 0;
            int index = mazeCreate.findList[addindex];

            int _row = index / col;
            int _col = index % col;

            GameObject column = (GameObject)Resources.Load("Prefabs/maze");
                    column = MonoBehaviour.Instantiate(column);
            column.transform.position = new Vector3(_row, 0, _col); 

            addindex++;
        }
	}

效果:


最後附上MazeCreate的完整程式碼:

/// <summary>
/// 迷宮生成類
/// 迷宮生成思路:
/// 規則:二維陣列中奇數行列視為圍牆,偶數為路徑
/// 從起始點開始,隨機從上下左右四個位置尋找周圍沒有被找到過的位置,找到後此點標記為1,並把此點與前一點之間的位置設定為1
/// 如果全部位置已經找到過,則退回到上一個點重複次邏輯,直到所有點都記錄到或 退回到起始點且沒有可用點
/// </summary>
public class MazeCreate : MonoBehaviour
{

    public enum PointType{
        wall = 0,//牆
        way = 1,//路
        startpoint = 2,//起始點
        endpoint = 3,//結束點
        nullpoint = 4,//空位置,不進行任何操作
    }

    //行數
    public int row{ get; private set; }
    //列數
    public int col{ get; private set; }
    //全部點數量
    int maxcount;

    private MazeCreate(int row, int col){
        this.row = row;
        this.col = col;
        Start();
    }

    public static MazeCreate GetMaze(int row, int col)
    {
        MazeCreate maze = new MazeCreate(row,col);
        return maze;
    }
    
    /// <summary>
    /// 迷宮生成過程中,記錄已經找到的點
    /// </summary>
    public List<int> findList = new List<int>();

    //迷宮資料
    public List<List<int>> mapList = new List<List<int>>();

    void Start(){

        maxcount = row * col;

        //初始化陣列
        for (int i = 0; i < row; i++)
        {
            mapList.Add(new List<int>());
            for (int j = 0; j < col; j++)
            {
                mapList[i].Add((int)PointType.wall);
            }
        }

        //for (int i = 0; i < 10; i++)
        //{
        //    for (int j = 0; j < 10; j ++){
        //        mapList[row / 2 - 2 + i][col / 2 - 2 + j] = (int)PointType.nullpoint;
        //    }
        //}


        //起始點
        int _row = Random.Range(1, row - 1);
        int _col = Random.Range(1, col - 1);
        if (_row % 2 == 0) {  _row += 1; }
        if (_col % 2 == 0) { _col += 1; }
 
        mapList[_row][_col] = (int)PointType.startpoint;

        int nowindex = _row * col + _col;
        findList.Add(nowindex);

        //遞迴生成路徑
        FindPoint(nowindex);
    }

    void FindPoint(int nowindex){
        if(findList.Count >= maxcount){
            return;
        }

        List<int> nearpoint = new List<int>();
        FindNearPoint(nearpoint,nowindex);
        while (nearpoint.Count > 0)
        {
            int rand = Random.Range(0, nearpoint.Count);

            //中間的格子
            int midindex = nowindex + (nearpoint[rand] - nowindex) / 2;
            SetPoint(midindex);

            //新的格子
            int newindex = nearpoint[rand];
            SetPoint(newindex);
            nearpoint.RemoveAt(rand);
            //遞迴
            FindPoint(newindex);

            FindNearPoint(nearpoint, nowindex);
        } 
    }

    //尋找附近可用的點
    void FindNearPoint(List<int> nearpoint,int index){
        nearpoint.Clear();
        int _row = index / col;
        int _col = index % col;
        //up
        if (_row >= 2)
        {
            AddNearPoint(nearpoint, (_row - 2) * col + _col);
        }
        //down
        if (_row < row - 2)
        {
            AddNearPoint(nearpoint, (_row + 2) * col + _col);
        }
        //left
        if (_col >= 2)
        {
            AddNearPoint(nearpoint, _row * col + _col - 2);
        }
        //up
        if (_col < col - 2)
        {
            AddNearPoint(nearpoint, _row * col + _col + 2);
        }

    }

    //設定路徑
    void SetPoint(int index)
    {
        int _row = index / col;
        int _col = index % col;
        mapList[_row][_col] = (int)PointType.way;

        findList.Add(index);
    }

    //附近的點是否滿足尋找條件
    void AddNearPoint(List<int> nearpoint, int p)
    {
        int _row = p / col;
        int _col = p % col;

        if (p >= 0 && p < maxcount && mapList[_row][_col] == (int)PointType.wall)
        {
            nearpoint.Add(p);
        }
    }


}