Unity 生成六角网格地图:矩形地图以及矩形地图内随机

 

 

 

unity生成六角网格地图:矩形地图以及矩形地图内随机

本文某些概念是参考国外大神的文章去做的,读者可能需要理解其中某些概念才能了解本文的一些做法

参考链接:https://www.redblobgames.com/grids/hexagons/

 

用到的地块贴图如下:

 

先放上六角网格地图效果图:

 

 

 

 前两个分别是是固定尺寸竖六边形和固定尺寸矩形,后两个是在前面两个的形状下,在里面随机生成。

 

开始之前需要定义一个结构体,用于建立六边形地图的坐标系,方便以后做距离判断和攻击范围判断,详细坐标系的介绍请查看链接中Coordinate Systems的Cube coordinates部分,后面的描述称之为立方体坐标

 

    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是为了方便做坐标相加运算。

 

另外定义一个静态List用于存放坐标偏移量,分别为左下、右下、下、右上、上、左上。 

 

    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用于存放所有地块的立方体坐标和世界坐标。

 

 

    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去做六角网格地图的。

 

如果有更好的改进建议,欢迎交流。

 

转载注明出处:)

 

posted @ 2018-11-26 10:56  黄进钿  阅读(6630)  评论(3编辑  收藏  举报