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去作六角網格地圖的。
若是有更好的改進建議,歡迎交流。
轉載註明出處:)