欢迎光临散文网 会员登陆 & 注册

【真正的和平模式】三、结构的生成

2023-08-21 21:39 作者:VUMO北极鹅  | 我要投稿

上一篇讲了任务系统的实现,内容毕竟偏,并非所有人都需要它。本篇讲结构如何生成。

开头想说点骚话,实在想不出就算了,直接步入正题。

本文首发于知乎,笔者是作者本人,第二转载到bilibili平台。原文链接: https://zhuanlan.zhihu.com/p/646365902

一、结构的分类

生成结构多种多样,为了方便区分,我根据生成方式大致可以分为三类:普通结构、多级结构和拼图结构。

从原版里举几个例子:

  • 埋藏的宝藏、沉船、沙漠神殿、丛林神庙等属于普通结构,特点是它们只有一个piece,而且通常使用nbt来保存结构的模板。

  • 林地府邸、要塞、下界要塞、海底神殿等属于多级结构,特点是它们有多个piece,生成分多个层级,而且没有nbt模板,仅通过代码控制生成,不同piece随着结构的生成通过某种状态转移来切换。

  • 掠夺者前哨站、村庄、堡垒遗迹(即猪堡)、远古城市属于拼图结构,特点是有一个中心、仍然是多层生成、有多个结构中可能存在的模板、模板中通过拼图方块控制接下来的生成方向与目标池。

需要注意的是,有些结构比较特殊,如雪屋这个结构似乎要介于普通结构与多级结构之间——取决于它是否有地下室,不过我们可以把它归入第二类中。

在代码实现方面,拼图结构无需额外代码控制生成,只需完成数据包即可。而其他两种则需要额外的结构注册与生成,尤其是第二种,需要相对多一些的代码量。

二、结构的注册

1.19.3及之后,结构和结构集本身无需多余的注册,通过数据包即可实现生成。不过StructureType和StructurePieceType依然需要常规注册。

StructureType用于世界生成时唯一标识当前区块包含的结构类型,并链接特定的CODEC完成序列化和反序列化。

以《真正的和平模式》中的五个结构举例(无需包含拼图结构),注册方法如下:

而StructurePieceType则用于世界生成中标识结构的每一个部分——比如多级结构中的要塞的每个房间、雪屋的主体、地下室和梯子等等,对于普通结构而言则只需要注册本体的PieceType:

注意,世界生成是允许动态注册的,因此没有客户端服务端的同步检测(因而仅修改世界生成的mod可以仅服务端加载)、甚至有些内容并不需要延迟注册(使用DeferredRegister)。你可以直接在模组的构造时加载这个注册工具类。

以废弃的魔法池为例,生成Piece的写法如下:

addPieces方法则是入口方法,将结构的所以部分加入StructurePieceAccessor参与世界生成。注意ABANDONED_MAGIC_POOL表征了结构nbt的路径,它被放在数据包的data\real_peaceful_mode\structures中。

AbandonedMagicPoolPiece继承了TemplateStructurePiece结构部分模板类,若无其它内容,只需重写一个空的handleDataMarker函数即可——如果你使用了数据模式的结构方块,在这里当然需要作额外处理,这部分接下来再讲;如果有其它需要存储的数据,则需要重写addAdditionalSaveData函数;另外生成过程中可能需要额外的方块变化,如随机苔藓化、裂纹、氧化等等,这部分可以用postProcess来实现,其过程中调用this.placeBlock函数改变模板的方块来放置到世界中——本例则实现了砖块和木头的随机破坏和替换为蜘蛛网。

最终效果

那么入口函数如何调用呢?此处需要一个继承Structure并定义CODEC的类来解决:

其中findGenerationPoint用来找到区块中哪个位置可用于结构生成,如果存在便执行生成函数——generatePieces是生成结构的函数,addPieces入口便是在这里被调用。type则是生成结构的类型,对应着前文的StructureType。

以前还需重写step函数,否则会抛出异常——1.19.3后不再需要多此一举,因为可以直接在数据包的json里写了。

三、普通结构数据包的编写

前文只是实现了结构的注册和生成逻辑的编写,还没能真正将结构生成在世界中。由于1.19.3代码结构的大改,仅需实现数据包即可实现结构的生成。

以废弃的魔法池为例,我们在data\real_peaceful_mode\worldgen\structure目录下创建abandoned_magic_pool.json:

其中type对应着StructureType,需要与注册保持一致;spawn_overrides重写了结构内部的生物生成,如古城不会生成任何生物、掠夺者前哨站会源源不断生成掠夺者等;step表示生成的步骤,最常见的则是"underground_structures"和"surface_structures";terrain_adaptation是生成后的地形调整,建议地表建筑使用"beard_thin"保证不会悬空,而地下建筑则多为"bury",古城使用了"beard_box",悬空建筑(如水晶头骨浮岛)则留空;最重要的,biomes表示结构生成的群系,最好使用TagKey表示,于是我们在data\real_peaceful_mode\tags\worldgen\biome\has_structure目录下创建abandoned_magic_pool.json:

实现了结构,接下来实现结构集——即多个变种的相同类型结构的集合,如村庄结构集包括平原、热带草原、沙漠、雪原和针叶林五个变种,废弃的传送门结构集则包括下界、高山、深海、沙漠和普通等变种。

我们在data\real_peaceful_mode\worldgen\structure_set目录下创建abandoned_magic_pools.json:

salt建议随机生成,不要跟任何结构重复;separation是同一结构集内两个结构间的最小距离(单位:区块),而spacing则是两个结构间的平均距离(单位:区块),显然这个值要大于separation;type则是结构的分布,random_spread是随机分布,而concentric_rings则是类似要塞的分布方式——若是random_spread,还有可选的字段spread_type,可选值包括linear和triangular,即距离分布的类型,triangular使得结构更加平均分散,而linear距离的方差则更大。structures包括了这个结构集的所有结构,并以权重组织着它们的生成。

按照这个步骤走下来的话,你的结构大概率就生成在世界中了。不过还有一些特殊情况,比如拼图结构的定义,以及processor list的编写。

四、拼图结构的模板池和processor list

首先给原版知识储备较少的朋友们介绍一下什么是拼图结构。在处理结构群(如村庄)或较大面积结构的生成(如古城)时,或者希望在结构生成过程中不同“部分”的连接引入随机性时(如堡垒遗迹),可以考虑使用拼图结构。拼图结构的每个模板结构都应该包含拼图方块,并指定name(拼图名称)、final_state(拼图方块最终转变成什么方块)、joint(拼接类型)、pool(目标池)、target(需要对接的拼图名称)——两个拼图方块之间分别以相同的name与target互相连接,而生成过程中拼图方块则从pool目标模板池中选择新的结构并拼接好后生成。

这部分同样只需要添加数据包。以苦力怕小镇为例,需要在data\real_peaceful_mode\worldgen\template_pool\creeper_town目录下编写模板池,以房屋池为例(houses.json):

elements包含了所有池内的模板结构,每个结构由element和weight组成,分别表示结构细节和权重。

element_type常有以下三种取值:

  • minecraft:empty_pool_element:即空结构,表示在一定权重下,该位置不生成新的模板。

  • minecraft:legacy_single_pool_element:单结构,通过location定位在structures文件夹中的模板结构。

  • minecraft:feature_pool_element:即地物结构,直接生成一个地物,通过feature定位一个地物的注册名。

另外两个很好理解,主要介绍一下单结构的一些属性——

  • location:结构模板的id

  • processors:processor list的id

  • projection:投影类型,包括rigid(视为整体放置,多用于房屋、农田、水井等不因地形改变而改变的结构部分)和terrain_matching(匹配地形,多用于道路)。

那么processor list又是什么?其实就是一个结构的部分生成结束后再进行的处理,与前文的postProcess函数功能类似。如苦力怕小镇的道路使用了如下处理,使得水和极少部分的沙砾被替换为了凝灰岩砖(street_creeper_town.json):

其详细写法,建议参考官方wiki:https://minecraft.fandom.com/zh/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90/processor。

五、怎么生成和编辑结构nbt文件?

前文的所有介绍,默认你知道了所有关于生成nbt文件的知识,这里额外多提一嘴,防止有人不知道这个问题的答案,影响阅读体验。

首先随便建点什么,想生成啥就建啥:

粉色苦力怕的“结构”

然后拿出结构方块,调整到save模式,然后调整边框和大小使得整个结构完全被白线包裹:

结构方块UI

然后输入结构名——这是个id,以命名空间:名称的方式命名。如果需要储存实体,请将右侧的Include Entities调为ON。

最后点击右侧SAVE保存,便可在世界文件夹/generated/<命名空间>/structures目录下找到它。将这个nbt复制到你的模组数据包目录的对应位置中,必要时可打开NBTExplorer,甚至IDEA安装Minecraft Development插件也可直接编辑:

IDEA中直接编辑nbt内容

这些nbt由三部分构成——方块、实体、调色板。方块即结构每个位置的方块,包括方块实体等;而实体则在开启Include Entities时被保存;调色板包括所有可能的方块状态,方块中state数值即为调色板的下标。

接下来我可能会讲讲地物和群系的生成,这部分也以数据包为主,涉及代码较少,敬请期待!

笔者从事开发不久,文章如有疏漏,敬请斧正!


【真正的和平模式】三、结构的生成的评论 (共 条)

分享到微博请遵守国家法律