浅析Minecraft地形生成(SP)——结构

利用数据包,你也可以为你的光秃秃的世界生成一些自定义的结构,让它更加有探索价值。值得注意的是,结构(structure),或者说结构地物(structure feature)和地物(feature)是不一样的两个东西,最显著的一个差异就是结构可以用/locate命令被找到,而地物不可以。

在接下来的教程中,我们将会向一个自定义维度中添加自定义的结构。当然,如果你重写原版维度的文件,你也可以在原版中加入你想要加入的自定义结构。

在用数据包写地形的过程中,非常容易因为各种问题导致数据包被认为是“损坏的”或者“错误的”而无法加载。你可以使用DHP(Datapack Helper Plus)插件,它可以自动检查你的JSON中的语法错误。如果语法没有错误,那你可能需要查看游戏日志来完成。通常我会推荐使用HMCL启动器,因为你可以在版本的管理页面选择测试游戏选项,从而在打开游戏的同时打开游戏的日志窗口。游戏在运行过程中会产生很多日志,为了找出错误的核心,你可以试着在数据包出现错误的时候,清空日志窗口中的日志,然后再次加载数据包,这个时候的错误日志就基本全是数据包的错误啦。

保存结构

如果你想要生成一个结构,那首先要做的当然就是保存这个结构到你的数据包中。你可以使用结构方块来保存你的结构,结构方块的用法这里就不再赘述了,详细的可以去看Wiki。

在结构方块中保存一个结构后,你可以在存档文件夹下的generate/命名空间/structures中找到结构方块保存下来的结构文件。复制它,然后粘贴到你数据包命名空间下的structures文件夹中。之后,你就可以在另一个存档中导入你的数据包,然后用和命名空间id的格式在结构方块中加载你的结构。在维度配置文件中,也是使用命名空间id来加载你的结构的。

结构文件是一个二进制的NBT文件,后缀是.nbt。在vscode中,你可以下载一个叫做NBT Viewer的插件来查看这个文件,特别是对于结构来说,这个插件可以渲染你的结构,甚至允许你修改其中方块实体的NBT数据。但是有个小问题就是,它有时候会不能加载出来(现在咱也不知道为什么)。

定义你的结构

要添加一个自定义的结构,你至少需要了解一下几个概念:结构类型,结构池(或者说拼图池,模板池),已配置的结构地物,结构集。

结构类型

Minecraft定义了许多结构类型,用于生成原版的各种结构,其中不少是硬编码的。如果你要在维度中使用你自定义的结构,你应该使用jigsaw类型的结构,因为它允许开发者使用自定义的拼图模板来生成各种结构。在这里你可以找到所有可用的结构类型,以及它们的描述。

拼图与结构池

拼图方块(jigsaw)对于各位读者可能会显得稍微陌生一些,但是在生成随机结构方面,它是至关重要的东西。目前,拼图方块被用来生成掠夺者前哨站、村庄、堡垒遗迹、远古城市、古迹废墟和Trial Chambers(那个1.21新加的超级大的结构)。

拼图方块本身的使用又是一个很大的坑,限于篇幅和内容量,这里对拼图方块的使用不多做述,在之后的内容中我们可能会详细地介绍拼图方块的使用和随机结构的生成。

那么对于我们今天,想要生成的单个结构文件组成的结构来说,其实并不需要拼图方块,但是我们仍然要写结构池文件。

结构池文件存放在命名空间/worldgen/template_pool中。下面是Wiki中结构池JSON的格式说明:

跳过必须写但是又没有用的name,我们来看后面两个比较重要的键,fallback和elements。如果不了解拼图方块的运作模式,这里可能理解起来有些费解,所以我们稍微补充说明一下。

当使用拼图生成一个文件的时候,拼图方块是像一个树一样生成的,先生成一个根结构,然后这个根结构中又有几个拼图方块,这些拼图方块会继续生成其他的结构,这些由根结构生成的结构就是第二层结构,如此循环,直到无法生成。而无法生成有两种情况,第一种就是,当前生成的层数已经达到上限了(上限是由拼图方块或者已配置的结构地物设置的),第二种就是,没有一个适合的结构能用来放在这个位置了。

那么在拼图方块无法继续生成的时候,就会使用fallback中定义的结构来生成。如果定义为minecraft:empty,就是空,最后什么都不会生成,直接结束。

接下来就是elements。这个列表定义了一系列的元素。在结构生成的时候,会在其中随机抽取一个来生成根结构,也就是起始的那一个结构。其中,weight为权重,没有什么需要说明的地方。element中,projection表示结构生成的时候是否适应地形。比如你生成的是房子,就不需要适应地形,但是如果是路,你就需要让它和地面尽可能贴合,所以要适应地形。至于element_type则又有四种,如果是feature_pool_element,就需要填写一个地物的命名空间id,这样就能生成一些花花草草什么的作为结构;如果使用list_pool_element,就需要再填写一个列表,这个列表和当前的列表格式是一样的,意思是,如果选中了这个元素,那么还要从这个元素中的这个列表中继续随机选择一个元素;剩下两个就是直接生成一个结构,但是对于空气方块的处理有差异,具体的就看上面图片中的wiki资料啦。下面是一个简单的小例子:

{
    "name": "monument",
    "fallback": "minecraft:empty",
    "elements": [
        {
            "element": {
                "element_type": "minecraft:legacy_single_pool_element",
                "projection": "rigid",
                "processors": "minecraft:empty",
                "location": "dreamland:miscellaneous/witch_hut"
            },
            "weight": 1
        }
    ]
}

你可能已经注意到了一个叫做处理器列表的东西,这个列表中包含了多个处理器,而处理器则是用来对结构中的方块进行进一步处理的,比如将其中的石砖随机变成裂纹石砖,或者随机替换成空气。在我们的例子中,这里填写的是minecraft:empty,表示不需要对结构进行进一步处理,但是如果你希望对你的结构进行二次处理,就可以看看这里的JSON格式。Wiki中对此的介绍非常详细易懂,而例子则可以参考原版数据包中的处理器列表。

现在我们已经有了一个结构池,在重新加载存档后(注意worldgen中的所有改动都需要重新进入一次存档或者重启服务器后才会生效),你可以使用命令/place template <template> [<pos>] [<rotation>] [<mirror>] [<integrity>] [<seed>]。命令的语法参见Wiki。什么,不想看wiki,嘛,那我稍微说一下好了。第一个参数template就是你刚刚写的结构池的命名空间ID,而pos就是要生存的位置,rotation是旋转角度(只有四个可用选项,不是任意旋转的说),mirror就是是否镜像,而后面两个就是指定结构完整度和当结构完整度不是0的时候用于随机破坏结构时使用的种子。

已配置的结构地物

如果上面的一切已经完成,我们就可以接着配置我们的结构。

已配置的结构地物JSON文件存放在命名空间/worldgen/structure中。它的部分JSON格式参考如下:

第一个键type就是我们上文说到的结构类型,这里我们要生成自定义的结构,因此填写jigsaw即可。然后就是可以生成的生物群系列表,没有什么好多说的。之后的step就是这个结构在世界生成的什么阶段生成,一般填写surface_structures就好了。下一个键用于控制结构是怎么适应地形的。none即为不适应地形,beard_thin就是贴着地面生成,比如村庄这种就需要贴着地面生成。beard_box表示结构会被埋在地下,但是根据结构的体积留有空腔,比如远古城市,而bury则表示结构完全被埋在地下,不留空腔,比如要塞。spawn_overrides用于控制结构中生物生成的情况,具体可以参考wiki,此处不多赘述。

此后,根据我们在type中填写的值,JSON中还会有其他的附加参数。在这里我们填写的是jigsaw,因此剩余的JSON格式如下:

第一个start_pool就是这个结构生成根结构,即第一层结构的时候要使用的结构池,也就是我们上面定义的那个结构池(的命名空间ID)。然后是size,即上文中提到过的拼图的最大层数。然后是一个start_height。这是一个高度提供器,它或者下面的project_start_to_heightmap决定了结构开始生成的高度。再下一个,即连接起始模板的拼图方块的名称,意思是假设我们最开始的那个结构中中有多个拼图,那么应该让哪个拼图作为最开始触发生成的那个拼图。在本文的例子中我们不需要填写它。max_distance_from_center用于保证结构生成在一个正方形范围内,它的值就是正方体边长的一半,结构中心就是正方体的中心。再下面一个选项,只有在你的结构是一种村庄的时候才去启用它。最后一个模板池映射是最近几个版本新加的东西,用于把一个模板池映射为另一种模板池,可以理解为替换。

下面是一个简单的例子,定义了一个会在自定义生物群系中生成的一个自定义小屋:

{
  "type": "minecraft:jigsaw",
  "biomes": ["dreamland:scarlet"],
  "step": "surface_structures",
  "terrain_adaptation": "beard_thin",
  "spawn_overrides": {},
  "start_pool": "dreamland:nightmare/witch_hut",
  "project_start_to_heightmap": "WORLD_SURFACE_WG",
  "size": 1,
  "start_height": {
    "absolute": -180
  },
  "max_distance_from_center": 80,
  "use_expansion_hack": false
}

高度提供器

高度提供器能提供一个数值,控制结构生成的高度。你可以直接填写一个固定的数值,这样它就固定在这个高度上方。当然,你可以使用更复杂的JSON格式来让结构的高度分布更加丰富。

constant表示一个常数,和直接填写一个整数类似,让结构只会在这样的高度上生成。

uniform表示一个均匀的随机分布,两个参数分别指定最高高度和最低高度。

biased_to_bottomvery_biased_to_bottom则表示靠近最低高度的概率更大,后者比前者靠近底部的概率还要大,两个参数同样指定了最高高度和最低高度,而inner参数是一个正整数,同时不能保证inner+1不能大于上限和下限之差。inner用于控制结构的分布情况,inner越大,那么结构越可能出现在靠近底部。

trapezoid表示一个梯形分布。前两个参数和其他的一样,而plateau用于控制梯形分布的平台区。分布的密度函数呈现一个底为最小值加最大值,顶为plateau大小的等腰梯形。

最后一个则是随机选择一种分布类型,就不多说了。

结构集

结构集是定义结构的最后一步,也是locate命令选择的对象。结构集定义了结构应该在世界中怎样随机分布,因此你可以通过结构集控制结构在世界中的生成密度。结构集的JSON文件存放在数据包的命名空间/worldgen/structure_set下。它的JSON格式如下:

第一个键structures不必多说,就是从中随机抽取一个结构出来。之后的placement中则定义了结构应该如何随机生成。盐随便填一个数字就好。frequency表示了结构生成的概率,即假设这个地方应该生成一个结构,而实际上真的在这个生成了结构的概率。但是值得注意的是,这个概率是在游戏认为这里可以生成结构之后再生效的,假设这里因为地形问题不能生成结构,那么这个概率就不会有任何作用。下面的键表示了随机的方法,主要决定结构的随机会和世界本身的哪些东西相关。exclusion_zone定义了这个结构不能生成在哪些结构附近,这样你就可以避免村庄生成在哨站旁边。locate_offset定义了这个结构在生成的时候应该偏移多少距离。locate命令在执行的时候找到的是结构原来生成的地方,如果这里填写了值,那么结构实际上就会从原来的地方偏移一定的距离。

type即为两种生成的方式,要塞式或者随机的分布。如果你之前有注意过,你应该会知道要塞在主世界中是呈现环状分布生成的。而其他结构,比如沼泽的女巫小屋,是均匀的随机分布。两种分布方式所需要的具体的参数在Wiki中写的非常详细,这里就不赘述了。

下面是一个简单的例子,即上面的自定义小屋:

{
    "structures": [
        {
            "structure": "dreamland:nightmare/witch_hut",
            "weight": 1
        }
    ],
    "placement": {
        "type": "minecraft:random_spread",
        "spacing": 18,
        "separation": 14,
        "salt": 1118191848
    }
}

总结

至此,所有的步骤已经完成,如果一切顺利,你应该已经向你的世界中添加了一个新的结构!如果你还在其中遇到了其他问题,不妨看看原版数据包中,是怎么定义一个结构的,尝试把原版文件进行复制,修改,看看会发生什么。

类似文章

发表回复