Unity 生成六角網格地圖:矩形地圖以及矩形地圖內隨機

 

 

 

unity生成六角網格地圖:矩形地圖以及矩形地圖內隨機dom

本文某些概念是參考國外大神的文章去作的,讀者可能須要理解其中某些概念才能瞭解本文的一些作法ide

參考連接:https://www.redblobgames.com/grids/hexagons/函數

 

用到的地塊貼圖以下:this

 

先放上六角網格地圖效果圖:spa

 

 

 

 前兩個分別是是固定尺寸豎六邊形和固定尺寸矩形,後兩個是在前面兩個的形狀下,在裏面隨機生成。code

 

開始以前須要定義一個結構體,用於創建六邊形地圖的座標系,方便之後作距離判斷和攻擊範圍判斷,詳細座標系的介紹請查看連接中Coordinate Systems的Cube coordinates部分,後面的描述稱之爲立方體座標orm

 

    public struct CubeCoordinate
    {
        public int q;
        public int r;
        public int s;

        public CubeCoordinate(int q, int r, int s)
        {
            this.q = q;
            this.r = r;
            this.s = s;
        }

        public CubeCoordinate CubePositionAdd(CubeCoordinate offset)
        {
            return new CubeCoordinate(q + offset.q, r + offset.r, s + offset.s);
        }
    }

在結構體中定義CubePositionAdd是爲了方便作座標相加運算。blog

 

另外定義一個靜態List用於存放座標偏移量,分別爲左下、右下、下、右上、上、左上。 ci

 

    private static readonly List<CubeCoordinate> hexDirectionOffset = new List<CubeCoordinate> {
            new CubeCoordinate(-1, 0, 1), new CubeCoordinate(1, -1, 0), new CubeCoordinate(0, -1, 1),
            new CubeCoordinate(1, 0, -1), new CubeCoordinate(0, 1, -1), new CubeCoordinate(-1, 1, 0)
    };

 

另外定義兩個List用於存放全部地塊的立方體座標和世界座標。get

 

 

    private List<CubeCoordinate> cubePosList;
    private List<Vector2> worldPosList;

 

定義兩個float用於存放六邊形之間的寬距離和高距離,參考見連接中Geometry的Size and Spacing部分。

    private float widthDistance;
    private float heightDistance;

    private void InitHexsDistance()
    {
        widthDistance = hexSize * 0.75f * 0.01f;
        heightDistance = Mathf.Sqrt(3f) * hexSize * 0.5f * 0.01f;
    }

 

生成固定尺寸豎六邊形的函數以下:

    private void CreateHexagonalMap()
    {
        Queue<CubeCoordinate> cubePosQueue_BFS = new Queue<CubeCoordinate>();

        CubeCoordinate currentCubePos;
        CubeCoordinate nextCubePos;
        Vector2 currentWorldPos;
        Vector2 nextWorldPos;

        cubePosList.Add(new CubeCoordinate(0, 0, 0));
        worldPosList.Add(Vector2.zero);

        cubePosQueue_BFS.Enqueue(cubePosList[0]);

        Instantiate(landBlockPrefab, worldPosList[0], Quaternion.identity, transform);

        while (cubePosQueue_BFS.Count > 0)
        {
            currentCubePos = cubePosQueue_BFS.Dequeue();
            currentWorldPos = worldPosList[cubePosList.IndexOf(currentCubePos)];

            for (int j = 0; j < 3; j++)
            {
                nextCubePos = currentCubePos.CubePositionAdd(hexDirectionOffset[j]);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextCubePos.q >= -mapSize &&
                    nextCubePos.q <= mapSize &&
                    nextCubePos.r >= -mapSize * 2 &&
                    nextCubePos.s <= mapSize * 2)
                {
                    nextWorldPos = currentWorldPos + hexPositionOffset[j];

                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    cubePosQueue_BFS.Enqueue(nextCubePos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }

由於地塊貼圖的左下、下、右下是有邊緣的樣式,因此要隨機,又要不讓非邊緣的地塊不露餡,須要按下面的順序去生成纔不會致使0顯示在3的上面,因此上面hexDirectionOffset沒有按照左下、下、右下、右上、上、左上而是按照左下、右下、下、右上、上、左上的緣由。

 

另外,邊界條件的判斷,參考下圖的示意,x軸管左右兩邊,y軸管右下、左上,z軸管左下、右上。 

 

爲方便生成固定尺寸矩形,須要參閱連接中Coordinate Systems的Offset coordinates部分,以及Coordinate conversion的Offset coordinates部分Odd-q,下文稱之爲偏移座標系。

 

如下是偏移座標系轉換立方體座標系的方法。

    private CubeCoordinate OffsetToCube_Oddq(int col, int row)
    {
        int x = col;
        int z = row - (col - (col & 1)) / 2;

        int y = -x - z;

        return new CubeCoordinate(x, y, z);
    }

 

如下是生成固定尺寸矩形的函數,須要注意的是爲了保證地塊的疊加順序,因此生成是按下圖所示,一行一行生成的,01一行生成完,接23一行,最後45一行。

 

    private void CreateRectangleMap()
    {
        for (int i = 0; i < rectangleHeight * 2; i++)
        {
            for (int j = i % 2; j < rectangleWidth; j += 2)
            {
                cubePosList.Add(OffsetToCube_Oddq(i, j));

                Instantiate(landBlockPrefab, new Vector2(j * widthDistance, -heightDistance * 0.5f * i), Quaternion.identity, transform);
            }
        }
    }

 

有了上面的兩個座標系的創建,後面兩個地圖的隨機就很好作了。

 

大豎六邊形的隨機,主要是在左下、右下其中的一個或兩個方向生成,邊界判斷同上文,。

    private void CreateRandomHexagonalMap()
    {
        Queue<CubeCoordinate> cubePosQueue_BFS = new Queue<CubeCoordinate>();

        CubeCoordinate currentCubePos;
        CubeCoordinate nextCubePos;

        Vector2 nextWorldPos;

        int times = 1;
        int curentDirection = -1;

        cubePosQueue_BFS.Enqueue(new CubeCoordinate(0, 0, 0));
        cubePosList.Add(new CubeCoordinate(0, 0, 0));
        worldPosList.Add(Vector2.zero);

        Instantiate(landBlockPrefab, Vector2.zero, Quaternion.identity, transform);

        while (cubePosQueue_BFS.Count > 0)
        {
            times = Random.Range(1, 3);

            currentCubePos = cubePosQueue_BFS.Dequeue();

            for (int i = 0; i < times; i++)
            {
                if (times == 1)
                {
                    curentDirection = Random.Range(0, 2);
                }
                else
                {
                    curentDirection = i;
                }

                nextCubePos = currentCubePos.CubePositionAdd(hexDirectionOffset[curentDirection]);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextCubePos.q >= -mapSize &&
                    nextCubePos.q <= mapSize &&
                    nextCubePos.r >= -mapSize * 2 &&
                    nextCubePos.s <= mapSize * 2)
                {
                    nextWorldPos = worldPosList[cubePosList.IndexOf(currentCubePos)] + hexPositionOffset[curentDirection];

                    cubePosQueue_BFS.Enqueue(nextCubePos);
                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }

 

 矩形內隨機地圖生成函數,以下。隨機也是在左下、右下其中的一個或兩個方向生成,有了偏移座標系,邊界判斷就變得簡單了。固然,爲了疊加順序,一樣按照上面的生成順序,一行by一行。

 

    private void CreateRandomRectangleMap()
    {
        Queue<OffsetCoordinate> offsetPosQueue_BFS = new Queue<OffsetCoordinate>();

        OffsetCoordinate currentOffsetPos;
        OffsetCoordinate nextOffsetPos;
        CubeCoordinate nextCubePos;

        Vector2 nextWorldPos;

        int[] direction = new int[2] { -1, 1 };

        int times = 1;
        int curentDirection = -1;

        for (int i = 0; i <= rectangleWidth; i += 2)
        {
            offsetPosQueue_BFS.Enqueue(new OffsetCoordinate(i, 0));
            cubePosList.Add(OffsetToCube_Oddq(i, 0));
            worldPosList.Add(new Vector2(i * widthDistance, 0));

            Instantiate(landBlockPrefab, new Vector2(i * widthDistance, 0), Quaternion.identity, transform);
        }

        while (offsetPosQueue_BFS.Count > 0)
        {
            times = Random.Range(1, 3);

            currentOffsetPos = offsetPosQueue_BFS.Dequeue();

            for (int i = 0; i < times; i++)
            {
                if (times == 1)
                {
                    curentDirection = direction[Random.Range(0, 2)];
                }
                else
                {
                    curentDirection = direction[i];
                }

                if (currentOffsetPos.col % 2 == 0)
                {
                    nextOffsetPos = currentOffsetPos.CubePositionAdd(new OffsetCoordinate(curentDirection, 0));
                }
                else
                {
                    nextOffsetPos = currentOffsetPos.CubePositionAdd(new OffsetCoordinate(curentDirection, 1));
                }
                
                nextCubePos = OffsetToCube_Oddq(nextOffsetPos.col, nextOffsetPos.row);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextOffsetPos.col >= 0 &&
                    nextOffsetPos.col <= rectangleWidth &&
                    nextOffsetPos.row >= 0 &&
                    nextOffsetPos.row <= rectangleHeight)
                {
                    if (nextOffsetPos.col % 2 == 0)
                    {
                        nextWorldPos = new Vector2(nextOffsetPos.col * widthDistance, -heightDistance * nextOffsetPos.row);
                    }
                    else
                    {
                        nextWorldPos = new Vector2(nextOffsetPos.col * widthDistance, -heightDistance * 0.5f - heightDistance * nextOffsetPos.row);
                    }

                    offsetPosQueue_BFS.Enqueue(nextOffsetPos);
                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }

 

下面貼出完整的代碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapBehaviour : MonoBehaviour
{
    public struct CubeCoordinate
    {
        public int q;
        public int r;
        public int s;

        public CubeCoordinate(int q, int r, int s)
        {
            this.q = q;
            this.r = r;
            this.s = s;
        }

        public CubeCoordinate CubePositionAdd(CubeCoordinate offset)
        {
            return new CubeCoordinate(q + offset.q, r + offset.r, s + offset.s);
        }
    }

    public struct OffsetCoordinate
    {
        public int col;
        public int row;

        public OffsetCoordinate(int col, int row)
        {
            this.col = col;
            this.row = row;
        }

        public OffsetCoordinate CubePositionAdd(OffsetCoordinate offset)
        {
            return new OffsetCoordinate(col + offset.col, row + offset.row);
        }
    }

    public GameObject landBlockPrefab;

    public int hexSize;
    public int mapSize;

    public int rectangleWidth;
    public int rectangleHeight;

    private static readonly List<CubeCoordinate> hexDirectionOffset = new List<CubeCoordinate> {
            new CubeCoordinate(-1, 0, 1), new CubeCoordinate(1, -1, 0), new CubeCoordinate(0, -1, 1),
            new CubeCoordinate(1, 0, -1), new CubeCoordinate(0, 1, -1), new CubeCoordinate(-1, 1, 0)
    };

    private List<Vector2> hexPositionOffset;

    private List<CubeCoordinate> cubePosList;
    private List<Vector2> worldPosList;

    private float widthDistance;
    private float heightDistance;

    // Use this for initialization
    void Start()
    {
        InitHexsDistance();
        InitHexPosOffset();

        cubePosList = new List<CubeCoordinate>();
        worldPosList = new List<Vector2>();

        CreateHexagonalMap();
        //CreateRectangleMap();
        //CreateRandomHexagonalMap();
        //CreateRandomRectangleMap();
    }

    private void InitHexsDistance()
    {
        widthDistance = hexSize * 0.75f * 0.01f;
        heightDistance = Mathf.Sqrt(3f) * hexSize * 0.5f * 0.01f;
    }

    private void InitHexPosOffset()
    {
        hexPositionOffset = new List<Vector2> {
            new Vector2(-widthDistance, -heightDistance*0.5f),new Vector2(widthDistance, -heightDistance*0.5f), new Vector2(0,-heightDistance),
            new Vector2(widthDistance, heightDistance*0.5f),new Vector2(0, heightDistance), new Vector2(-widthDistance,heightDistance*0.5f)
        };
    }

    public float GetTwoHexDistance(CubeCoordinate a, CubeCoordinate b)
    {
        return (Mathf.Abs(a.q - b.q) + Mathf.Abs(a.r - b.r) + Mathf.Abs(a.s - b.s)) / 2;
    }

    private void CreateHexagonalMap()
    {
        Queue<CubeCoordinate> cubePosQueue_BFS = new Queue<CubeCoordinate>();

        CubeCoordinate currentCubePos;
        CubeCoordinate nextCubePos;
        Vector2 currentWorldPos;
        Vector2 nextWorldPos;

        cubePosList.Add(new CubeCoordinate(0, 0, 0));
        worldPosList.Add(Vector2.zero);

        cubePosQueue_BFS.Enqueue(cubePosList[0]);

        Instantiate(landBlockPrefab, worldPosList[0], Quaternion.identity, transform);

        while (cubePosQueue_BFS.Count > 0)
        {
            currentCubePos = cubePosQueue_BFS.Dequeue();
            currentWorldPos = worldPosList[cubePosList.IndexOf(currentCubePos)];

            for (int j = 0; j < 3; j++)
            {
                nextCubePos = currentCubePos.CubePositionAdd(hexDirectionOffset[j]);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextCubePos.q >= -mapSize &&
                    nextCubePos.q <= mapSize &&
                    nextCubePos.r >= -mapSize * 2 &&
                    nextCubePos.s <= mapSize * 2)
                {
                    nextWorldPos = currentWorldPos + hexPositionOffset[j];

                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    cubePosQueue_BFS.Enqueue(nextCubePos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }

    private void CreateRectangleMap()
    {
        for (int i = 0; i < rectangleHeight * 2; i++)
        {
            for (int j = i % 2; j < rectangleWidth; j += 2)
            {
                cubePosList.Add(OffsetToCube_Oddq(i, j));

                Instantiate(landBlockPrefab, new Vector2(j * widthDistance, -heightDistance * 0.5f * i), Quaternion.identity, transform);
            }
        }
    }

    private CubeCoordinate OffsetToCube_Oddq(int col, int row)
    {
        int x = col;
        int z = row - (col - (col & 1)) / 2;

        int y = -x - z;

        return new CubeCoordinate(x, y, z);
    }

    private void CreateRandomHexagonalMap()
    {
        Queue<CubeCoordinate> cubePosQueue_BFS = new Queue<CubeCoordinate>();

        CubeCoordinate currentCubePos;
        CubeCoordinate nextCubePos;

        Vector2 nextWorldPos;

        int times = 1;
        int curentDirection = -1;

        cubePosQueue_BFS.Enqueue(new CubeCoordinate(0, 0, 0));
        cubePosList.Add(new CubeCoordinate(0, 0, 0));
        worldPosList.Add(Vector2.zero);

        Instantiate(landBlockPrefab, Vector2.zero, Quaternion.identity, transform);

        while (cubePosQueue_BFS.Count > 0)
        {
            times = Random.Range(1, 3);

            currentCubePos = cubePosQueue_BFS.Dequeue();

            for (int i = 0; i < times; i++)
            {
                if (times == 1)
                {
                    curentDirection = Random.Range(0, 2);
                }
                else
                {
                    curentDirection = i;
                }

                nextCubePos = currentCubePos.CubePositionAdd(hexDirectionOffset[curentDirection]);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextCubePos.q >= -mapSize &&
                    nextCubePos.q <= mapSize &&
                    nextCubePos.r >= -mapSize * 2 &&
                    nextCubePos.s <= mapSize * 2)
                {
                    nextWorldPos = worldPosList[cubePosList.IndexOf(currentCubePos)] + hexPositionOffset[curentDirection];

                    cubePosQueue_BFS.Enqueue(nextCubePos);
                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }

    private void CreateRandomRectangleMap()
    {
        Queue<OffsetCoordinate> offsetPosQueue_BFS = new Queue<OffsetCoordinate>();

        OffsetCoordinate currentOffsetPos;
        OffsetCoordinate nextOffsetPos;
        CubeCoordinate nextCubePos;

        Vector2 nextWorldPos;

        int[] direction = new int[2] { -1, 1 };

        int times = 1;
        int curentDirection = -1;

        for (int i = 0; i <= rectangleWidth; i += 2)
        {
            offsetPosQueue_BFS.Enqueue(new OffsetCoordinate(i, 0));
            cubePosList.Add(OffsetToCube_Oddq(i, 0));
            worldPosList.Add(new Vector2(i * widthDistance, 0));

            Instantiate(landBlockPrefab, new Vector2(i * widthDistance, 0), Quaternion.identity, transform);
        }

        while (offsetPosQueue_BFS.Count > 0)
        {
            times = Random.Range(1, 3);

            currentOffsetPos = offsetPosQueue_BFS.Dequeue();

            for (int i = 0; i < times; i++)
            {
                if (times == 1)
                {
                    curentDirection = direction[Random.Range(0, 2)];
                }
                else
                {
                    curentDirection = direction[i];
                }

                if (currentOffsetPos.col % 2 == 0)
                {
                    nextOffsetPos = currentOffsetPos.CubePositionAdd(new OffsetCoordinate(curentDirection, 0));
                }
                else
                {
                    nextOffsetPos = currentOffsetPos.CubePositionAdd(new OffsetCoordinate(curentDirection, 1));
                }

                nextCubePos = OffsetToCube_Oddq(nextOffsetPos.col, nextOffsetPos.row);

                if (!cubePosList.Contains(nextCubePos) &&
                    nextOffsetPos.col >= 0 &&
                    nextOffsetPos.col <= rectangleWidth &&
                    nextOffsetPos.row >= 0 &&
                    nextOffsetPos.row <= rectangleHeight)
                {
                    if (nextOffsetPos.col % 2 == 0)
                    {
                        nextWorldPos = new Vector2(nextOffsetPos.col * widthDistance, -heightDistance * nextOffsetPos.row);
                    }
                    else
                    {
                        nextWorldPos = new Vector2(nextOffsetPos.col * widthDistance, -heightDistance * 0.5f - heightDistance * nextOffsetPos.row);
                    }

                    offsetPosQueue_BFS.Enqueue(nextOffsetPos);
                    cubePosList.Add(nextCubePos);
                    worldPosList.Add(nextWorldPos);

                    Instantiate(landBlockPrefab, nextWorldPos, Quaternion.identity, transform);
                }
            }
        }
    }
}

獲取兩個六邊形地塊之間的距離,在GetTwoHexDistance函數中,由於立方體座標系的存在而變得很簡單了。:)

 

後面有時間,會出些講基於六角網格地圖的攻擊範圍、視線、尋路等等,還有unity的tilemap去作六角網格地圖的。

 

若是有更好的改進建議,歡迎交流。

 

轉載註明出處:)

相關文章
相關標籤/搜索