在Unity3D中生成六边形镶嵌是一种常见的需求,尤其是在游戏开发和图形设计领域。本文将介绍如何从中心点开始,逐步生成多层六边形镶嵌,直至达到指定的环数。
六边形镶嵌的生成始于一个中心点,然后围绕这个中心点生成第一层六边形,这一层的六边形中心又将成为下一层六边形的中心点。这个过程会一直持续,直到达到预设的环数。在下面的图像中,原始中心点被标记为红色,第一层环被标记为黄色,第二层环被标记为绿色(第二层环的中心点由红色和黄色的六边形组成)。
生成六边形镶嵌需要以下输入参数:
算法可以输出每个生成的六边形的中心点;然而,在本实现中,将使用Unity3D来绘制六边形。
算法的核心思想是通过观察上一个生成的中心点来生成新的六边形中心。首先在二维欧几里得空间中固定一个点O作为中心,并设定基底{(1,0), (0,1)}为坐标轴(一个简单的笛卡尔平面,具有正交的x和y轴)。
将从点(0,0)开始,这将是第一个六边形的中心。设S为六边形的边长。可以按照“螺旋”的方式生成所有六边形的中心(见下图:最暗的六边形是第一个,最亮的是最后一个;中间,六边形的阴影代表了生成的顺序,颜色越深表示“先生成”,颜色越浅表示“后生成”)。
使用这种方法生成点Pn,只需要知道点Pn-1的坐标,然后执行Pn = Pn-1 + (a,b),其中(a,b)是一个可以很容易找到的数对,通过记住六边形的性质:
DR - (1.5, -sqrt(3)/2)
DX - (0, -sqrt(3))
DL - (-1.5, -sqrt(3)/2)
UL - (-1.5, sqrt(3)/2)
UX - (0, sqrt(3))
UR - (1.5, sqrt(3)/2)
其中:sq3 = sqrt(3),U/D表示方向UP/DOWN,L/X/R表示方向LEFT/NONE/RIGHT。
为了从中心(0, 0)生成“六边形螺旋”的中心,需要执行以下操作:
nDR - nDX - nDL - nUL - nUX - UX - Exit? - nUR
其中n是当前环数,n
以下是Unity3D中C#语言的实现代码:
using UnityEngine;
public class GenerateHexFloor : MonoBehaviour {
public GameObject Hexagon;
public uint Radius;
public float HexSideMultiplier = 1;
private const float sq3 = 1.7320508075688772935274463415059F;
void Start () {
Vector3 currentPoint = transform.position;
if (Hexagon.transform.localScale.x != Hexagon.transform.localScale.z) {
Debug.LogError("Hexagon has not uniform scale: cannot determine its side. Aborting");
return;
}
Vector3[] mv = {
new Vector3(1.5f, 0, -sq3*0.5f), // DR
new Vector3(0, 0, -sq3), // DX
new Vector3(-1.5f, 0, -sq3*0.5f), // DL
new Vector3(-1.5f, 0, sq3*0.5f), // UL
new Vector3(0, 0, sq3), // UX
new Vector3(1.5f, 0, sq3*0.5f) // UR
};
int lmv = mv.Length;
float HexSide = Hexagon.transform.localScale.x * HexSideMultiplier;
for (int mult = 0; mult <= Radius; mult++) {
int hn = 0;
for (int j = 0; j < lmv; j++) {
for (int i = 0; i < mult; i++, hn++) {
GameObject h = Instantiate(Hexagon, currentPoint, Hexagon.transform.rotation, transform);
h.name = string.Format("Hex Layer: {0}, n: {1}", mult, hn);
currentPoint += (mv[j] * HexSide);
}
if (j == 4) {
GameObject h = Instantiate(Hexagon, currentPoint, Hexagon.transform.rotation, transform);
h.name = string.Format("Hex Layer: {0}, n: {1}", mult, hn);
currentPoint += (mv[j] * HexSide);
hn++;
if (mult == Radius)
break;
}
}
}
}
}
有了这个想法,编写代码变得非常简单。以下是做出的一些实现选择的评论。
首先,对于不了解Unity3D的人来说,基本上每个从MonoBehaviour继承的类的公共字段都可以在编辑器中设置,并用作输入字段,这样每个类的实例都可以轻松地从编辑器中设置自己的参数。所以要求提供六边形的3D模型(可以在本文附件中找到,使用Blender 3D建模),镶嵌的半径,以及HexSideMultiplier字段用于乘以六边形边长的长度。通过查看模型的缩放比例来获取六边形边长(这就是为什么要求x和z缩放相同)。
然后从附加脚本的gameObject的原点开始:在Unity中,每个从MonoBehaviour继承的类都可以附加到任何GameObject上。在这种情况下,这个GameObject是3D空间中的一个简单点,提供x、y和z坐标。所以从这个点开始构建六边形镶嵌。
代码的其余部分,提供了之前解释的想法,是自解释的:只是从上一个点(称之为currentPoint)开始生成每个六边形的中心。