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

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

    转载注明出处:)

  • 相关阅读:
    uni-app快速上手
    uni-app快速上手
    什么是uni-app?
    什么是uni-app?
    美颜小程序准备
    美颜小程序准备
    vue的基本使用
    vue的基本使用
    Web前端开发(高级)下册-目录
    Web前端开发(高级)下册-目录
  • 原文地址:https://www.cnblogs.com/JinT-Hwang/p/10008663.html
Copyright © 2020-2023  润新知