地表规则——浅谈Minecraft地形生成(五)
在之前的文章中,我们介绍了密度函数,并逐层逐步解析了简化版主世界密度函数是如何控制主世界地形生成的。但是,密度函数只能决定这个地方有什么方块,至于是什么方块,其中一个重要的决定步骤就是地表规则,有时也叫做表面规则。在过去,地表规则被单独放在一个json文件中,而现在,地表规则和噪声设定(noise_settings
)被放在一起,作为surface_rule
键的值来参与地形的生成。
地表规则就像铺地毯一样,将世界的表面替换为指定的方块。比如,在平原上,就是泥土加草方块,而在沙漠里面,就是沙子和砂岩。通过地表规则的嵌套组合,能够让世界的地表更加丰富多彩。
Contents
基本语法格式
地表规则的语法相当的简单,主要有四种类型:bandlands
(原文如此),block
,condition
和sequence
,分别的用处是,用于恶地,用于放置方块,用于判断条件然后执行另一个地表规则,用于按照顺序执行一系列地表规则。
地表规则的JSON格式如下:
地表规则条件只用于condition
类型的地表规则。它的种类较为繁多,我们会在接下来的小节中详细介绍。
此外,地表规则还需要关注的一个量就是表层厚度。表层厚度并不是直接表示地表规则的影响范围,或者是表面的总厚度。通常情况下,地表规则会配合表层高度,来决定表面的总厚度。表层厚度由噪声minecraft:surface
决定,将(X,0,Z)处的噪声值 × 2.75 + 3.0
作为表层厚度的值。
小栗子
现在让我们看一个表面规则的例子:
{ "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } }
这是一个最简单的地表规则的例子,它的类型是block
,因此会放置泥土方块。这个表面规则应用后,将会把所有的默认方块(一般是石头)都替换为泥土。
(对了,表面规则也可以使用misode的生成器Noise Settings Generator – Minecraft 1.18, 1.19, 1.20 (misode.github.io)预览哦)
但是需要注意的是,misode的生成器对某些地表规则条件的支持并不完善,并且有一些bug,在生成器中遇到某些费解的问题时,请务必打开游戏,写一个小的测试数据包,看看游戏的里面的实际运行情况是什么样子的。
所以说,地表规则其实不仅仅是“表面”,或者说这个所谓的”表面“其实不会有固定厚度。这也很容易想到,毕竟平原地区的泥土就是薄薄一层,而沙漠的沙子和砂石则是厚厚的一层。所以怎么确定表面规则的高度呢?那就看看下面这个例子吧:
{ "type": "minecraft:condition", "if_true": { "type": "minecraft:stone_depth", "offset": 2, "surface_type": "floor", "add_surface_depth": false, "secondary_depth_range": 0 }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } } }
这个例子中使用了stone_depth
的地表规则条件,用来限制地表规则作用的深度。其中offset
控制深度为2+1=3格,而then_run
则是如果条件满足,也就是深度在三格以内的时候,要执行的地表规则,在这里是放置泥土。它的效果是这样子的:
看起来有一点生机!但是泥土也是光秃秃的,就像是废土一样。我们还需要在上面添加一层草皮,也就是放置一层草方块才行。转换为程序逻辑,就是在深度为1以内的时候,放置草方块,此后,在深度为3以内但是不在1以内的时候,放置泥土。
sequence
类型就是为这样的需求服务的。sequence
类型是一系列地表规则的列表,在执行的时候,它将会从上到下开始执行,并且如果成功执行了一个,之后的就不会执行了,也就是说只会应用第一个成功执行的规则。
下面是我们修改后的例子,可以给我们的地表加上一层草皮:
{ "type": "minecraft:sequence", "sequence": [ { "type": "minecraft:condition", "if_true": { "type": "minecraft:stone_depth", "offset": 0, "surface_type": "floor", "add_surface_depth": false, "secondary_depth_range": 0 }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:grass_block" } } }, { "type": "minecraft:condition", "if_true": { "type": "minecraft:stone_depth", "offset": 2, "surface_type": "floor", "add_surface_depth": false, "secondary_depth_range": 0 }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } } } ] }
它的效果是这样的:
草皮,泥土,水池,看起来已经是一个生机勃勃的小世界啦。
当然,实际情况主世界的生成肯定比这个复杂得多,因为不同的生物群系都有不同的地表规则,而这所有的地表规则都在一个json文件中。如果你打开主世界的json文件你就会看到两千行的地表规则。但是如果你仔细看的话,你就会很快理解它的含义——它只是很长,其实还是很容易理解的。
地表规则条件
之前的例子中我们使用了一个叫做stone_depth
的地表规则条件用来控制地表规则可以应用的深度,而接下来我们将介绍更多的地表规则条件。我们会先给出它的JSON语法格式,然后再解释这个地表规则条件的具体用法。
biome
用于控制地表规则可以应用的生物群系,biome_is
就是可以应用的生物群系的列表。
noise_threshold
假设检测的方块的坐标的坐标是(x, y, z),则检测(x, 0 ,z)处指定噪声的值,并判断它是否在指定的闭区间内
vertical_gradient
首先确定两个y的坐标,然后在两个坐标间进行过渡渐变处理。过渡的过程中将会随机放置方块用于渐变。两个y的坐标都是垂直锚点的形式,即有三种方式确定一个y坐标——绝对高度,相对世界底部的高度,相对世界顶部的高度。
absolute
指定了一个y坐标的绝对值。在这个时候,显然y<true_at_and_below的时候条件会永远通过,而y>false_at_and_above的时候条件会永远不通过,而在期间就会过渡地随机通过。在下面的例子中,将上界设置为了60,下界设置为54,你可以观察到60以上条件永远不通过,因此是石头,54以下条件永远通过,因此都执行了then_run
中的地表规则,全是泥土,而在它们之间,泥土和石头进行了平滑的过渡。
{ "type": "minecraft:condition", "if_true": { "type": "minecraft:vertical_gradient", "random_name": "minecraft:deepslate", "true_at_and_below": { "absolute": 54 }, "false_at_and_above": { "absolute": 60 } }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } } }
above_bottom
指定了从世界底部开始计算的相对高度。注意这里的世界底部不是地形的底部,而是世界高度限制意义上的底部。例如主世界的高度限制中,y的最小值为-64,那么如果将此项设置为54,对应的y轴坐标就是-10。
{ "type": "minecraft:condition", "if_true": { "type": "minecraft:vertical_gradient", "random_name": "minecraft:deepslate", "true_at_and_below": { "above_bottom": 54 }, "false_at_and_above": { "above_bottom": 60 } }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } } }
below_top
指定了一个从世界顶部开始计算的相对高度,这里的顶部也指的是世界高度意义上的顶部,即大家熟知的y轴上限。值得注意的是,和前面两个不同,在这里true_at_and_below
应当是较大的一个,这样用y轴上限-true_at_and_below
得到的y轴坐标才会是较小的那一个。
{ "type": "minecraft:condition", "if_true": { "type": "minecraft:vertical_gradient", "random_name": "minecraft:deepslate", "true_at_and_below": { "below_top": 270 }, "false_at_and_above": { "below_top": 256 } }, "then_run": { "type": "minecraft:block", "result_state": { "Name": "minecraft:dirt" } } }
y_above
y_above
和vertical_gradient
有些类似,但是它没有两个坐标之间的过渡地带,只有一个坐标,大于这个坐标则条件成立,小于这个坐标则条件失败。(注意vertical_gradient
是小于成功,大于失败,是反着的)。
surface_depth_multiplier
在后续的条件中也会用到,用于对anchor得到的y坐标进行进一步的修改,具体计算如上图所示。
add_stone_depth
也会在后续的条件中见到。它的作用是,当判断到假设y坐标为a的部分时,不直接判断a < anchor是否成立,而是先看从它的位置到距离它上方最近的空气方块之间的非液体方块有多少格(包括自己),即得到add_stone_depth值,最后用y+add_stone_depth值和anchor或者被表层厚度处理完毕后的anchor值比较,判断条件是否通过。简单来说,它考虑了从当前方块向上直到空气方块之间所有非液体方块的数量,然后将这个数量加到当前的Y坐标上,以决定是否应用某个规则。注意,在本文撰写的时候,misode的预览器在此选项的处理上有错,请以实际游戏内表现为准。
在右图中,我们将anchor设置为了60。要让一个y < 60的方块对此条件通过,那么它上方的固体方块的数量必然需要大于它与y=60之间的距离。也就是说,它和y=60之间,至少不可能能存在空气方块。图中左侧的小洞穴中的空气方块的存在,就让洞穴下方方块的add_stone_depth值无法达到要求,因此不会被替换为泥土。
water
water
通常用于水下地形的生成,用于检测当前位置距离上方最高液体方块表面的距离是否小于指定的值。注意这里由于offset为负数,因此距离应该是wiki中描述的”相对于上方液面的高度“的绝对值。
例如,在y=0的位置有一个石头,而在y=1的地方为水,y=2的地方为空气。现在对y=0的位置检查water
条件,那么水和空气的交界处的y的坐标是2,因此从石头的位置(0)到液面的位置(2)就是-2(0-2)。
如果把offet
设置为-1或者更大的数,那么就只有当这个方块上方没有液体的时候才会通过条件了。
其余的两个参数和y_above
中的相同,这里就不赘述了
temperature
此处的温度是否小于0.15,即是否可以下雪
steep
not
not
用于条件取反,invert
中的参数表示要取反的条件,即invert
中的条件不通过,这个not
条件才会通过
hole
above_preliminary_surface
stone_depth
和water
类似,检测当前位置和上方表面是否小于等于指定的距离。在之前的小栗子中我们使用过这个条件。具体的参数和water
和y_above
较为类似,此处不再赘述
总结
由此,地表规则的内容就已经介绍完毕了。在此文撰写的过程中,感谢Nickid对笔者问题的解答。对于某些问题,笔者深感自己能力有限,某些方面无法解释清楚,如果读者有更好的见解,欢迎斧正。