前言
这期在上期的基础上添加了更多视觉上的细节物,代码方面的难度不高。这里也可以替换上自己的模型使地图更好看一些。
本期原文地址:https://catlikecoding.com/unity/tutorials/hex-map/part-11/
这是HexMap系列的第11篇教程,内容是有关添加塔楼、桥梁和一些特殊的地形特征物到地图中。
一个细节丰富的大陆
1塔楼
在上篇教程中已经在地图上添加了城墙的编辑功能,它们只是由笔直的城墙段组成,没有其他容易辨识的特征了。现在要让城墙显得更为有趣一些,在上面添加塔楼。
城墙墙体的部分必须由代码生成以适应地形,但是塔楼并不需要这样,我们可以用一个简单的预制体来表示塔楼。创建一个由两个立方体组成的塔楼形状,使用城市的红色材质球。塔楼的基座的长宽为2乘2,高为4,所以它比城墙的墙体既高且厚。在这个底座上放一个单位大小的立方体表示塔顶。和其他预制体一样,这些立方体不需要碰撞盒。
由于塔楼由多个物体组成,所以放到一个根节点下面,然后基于塔楼的底部移动根节点的坐标,这样一来根节点的局部坐标就位于塔楼底部,不用再担心塔楼高度的问题了。
塔楼预制体
将对该预制体的引用添加到HexFeatureManager中并与物体关联起来。
添加塔楼预制体的引用
1.1构建塔楼
我们先在每一段城墙中都放一座塔楼,在AddWallSegment方法的末尾实例化一个塔楼,它位于城墙段左右顶点的平均值上。
每段城墙都有一个塔楼
可以看到沿着城墙生成了很多塔楼,但它们的方向没有变化。我们应该调整塔楼的旋转使之与城墙对齐。由于我们知道城墙的左右端点,所以能知道城墙的右边是哪个方向。用这格方向就能确认城墙和塔楼的朝向。
我们并不需要去计算塔楼该往哪旋转多少度,只需要为transform.right赋上正确的值,Unity的代码会负责物体的旋转,使其自身的right方向与给定的向量对齐。
塔楼的方向对着城墙朝向
设置Transform.right是如何工作的?
它使用Quaternion.FromToRotation方法推导出旋转,下面是这个属性的代码。
- 设置Transform.right是如何工作的?
- 它使用Quaternion.FromToRotation方法推导出旋转,下面是这个属性的代码。
- public Vector3 right {
- get {
- return rotation * Vector3.right;
- }
- set {
- rotation = Quaternion.FromToRotation(Vector3.right, value);
- }
- }
复制代码
1.2减少塔楼数量
每一段城墙上都有一座塔楼有点太多了,因此在AddWallSegment方法中添加一个布尔参数,使放置塔楼成为可选的。给其一个默认的false值,这样所有的塔楼都会消失。
让我们将塔楼限制在只在角落上的单元格连接处放置,塔楼就会呈现有规则的距离间隔。
塔楼只出现在角连接处上
这看起来就很不错,但是你可能想要不太规则,塔楼更少的布局样式。与其他的地形特征物一样,可以使用哈希表的概率来决定是否放置塔楼,所以使用角落上城墙段的中心坐标对网格进行采样,然后将其中一个哈希值与塔楼的出现阈值进行比较。
阈值是在HexMetrics里定义的。设置为0.5会让塔楼的出现次数减少一半,尽管还是有可能出现塔楼更多或者根本没有塔楼的情况。
随机出现的塔楼
1.3避免塔楼出现在斜坡上
现在放置塔楼时并没有考虑到地形因素,然而在斜坡上放置塔楼并没有多大意义,那里的城墙是斜着的,可能会从塔楼顶端穿出去。
斜坡上的塔楼
为避免出现斜坡情况,检测左右单元格的海拔高度是否一样。在一样的情况下我们才有概率去放置一座塔楼。
斜坡上不会再出现塔楼了
1.4将城墙与塔楼平置于地面上
虽然塔楼不会出现在倾斜的墙壁上,但城墙两侧的单元格还是可以有不同的海拔高度,甚至相同高度的单元格由于顶点扰动的原因实际垂直高度也会不一样,城墙是可以沿着倾斜部分放置,但塔楼会在这种情况下显得好像是漂浮起来。
漂浮的塔楼
实时上城墙也会在倾斜情况下显得漂浮,只不过不像塔楼那样引人注目。
漂浮的城墙
解决这个问题最简单的方法是将城墙的基准点挪到地面下,向HexMetrics添加城墙的Y轴偏移,约一个标准单位就足够了,然后将城墙的高度增加相同的量。
修改HexMetrics.WallLerp使用这个新的偏移量。
还需要修改塔楼预制体,让其底部基点下沉一个单位,并底座的高度增加一个单位高度。
四周被环绕的城墙和塔楼
2桥梁
目前我们的道路无法跨越河流,可以去添加桥梁了。先用一个简单的立方体表示桥梁的预制体。河流的宽度各不相同,但道路中心到每边的长度大约是7个标准单位,所以将其缩放设置为(3,1,7)。还是给它红色的城市材质并去掉碰撞盒。与城墙塔楼一样,把立方体放入一个根节点中,这样它的实际几何形状就不重要了。
在HexFeatureManager里添加桥梁预制体的引用,并建立关联。
赋值桥梁预制体
2.1放置桥梁
在HexFeatureManager里添加一个AddBridge方法,桥梁应该位于河岸两边道路的中心之间。
因为桥梁要穿过未被噪声扰动的道路中心,所以先要扰动作为参数的道路中心再放置桥梁。
这里可以使用与旋转塔楼相同的办法对齐道路,在这里用道路的中心点得到桥梁的forward向量,当在一个单元格内进行计算时,这个向量一定是水平的,所以也不需要把Y方向上的分量设置为0了。
2.2在笔直的河流上架设桥梁
需要架设桥梁的河流只有笔直的和平滑弯曲的两种,在锯齿急弯的河流中只会有一边有道路。
先来考虑笔直的河流,在HexGridChunk.TriangulateRoadAdjacentToRiver方法中的第一个else if中负责修改道路让其贴近单元格,所以就在这里架设桥梁。
我们现在处于河流的其中一边,道路的中心点从河流中推开了并且单元格的中心点也移动了。要找到道路中心反方向的一边,我们需要在相反的方向上移动相同的长度,而这一切要在道路中心改变之前完成。
桥梁穿过笔直的河流
桥梁出现了,但是现在我们在没有河流穿过时在每个方向上都生成了一个桥梁的实例。我们得确保每个单元格只生成一座桥,这里可以选择一个相对于河流的方向生成桥,具体是哪个方向不重要。
此外,我们应该只在河流两边都有道路时才添加河流。在这个地方我们已经能确认当前边上是有道路穿过过,所以我们还要检测另一边是否有道路穿过。
桥梁只在两边都有路的时候才会架设
2.3在弯曲的河流上架设桥梁
在弯曲河流上架桥的方法类似,但结构略有不同。当在河流外侧时需要添加一座桥,这是最后一段else代码块中的情况,这里的中间方向是用来偏移道路中心点的。我们需要使用这个偏移量的不同比例两此,所以把其存储在一个变量中。
曲线外侧的偏移比例是0.25,但是内侧则为HexMetrics.innerToOuter*0.7f,使用这些值去放置桥梁。
桥梁穿过弯曲的河流
还是要防止出现重复的桥梁,这次只用在中间方向添加桥梁就可以了。
同样还的确保另一边也有道路穿过才能架设桥梁。
只在两边都有路时才架设桥梁
2.4桥梁的缩放
由于地形的顶点受噪声图的扰动,所以道路中心点到河流两边的的距离是不同的。我们的桥梁有时显得太短,有时又太长。
河流的宽度会变化,但是桥梁的长度是固定的
虽然我们已经设计好了桥梁的长度固定为7,但是还是可以对桥梁进行缩放好让其吻合道路中心点之间的实际距离,这意味着桥梁的模型会产生形变。由于距离不会偏差太大,这种形变应该比距离不适合的统一长度的桥梁更容易接受一些。
为了进行正确的缩放,需要获取桥梁预制体的长度,并将其存储在HexMetrics中。
现在我们可以设置桥梁实例的Z轴上的缩放为道路中心点的距离除以原始长度。由于预制体的根节点是单位缩放,所以桥梁的模型可以显示正确的伸缩。
变化的桥梁长度
2.5桥梁外形设计
可以使用更有意思的桥梁模型而不是单个立方体。例如可以使用三个立方体进行缩放旋转后组成一个粗糙的拱桥形状。当然也能使用更复杂奇特的3D模型,甚至包括道路引桥部分。但是要记得整个模型会被压缩和拉伸。
各种长度的拱桥
3特殊的特征物体
目前为止,单元格内可以包含如城市,农田和植物之类的特征物。但即使它们每一种都有三个大小类别,但与一整个单元格比起来还是较小。如果我们要添加一个较大的结构,比如说城堡呢?
所以为地形添加一个特殊的特征物类型,它们的大小足以占据整个单元格。每一种都是很独特的形状,需要一个自己的预制体。例如一个简单的城堡形状可以创建一个大的立方体再在四边加上塔楼。中心立方体的缩放为(6,4,6),边上的四个塔楼为(2,6,2)。虽然城堡显得很大,但还是可以包含在形变的单元格之中。
城堡预制体
另一个特殊的特征物可以是一个由三个立方体堆叠成的金字塔形的建筑,其底部立方体的缩放为(8,2.5,8)。
神庙预制体
特殊特征物可以是任何东西,不必局限于建筑结构。比如一组表示树木的立方体,代表这个单元格内被巨大植物覆盖。
巨大植物预制体
在HexFeatureManager里添加一个数组存放这些预制体。
分别把三个特殊特征物添加到数组中。
配置特殊特地形特征物
3.1定义含有特殊特征物的单元格
HexCell现在需要一个下标来确认它所包含的特殊特征物(如果有的话)。
像其他特征物一样,给其一个属性来获取和设置这个值。
默认情况下单元格不包含特殊特征物,我们用下标0表示。添加一个属性来确认单元格是否包含特殊特征物。
在HexMapEditor里添加设置特殊特征物的方法,就跟设置城市,农场和植被的等级一样。
在UI中添加一个滑动条组件来控制它的值,由于现在只有3个特殊特征物,就将取值范围设置为0-3,0代表没有,1是城堡,2是金字塔,3代表巨型植物。
特殊特征物的滑动条
3.2在地形上添加特殊特征物
现在可以为单元格设置特殊特征物了,在HexFeatureManager里添加一个新方法。它用于实例化所需的特殊特生物,并放置于指定位置上。因为现在0表示没有特征物,故而在访问预制体数组之前需要将下标减去1。
使用哈希网格为特征物指定随机朝向。
当在HexGridChunk里对单元格进行三角剖分时检测单元格是否有特殊特征物,如果有就调用新方法,就像AddFeature一样。
特殊特征物比一般的要大得多
3.3避免出现在河流上
因为特殊特征物位于单元格的中心,所以没法与河流结合在一起,最终会漂浮在河流上。
处于河流上的特征物
为避免特殊特征物出现在河流之上,修改HexCell.SpecialIndex属性,让其只能在单元格内没有河流时才能更改下标。
此外在添加河流时也需要去掉单元格上的特殊特征物,这可以通过在HexCell.SetOutgoingRiver方法中设置特征物下标为零来实现。
3.4避免出现在道路上
与河流一样,道路也无法与特殊特征物结合,尽管看起来没在河流上那么糟糕。可能你觉得让道路保持原样比较好,也许有一些其他的特征物能与道路结合,但现在我们还是简单的让其不能共存。
道路上的特征物
在这种情况下我们让特殊特征物顶替道路。所以在修改特殊特征物下标时,也要从单元格中删除所有道路。
如果要去掉特殊特征物的时候会怎么样?
如果把下标设置为0,这意味着单元格内已经有一个特殊特征物了。因此这个单元格内也不会有道路存在,所以我们不需要另一个不同的方法。
这也意味着在尝试添加道路时需要额外的检测,只有当两个单元格内都没有特殊特征物时才能够添加道路。
3.5避免出现在其他特征物上
特殊特征物也不会与其他特征物混合,让它们重叠在一起会显得很混乱。同样,这里也是根据不同特征物可以有变化的地方,但目前还是使用统一的方法。
不同特征物交叉叠在一起
在这种情况下我们就不添加次级的特征物了,就像检测到处于水下一样。这次把检测过程放在HexFeatureManager.AddFeature里。
3.6避免出现在水体上
最后还有在水体上的问题,特殊特征物是否能在水下存在?当在水下单元格里处理其他特征物时,也对特殊特征物做同样的处理。
特征物在水里
在HexGridChunk.Triangulate里对特殊特征物同样加上是否在水下的检测。
既然两个if语句内都要检测是否在水下,干脆就提取出来。
本期工程地址:tank1018702/Hex-Map-Learning
有意参与线下游戏开发学习的童鞋,欢迎访问http://levelpp.com/。
系列文章
HexMap学习笔记(一)——创建六边形网格
HexMap学习笔记(二)——单元格颜色混合
HexMap学习笔记(三)——海拔高度与阶梯连接
HexMap学习笔记(四)——不规则化
HexMap学习笔记(五)——更大的地图
HexMap学习笔记(六)——河流
HexMap学习笔记(七)——道路
HexMap学习笔记(八)——水体
HexMap学习笔记(九)——地形特征
HexMap学习笔记(十)——城墙
HexMap学习笔记(十一)——更多种地形特征物
作者:沈琰
专栏地址:https://zhuanlan.zhihu.com/p/62880978