从零开始做一个SLG游戏(一):六边形网格

作者:观复 2019-10-08

本文的主要工作是六边形网格的绘制。


如图所示。六边形有6个方向,6个顶点,同时定义中心点到边的最短距离为内径innerRadius,定义中心点到顶点的距离为外径outerRadius。

六边形可以拆分为6个等边三角形,所以很容易得出:


定义游戏中六边形的边长(即外径)为10.

用一个静态类以及一个枚举将这些定义下来:

  1. using UnityEngine;



  2. public static class HexMetrics

  3. {

  4.         /// <summary>

  5.         /// 外径,中心到顶点距离

  6.         /// </summary>

  7.         public const float outerRadius = 10f;

  8.         /// <summary>

  9.         /// 内径,中心到边距离,0.866025404为二分之根号三的近似值

  10.         /// </summary>

  11.         public const float innerRadius = outerRadius * 0.866025404f;

  12.         

  13.         /// <summary>

  14.         /// 六边形的六个顶点坐标

  15.         /// </summary>

  16.         public static readonly Vector3[] corners = {

  17.                 new Vector3(0f, 0f, outerRadius),

  18.                 new Vector3(innerRadius,0f,0.5f*outerRadius),

  19.                 new Vector3(innerRadius,0f,-0.5f*outerRadius),

  20.                 new Vector3(0f,0f,-outerRadius),

  21.                 new Vector3(-innerRadius,0f,-0.5f*outerRadius),

  22.                 new Vector3(-innerRadius,0f,0.5f*outerRadius)

  23.         };

  24. }



  25. /// <summary>

  26. /// 六边形的方向

  27. ///                NW /\ NE

  28. ///                W |  |E

  29. ///                SW \/ SE

  30. /// </summary>

  31. public enum HexDirection

  32. {

  33.         NE,

  34.         E,

  35.         SE,

  36.         SW,

  37.         W,

  38.         NW,

  39. }
复制代码

之后开始写关于图片绘制的代码:

将六边形分解为6个等边三角形,三角形的三个顶点分别为六边形中心点,以及六边形一条边的两个顶点:

即HexMetrics.corners和HexMetrics.corners[i+1]

因为第六条边的时候,i=5,i+1=6,corners[6]不存在,所以在定义里加一个coners[6],和coners[0]相等:

  1. public static readonly Vector3[] corners = {

  2.                 new Vector3(0f, 0f, outerRadius),

  3.                 new Vector3(innerRadius,0f,0.5f*outerRadius),

  4.                 new Vector3(innerRadius,0f,-0.5f*outerRadius),

  5.                 new Vector3(0f,0f,-outerRadius),

  6.                 new Vector3(-innerRadius,0f,-0.5f*outerRadius),

  7.                 new Vector3(-innerRadius,0f,0.5f*outerRadius),

  8.                 new Vector3(0f,0f,outerRadius),

  9.         };
复制代码

所以绘制三角形的时候,代码可以写成这样:

        
  1. /// <summary>

  2.         /// 绘制地形

  3.         /// </summary>

  4.         public void Draw()

  5.         {

  6.                 Clear();

  7.                 Vector3 center = Vector3.zero;

  8.                 for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)

  9.                 {

  10.                         AddTriangle(center,

  11.                                 HexMetrics.corners[(int)dir],

  12.                                 HexMetrics.corners[(int)dir + 1]);

  13.                 }



  14.                 UpdateMesh();

  15.         }
复制代码

然后贴上全部代码:

  1. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider))]

  2. public class HexCell : MonoBehaviour {

  3.         private Mesh mesh;

  4.         private List<Vector3> vertices;

  5.         private List<int> triangles;



  6.         private void Awake()

  7.         {

  8.                 GetComponent<MeshFilter>().mesh = mesh = GetComponent<MeshCollider>().sharedMesh = new Mesh();

  9.                 GetComponent<MeshCollider>().convex = true;

  10.                 mesh.name = "Hex Cell";

  11.                 vertices = new List<Vector3>();

  12.                 triangles = new List<int>();

  13.                 Draw();

  14.         }



  15.         // Use this for initialization

  16.         void Start () {

  17.                

  18.         }

  19.         

  20.         // Update is called once per frame

  21.         void Update () {

  22.                

  23.         }

  24.         /// <summary>

  25.         /// 绘制地形

  26.         /// </summary>

  27.         public void Draw()

  28.         {

  29.                 Clear();

  30.                 Vector3 center = Vector3.zero;

  31.                 for (HexDirection dir = HexDirection.NE; dir <= HexDirection.NW; dir++)

  32.                 {

  33.                         AddTriangle(center,

  34.                                 HexMetrics.corners[(int)dir],

  35.                                 HexMetrics.corners[(int)dir + 1]);

  36.                 }



  37.                 UpdateMesh();

  38.         }



  39.         /// <summary>

  40.         /// 清空mesh数据

  41.         /// </summary>

  42.         private void Clear()

  43.         {

  44.                 mesh.Clear();

  45.                 vertices.Clear();

  46.                 triangles.Clear();

  47.         }

  48.         /// <summary>

  49.         /// 绘制mesh数据

  50.         /// </summary>

  51.         private void UpdateMesh()

  52.         {

  53.                 mesh.vertices = vertices.ToArray();

  54.                 mesh.triangles = triangles.ToArray();

  55.                 mesh.RecalculateNormals();

  56.                 mesh.RecalculateBounds();

  57.         }

  58.         /// <summary>

  59.         /// 添加三角形。

  60.         /// </summary>

  61.         /// <param name="v1"></param>

  62.         /// <param name="v2"></param>

  63.         /// <param name="v3"></param>

  64.         private void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3)

  65.         {

  66.                 int count = triangles.Count;

  67.                 vertices.Add(v1);

  68.                 triangles.Add(count++);

  69.                 vertices.Add(v2);

  70.                 triangles.Add(count++);

  71.                 vertices.Add(v3);

  72.                 triangles.Add(count++);

  73.         }

  74. }
复制代码

在unity中新建一个空的物体,挂上这个脚本,然后随便扔一个材质上去,运行就会出现如下图片:


接下来就是构建地图网格:


如图,仔细观察我们就可以我们可以发现:

1.同一行的相邻六边形的间距为2个内径的距离

2.两列六边形,在纵坐标上相差1.5个外径的距离

3.列数为奇数的六边形在横坐标上,会向右偏移1个内径的距离。

所以我们可以通过如下方式将队列上的坐标(x,y)转换为空间上的坐标:

  1. private Vector3 GetPos(int x, int y)

  2.         {

  3.                 float posX = x;

  4.                 float posY = y;

  5.                 if ((y & 1) != 0)

  6.                 {

  7.                         posX += 0.5f;

  8.                 }

  9.                 Vector3 pos = new Vector3(2f * posX * HexMetrics.innerRadius, 0f, 1.5f * posY * HexMetrics.outerRadius);

  10.                 return pos;

  11.         }
复制代码

        
为了便于区分,再将网格的颜色变一下:

  1. cell.GetComponent<MeshRenderer>().material.color = new Color(posX / width, (posX + posY) / (height + width), posY / height);
复制代码

一个六边形的网格地图的雏形就做好了,运行一下,我们就可以得到下面图片:


参考文章:

Hex Map 1

当初也是因为这篇文章,才萌生了做六边形SLG游戏的念头的

相关阅读:

从零开始做一个SLG游戏(一):六边形网格
从零开始做一个SLG游戏(二):用mesh实现简单的地形

作者:观复
专栏地址:https://zhuanlan.zhihu.com/p/44306412


最新评论
暂无评论
参与评论