六边形网格地图2 | 混合单元格颜色

作者:arthliant
2017-10-09
10 11 3

前注

本文是 Jasper Flick 的 Unity 教程中的六边形网格地图系列教程的第二篇。
原文地址:http://catlikecoding.com/unity/tutorials/hex-map/part-2/
译者获得作者授权翻译转载于 indienova,后续教程将会陆续翻译。
本人也是初学者,如有错译,望海涵并及时纠正。

前言

连接相邻单元格

在三角形中进行颜色插值

创建混色区域

简化多边形网格

本教程是六边形网格地图系列教程的第二部分。上一篇教程我们制作了最基本的网格结构与一个简易的单元格编辑器。现在每个单元格能够拥有自己独特的颜色了,但是单元格与单元格之间的颜色转换十分生硬。这次我们来介绍如何制作在相邻单元格之间混合颜色的过度区域。
615-1506250176

具有过渡效果的单元格

相邻单元格:Cell Neighbor

在混合单元格之间的颜色之前,我们需要知道哪两个单元格是相连的。一个六边形单元格有六个相邻单元格,我们可以用六个罗盘方向来表示它们。这六个方向分别是 northeast, east, southeast, southwest, west, and northwest。我们创建一个枚举类型 enumeration 来表示它们。

public enum  HexDirection {
       NE, E, SE, SW, W, NW
}

枚举 enum 是什么?

你可以使用 enum 关键字来定义一个枚举类。枚举类拥有一个有序的名字列表,一个枚举类可以将列表中的一个名字作为它的值。在默认情况下,这些名字代表了由零开始的整数列。当你需要一个具有名字的有限个数的的选项列表时枚举类将会帮助到你。事实上,枚举类就是一个的整数。你可以对其做加、减与整型相互转换等运算。你也可以将其声明为其他类型,但正常来说应该是整形。

615-1506250224

Figure 1‑1:六个方向上的六个邻居

为了储存这六个邻居,我们为 HexCell 类添加一个数组。我们可以将其设置为公有的,也可以将其设置为私有的然后使用方法来访问他。同时要确保它能够被序列化以便相邻关系能够被重新编译。

[SerializeField]
HexCell[] neighbors;

我们需要储存相邻关系吗?

我们也可以通过坐标来确定相邻关系,并在网格中检索出所需的单元格。但是将它储存起来比较简单并直截了当。

这个邻居数组现在被显示在 inspector 上了。因为每个单元格有6个邻居,所以我们将其长度设置为6。
615-1506250268

Figure 1‑2:六个邻居的预制体空位

现在添加一个公有方法来检索某个方向上的邻居。因为方向总是从0到5的所以我们不需要检查是否越界。

public HexCell  GetNeighbor (HexDirection direction) {
        return neighbors[(int)direction];
}

同时添加一个方法来设置相邻单元格。

public void  SetNeighbor (HexDirection direction, HexCell cell) {
        neighbors[(int)direction] = cell;
}

相邻关系是双向的,所以在一个方向上设置之后我们需要对相反的方向进行设置。

public void SetNeighbor (HexDirection  direction, HexCell cell) {
        neighbors[(int)direction] = cell;
cell.neighbors[(int)direction.Opposite()]  = this;
}

615-1506250306

Figure 1‑3:双向的相邻关系

当然我们需要能够求出一个方向的相反方向才能实现它。我们可以通过为 HexDirection 创建一个扩展方 extension method 来实现这一切。在原有方向基础上加3以得到相反方向,这样只对头三个方向管用,后三个方向需要减3。

public static class HexDirectionExtensions  {
       public  static HexDirection Opposite (this HexDirection direction) {
              return  (int)direction < 3 ? (direction + 3) : (direction - 3);
       }
}

什么是拓展方法?

拓展方法就是一个静态类型中的静态方法,但是可以像调用某个类型的实例方法一样来调用它。这个类型可以是任何东西,比如说,类、接口、结构体、值、乃至于枚举类型。拓展方法的第一个参数需要this关键字,它定义了拓展方法对哪些实例类型起作用。

我们能够为任何东西添加拓展方法么?是的,就像是你能够将任何值作为静态方法的参数一样。使用拓展方法是一个好主意么?在适度使用的情况下,还不错。这是一个有着特定用途的工具,但是无节制的使用的话将会造成混乱。

连接相邻单元格:Connecting Neighbors

我们可以使用 HexGrid.CreateCell 方法初始化相邻关系。随着我们一行行一列列的创建单元格,我们知道哪些单元格已经被创建了。我们可以将已创建的单元格相连。

最简单的一种方式就是东西相连 E–W connection。每一行上的第一个单元格没有西向的邻居,其余的有。所有的这些邻居就是上一个被创建的多边形。因此,我们将其连接起来。
615-1506250358

Figure 1‑4:从东到西连接已创建的多边形
void CreateCell (int x, int z, int i) {
              …
              cell.color  = defaultColor;
              if  (x > 0) {
                     cell.SetNeighbor(HexDirection.W,  cells[i - 1]);
              }
              Text  label = Instantiate(cellLabelPrefab);
              …
       }

615-1506250395

Figure 1‑5:东西向的邻居已经被连接起来

剩余两组的双向连接都要跨越不同的行,而我们只能与上一行连接,这意味着我们必须将第一行舍弃。

if (x > 0) {
        cell.SetNeighbor(HexDirection.W,  cells[i - 1]);
}
if (z > 0) {
}

因为行与行之间是交错开的,所以奇偶行需要用不同的方式处理。首先来处理偶数行(译注:行号从零开始,零行舍弃),偶数行的所有单元格都有个 SE 方向邻居,将其连接。
615-1506250420

Figure 1‑6:在偶数行从 NW 向 SE 方向连接
if  (z > 0) {
        if  ((z & 1) == 0) {
                cell.SetNeighbor(HexDirection.SE,  cells[i - width]);
        }
}

为什么需要做 z&1 运算?

&&是“逻辑与“运算符,而&是“按位与“运算符。逻辑上是一样的操作,“按位与”对其操作数的每一个独立的位进行运算。两个数位需要都是1结果才是1。例如,10101010 & 00001111 = 00001010。

在计算机内部,数值是使用只有0和1的二进制数来表示的。数列1,2,3,4用二进制表示就是1,10,11,100。正如你所见,偶数的末尾总是0.

我们将某数与1做“按位与”运算可以留下第一位(译注:从右侧开始数)而舍去其他所有位。如果结果是0的话就说明原数是偶数。

我们也可以连接 SW 方向上的邻居,除了每行的第一个单元格。
615-1506251575

Figure 1‑7:在偶数行从 NE 向 SE 方向连接
if (z > 0) {
        if ((z  & 1) == 0) {
                cell.SetNeighbor(HexDirection.SE,  cells[i - width]);
                if  (x > 0) {
                        cell.SetNeighbor(HexDirection.SW,  cells[i - width - 1]);
                }
        }
}

奇数行遵循着同样的逻辑,但是相反。一旦完成,网格中所有的单元格的邻居都连接好了。

if (z > 0) {
        if ((z & 1) == 0) {
                cell.SetNeighbor(HexDirection.SE,  cells[i - width]);
                if (x > 0) {
                        cell.SetNeighbor(HexDirection.SW,  cells[i - width - 1]);
                }
        }
        else {
                cell.SetNeighbor(HexDirection.SW,  cells[i - width]);
                if (x < width -  1) {
                        cell.SetNeighbor(HexDirection.SE,  cells[i - width + 1]);
                }
        }
}

615-1506250598

Figure 1‑8:所有的相邻单元格都被连接

当然不是所有的单元格都恰好有6个邻居。网格边界上的单元格有2个到5个不等的邻居,我们应当注意这一点。
615-1506250628

Figure 1‑9:每个单元格的邻居

unitypackage

颜色混合:Blending Colors

颜色混合将会让每个单元格的三角剖分 triangulation 更加复杂,所以我们先将三角剖分这部分的代码独立出来。因为我们现在已经有方向概念了,所以我们用它来重写这部分的代码,以取代顶点索引。

void Triangulate  (HexCell cell) {
        for (HexDirection d =  HexDirection.NE; d <= HexDirection.NW; d++) {
Triangulate(d, cell);
        }
}

void Triangulate  (HexDirection direction, HexCell cell) {
       Vector3 center =  cell.transform.localPosition;
       AddTriangle(
              center,
              center + HexMetrics.corners[(int)direction],
              center +  HexMetrics.corners[(int)direction + 1]
       );
       AddTriangleColor(cell.color);
}

既然我们已经使用方向了,那就应该将顶角与方向做一个对应,而不是将方向转换为索引。

AddTriangle(
        center,
        center +  HexMetrics.GetFirstCorner(direction),
        center +  HexMetrics.GetSecondCorner(direction)
);

这就需要为 HexMetrics 类添加2个静态方法。而且这也可以使 corners 数组变为私有的。

static Vector3[]  corners = {
        new Vector3(0f, 0f, outerRadius),
        new Vector3(innerRadius, 0f, 0.5f  * outerRadius),
        new Vector3(innerRadius, 0f, -0.5f  * outerRadius),
        new Vector3(0f, 0f, -outerRadius),
        new Vector3(-innerRadius, 0f,  -0.5f * outerRadius),
        new Vector3(-innerRadius, 0f, 0.5f  * outerRadius),
        new Vector3(0f, 0f, outerRadius)
};

public static Vector3 GetFirstCorner  (HexDirection direction) {
        return corners[(int)direction];
}
public static Vector3 GetSecondCorner  (HexDirection direction) {
        return corners[(int)direction +  1];
}

逐三角形混色:Multiple Colors Per Triangle

到目前为止 HexMesh.AddTriangleColor 方法只有一个颜色参数。这样只能为三角形添加一个颜色。现在我们要使三角形的每一个顶角都拥有一个颜色。

void  AddTriangleColor (Color c1, Color c2, Color c3) {
        colors.Add(c1);
        colors.Add(c2);
        colors.Add(c3);
}

现在我们可以开始混合颜色了!从给边界上的两个顶点使用相邻三角形的颜色开始。

void Triangulate  (HexDirection direction, HexCell cell) {
        Vector3 center =  cell.transform.localPosition;
        AddTriangle(
                center,
                center + HexMetrics.GetFirstCorner(direction),
                center +  HexMetrics.GetSecondCorner(direction)
        );
HexCell neighbor =  cell.GetNeighbor(direction);
        AddTriangleColor(cell.color,  neighbor.color, neighbor.color);
}

不幸的是,这将引发一个 NullReferenceException 异常,因为网格边界上的单元格并没有足够的6个邻居。当单元格缺少邻居时应该如何做呢?一个使用的方法是使用三角形自身的颜色作为代替。

HexCell neighbor =  cell.GetNeighbor(direction) ?? cell;

"??"操作符是用来干啥的?

这是一个空合并运算符 null-coalescing operator。简言之,a ?? b 是 a != null ? a : b 的缩写。这里依旧有些问题,因为 Unity 在将对象拿来与 components 进行比较时需要做一些额外的工作,而这个操作通过与null 进行比较越过了这些操作。目前不用担心,只有你摧毁某一对象时这才是一个问题。

615-1506250757

Figure 2‑1:错误的颜色混合

单元格坐标标签哪里去了?

他们还在那,但是我在截屏中隐藏了 UI 层。

色彩均值:Color Averaging

颜色混合工作了,但是很明显,现在的结果并不是我们想要的效果。六边形网格边界上的颜色的值应该是两个相邻六边形颜色的混合值。

HexCell neighbor = cell.GetNeighbor(direction) ?? cell;
Color edgeColor =  (cell.color + neighbor.color) * 0.5f;
AddTriangleColor(cell.color,  edgeColor, edgeColor);

615-1506250791

Figure 2‑2:对边线混色

现在我们已经在边界混色了,但是得到的颜色边界依然是十分锋利的。这是因为六边形的每一个顶点是有三个六边形共享的。
615-1506250740

Figure 2‑3:三个邻居,四种颜色

这就意味着我们必须考虑前后两个方向的颜色。所以我们将使用四种颜色,每个方向上三种。

我们为 HexDirectionExtensions 类添加两个额外的方法来实现得到前后两个方向。

public static  HexDirection Previous (this HexDirection direction) {
        return  direction == HexDirection.NE ? HexDirection.NW : (direction - 1);
}
public  static HexDirection Next (this HexDirection direction) {
        return  direction == HexDirection.NW ? HexDirection.NE : (direction + 1);
}

现在我们可以检索到与一条边有关的全部三个邻居,并且进行两个三色混色。

HexCell prevNeighbor = cell.GetNeighbor(direction.Previous()) ??  cell;
HexCell  neighbor = cell.GetNeighbor(direction) ?? cell;
HexCell  nextNeighbor = cell.GetNeighbor(direction.Next()) ?? cell; 

AddTriangleColor(
        cell.color,
        (cell.color  + prevNeighbor.color + neighbor.color) / 3f,
        (cell.color  + neighbor.color + nextNeighbor.color) / 3f
);

615-1506250841

Figure 2‑4:对顶点混色

这样就产生了正确的混色效果,除了在网格边界的地方。因为在网格边界,相邻的网格对共同缺失的相邻网格没有进行一致的处理,所以你仍然可以在边界处看到锋利的边缘线。总体来说,我们现在的手段并没有取得一个令人足够满意的效果。我们需要一个更好的方法。

unitypackage

混色区域:Blend Regions

在六边形的整个表面进行混色导致了一些混乱,混色之后你再也无法分辨那些是独立的单元格了。你可以通过只在六边形临近边界的区域混色来解决这一问题。在单元格的内部预留出一个六边形使用其固有色渲染。
615-1506250873

Figure 3‑1:带有混色区域的固有色核心

相对于混色区域,固有色核心应该有多大呢?不同的数值会导致不同的效果。我们可以用相对于外接圆半径的百分数来定义核心的大小。先将其设定为75%。这产生了两个新的参数,相加等于100%。

public const float solidFactor = 0.75f; 
public const float blendFactor = 1f -  solidFactor;

我们可以使用这两个参数创建索引核心六边形顶点的方法。

public static Vector3 GetFirstSolidCorner  (HexDirection direction) {
       return  corners[(int)direction] * solidFactor;
}
public static Vector3 GetSecondSolidCorner  (HexDirection direction) {
       return  corners[(int)direction + 1] * solidFactor;
}

现在更改 Hexh.Triangulate 方法,使用核心的顶点而不是原来单元格的顶点。暂时使用它们现在的颜色。

AddTriangle(
       center,
       center  + HexMetrics.GetFirstSolidCorner(direction),
       center  + HexMetrics.GetSecondSolidCorner(direction)
);

615-1506250906

Figure 3‑2:不含边界区域的六边形核心

混色区域的三角剖分:Triangulating Blend Regions

我们需要填充缩小三角形留下的空白区域。六边形的每个方向上的空白区域是一个梯形。我们可以使用一个平面来覆盖它。创建一个方法来添加平面与其颜色。
615-1506250930

Figure 3‑3:梯形的边界
void AddQuad (Vector3 v1, Vector3 v2,  Vector3 v3, Vector3 v4) {
        int  vertexIndex = vertices.Count;
        vertices.Add(v1);
        vertices.Add(v2);
        vertices.Add(v3);
        vertices.Add(v4);
        triangles.Add(vertexIndex);
        triangles.Add(vertexIndex  + 2);
        triangles.Add(vertexIndex  + 1);
        triangles.Add(vertexIndex  + 1);
        triangles.Add(vertexIndex  + 2);
        triangles.Add(vertexIndex  + 3);
}

void  AddQuadColor (Color c1, Color c2, Color c3, Color c4) {
        colors.Add(c1);
        colors.Add(c2);
        colors.Add(c3);
        colors.Add(c4);
}

修改 HexMesh.Triangulate 方法以便让固有色核心的三角形使用单一的固有色,同时混色区域使用固有色与顶角的颜色进行混色。

void Triangulate (HexDirection direction,  HexCell cell) {
        Vector3  center = cell.transform.localPosition;
Vector3  v1 = center + HexMetrics.GetFirstSolidCorner(direction);
Vector3  v2 = center + HexMetrics.GetSecondSolidCorner(direction);

        AddTriangle(center,  v1, v2);
AddTriangleColor(cell.color);

        Vector3  v3 = center + HexMetrics.GetFirstCorner(direction);
        Vector3  v4 = center + HexMetrics.GetSecondCorner(direction);

AddQuad(v1,  v2, v3, v4);

        HexCell  prevNeighbor = cell.GetNeighbor(direction.Previous()) ?? cell;
        HexCell  neighbor = cell.GetNeighbor(direction) ?? cell;
        HexCell  nextNeighbor = cell.GetNeighbor(direction.Next()) ?? cell;

AddQuadColor(
                cell.color,
cell.color,
                (cell.color  + prevNeighbor.color + neighbor.color) / 3f,
                (cell.color  + neighbor.color + nextNeighbor.color) / 3f
        );
}

615-1506250955

Figure 3‑4:使用梯形边界进行混色

边界桥梁:Edge Bridges

效果看起来好多了,但是还没有达到我们的目标。两个相邻单元格之间的混色被附近其他的邻居所干扰。为了消除干扰,我们需要砍掉梯形的顶角让它变成一个矩形。这将在两个相邻单元格之间形成一座桥梁而在夹角出留出空隙。
615-1506250981

Figure 3‑5:边界桥梁

我们可以通过 v1、v2 的坐标求出 v3、v4 的新坐标,将其沿着垂直于边界的方向向外偏移就好了。那么偏移量是多少呢?我们可以求出三角形的中线,然后乘以我们之前求出的 blendFactor。这是 HexMetrics 类的工作。

public static Vector3 GetBridge  (HexDirection direction) {
        return  (corners[(int)direction] + corners[(int)direction + 1]) *
                0.5f  * blendFactor;
}

回到 HexMesh 类,修改 AddQuadColor 方法为只接受两个参数。

void  AddQuadColor (Color c1, Color c2) {
        colors.Add(c1);
        colors.Add(c1);
        colors.Add(c2);
        colors.Add(c2);
}

调整 Triangulate 方法,使其能对边界桥梁进行正确混色。

Vector3 bridge =  HexMetrics.GetBridge(direction);
Vector3 v3 = v1 + bridge;
Vector3 v4 = v2 + bridge;

AddQuad(v1, v2, v3, v4);

HexCell prevNeighbor =  cell.GetNeighbor(direction.Previous()) ?? cell;
HexCell neighbor =  cell.GetNeighbor(direction) ?? cell;
HexCell nextNeighbor =  cell.GetNeighbor(direction.Next()) ?? cell;

AddQuadColor(cell.color,  (cell.color + neighbor.color) * 0.5f);

615-1506251005

Figure 3‑6:被正确着色的带有顶点空隙的边界桥梁

填充空隙:Filling the Gaps

我们现在在三个三角形交汇的顶点留下了一个三角形空隙。是我们将六边形边界截取为矩形桥梁时得到的。我们现在要将三角形补回来。

先考虑连接着当前方向前一个方向邻居的那个小三角形。其第一个顶点(译注:v1,看上面的图3-5)是单元格的固有色,第二个顶点(左侧无名点,译注)是三个单元格的混合色,最后一个顶点(译注:v3)和边界桥梁的中点是相同的颜色。

Color  bridgeColor = (cell.color + neighbor.color) * 0.5f;
AddQuadColor(cell.color,  bridgeColor);

AddTriangle(v1, center +  HexMetrics.GetFirstCorner(direction), v3);
AddTriangleColor(
        cell.color,
        (cell.color +  prevNeighbor.color + neighbor.color) / 3f,
        bridgeColor
);

615-1506251030

Figure 3‑7:差不多了

最后,用同样的方式为最后一个小三角形着色。注意,第2、3顶点顺序相反。

AddTriangle(v2, v4, center + HexMetrics.GetSecondCorner(direction));
AddTriangleColor(
        cell.color,
        bridgeColor,
        (cell.color  + neighbor.color + nextNeighbor.color) / 3f
);

615-1506251054

Figure 3‑8:完成填充

现在我们已经拥有了一个可以任意设置大小的混色区域。模糊的还是清晰的六边形边界现在任你选择。但你可能注意到了接近网格边的地方混色效果依旧存在问题。我们先不要管它,关心一下另外一个问题。

但是颜色转换依然很丑?

这是线性颜色混合的局限性。在只有单一的颜色的时候确实不是很好看。我们将会在未来的教程中加入地形材质并做出更绚丽的混色。

融合边界网格:Fusing Edges

看一看我们网格的解剖结构。有哪些不同的形状呢?如果我们忽略网格边界的话,我们会发现3种不同的形状。单一颜色的六边形核心,双色混色的矩形桥梁,三色混色的三角形连接处。你可以在所有的三个单元格的交汇处找到这三种形状。
615-1506251081

Figure 4‑1:三种可见结构

每两个相邻六边形都由一个矩形桥梁连接。每三个六边形都由一个三角形连接。但是我们现在却用了一个更复杂的方式去将其三角形网格化。我们现在在两个六边形交接处使用了两个平面而不是一个,每三个六边形交界处使用了六个三角形而不是一个,十分消耗资源。另外,如果我们使用更简单的网格连接的话就不用做数量如此多的颜色差值来混色了。所以现在我们要降低网格的复杂度,使用更少的资源,更少的三角形。
615-1506251099

Figure 4‑2:过于复杂的网格

为什么我们不一开始就这样做呢?

你将会在你的生命中多次遇到这样的问题。这就是后见之明。这是一个代码以合理的方式进行演变的例子,新的见解将给我们以新的方法。思考刚刚做过的事之后总会给你新的智慧。

使用一个桥直接相连:Direct Bridges

我们的边界桥现在包含两个平面。我们需要加倍边界的长度好让他们穿越到下一个六边形。这意味着我们不再需要在 HexMetrics.GetBridge 方法中乘0.5了,只需要将其相加并乘上混合系数。

public static Vector3 GetBridge  (HexDirection direction) {
        return  (corners[(int)direction] + corners[(int)direction + 1]) *
blendFactor;
}

615-1506251120

Figure 4‑3:横穿两个六边形并且相互覆盖的边界桥

现在桥直接将两个相邻六边形相连了。但是每个连接依旧有两个平面,只不过是相互重合而已。所以,接下来我们让相邻的连个六边形只有一个生成桥。

我们开始简化我们的三角形剖分代码。移出所有处理边界三角形与混合颜色的部分。将创建边界桥的代码移到一个新的方法中。将前面的两个顶点作为参数传入这个方法,这样我们就不必重复推导了。

void Triangulate (HexDirection direction,  HexCell cell) {
        Vector3  center = cell.transform.localPosition;
        Vector3  v1 = center + HexMetrics.GetFirstSolidCorner(direction);
        Vector3  v2 = center + HexMetrics.GetSecondSolidCorner(direction);

        AddTriangle(center,  v1, v2);
        AddTriangleColor(cell.color);

TriangulateConnection(direction,  cell, v1, v2);
} 

void  TriangulateConnection (
        HexDirection  direction, HexCell cell, Vector3 v1, Vector3 v2
        )  {
                HexCell  neighbor = cell.GetNeighbor(direction) ?? cell;             

                Vector3  bridge = HexMetrics.GetBridge(direction);
                Vector3  v3 = v1 + bridge;
                Vector3  v4 = v2 + bridge; 

                AddQuad(v1,  v2, v3, v4);
                AddQuadColor(cell.color,  neighbor.color);
}

现在我们可以方便地限制连接处的三角剖分了。从 NE 方向上的连接只添加一个桥开始。

if  (direction == HexDirection.NE) {
        TriangulateConnection(direction,  cell, v1, v2);
}

615-1506251158

Figure 4‑4:NE 方向上的单桥

似乎我么只需要为头三个方向创建桥就可以覆盖所有的连接。所有接下来是 NE、E 和 SE。

if (direction <= HexDirection.SE) {
        TriangulateConnection(direction,  cell, v1, v2);
}

615-1506251192

Figure 4‑5:网格内部与边界上的桥

所有的连个相邻单元格的连接都被覆盖了。但是我们在网格边界的外部也得到了一些。我们通过修改 TriangulateConnection 方法中没有邻居的情况来去掉他们。我们不再用单元格自己代替它并不存在的邻居。

void  TriangulateConnection (
        HexDirection direction, HexCell  cell, Vector3 v1, Vector3 v2
        ) {
                HexCell neighbor =  cell.GetNeighbor(direction);
                if (neighbor == null) {
                        return;
                }
         …
}

615-1506251215

Figure 4‑6:只有网格内部的桥

三角形连接部分:Triangular Connections

我们需要重新填补上三角形的洞。首先我们创建链接当前方向下一个方向邻居的三角形。再一次地,我们只在六边形的确存在邻居时才创建它。

void TriangulateConnection (
        HexDirection direction, HexCell  cell, Vector3 v1, Vector3 v2
        ) {
         …
        HexCell nextNeighbor =  cell.GetNeighbor(direction.Next());
        if (nextNeighbor != null) {
                AddTriangle(v2, v4, v2);
                AddTriangleColor(cell.color,  neighbor.color, nextNeighbor.color);
        }
}

第三个顶点的坐标是多少呢?我先使用 v2作为占位符,但这显然是不正确的。因为三角形的每一条边都与一个桥相连,我们可以在沿着桥方向的邻居上找到它。

AddTriangle(v2,  v4, v2 + HexMetrics.GetBridge(direction.Next()));

615-1506251238

Figure 4‑7:重新划分三角形的网格

我们已经完成了么?还没有,我们现在产生了重复的三角形。因为每三个六边形共享一个三角形连接,我们只需要为每个六边形添加两个连接。只有 NE 和 E 方向上的。

if (direction <= HexDirection.E  && nextNeighbor != null) {
        AddTriangle(v2,  v4, v2 + HexMetrics.GetBridge(direction.Next()));
        AddTriangleColor(cell.color,  neighbor.color, nextNeighbor.color);
}

下一片教程是阶梯状地形实现

unitypackage
原版 PDF

近期点赞的会员

 分享这篇文章

arthliant 

SE本科在读 独立开发者  

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. xggame 2017-10-09

    很不错的教程,实用性很强

  2. Simidal 2017-10-10

    。-。呃 这些教程很好用,可以扩展成地形编辑器的样子

  3. 末影Ender Horror 2017-10-10

    这让我想到了一个叫末日拾荒者的游戏,也是用这种六边形构成的地图

您需要登录或者注册后才能发表评论

登录/注册