浅析 1.13 世界生成
摘要
世界生成是我的世界的一个重要内容。Minecraft 在发展,世界生成的代码却在很长的一段时间里没有发生太大的变化,而 1.13 正是对这一切进行变革的一个版本。在之后各个版本的世界生成中,1.13 版本的核心价值一直在不断地体现。这就是为什么会有这篇文章:因为这是一次划时代的更新。本文从世界生成的各个方面,逐一探讨其中的奥秘,揭开新版世界生成神秘复杂的面纱。
引子:为什么?
世界生成(World Generation, or WorldGen for short)是我的世界的一个重要内容。Minecraft 在发展,世界生成的代码却在很长的一段时间里没有发生太大的变化,而 1.13 正是对这一切进行变革的一个版本。那么为什么我们需要推翻一个使用这么长时间的、看似并没有太大问题的世界生成机制呢?我们为什么要这样变?这样变又有什么好处呢?
要解答这些问题,就要从旧版的世界生成说起,在这里,我不能把 1.12 及以前的地形生成解析一遍,在文末的相关链接我给出了土球的一个简要解析,供读者参考。也许在实际的游戏中,我们很难看出什么区别,但潜移默化间,Mojang 已经向前迈出了一大步。
这次 Mojang 代码的重构采用了全新的设计模式,增加了代码的可扩展性,主要体现在:
- 将世界生成的功能被集中在了区块生成器和生物群系两部分上面,而不是离散在方方面面,更便于对代码之间的关系进行分析。
- 细化了分类和世界生成的步骤,将不同的功能分离开,实现逻辑分层和模块化,代码更加清晰,复用性更高。
- 比如把定位和生成分离,灵活组合,告别了过去一写特性就花半天写定位代码或复制定位代码的冗余。
- 因为步骤被细化了,更加利于分配任务,所以世界生成可以异步了。
- 采用大量函数式接口,助力函数式大潮,让传递代码成为一种时尚。
- 采用了全新的设计模式,告别了过去Mojang一贯的代码风格,打破了我们的刻板印象。
- 如
(X-XType)
模式,即用X
表示对象本身,实现它的功能,而XType
描述这类对象性质,提供对象的工厂方法。 - 如
(X-XConfig)
模式,即用X
实现功能,而用XConfig
来传递实现此功能的参数,通过泛 型避免不同行为的参数不同和大量无意义的类型转换。 - 如有限状态机,用以表示区块生成的不同阶段和更加灵活地方式区分生物群系,删除了过去的硬编码。
- 如
- 统一化的世界生成接口,让所有生成的装饰都归于一个概念——特性。避免了以往的混乱不堪、互相踩脚的弊端。
- 开放了生物群系的接口,提供了生物群系的
Builder
,让你体验搭积木式的快感,让你不用Forge也能加特性。
在之后各个版本的世界生成中,1.13 版本的核心价值一直在不断地体现。这就是为什么会有这篇文章:因为这是一次划时代的更新。下面我们将从世界生成的各个方面,逐一探讨其中的奥秘,揭开新版世界生成神秘复杂的面纱。
区块生成概述
区块(Chunk)是Minecraft世界里一个大小为 16×256×16 的部分 ——Minecraft 中文维基
一个存档有多个维度,每个维度都有一个世界,世界是由一个个区块组成的,所以生成世界的实质就是生成区块。
在过去,区块生成分为 Generation 和 Population 两个阶段,而在 1.13 以后,不再这样笼统的区分,每个区块都拥有一个状态,每个状态都表示他已经完成的某一个任务,并提供下一个任务,以此实现异步的区块生成。
按顺序,它们分别是:
状态 | 翻译 |
---|---|
empty | 空 |
base | 基础 |
carved | 镂空 |
liquid_carved | 流体镂空 |
decorated | 装饰 |
lighted | 光照 |
mobs_spawned | 生物生成 |
finalized | 收尾 |
fullchunk | 完整区块 |
postprocessed | 处理后 |
前面的阶段属于样板(proto)区块,而最后两个属于存档(level)区块,后者已经可以序列化为 NBT 了。这个两种类型定义在ChunkStatus.Type
枚举中。
[提示] 为了更加直观的观看区块生成时所处的不同状态,可以观看 1.14 世界加载时的动画,其中不同的颜色代表不同的状态,可以说没有 1.13 的变革也不会有 1.14 的进步。
区块生成任务
接下来详细讲解每一个各个任务的内容。
[注意] 再说一遍是对应的任务的内容,其他内容不再此列。
基础
选定生物群系
生物群系选定是区块生成的第一步,所以也是世界生成的第一步。因此重要程度不言而喻。随着版本更迭,这一部分变化也很大。有关这一部分的内容,请参考我的新作。
高度图
高度图记录每个(x, z)
下最高的方块,用于描述地形的起伏升降,根据“最高方块”的标准不同,高度图分为以下几种,注意且的优先级是高于或的。其中有_WG
后缀的都是为世界生成准备的。
枚举名 | 含义:最高的 非 (...) 的方块 |
---|---|
LIGHT_BLOCKING | 空气 或 透明 |
MOTION_BLOCKING | 空气 或 允许移动 且 不包含流体 |
MOTION_BLOCKING_NO_LEAVES | 空气 或 树叶 或 允许移动 且 不包含流体 |
OCEAN_FLOOR | 空气 或 允许移动 |
OCEAN_FLOOR_WG | 空气 或 流体 |
WORLD_SURFACE | 空气 |
WORLD_SURFACE_WG | 空气 |
[说明]
- 透明:即不透明度为0的方块,不透明度会影响光的传递
- 树叶:即拥有
minecraft:leaves
标签(tag)的方块 - 允许移动:允许实体进入方块并在其中移动,比如树苗、按钮、花盆、地毯
- 不包含流体:即该方块里面没有流体浸入,比如正常的梯子,就属于 允许移动 且 不包含流体,但是如果梯子里面倒桶水,它就“包含流体”了
这些类型都可以在Heightmap.Type
找到,这两种用途对应枚举Heightmap.Usage
的值。
为了构造地表,我们需要一些高度图,高度图怎么得来,生成随机数?那样生成的地形七上八下支离破碎,为了确保地形看上去更加平滑真实,就需要用到噪声算法,可以最大程度保证不出现极端的高度跳跃。
上面说到的高度图,指的是世界生成专用的高度图,而其他的高度图,则是在每个区块生成的任务执行前进行更新。区块生成的前面四个阶段都不会更新高度图,而剩下的都会更新高度图。
地表构造器
有了高度图,我们就可以据此生成地表了。新版本生成地表的工具便是地表构造器。
[注意] 地表构造器(ISurfaceBuilder
)的Builder
跟通常的Builder
不同,它本身就是功能性的一个类,这个Build
指的是地形的构造,而不是对象的构造
地表构造器就是原来的Biome#genTerrainBlocks
,独立出来之后,它变得更加灵活、复用性更强。
而地表构造器配置提供至少两个信息,即顶层(top)方块和中层(middle)方块,比如在普通的平原生物群系,顶层(top)方块就是草方块,而中层方块则是泥土方块,此外还会有水底方块的信息(比如默认为砂砾)。
基岩的生成代码独立出来,而不是像过去一样包含在Biome#genTerrainBlocks
的代码里,这也就意味着你不会因为不谨慎的覆盖而导致基岩的生成被“取消”了。
镂空、流体镂空
所谓镂空就是钻洞注水,采用IWorldCarver
。
GenerationStage.Carving
枚举的AIR
表示镂空,LIQUID
表示流体镂空。
镂空是生物群系相关的,因为镂空器是从生物群系那里取得的。
[注意] GenerationStage 中的那个与旧版的 Generation 无关。
装饰
装饰可以说是整个地形生成中最为重要的一部分了,你不想看到光秃秃的世界吧,在这个阶段,所有可掉落掉落的方块都会立即掉落(但是镂空阶段却不会,这也就是为什么原版生成的地图有这么多浮沙)。
装饰的载体是生物群系,它实际上就是从原来的Biome#decorate
独立而成,独立出来之后,它变得更加灵活、复用性更强,不过在过去,有一个叫装饰器(decorator)的东西,被彻底移除了,现在用特性来统一管理所有生物群系特性,分为三类。
类别 | 说明 |
---|---|
特性 | 无 |
花 | 可以通过用骨粉催熟后天生成的特性 特殊之处:有一个专门的列表以便骨粉迭代 |
结构 | 它们的一些数据会被单独保存在世界里面,我们会在下面单独谈到 特殊之处:有一个专门的映射以便保存相关配置 必须要调用两个方法来注册,分别加入特性列表和添加配置的映射 |
GenerationStage.Decoration
枚举里面的值列出了装饰的所有阶段,依次是:
状态 | 翻译 | 例子 |
---|---|---|
raw_generation | 原生生成 | 目前只有末地岛 |
local_modifications | 本地修饰 |