再析世界生成:生物群系
摘要
世界生成是我的世界的一个重要内容。如果把世界生成比作一首长诗,那么生物群系就是它的诗眼。本文详尽梳理了生物群系和世界生成,多方面探究世界生成的来龙去脉,并凸显它的核心——生物群系。
这次的创作还包含了前所未有的大胆尝试,想要带给大家的不再仅仅是干瘪的文字:因为可视化是展示世界之美的重要一环。前作可是一幅图都没有;这次不仅有图,我还准备了视频以供欣赏。
在本文发布的时候,前作已经做了修订:特别的,删除了生物群系的部分;但鉴于版本差异,仅供参考。
文章链接
-
前作:浅析 1.13 世界生成
-
本文:再析世界生成:生物群系
- MCBBS:再析世界生成:生物群系
-
第二章内容的视频链接见 2.5 节。
-
友链见文末。
凡例
体例
文章分为多个章,每章有多个节,一节即为一个文件。注意除了附录之外章头也是有内容的,请勿遗漏。
引言
可以看到,本文的引言非常丰富。这里特别说明一下
你好(世界)
中的括号是原文括号
你好(世界)
中的括号是我备注的内容
译名
还是那句话,这些(mcp、yarn 中的各种名字)都是社区命名的
——3TUSK
社区的盲目性使其可能得出完全不符合事实的译名。正是因为如此,这一次我两方的名字都在参考,却没有完全采纳。
大多数名字都是可以直接找到,或者间接猜到其对应的 mcp name 和 yarn name,对于少数疑难名字,可以参考附录。
1.16 后,官方映射表逐渐走入寻常百姓家,因此本教程也在向官方靠拢,但出于历史原因做不到完全一致。
图片注解
图片后面可能会紧随一行文字对图片内容进行注解。
颜色代码
本文的所有颜色代码使用十进制数表示。不包含 alpha 通道。
部分前置知识
对于个别艰深的步骤,在阐释的同时,会贴少量 Java 代码辅助:例如位运算。这不是必须的。
你需要知道一些伪随机数(pseudo random number)和噪声(noise)的知识。
文中有时使用区间(range)表示实数的或整数的范围,注意开闭。
欧氏距离(Euclidean distance),即我们日常中最熟悉的距离,两点各个坐标差的平方和开平方根。
文中会把函数(或者说一种映射关系)表示成这样:(parameter: ParameterType)->ReturnType
权重(weight),简称权。如盒子里面装了4个红球,1个黄球,2个蓝球,随机抽取其中一个,抽到的概率分别为 4/7、1/7、2/7。则 4、1、2 就是他们的权,计算抽到某个球的概率公式为该球的权除以所有球权的和。用权表示的好处在于不用写出(对于所有项来说都相同的)概率的分母,简洁明了。
引入
潭西南而望,斗折蛇行,明灭可见。
——柳宗元《小石潭记》
让我们开始吧。
The World
本节标题和内容与《JOJO的奇妙冒险》无关。
引子:我的世界
早年的 Minecraft 中文圈子,最常见的一个译名就是我的世界。当然也有老玩家认为,“我的世界”在之前从未被官方承认,不应该作为“真正的 mc 粉丝”所用之语。但我认为既然是粉丝行为,能够被广泛接受,也不存在什么原则问题,是否被官方承认并不重要。
后来随着网易代理,这个名字终于得到了正式承认。不幸的是,网易的所作所为彻底搞臭了这个名字,以至于很多人把“我的世界”和“Minecraft”对立起来,认为是中国版和国际版的象征。作为多年的老玩家,这样的做法我无法认同,但也无可奈何。
但不可否认的是,这个译名承载了太多。如果你向朋友推荐,或者朋友向你打听,“我的世界”依旧是中文环境下最合适的名字。为什么多年前,许多人能普遍认同这个译名?因为它直观的反映了这个游戏带给我们的——一个属于自己的世界。在玩家的讨论中,世界也是非常常见的一个主题,而本文标题中也有个世界两字。然而,世界(World) 一词,往往指代了许多的概念。
「世界」的现实来源
四方上下曰宇,往古来今曰宙
——尸佼《尸子》
古往今来曰世,上下四方曰界
——佚名,网络
在我国古代,「宇宙」的概念最先被提出,意为“时空”。而「世界」则源于佛教用语。诸如“大千世界”之类的词也是由此而来。
这两个词语的含义,从最初的边界,逐渐转向代称其中的一切事物。近代之后,宇宙的意思固定位为 universe,而世界的意思固定为了 world。宇宙的含义走向单一,而世界的含义走向多元。
We are the world, we are the children We are the ones who make a brighter day
——Michael Jackson《We Are The World》
世界一词的多元含义的一个体现在于:与宇宙不同,世界同时也是一个社科概念。在此我不加赘述,相信高中的必修课已经带领各位领略一二。
全世界指的是什么呢?是所有国家?是地球?是宇宙?还是...MC 的一个存档?
「世界」在 MC 语境下的多重含义
Sometimes when they are deep in dreams, I want to tell them, they are building true worlds in reality. Sometimes I want to tell them of their importance to the universe. Sometimes, when they have not made a true connection in a while, I want to help them to speak the word they fear.
有时当它们深陷梦境中时,我想要告诉它们,它们在现实中创造了真实的世界。有时我想告诉它们它们自身对宇宙的重要性。有时,当它们和现实失去了联系,我想帮助它们与它们所惧怕的世界交流。
——Julian Gough《终末之诗》
世界在不同语境有不同的含义:
语境 | 含义 | 英文 |
---|---|---|
创建新的世界 | 存档 | Save |
下界真可怕 | (享元的)维度 | Dimension |
这个世界的下界 | (存档的某个)维度 | Level |
局域网世界 | 服务器 | Server |
需要注意到,Dimension 是指的某个维度的概念,而 Level 是存档中的一部分,换句话说就是:不同 Save 的同一个 Dimension 的 Level 可能会有所不同。
有的时候,定语修饰的,也不是同一个“世界”。 斜体 表示它实际上修饰的对象。
语境 | 含义 | 英文 |
---|---|---|
世界类型: 超平坦世界 | 存档的 类型,决定了 一个 维度 (主世界)的生物群系来源 | WorldType BiomeSource |
沙漠世界 | 一个 维度 (主世界)(世界性的)的生物群系 | Biome |
创造世界 | 玩家的 游戏模式 或(尤指 服务器的)存档的 默认游戏模式 | Gamemode |
极限世界 | 存档的 一个属性 | Hardcore |
和平世界 | 存档的 一个属性 | Difficulty |
死亡不掉落的世界 | 存档的 一个游戏规则 | Gamerule |
世界边界 | 存档(决定)的 每个 维度的 边界 | WorldBorder |
世界生成 | 存档(决定)的 每个 维度的 生成 | WorldGen |
一个世界可以同时是极限、和平、创造、死亡不掉落的吗?当然可以!但是他们的含义实际上各不相同,因此用“世界”一词可能歧义很大。本文中,如果是对某个概念的特指,我会避免使用“世界”一词;但是如果是用它来概括这方面的内容,正是因为它的多义性,反而再合适不过。
世界的结构
MC 的世界是很大的,因此绝不可能一言以蔽之,分层讨论势在必行。从顶层到底层,MC 的世界结构可以描述为下面这些层次:
中文 | 英文 | 包含... | 意义(仅举一例) |
---|---|---|---|
存档 | Save | (原版的)三个维度 | 整个存档 |
维度 | Level | 大约 60 M x 256 x 60 M 个方块 | 文件夹级存储的单位 |
区域 | Region | 32 x 32 个区块 | 文件级存储的最小单位 |
区块 | Chunk | 16 个区段,即 16 x 256 x 16 个方块 | 文件内部存储的最小单位 |
区段 | Section | 4 x 4 x 4 个区元,即 16 x 16 x 16 个方块 | 调色盘和光照储存的最小单位 |
区元 | Area | 4 x 4 x 4 个方块 | 生物群系生成的最小单位 |
方块 | Block | 世界构成的最小单位 |
接下来我会对这些结构进行详细的阐述。
生物群系与世界生成
世界生成
世界生成(World Generation, or WorldGen for short)是我的世界的一个重要内容。Minecraft 在发展,世界生成的代码却在很长的一段时间里没有发生太大的变化,而 1.13 正是对这一切进行变革的一个版本。那么为什么我们需要推翻一个使用这么长时间的、看似并没有太大问题的世界生成机制呢?我们为什么要这样变?这样变又有什么好处呢?
——Yaossg《浅析1.13世界生成》
世界生成是我的世界的一个重要内容。Minecraft 在发展,世界生成的代码却在很长的一段时间里没有发生太大的变化,而 1.13 正是对这一切进行变革的一个版本。在之后各个版本的世界生成中,1.13 版本的核心价值一直在不断地体现。这就是为什么会有这篇文章:因为这是一次划时代的更新。本文从世界生成的各个方面,逐一探讨其中的奥秘,揭开新版世界生成神秘复杂的面纱。
——Yaossg《浅析1.13世界生成》摘要
世界生成(World Generation, or WorldGen for short)是我的世界的一个重要内容。1.13 重构以来,世界生成的代码在不断地发生变化。尽管如此,仍然有完全不同于其它版本的大改出现——作为分水岭,划时代的 1.17-1.18 更新让我们驻足 1.16。本文就主要围绕这个版本展开。
实际上,世界生成的奥秘无穷,神秘而复杂。要想深入分析世界生成,就必须从它架构说起。
世界生成的基本架构
这次Mojang代码的重构采用了全新的设计模式,增加了代码的可扩展性,主要体现在:
- 将世界生成的功能被集中在了区块生成器和生物群系两部分上面,而不是离散在方方面面,更便于对代码之间的关系进行分析。
——Yaossg《浅析1.13世界生成》
剖析世界生成的结构,可以从顶层和底层两个角度观察:
-
从上往下看,维度决定了世界生成,实际上就是由它提供的区块生成器和生物群系来源。
-
从下往上看,区块、区元承载了世界生成,构成了整个世界。
两者相辅相成缺一不可,共同构建 MC 世界生成的体系。
接下来,我们重点介绍生物群系知识,来探究生物群系在世界生成中的重要地位。
生物群系的基本概念
如果你不知道什么叫作“生物群系”,我可以告诉你,它是游戏中用于设置何种地表(沙子?还是草?),是下雨还是下雪,长什么树,以及允许何种动物生成的气候区。
——Jens "Jeb" Bergensten
曾几何时,混沌初开 方块大陆上的生物们还在试图理解「为啥周围都变方了」 很快他们注意到了这个大陆上的奇妙的气候 特定的区域的气候是常年不变的,但降水却是随机的 相邻的两块区域的气候基本上是相近的,但有时候也会有雪地连着沙漠的情况 这些生物们开始慢慢适应这奇妙的环境,并最终与这奇妙的环境融为一体
——3TUSK 关于生物群系的诠释
生物群系(Biome) 是描述游戏中不同位置特定属性的享元(Flyweight),它决定了游戏中的自然环境。是的,这个世界无处没有生物群系,这个世界就是由生物群系组成,这个世界的万物包含在生物群系之中。每当加入新的生物群系时,我们都会为之侧目,因为我们知道,这不单单是加入了一个生物群系,更是加入了一个完整的系统。
生物群系——生成区块的第一步
Minecraft会首先获取当前区块下所有的生物群系。
——ustc-zzzz《答知乎提问:Minecraft 的地形生成算法是什么?》
在 1.13 以后,不再这样笼统的区分,每个区块都拥有一个状态,每个状态都表示他已经完成的某一个任务,并提供下一个任务,以此实现异步的区块生成。
(第一个状态)基础(base)状态的第一步:生物群系将会被选定
——Yaossg《浅析1.13世界生成》
在代码层面,生物群系的世界生成的影响也十分深远。正如小标题所言:无论是 1.13 之前还是之后,生成生物群系都是生成区块的的第一个步骤。这样的特殊位置也就意味着,要研究世界生成,第一步就是研究生物群系。
而正是这一点,昭示了生物群系在世界生成中如此特殊的地位,本文的标题和主旨也由此而来。
生物群系的历史回顾
在历史上生物群系发生了许多重大的变迁。下面我们一起走进历史,领略一下过往的经验与教训。
这一部分资料部分整理自 Minecraft 官方维基百科
Alpha 1.2
生物群系正式加入,主世界有 10 种,下界 1 种,2 种未使用。在这个版本中,温度和降水量的概念已经有所体现。生物群系的布局根据温度和降水量有关,因此不会出现突兀的温度变化。但是也由于温度和降水量基于噪声算法,导致你会看到犬牙交错的边界,而非平滑的过渡,因此可以认为边缘宏观平滑,微观破碎。同时由于这个版本的生物群系生成的普遍较小,导致破碎的根绝更为明显。丘陵、断崖、海洋、沙滩并非作为生物群系出现,而是用算法在生成好的生物群系上进行的二次创作。
我相信这样的破碎可能不会适用于拥有大好风光的主世界,但是如果是一个本来就混沌、荒芜的世界呢?谁能想到,多年之后,这样的生成方式经过一些改进,被用于下界的生物群系生成?
Beta 1.8 - 1.1
生物群系重写。采用了基于分形(Fractal)的算法,这实际上奠定了长期以来世界生成的方法,一直沿用至今。生物群系变得更大,海洋变得更广更深。河流会在生物群系里或者生物群系之间流动并注入海里。河流、丘陵、山地、海洋、沙滩也先后成为生物群系。除了原有的山地(Mountains),还加入了各色的丘陵(Hills),积雪的生物群系又被重新加入。相邻生物群系的温度降雨量的渐变不再体现,常有河流间隔。此外水和植被的颜色有了平滑的过渡。因此可以认为边缘宏观破碎,微观平滑。
1.2
采用了全新的世界格式 Anvil。 与过去不同的是,生物群系信息不再动态计算,而是被储存在了区块文件中:世界一旦生成,这一数值就不会发生改变。这种做法的提高了游戏运行效率,当然这也给外部修改提供了可能。
加入了丛林,预留给树木的四个数据值占满了,大家都以为树木“齐了”,以后还会有更多树吗?
1.7
迪斯科山!(Jeb 语) 平顶山的奇丽景色可能让人第一时间让人关注到这次更新上来。
此次更新加入的生物群系比以前所有版本加入的生物群系加起来还要多,引入了大量的变种生物群系。变种是多样化生物群系的好手段。比如在 1.6 时移除的沙漠湖泊,被作为沙漠的一个变种——一种带湖泊的沙漠重新加入。
相邻的生物群系会尽可能的避免突变(从最热到最冷,从最干到最湿),宏观上平滑度有所增加。但也只是尽可能的,所以也比较有限。
移除了过多的海洋、加入了深海。更新前的几乎无尽的海洋不复存在,世界超过半数面积变为了陆地,为新加入的生物群系提供了空间。而深海的加入使海洋的层次更为分明。更新前,玩家分明是生活在一座座被无限大海洋的包围的巨型岛屿上的;更新后,反而成了海洋破碎分布在大陆之间。
重磅回归的热带草原上长着新加入游戏的树木。新树木,Mojang 开了几个新的类,分配了几个新的 id。新沙石,方块好说,台阶却满了,于是又给红砂石台阶分配了新的 id。而在开发平顶山的时候,Mojang 发现之前地表构造器的完全无法复用,索性写了一个完全不同于其他所有生物群系的。
附录里收录了我还原的当年维基百科上挂过的一张表。当时这样的安排可以认为是故意的——显然在 Mojang 看来,获取某个生物群系的变种只不过是 +128 那么简单罢了。新生物群系的命名也极度诡异,诸如M、F、+此类莫名其妙的记号,后来到了扁平化的时候背悉数消灭(但到了最后还是留下了一个+),就连标志性的平顶山也被重命名为“恶地”,让很多人误以为是机翻。
从我前面的描述,你可以感受到的是一副宏伟的画卷在逐步展开,可当你驻足观望,不难发现一片盛景之下潜伏的是无处不在的种种危机。扁平化呼之欲出,重写的腥风血雨正在降临。
1.13
1.13 Mojang 做出的努力使我眼前一亮,世界生成可以说是最重要的一部分,Mojang 迈出了第一步,希望这不是最后一步。
——Yaossg《浅析1.13世界生成》
扁平化(The Flattening),世界生成重写。详见前作《浅析1.13世界生成》
末地的生物生物群系不再全是 sky 扁平化后:the_end,在末地外岛,不同的地形对应了不同的生物群系,这也就贯彻了生物群系的设计初衷。与主世界的层层叠加不同,末地生物群系的生成比较简单。
水域更新让海洋拥有了温度,根据噪声算法来决定。温度不同——生物群系不同,这贯彻了生物群系的设计初衷。
这次Mojang代码的重构采用了全新的设计模式,增加了代码的可扩展性,主要体现在:
- 将世界生成的功能被集中在了区块生成器和生物群系两部分上面,而不是离散在方方面面,更便于对代码之间的关系进行分析。
——Yaossg《浅析1.13世界生成》
生物群系开始把握了世界生成的大量内容。毫无疑问,五彩斑斓的世界正是生物群系所赐。同时我们也应该注意到区块生成器在协调区块与生物群系生成关系方面的作用。
删除了自定义世界类型,Mojang 说它一定会在将来的某个版本回归的。取而代之的自选世界,相比之下自由度少了很多,因而似乎不那么收欢迎——曾经沧海难为水嘛。
1.14
在 1.13 更改的基础上进行了强化。
重写了主世界的生成系统,最终不再是三个层,而是一个。不再使用数组,而是改用套娃的方法。
1.15
当然,生物群系和高度无关。
——ustc-zzzz《答知乎提问:Minecraft 的地形生成算法是什么?》
加入了生物群系的在 y 轴上的支持,并在末地和下界更新中被使用。当然了,主世界依然与高度无关。采用的新的生物群系缓存方式。
20w14∞
愚人节快照...无限的维度、无限的生物群系?!有没有搞错?
看似是愚人节快照,实际上透露了接下来最大的两个方向——自定义维度和生物群系。这项内容的准备工作持续了好几个大版本:当你读到世界生成代码的时候,发现的到处都是的 Deserializer
,看似莫名其妙,细品实在不简单。
1.16
我相信这样的破碎可能不会适用于拥有大好风光的主世界,但是如果是一个本来就混沌、荒芜的世界呢?谁能想到,多年之后,这样的生成方式经过一些改进,被用于下界的生物群系生成?
——Yaossg《再析世界生成:生物群系》生物群系的历史回顾:Alpha 1.2
下界更新!引入了气候参数,用来生成下界的生物群系。等会……这方法,好像在 Alpha 1.2 见过?
自定义维度!当年 Mojang 删除自定义世界类型的时候,曾放言说它一定会回来的。它终于回来了。
1.16.2
1.16.2,又名 1.17
——Yaossg 看代码前调侃道
1.16.2 之于 1.16,相当于 1.15 之于 1.14
为什么这么说?因为 1.16.2 的更改,太像一个 major version 了。
- 新的生物
- 资源包版本提升
- 命令、数据包修改
- 自定义生物群系等(也引发了生物群系相关代码大改)
好家伙,估计是 1.15 被骂惨了才让 1.16.2 承受了不该承受的重量。
最值得本文注意的是自定义生物群系了:情理之中,意料之外。本来以为会是 1.16 的内容,结果 1.16 没有。是不是 1.17?也不是。1.16.2 就离谱。“小版本,大更新”说的就是 1.16.2。前面也提到过:它对我的教程的打击是毁灭性的。
最终,我的教程是基于 1.16.5 的。不必继续往下看了。某种意义上,也不必往回看了。
1.17
空白
什么,为什么是空白?这指的是 1.17 的更新内容,不是我的文章。这是一个巨大的落差,他们几乎删除了 1.17:真正的内容还要等 1.18。
好像当时不少人并没有意识到问题的严重性...算了吧,无所谓。
1.18+
Mojang 最终放弃了这套系统。又是一个新的开始!
我很难想象我亲历了这一切;此后之事,就留待后人罢。
生物群系属性
生物群系之所以不同,是因为他们有不同的生物群系属性。生物群系属性是表征生物群系特性的数据。
生物群系的属性主要有两个方面的作用:
- 提供直观的视觉体验、听觉体验等。——客户端
- 提供生物群系生成、世界逻辑运转的依据等。——服务端
而这些属性,大部分都可以在自定义生物群系时指定。
原版生物群系属性的具体值,详见本文附录。
下面我们概述生物群系的所有属性,并详细介绍一些重要的属性。
类别
类别(Category) 描述的是某一类生物群系所拥有的共同特征。例如,当我说“恶地”的时候,浮现在你脑海里的,绝对不止一个生物群系;那具体有几个恶地、分别叫什么呢?可能就会难倒不少人了。但有一点毫无疑问:反正它跟恶地是一类嘛,这也就是类别的意义。
类别在许多领域都有应用,这里没法一一举例了。而它在生物群系生成中扮演的重要的角色,将在主世界生成中的相似生物群系中大显光彩。
气候
气候(Climate) 是生物群系贯穿始终的最重要的属性,是对生物群系特征的直观感受的概括。粗略分为温度和降水量两个部分。
温度、温度修饰符
温度(Temperature) 这一属性代表了这个生物群系的默认温度。默认温度常常参与的是未知空间上下文的情况的计算。而具体到每个方块上的温度,这里不妨叫当地温度。当地温度是默认温度先经过温度修饰符(TemperatureModifier) 进行修饰,再根据高度调整得到。
温度修饰符 | 生物群系 | 特点 |
---|---|---|
none | 其它生物群系 | 直接返回默认温度 |
frozen | 冻洋、封冻深海 | 根据噪声进行修饰 |
相比而言,高度占主导因素:在 y > 64 后,当地温度会逐渐减小。
降水量和降水类型
降水量(Downfall) 代表了这个生物群系的具体湿度。主要用于决定草的颜色,此外降水量大于 0.85 的生物群系的对火的蔓延有抑制作用。而与降水量定量不同,降水类型(Precipitation) 更多的是定性。
以上属性造成影响如下表所示:
当地温度 | 降水类型 | 影响 |
---|---|---|
- | snow | 雪地兔兔~ |
大于等于 0.15 | rain | 判定为下雨 |
大于等于 0.15 | - | 不会有雪、不会有冰 |
小于 0.8 | - | 雪人走过的地方会产生雪片 |
大于 1 | - | 雪人受伤 |
此外,温度和降水量还会参与到植物颜色的渲染等。
地形
地形 是生物群系生成中的重要特征,与地形的噪声密切相关。
深度(Depth) 描述的是生物群系的平均海拔。注意这是一个平均值,比如若该数值为 0,虽说理论上是和海平面齐平,但其实相当低洼,因为真正的高度会根据噪声和规模有所起伏。
规模(Scale) 描述的是生物群系的高度差值。这个数值越大,地形的高低起伏越大。放大化(Amplified) 世界类型、旧版自定义(Customize) 世界类型的“山脉狂魔”预设就是扩大了这个数值。放大化在此之外还做了一些特殊处理。
如果说最初的噪声是随机变量 ,深度是 ,规模是 ,那么最后地形高度的随机变量 即为:
世界生成设置
世界生成设置(GenerationSettings) 包含了一些与世界生成有关(主要是地形和特性方面)的内容,包含
- 地表构造器(SurfaceBuilder) 地表的大的风貌构造,例如恶地的风貌就比较特殊。
- 镂空器(Carver) 凿空方块,添加空气或者流体。
- 特性(Feature) 花草树木,矿石矿井等都是特性。
- 结构(Structure) 特别的,如村庄、要塞、废弃矿井等都属于结构。
- 花(Flower) 一格高的,可自然生成的花。
花
(双手托起头)我是一朵花
——Yaossg 早年迷惑行为
花(Flower) 种类繁多,极大的美化了 MC 的自然景观。在生物群系属性的世界生成设置中单独列出的,是一格高的,可自然生成的花。
花有两种途径获得,一种是世界生成时自带的花,另一种是使用骨粉对草方块右键得到的草丛中会有少许的花。这两种途径获得的花是一致的。
在绝大多数生物群系中,都只会生成虞美人和蒲公英(两者生成的权重为2:1),而沼泽只会生成兰花。蘑菇岛和恶地不生成花。
但是在一些特殊的生物群系中,如(普通或者向日葵)平原、繁华森林中,会根据生成坐标的当地噪声,由方块状态提供器(BlockStateProvider) 从可选的方块状态中选择一些花进行放置。由于与当地噪声直接相关,这样生成出来的花,即使在随机之中也存在一些规律。这种规律在离散的花丛之间无法观察到,我们不妨来看看如果整个屏幕都被花铺满会是什么样子。
平原花
平原花主要分为两类——郁金香和非郁金香。
只有在当噪声低于阈值时,即为郁金香,每种颜色的郁金香的概率是相等的。可以发现,这样就构成了一片片郁金香区域。只有区域之内才可以是郁金香。
其它地方生成花朵种类和对应的权重如下表所示:
花 | 权重 |
---|---|
蒲公英 | 2 |
虞美人 | 1 |
茜草花 | 1 |
滨菊 | 1 |
矢车菊 | 1 |
实际上,对历史略知一二就不难注意到,后四种花(除了后来加入的矢车菊)从前使用的是同一个数据值,姑且叫做“虞美人系”,无论是前面提到的“绝大多数生物群系”,还是繁华森林,“虞美人系”与蒲公英生成权重的比值都是2:1。只不过在繁华森林中虞美人有了更多“变种”罢了。
森林花
如果说平原花还带点随机的影子,森林花属于是直接不演了——只要生物群系是繁华森林,确定位置的花是确定的。由于是由噪声决定的,所以可以非常明显的看出条带状的边界,和他们共同形成的“阶梯”。这其实给我们一点启示,我们可以根据我们对不同颜色染料的需求,选择在特定的区域刷花。
噪声从低到高,它们分别是:
- 蒲公英
- 虞美人
- 绒球葱
- 茜草花
- 红色郁金香
- 橙色郁金香
- 白色郁金香
- 粉红色郁金香
- 滨菊
- 矢车菊
- 铃兰
其实我后来根本不想写这一小节了,完全是依仗不舍得丢两幅图给的动力,真可谓是为了这碟醋才包的饺子。
生物生成设置
生物生成设置(SpawnSettings)
- 生物生成概率:数值越高,自然生成的动物就越多。寒冷地区这个值偏低。
- 候选生成生物和生成密度。(在主世界的体验并不明显,但下界的区别是非常明显的)
- 是否适于玩家出生。(玩家会尽可能优先在这些生物群系中出生)
气氛
生物群系特效(Effects),主要带来视听体验,营造气氛(Ambience)。
- 雾的颜色
- 水的颜色
- 水中雾的颜色
- 天空颜色
- 树叶颜色
- 草颜色
- 草颜色修饰符
- 粒子效果
- 循环音效
- 环境音效
- 额外音效
- 音乐
并非所有生物群系都有上述特效。
区块
I used to rule the world 我曾统治这世界
Chunks would load when I gave the word
区块会在我的命令下加载世界听我号令运转——CaptainSparklez《Fallen Kingdom》
区块(Chunk) 作为一个符号,不得不承认,虽然并不总是如此,但它的的确确成为了世界生成乃至世界的象征。无论是否与世界生成有关,在各种场合,区块这个概念总是被经常提及——好像世界就应该这么分一样。确实,世界生成确实也应该从它讲起。
区块生成器
区块生成器(ChunkGenerator),是之前已经提到的,两大非常重要的世界生成提供者之一。
区块生成器分为三种:噪声区块生成器(NoiseChunkGenerator)、平坦区块生成器(FlatChunkGenerator)、调试区块生成器(DebugChunkGenerator)。在这里,我们主要介绍噪声区块生成器。另外两个区块生成器参见第三章。
噪声区块生成器主要负责(不分先后):
- 要塞的选址
- 生物群系的选择
- 应用镂空器
- 特性(包括结构和花)的放置
- 地表和基岩的放置
- 生成原生生物(伴随世界生成产生的生物)
期间与生物群系来源的合作十分紧密。
区块状态
这些功能在区块生成的不同状态时被调用,前作已经有过介绍。1.16 中这些状态被细化成了下面 13 个状态:
状态 | 颜色 | 高度图类别 | 范围 | 等级 |
---|---|---|---|---|
empty | 5526612 | 甲类 | -1 | 44+ |
structure_starts | 10066329 | 甲类 | 0 | 36-43 |
structure_references | 6250897 | 甲类 | 8 | |
biomes | 8434258 | 甲类 | 0 | |
noise | 13750737 | 甲类 | 8 | |
surface | 7497737 | 甲类 | 0 | |
carvers | 7169628 | 甲类 | 0 | |
liquid_carvers | 3159410 | 乙类 | 0 | 35 |
features | 2213376 | 乙类 | 8 | 34 |
light | 13421772 | 乙类 | 1 | |
spawn | 15884384 | 乙类 | 0 | |
heightmaps | 15658734 | 乙类 | 0 | |
full | 16777215 | 乙类 | 0 | 33- |
区块状态仍然分为两类,只有最后一个状态是存档区级(Level Chunk) 用于正常的游戏逻辑,而前面的状态都是原型区块(Proto Chunk) 专用于世界生成。
高度图类型分为两种,决定了出于当前状态下需要更新的高度图有哪些。可以看出,甲类的均为世界生成专用的高度图,乙类均为正常游戏逻辑使用的高度图。
高度图类别 | 高度图 |
---|---|
甲类 | OCEAN_FLOOR_WG, WORLD_SURFACE_WG |
乙类 | OCEAN_FLOOR, WORLD_SURFACE, MOTION_BLOCKING, MOTION_BLOCKING_NO_LEAVES |
区块加载与世界生成
上表中的范围(Range)决定的是,某区块进入该状态时,需要加载的周围区块的曼哈顿半径 r
(该半径下构成的正方形区域边长是 2 * r + 1
)。-1 无意义。
那么所加载的区块至少需要到那个状态呢?这与区块的等级(Level)有关。我们可以从世界生成加载界面中的图案中得到一些启示。在世界生成加载界面中,每一个生物群系状态对应一种颜色。图片正中心像素的 level = 22,向外每延伸一格,level 加一。颜色和等级对应的区块状态见上表。
(图中的橙色青色为助于识别的辅助线、辅助点和长度标记,实际界面中并不存在)
这张图并非总是那么规整,实际上,当出现结构需要加载的时候,level 会被故意提高,保证结构的完整的加载。
触发区块 level 改变的不仅仅只有创建世界。实际上,Mojang 引入了票(Ticket) 的概念来管理区块加载。欲知更多相关内容,可以转阅海螺的文章(链接见附录)
Configuration 设计模式
无论是特性、放置器,还是镂空器、地表构造器(X = Feature, Placement, Carver, SurfaceBuilder
),都存在 X ~ XConfig
的设计模式,即 X
提供逻辑,XConfig
(译作 X 的配置)提供逻辑需要的参数。把 X ~ XConfig
绑定在一起的类被称为 ConfiguredX
(译作 已配置的 X)。其结构如下图所示。
接下来不分先后的介绍这四个在区块和生物群系生成中的特点和作用。
镂空器
镂空器(Carver),又译作雕刻器,是用来为世界打洞的。这也是为什么我把它译作镂空器的原因。以打洞结束之后填充物的不同,可以分为空气镂空和液体镂空两类。主世界的普通和水下洞穴和峡谷,下界的洞穴,都是它完成的。
特性放置
特性(Feature),又译作地物,是世界生成的基本元素。特性的装饰(Decorate)遵守一个基本的次序,如下表所示。
英文 | 翻译 | 内容 |
---|---|---|
RAW_GENERATION | 原生生成 | 末地小岛 |
LAKES | 湖 | 湖和岩浆湖 |
LOCAL_MODIFICATIONS | 本地修饰 | 地面苔石、冰山、玄武岩柱 |
UNDERGROUND_STRUCTURES | 地下结构 | 略 |
SURFACE_STRUCTURES | 地面结构 | 略 |
STRONGHOLDS | 要塞 | 要塞 |
UNDERGROUND_ORES | 地下矿石 | 主世界的矿物 |
UNDERGROUND_DECORATION | 地下装饰 | 主要为 下界的矿物 |
VEGETAL_DECORATION | 植物装饰 | 花草树瓜藤竹菌 |
TOP_LAYER_MODIFICATION | 顶层修饰 | 海面的冰,虚空平台,超平坦层 |
虚空平台和超平坦层与超平坦有关,参见 3.1 节。
特性的放置还与放置器(Placement) 有关。特性只决定特性本身的形状,而放置器决定放置次数或概率和位置的选择。每一层放置器都会套上一层特性,以便于下一个放置器使用。最后包装好的特性会被分门别类存在生物群系对应的装饰阶段的列表里。一般而言,最终包装好的特性如下图所示。
地表与基岩构造
地表构造器(SurfaceBuilder) 是营造地面景观最直接的工具。不同于特性的特定而离散,地表构造器的工作广泛而连续。须知,世界原本只有贫瘠的石头水和空气,最经典的地表——一层草块三层土——便是这个时候建造的。
过去基岩的添加也是这个阶段所为。但是这其实是比较荒诞的,也就是,如果你覆盖了地表构造,就必须兼顾基岩生成。更加让人感到啼笑皆非的是,基岩生成是从 255 格开始向下判定 😅!现在基岩生成独立出来了,也不会从那么高就开始判断生成基岩了。
原生生物生成
原生生物指的是世界生成之后就已经在世界中的生物。原生生物的生成次数 符合几何分布,即 。其中 是生物群系生物生成信息中的生物生成概率。每次生成会根据权重随机在候选列表中选一个进行生成。
区段
区段(Section) 是区块沿 y 轴切割得到的 16 等分之一。区段是区块文件中存储方块状态、亮度的最小单位。这里我们主要介绍方块的储存。
调色盘(Palette) 是方块状态储存中用到的一种压缩方式。如何理解调色盘呢,我们可以从字面意思来类比说明。
对于一幅不透明的像素画,我们当然可以选择用一个二维数组来储存每个像素的颜色值。以 RGB 为例,这需要至少 24 bits 才能储存一个像素。对于一幅稍大点的画作,这样的储存方式将会大的惊人(例如一幅 1920 x 1080 的高清图片,至少需要 47 MB 的空间)。
类似的,我们也可以用一个三维数组来存每个位置的方块状态,但是储存一个方块所需要的空间可远远不止 24 bits。移除方块的数字 id 之后,需要使用完整的名字(例如"minecraft:stone"
)和表示状态的字符串到字符串的哈希表(例如{"facing": "north", "waterlogged": "false"}
)来表示一个方块状态,这样的话表示一个方块状态所需的内存将非常的大!更别提世界中不可胜数的方块!
如何压缩呢?不难想到一幅画里的不少像素的颜色其实是一样的,既然如此,我们可以设立一个调色盘,用给图片里所有使用的颜色编号,然后在二维数组中使用这个编号来代替这个颜色。储存编号所需要的空间取决于这幅画有多少种颜色,而这几乎肯定是少于 24 bits 的(例如一幅 1920 x 1080 的高清图片,里面用了 60 种颜色,那么储存一个编号就需要 6 bits,加上一个字典,大约需要 12 MB 的空间)。
类似的,在储存方块的时候,我们也可以这么做。用一个调色盘,或者说字典,给所有区段里的方块编号,然后在三维数组中用这个编号来代替这个方块。实际上,换成整数之后,Mojang 采用了一种比三维数组更紧凑的方法来存储编号。因此,虽然压缩图片的效果不算明显,但是对于方块状态的情形就完全不一样了。这样的压缩算法可以高效使所需空间大大减少。
值得注意的是,在调色盘的例子里面,当颜色数量非常大(以至于非常接近 224)的时候,储存编号和储存颜色所需空间相仿,导致调色盘压缩失灵——你甚至额外还存了一个巨大的调色盘。但是对于区段来说,这几乎不会成为问题——调色盘所占用的额外空间,在储存方块所需空间面前几乎可以忽略不计。何况在一个小小的区段里塞下数千个不同的方块状态,这本身就非常困难。
空的区段(即,全是空气的区段)不会被储存,因此你不需要担心每个区块上空那些区段会不会占用很多空间,同时你也能够找到某些服务器会限高的部分原因了。
区元
然而,除了区块,其实还有很多有关世界生成的大小分块。Sebrarin 的说法并不严格,因为:区元(Area) 是 MC 生成生物群系的最小单位。
区元的来龙去脉
在 1.15 之前,每个区块的生物群系被储存在一个大小为 16 x 16 的数组里面,储存了区块内每一个 1 x 1 x 256 的生物群系。
1.15 更新之后,每个区块的生物群系被储存在一个大小为 4 x 4 x 64 的数组里面,数组里的每一个元素,代表的是一个区元里的生物群系,至于每个方块的生物群系,会动态地计算。
尽管加入了高度支持,但目前所有的生物群系来源生成得到的 x 和 z 相同而 y 不同的区元仍然总是包含相同的生物群系;真正高度上的差别是体现在最后打磨后的,方块尺度上的。不过希望还是要有,我们离 3D 生物群系的支持实际上只差一步之遥。在多重噪声生物群系来源代码中,有一个 boolean 值,直接决定是否让 y 参与计算。目前它_永远_为 false,不过我相信一定不会_永远_为 false 的。(补:这一点在 1.17、1.18 中应验)
在接下来的叙述中,本文用(x: int, z: int)
这样的二元组来表示 x 和 z 相同的 16 个区元的坐标,其他(如方块、区块等)的坐标会另做说明。
区元命名杂谈
在最初翻译 Area 时,我不假思索的用了“区域”一词。这本来无可厚非,Area 最常见的翻译就是这个。然而当我意识到它与已有的一个概念——区域(Region),撞车的时候,已经有一段时间了。为此,我当时不得不在文章里写到
注意:Region(32 x 32 区块)的翻译也是“区域”。本文无特别说明区域均指 Area,请勿混淆。
后来觉得这样欠妥,决定还是换个名字。作为生物群系生成的最小单位,作为世界生成中用到的最小的方块集合,也就是最小的一个“区”的概念,“元”一字它当之无愧。
不过此名字一出,便遭各路嘲讽——这分明就是“屈原”嘛。是这样的,对此屈原也很恼火。我最喜欢屈原的一句话是
路漫漫其修远兮,吾将上下而求索
——屈原《离骚》
被嘲讽的可不仅是名字:说实话,写这个教程,何尝不是一个“上下求索”的过程呢?
举目见日,不见长安
——晋明帝语,《世说新语》
梦想遥不可及,现实却触手可得,然而有时候现实的残酷反而把人推向梦想那一边。可梦是做不完的,无尽的梦终究会成为无谓的梦。有时候香肠是一位理想主义者,幻想着心中的长安。但有时候又是一位现实主义者,因为永远也到不了长安。香肠正陷于这无尽的矛盾之中。
香肠的梦是什么?本节内容结束
维度与生物群系
维度
原版有三个维度,他们是主世界(Overworld)、下界(The Nether)、末地(The End)。
维度决定了世界生成。三个维度,风格迥异,得益于他们提供的不同的区块生成器和生物群系来源。
上一节已经介绍了区块生成器和区元。下面介绍生物群系来源。
生物群系来源
生物群系来源(BiomeSource) 是区元生物群系的提供者。值得注意的是,1.16.5 的原版生物群系来源在生成生物群系的时候,不同 y 的区元的生物群系总是相同的。因此,在省略 y 的情况下,它的主要功能可以被描述为一个函数:
BiomeSource: (x: int, z: int) -> Biome
下表列出的是 1.16 所有的生物群系来源。
生物群系来源 | 中文 | 参数 | 特点 |
---|---|---|---|
checkerboard | 棋盘 | 生物群系池、规模 | 生物群系呈现棋盘状分布 |
fixed | 固定 | 单个生物群系 | 全世界生物群系相同,用于超平坦、自选世界。 |
vanilla_layered | 原版层 | 生物群系池、种子、巨型生物群系 | 用于默认主世界的生物群系生成 |
the_end | 末地 | 生物群系池、种子 | 用于末地的生物群系生成 |
multi_noise | 多重噪声 | 生物群系(池、属性、参数)、种子 | 用于下界的生物群系生成 |
生物群系缓存
每个区元生物群系只生成一次。在生成之后,每个区元的生物群系将会被缓存在区块的一个数组中。一个区块有多少个区元,就有多少个整数。注意这里仍然使用整数,而不是生物群系名字,在 1.18 之后才终于采用了与方块类似的调色盘来储存。
这样的缓存可以避免生物群系不断地生成,节省了不少的时间。但同时,又不得不向空间妥协——这样的缓存单位是区元而不是方块。
生物群系访问
那么,同一个区元里的不同方块的生物群系是什么?从缓存到实际访问到的方块的生物群系,还有最后一步要走。
粗略访问
首先,粗略(Rough)地说,就是这个区元的生物群系嘛。毕竟就算有差,差的也不大。实际上,生物生成的时候参考的生物群系就是粗略的生物群系。
但是如果硬要精确到一个方块,粗略的后果就是边缘呈四格宽的锯齿状,太怪了。所以我们需要有所模糊。
模糊——实时放大化
为了利于讲解,这里我们考虑二维的情形。实际上模糊是三维上进行的。
如图所示,黑色线条是区元的边界,每个区元被灰色的线等分成四个部分。区元大小是 4 x 4 方块,那么每个部分实际上就是 2 x 2 方块。现在像图中这样上色,相同颜色的部分里的每个方块的生物群系是这个颜色所占据的四个区元中的其中一个的生物群系。离哪个区元的中心越近越可能是那个区元的生物群系。
这种模糊实际上是一次特殊的实时放大化。这次放大化,变的不是生物群系的尺寸,而是生物群系的精度,放大的不是两倍而是四倍,不是在世界生成时就算好,而是实时进行运算。有关放大化的内容,可以参见 2.2 节 的内容。
维度与模糊策略
不同维度的模糊策略不同,例如主世界在模糊的时候,会故意传入 y = 0,造成所有高度的生物群系都仿佛(as if)是 y = 0 上的生物群系。因此在主世界,不同高度的方块生物群系总是相同的。其他维度没有这样的处理,因此会存在高度不同的时候生物群系存在微小的差异。
正如前文所言,1.16.5 中,不同 y 的区元的生物群系总是相同的。因此下界和末地只能造两个生物群系的水平边缘上才能找到 y 不同时生物群系不同的情况,且这种情况并不是由 y 上区元生物群系的差异引起的(因为他们总是相同的)。但是需要申明:这样的模糊策略实际上是三维的。这也为 1.17+ 的更新做好了准备。
主世界与异界
在了解了本章的基本知识之后,我们可以深入分析 MC 的生物群系生成了。
接下来的两章里,我们会分别分析主世界和异界的生物群系生成。从不同维度观察维度的生存。
主世界
主世界(Overworld) 是玩家出生、长期生存的维度,也是生物群系资源最为丰富的维度。主世界的生物群系究竟是怎么生成的呢?
世界类型
在创建世界的时候,我们选择世界类型(WorldType) 实际上最主要的目的是指示主世界的区块生成器和生物群系来源。
气抖冷:实际上,所有世界类型的下界和末地的生成都是一样的
世界类型 | 英文 | 区块生成器 | 生物群系来源 |
---|---|---|---|
默认 | Default | 噪声 | vanilla_layered |
超平坦 | Superflat | 平坦 | fixed |
放大化 | AMPLIFIED | 噪声 | vanilla_layered |
巨型生物群系 | Large Biomes | 噪声 | vanilla_layered |
单一生物群系 | Single Biome | 噪声 | fixed |
洞穴 | Caves | 噪声 | fixed |
空岛 | Floating Islands | 噪声 | fixed |
调试模式 | Debug Mode | 调试 | fixed |
fixed 的经典代表是超平坦。1.13 删除自定义世界时,就加入了自选世界,而后被拆分为了单一生物群系、洞穴、空岛三种不同的世界类型,但都提供了选择一个全世界共同生物群系的选项。而调试模式中生物群系毫无意义。我会下一章详细介绍这种生物群系来源。
用层生成的主世界
vanilla_layered,顾名思义就是~~层叠香草~~原版分层。这里译作“原版”或许有些奇怪,原版还会称自己原版?我想这应该是时间上的原版、体验上的原版。理由很简单:fixed 的世界类型明显都是用来整活的。而默认是最经典的世界生成,曾经是唯一的世界类型。放大化、巨型生物群系可以认为是它的变种,他们才是经典 mc 世界生成的主力。
本章将主要围绕它来展开。
层
对于(主世界)生物群系的获取,Minecraft代码是使用一种类似于Decorator模式的方式提供生物群系的信息的。换句话说,它就像流水线一样,每个环节都对当前生物群系进行一次处理。
——ustc-zzzz《答知乎提问:Minecraft 的地形生成算法是什么?》
层(Layer) 是生成生物群系的重要工具。
世界好比一块画布,“层‘就是画布上涂刷的一层层的颜料。最后得到的画作便是每一层颜料共同作用的结果。
在这里,我们说的层,不仅仅是层本身,还是本层内容和上一层内容的混合方式。不难看出,每一层都可以描述为一个函数,从上一层的像素映射到本层的像素。
像素是图片的最小单位,而生物群系生成的最小单位是区元。所以本文介绍的层是上一层区元到本层区元的生物群系的映射。
层的历史回顾
层的历史,主要是两个阶段。本文介绍的是 1.13+ 的层的内容。
1.12-
层主要以数组形式存储。
层会不断在上一层的数组上修改,如果是放大化操作,就会导致 3/4 的数据直接丢失。
即对于 n
个区块,每个区块有 m
层,则共求值 n * m
次,即每层求值次数相同。
1.13+
层主要以哈希表的形式存储
每一层根据上一层的表延时计算。
越靠近原始层的层求值次数越少。
层的特点
元
我们可以大致把层分为三种:原始层、一元层、二元层。
原始层就是画布最初的样子。可以被描述为
OriginLayer: (x: int, z: int) -> Biome
一元层就是在现有层的基础上再加一层。可以被描述为
UnaryLayer: (x: int, z: int, parent: Layer) -> Biome
二元层能合并两个画布上的内容,可以认为是两个层的叠加。可以被描述为
BinaryLayer: (x: int, z: int, mother: Layer, father: Layer) -> Biome
注意到,所有层都有x, z
这样的二元组来表示 x 和 z 相同的 16 个区元。
远亲不如近邻
一个区元的生物群系,往往与周围的区元的生物群系有关。层为了处理周围的生物群系,往往也需要周围的生物群系作为参考。我们大致可以把这样的参考关系分为三种:
普通层:不参考附近的生物群系
車层:参考东南西北四个方向的区元的生物群系
象层:参考东北、西北、东南、西南四个方向的区元的生物群系。
下文会用四周(車)和四周(象)来区分这后两种情况。
三条线
主世界的生物群系就是用上面介绍的层生成的。根据层层叠加的关系,可以分为三条线,我把他们取名为main(主线)、river(河流支线) 和 ocean(海洋支线)。这三条线最后经过合并得到最后的一个层。
这里简述一下支线的大致情况:海洋支线极为简单,在创建之后简单处理后便直接与主线合并。而河流支线复杂绵长,且不仅最终层被合并到了主线,中间层也被主线用作参考,不过好在河流支线并没有反过来参考主线。
在每一段解释的前面都配有一张或者几张对应的图,图中的像素的颜色代表不同的含义,除了主线的最后一张经过打磨之后可以精确到每一个方块,每一张图的色块其实都是由 4 x 4 的像素组成的,代表了一个区元里的 16 个方块。所有图的大小都是 1600 x 900 也就是 400 x 225 个区元。所有的图都遵循着上北下南左西右东的原则。
主线
主线承载了主要生物群系生成任务。
第一阶段
原始层
主线的第一阶段并非用实际上的生物群系来标记区元的,而是用 1、2、3、4 来表示大陆区元的温度(分别表示炎热、温暖、凉爽、寒冷);海洋、深海和蘑菇岛仍然使用生物群系 id 表示(混合表示法)。
主线的原始层 90% 被海洋覆盖,剩下 10% 是炎热大陆,是随机分布的。特别地,区元0, 0
一定是炎热大陆。这就是你出生在大陆上比直接被扔进海里的概率高的原因之一(另一个原因是基于生物群系的出生点选择)
锯齿放大化,这是唯一一次又是使用锯齿放大化,而不是普通的放大化。
放大化
放大化(Zoom)将原本占有一个区元的生物群系扩散到 2 x 2 的四个区元。注意这个放大化和放大化世界类型没有关系,反倒是和巨型生物群系世界类型有关系,后面会提到。有的时候翻译总是那么捉弄人,就像上文提到的区域和区元一样,不是吗?
如图所示(m ≥ 0, n ≥ 0,负半轴与此图水平、垂直镜像),方格外的坐标表示的是放大化之后的区元坐标,方格里面标有坐标的表示的是放大化之前对应的区元坐标,表示与原该坐标对应生物群系一致。接下来游戏会分别生成 A、B、C 三个区元的生物群系。我们不妨把n, m
、n, m + 1
、n + 1, m
、n + 1, m + 1
的生物群系分别记为 a、b、c、d。A 的生物群系即在 a 和 b 中随机选择一个,B的生物群系即在 a 和 c 的生物群系中随机选择一个。对于 C 的处理,两种放大化各有不同。如果是锯齿放大化,则直接随机选择四个其中的一个。如果是普通的放大化,则按照少数服从多数的原则进行挑选,具体规则如下:
- 如果存在三个相同的(如 a = b = c),那么就选择这一种(a)。
- 否则如果存在两个相同的,且另外两个各自不同(如 a = b 且 c ≠ d),那么就选择这一种(a)。
- 否则就不存在“多数”了,所以只能直接随机选择四个其中的一个。
扩张
实际上扩张不总是陆地在扩张,如果有有兴趣的朋友可以去细致对比一下:有的时候是海洋扩张了。不过总体来看陆地扩张较多。
这种层在第二阶段也被使用,与第一阶段的混合表示法不同,第二阶段的幻数直接表示生物群系。
- 如果自己不是海,四周(象)其中一个是海,那自己就会变成海(除非自己是寒冷,就还是寒冷)。
- 否则如果四周(象)也是海,那自己就还是海。
- 否则变为炎热,有一定概率变为四周(象)的生物群系。
最后这一分支计算比较复杂,不便于口述,干脆直接代码奉上(其中context.nextInt(n)
生成一个位于[0, n)
的随机数,ceneter
为原区元)。
int i = 1;
int ret = HOT;
if (!isShallowOcean(northeast) && context.nextInt(i++) == 0) ret = northeast;
if (!isShallowOcean(northwest) && context.nextInt(i++) == 0) ret = northwest;
if (!isShallowOcean(southwest) && context.nextInt(i++) == 0) ret = southwest;
if (!isShallowOcean(southeast) && context.nextInt(i++) == 0) ret = southeast;
return context.nextInt(3) == 0 ? ret : ret == COLD ? COLD : center;
放大化。
三次扩张。如果看过 1.6 生物群系图的朋友应该能看出这其实就是整个世界的雏形了。从图可看出,所有海洋都是联通的,海洋总面积过半。
抽干海洋
这一层_可以认为_是在 1.7 加入的。不难看出,前面扩大陆地这么多次,不如这一次来的效率高。图可看出,与之前恰恰相反:几乎所有陆地都是联通的,陆地总面积过半
降温
将原本有的炎热大陆群系变为冰冷和凉爽,各有 1/6 的概率。剩下的 2/3 陆地还是炎热。
扩张
转化:凉爽化。
转化:温暖化。
转化:稀有化。
转化
这个名字非常平庸,我胡乱起的。主要原因是第三个和前两个几乎毫无联系。
凉爽化:如果自己是炎热,且周围(車)是凉爽或寒冷,就变成温暖。
温暖化:如果自己是寒冷,且周围(車)是炎热或温暖,就变成凉爽。
稀有化:如果自己不是海,就有 1/13 的几率进行不可描述之事:
value |= 1 + context.nextInt(15) << 8 & 0xF00;
其中 value 就是这个区元的生物群系 id。注意运算符优先级哦。可以看出,这个数字的高位被标记了一下。后面这个标记的数字会派上用场。我用另一种颜色的像素来表示这样的区元。
两次放大化
扩张
蘑菇岛
把 1% 的四周(象)也是海洋的海洋区元变为蘑菇岛区元。
加入深海
将四周(車)也是海洋的海洋区元变为深海区元。
河流支线:水源
第二阶段
生物群系层
生物群系层(BiomeLayer) 是最为关键的一层之一。这一次实际上选择了具体的生物群系。前面的海洋、深海、蘑菇岛不变,其余区元按下列规则选择生物群系:
- 先选择行:如果之前区元的低位不属于下表第一列中任何一个,即为蘑菇岛。
- 除寒冷之外,若之前区元高位有(转化:稀有化中提到的)数字,则取稀有列,否则取普通列。寒冷只有普通列。
- 行和列都确定了,选中格子后随机选择格子中的一个生物群系。后面括号里的数字代表权值,没有数字默认为 1
之前区元 | 普通 | 稀有 |
---|---|---|
炎热 | 沙漠(3) 热带草原(2) 平原 | 恶地高原 繁茂的恶地高原(2) |
温暖 | 森林、黑森林、山地、平原、桦木森林、沼泽 | 丛林 |
凉爽 | 森林、山地、针叶林、平原 | 巨型针叶林 |
寒冷 | 积雪的冻原(3) 积雪的针叶林 | - |
如果前面的区元图只是一块干巴巴的面包片,这一层浇上去的便是五彩的酱汁。
竹林
把 10% 的丛林变成竹林
两次放大化
加入边缘
加入丘陵
边缘(Edge)丘陵(Hills)海滩(Shore) 是 MC 插入在两种不同生物群系中间用于过渡的生物群系,海滩会在后文中提到,下面讲解的是边缘和丘陵。
在介绍他们之前,需要先介绍两个概念
相似生物群系
在 1.15 之前,相似生物群系的概念还比较复杂。1.16 对此作出了简化。
Mojang 把生物群系分为了若干个相似组,一个生物群系只属于一个相似组,相似组里的所有生物群系互相相似。
相似组的划分基本上与生物群系类别的划分一致(后者参见附录)。但是有几点不同:
- 参与相似比较的生物群系均为主世界生物群系,故无
THEEND
和NETHER
相似组 - 类别
MESA
中的生物群系被进一步细分为两个相似组:
生物群系 | 类别 | 相似组 |
---|---|---|
badlands | MESA | MESA |
wooded_badlands_plateau | MESA | BADLANDS_PLATEAU |
badlands_plateau | MESA | BADLANDS_PLATEAU |
eroded_badlands | MESA | MESA |
modified_wooded_badlands_plateau | MESA | MESA |
modified_badlands_plateau | MESA | MESA |
注:1.16.5 offical 使用 isSame
描述相似属实欠妥。本文译作“相似”而不是相同。
变种生物群系
曾几何时,某些生物群系之间存在特殊的从属关系,被作为主世界生物群系生成时的参考之一。我们说:从属生物群系是被从属生物群系的变种(Mutation),被从属生物群系是从属生物群系的亲本(Parent)。例如向日葵草原是草原的变种,草原是向日葵草原的亲本。
这个 1.7 就加入的特性在 1.13 时被弱化,1.16 则几乎完全移除,只留下一个简单的对应关系(参见附录)。
一般来说,变种生物群系要比亲本更少见,更有特色,因而更能吸引冒险家的青睐。其中最著名莫过于变种丛林边缘(Modified Jungle Edge,MJE)——它苛刻的生成条件,使其成为 MC 中自然生成的最稀有的生物群系。结合本文内容,尤其是接下来的两节,你能分析一下原因吗?
边缘
边缘层有一个奇妙的传奇往事,是关于那个神秘的山地边缘的。1.16 之后相关内容被删干净了,如果想了解更多,参见废稿。
现在的逻辑是:如果与山地相似,就维持现状,否则:
如果自己是... | 且四周(車)任意一个不相似于... | 自己就变为... |
---|---|---|
繁茂恶地高原 | 自己 | 恶地 |
恶地高原 | 自己 | 恶地 |
巨型针叶林 | 自己 | 针叶林 |
如果自己是... | 且四周(車)任意一个是... | 自己就变为... |
---|---|---|
沙漠 | 积雪的冻原 | 繁茂的山地 |
沼泽 | 沙漠、积雪的针叶林或积雪的冻原 | 平原 |
沼泽 | 丛林或竹林 | 丛林边缘 |
如果都这些尝试都失败了,就维持现状。
丘陵
丘陵层一个二元层,一个参数是主线层,另一个是河流层。主线层的生物群系记为a
。前文提到,河流层生成的是[0+2, 299999+2)
之间的一个随机噪声,而丘陵层使用的数据是该层减 2,即[0, 299999)
,并对 29 取模记为r
。
第一次尝试成功的条件是:如果a
不是浅海且不是变种生物群系,同时r
为 1 (概率约为1/29)。
如果成功:
- 如果
a
有变种就变为a
的变种 - 如果
a
没有变种就保持a
不变
如果失败,则进入第二次尝试:
- 当
r
不为 0 时,就有 1 / 3 的概率进入第二次尝试,并根据a
选择对应的丘陵生物群系记为h
。如果没有对应的h
,这次尝试也会失败。把h
记为ret
。 - 当
r
为 0 时,直接进入第二次尝试,并根据a
选择对应的丘陵生物群系及其变种记为h
和mh
。如果没有对应的h
,或者h
没有变种,这次尝试也会失败。把mh
记为ret
。
如果四周(車)至少有 3 个区元的生物群系相似于a
,尝试成功,该区元变为ret
。如果上述尝试都失败,就保持a
不变。
下表是经过我整理后得出,在这里可能出现的原生物群系与对应丘陵生物群系的对照表。和上文相同,后面括号里的数字代表权值,没有数字默认为 1。
原生物群系 | 丘陵生物群系 |
---|---|
沙漠 | 沙漠丘陵 |
森林 | 繁茂丘陵 |
桦木森林 | 桦木森林丘陵 |
黑森林 | 平原 |
针叶林 | 针叶林丘陵 |
巨型针叶林 | 巨型针叶林丘陵 |
积雪的针叶林 | 积雪的针叶林丘陵 |
平原 | 繁茂山地、森林(2) |
积雪的冻原 | 积雪的冻原丘陵 |
丛林 | 丛林丘陵 |
竹林 | 竹林丘陵 |
海洋 | 深海 |
山地 | 繁茂山地 |
热带草原 | 热带高原 |
与恶地高原相似 | 恶地 |
1 / 3 的深海 | 平原、森林 |
向日葵平原
把 1/57 的平原变为向日葵平原
生物群系大小
上图连续放大了四次,其中前两次还夹带了私活,这一阶段实际上是根据世界生成的一个重要参数——生物群系大小(BiomeSize) 进行的。原版的默认(Default) 世界类型这个数字是 4,而巨型生物生物群系(LargeBiomes) 世界类型这个数字是6。根据前面对放大化的解释可以知道,巨型生物群系的生物群系的面积将会是默认的 16 倍,即 x 和 z 方向各 4 倍。
在第一次放大化完成之后,将会再进行最后一次扩张。详解见上文扩张一节。
在第二次放大化完成之后(如果生物群系大小仅为 1,则在扩张完成后),添加海滩生物群系。
海滩
生成海滩的前提是自己不是海、不是沼泽。原则是:现在第一列找到自己的类别,判断周围的条件,如果失败就不再尝试、维持现状。唯一的例外是丛林有两次机会。
自己 | 周围(車) | 海岸 |
---|---|---|
蘑菇岛 | 任意一个是浅海 | 蘑菇岛海岸 |
类别为丛林 | 任意一个不兼容丛林1 | 丛林边缘 |
类别为丛林 | 任意一个是海洋 | 沙滩 |
山地或繁茂山地 | 任意一个是海洋 | 石滩 |
降水类型为雪 | 任意一个是海洋 | 积雪的沙滩 |
恶地或 繁茂的恶地高原 | 任意一个类别不是恶地 且全都不是海洋 | 沙漠 |
其它 | 任意一个是海洋 | 沙滩 |
1兼容丛林的生物群系有:类别为丛林的生物群系、森林、针叶林、海洋
平滑
平滑层会根据四周(車)的生物群系改变自己的生物群系,规则如下:
- 若东等于西且南等于北,则随机选择西或北
- 若仅东等于西,则选择西
- 若仅南等于北,则选择北
- 上述尝试都失败,维持现状。
混合河流
如果主线是海洋,或河流支线不是河流(7),就维持原状,否则根据下表选择一个河流。
原生物群系 | 河流生物群系 |
---|---|
积雪的冻原 | 冻河 |
蘑菇岛或蘑菇岛海滩 | 蘑菇岛海滩 |
其他 | 河流 |
混合海洋
创建、合并了海洋支线,使主线层的海水有了温度。可惜这幅图中无法体现,因为刚好没出现其他温度的海洋。
混合规则:如果周围特定区元(如上图所示,紫色为自己,紫色和红色为检测区元,相邻两个非黑区元间隔了三个区元,即步长为4)有非海洋区元,暖水海洋变为温水,冻洋变为冷水海洋。否则,如果主线层是深海,还会对海洋支线层传来的浅海转变为对应的深海生物群系。
注意:暖水海洋不会进行这样的转换,这也就是原版有暖水深海却不会生成的原因。
至此,所有层的工作都完成了。所得的便是每一个区元的生物群系。
完成:实时放大化
最后,当细化到某个具体的方块时,我们还需要一次实时放大化。这一部分我们在 1.6 节 就已经讲过了,这里不再赘述。
河流支线
水源
浅海区元的保持不变为 0。非海洋区元生成一个位于区间2 + [0, 299999) = [2, 300000]
中的随机噪声。但实际上,最终这个噪声只会进行奇偶性的判断,故我用两种不同的灰色区别了奇偶。
紧接着:两次放大化
河流距离
河流支线先被放大化。放大的次数取决于官方名称叫河流大小(RiverSize),而我更偏向于叫它河流距离(RiverDistance)。因为这个数值越大,河流之间的间距越大,而河流宽度并不会受此影响。默认为 4,自定义(Customized) 世界类型删除后便无法修改。接着迎接河流支线的是河流层和平滑层。
值得注意的是,生物群系大小的默认值和河流距离的默认值都是 4,所以放大化次数不会因为 biomeSize 的改变而改变,导致巨型生物群系世界类型下的河流和默认世界类型下的河流大小、位置几乎无差,使得默认世界中河流常常贴合生物群系边界的,巨型生物群系中则不总是如此。
河流
紧接着:河流层会对之前河流之前传来的数据做处理:
- 如果是海洋,保持海洋 0 不变。
- 如果大于等于 2,也就是河流,奇数取 3,偶数取 2。
如果自己处理后得到的数字和四周(車)处理后得到的数字全部相同,则该区元非河流(-1,图中用灰色标识),反之为河流(7)。
紧接着:平滑
合并河流
最后河流支线被合并
海洋支线
海洋支线的第一层,利用噪声算法,生成不同温度的浅海。
紧接着:六次放大化
紧接着:最后海洋支线被合并
三线合一
这个页面没什么内容,只是像放电影一样给出了三条线的变化过程
如果你更喜欢看视频,可以点这里观看
异界
本章标题和内容与《Re:从零开始的异世界生活》无关。
本章标题和内容与《Terraria: Otherworld》无关。
Fixed
目前这里只介绍主世界正常情况下的生成,下界和末界或者超平坦什么的再说(可能我会回来填坑呢)。
——ustc-zzzz《答知乎提问:Minecraft 的地形生成算法是什么?》
固定(Fixed)生物群系,说白了,就是全世界都是一种生物群系。最经典的例子便是超平坦世界类型。旧版本的自定义、新版本的自选和调试世界类型也可以实现。超平坦的默认和调试世界类型的生物群系都是平原。
超平坦
超平坦(Superflat) 创建速度惊人,得益于它固定的生物群系来源,和简单的区块生成器。在超平坦的预设界面,我们可以自定义世界各层方块——这对整个世界都是有效的。注意这个层和我们第二章讲述的层截然不同,超平坦定义的是方块层,而不是生物群系层;生物群系层也不是像方块层一样叠起来,而是相互作用得到一层的。这样的层不仅由区块生成器提供支持,甚至特性也提供了支持。尚不清楚 Mojang 重复提供支持的意图。
虚空
虚空(The Void) 生物群系只会生成一个 33 x 1 x 33 的石头平台以及最中间的一个圆石的生物群系,最简单的方式就是通过超平坦预设来创建。这个生物群系不会生成生物,因此不适合测试刷怪的特性;但正因为如此,没有烦人的史莱姆,这里成了测试其他特性的好地方。
超平坦的其中其中一个预设名字便是“虚空”,使用的就是这个生物群系。出生的石头平台并非依靠超平坦的生成机制,而是由生物群系产生的。
调试世界
调试模式(Debug Mode) 是一种较为特殊的世界类型。运用特殊的区块生成器,所有的方块状态(BlockState)都被展示在了空中。
调试世界的生物群系都是平原。
自选世界
自选(Buffet) 曾是作为自定义的替代品加入的,后独立为三种世界类型:单一生物群系(Single Biome)、洞穴(Caves)、浮岛(Floating Islands)。
自选世界在创建的时候就提供了选择生物群系的窗口,且只能选择一个生物群系。虽然后两者没有明说,这里选择的生物群系就成了这个世界全主世界固定的生物群系。
自选世界的主世界使用的都是噪声区块生成器,但使用的算法不同。仔细观察其实就能发现,这三种算法实际上就是默认世界在主世界、下界、末地三个维度使用的算法。
曾今的异界
在旧版本,下界和末地的生物群系在维度内都是完全一样的。因此也使用了这种来源。这也是上面的引用中土球将它们并列的原因。在后面的更新中:
- 末地在 1.13 在 x 和 z 上有了不同的生物群系
- 末地在 1.15 在 y 上有不同的生物群系
- 下界在 1.16 在 x y z 上有不同的生物群系
下面两节详细介绍这两个维度的生物群系。
The Nether
气候参数
气候参数(ClimateParameter) 包含下面这些分量。
参数 | 英文 | 最小值 | 最大值 |
---|---|---|---|
温度 | temperature | -2 | 2 |
湿度 | humidity | -2 | 2 |
海拔 | altitude | -2 | 2 |
诡异度 | weirdness | -2 | 2 |
偏移 | offset | 0 | 1 |
请勿与生物群系属性的温度、降水量、深度、规模混淆。它们之间没有任何直接或者间接的联系,只是名字相同或相近罢了。气候参数的最大值最小值仅用于限制自定义,原版数据均在此范围内。原版只有下界的五个生物群系有这些气候参数,如下表所示。
生物群系 | 温度 | 湿度 | 海拔 | 诡异度 | 偏移 |
---|---|---|---|---|---|
nether_wastes | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
soul_sand_valley | 0.0 | -0.5 | 0.0 | 0.0 | 0.0 |
crimson_forest | 0.4 | 0.0 | 0.0 | 0.0 | 0.0 |
warped_forest | 0.0 | 0.5 | 0.0 | 0.0 | 0.375 |
basalt_deltas | -0.5 | 0.0 | 0.0 | 0.0 | 0.17 |
在一些快照里面主世界的生物群系部分也有参数,可能是 Mojang 也用主世界来测试这一套系统
多重噪声
下界的生物群系采用多重噪声生物群系来源(Multi Noise Biome Source)生成。这种方法非常“复古”,因为早期版本的主世界就是这样生成的,同时也非常简单,两句话就可以说清楚:
- 多重噪声生物群系来源有五个独立的三维噪声生成器,对应五个气候参数。它们能生成下界任一个区元的气候参数。即
(x: int, y: int, z: int) -> (t: double, h: double, a: double, w: double, o: double)
(在 1.16.5 里会故意传入 y = 0,造成所有高度的生物群系都仿佛(as if)是 y = 0 上的生物群系)
- 哪个生物群系的气候参数与区元的气候参数最接近(欧氏距离),区元的生物群系就是谁。
但这是不是略有些抽象?下面来详细介绍一下。
距离与相似度
说起噪声,不由得让你回忆起 1.4 节#花 和 2.4 节 海洋。如果只有一个噪声,这很好想象。我们只要划定好阈值,就能得到边缘平滑的,层次分明的生物群系。
那如果噪声变为多个呢?在多个噪声上分别划定阈值?你可以自己动手试一下,你应该不难发现:这个过程非常痛苦和机械,你需要标定的量成倍增长且标定的区域很不直观,参数直接互相关联,牵一发而动全身,灵活性很差。
因此我们采用了另一种方法,给每个生物群系赋予一个标准的气候参数,以此为基准点,选择与当地气候参数选择相似度最高生物群系。这里我们采用的衡量相似度的方法是:把一个参数看做是五维空间中的一个点,两个点之间的(欧式)距离越小,两个参数之间的相似度就越大。
例如由上表得 nether_wastes(t: 0.0, h: 0.0, a: 0.0, w: 0.0, o: 0.0)
soul_sand_valley(t: 0.0, h: -0.5, a: 0.0, w: 0.0, o: 0.0)
则某区元的气候参数 (t: 0.0, h: -0.2, a: 0.0, w: 0.2, o: 0.0)
与它们的距离分别为
显然与 nether_wastes 距离更近,即更加相似。若该区元的生物群系只能在这两个中选取,那么一定是选前者。
由于原版中有两个参数恒为 0,我们可以讲剩下三个参数作为 x y z 坐标,在一个三维空间中画出这五个点。联系几何知识,不难想到,两个点中垂面上的任意一个点到两点的距离是相等的。以 P1(nether_wastes)为例,做出其与另外四个点之间的四个中垂面,然后绘制出交线,如图所示,可以大致看出在哪些范围内的参数会成为 nether_wastes。同样的工作对于另外四个生物群系来说也是类似的。更进一步的,也可以推广到五个参数的五维空间。
P1-5依次是上表中的五个生物群系的参数,红绿蓝坐标轴分别是温度、湿度、偏移
分布
下面无情地放出示例地图的下界的区元的生物群系分布和实时放大化之后 y = 0 和 y = 64 的分布。
粗糙
实时放大化
y = 0
y = 64
The End
末地
末地是玩家游戏结束的地方(而本教程也已经接近尾声),为了适应独特的需求,末地生物群系来源(The End Biome Source)也是独具一格。
主岛
末地最经典的便是主岛。原版对此的处理是:末地中心(0, 0)周围半径 64 的区元(即半径 256 格方块)的圆形区域的生物群系均为经典的末地生物群系(the_end)。
外岛
外岛则有一个高度噪声,用以决定生物群系,从低到高,分别是 end_barrens、small_end_islands、end_midlands、end_highlands。
分布
下面无情地放出示例地图的末地的区元的生物群系分布和实时放大化之后 y = 0 和 y = 64 的分布。
粗糙
实时放大化
y = 0
y = 64
Checkerboard
玩过国际象棋吗?即使没玩过,也会对它黑白相间的棋盘有印象。
棋盘(Checkerboard) 生物群系来源就是以此为灵感设计的。
这种生物群系来源有两个参数,size 和 biomes。size 是棋盘每个格子的边长,单位是区块。biomes 是候选的生物群系。
生物群系会按照 biomes 中的顺序,在世界中呈对角线条带生成。对于x / size + z / size
相同的区块来说,生物群系是相同的。
这是一个隐藏的游戏特性,不能直接开启,需要修改存档的 nbt 或者利用模组才能实现。具体方法可以参考 wiki。
你知道吗:本文的 logo就是取自于这张图,你能找到在哪里吗
数据表
版本:1.16.5,命名:offical
通用数据表
下表中整理汇总了原版 79 个生物群系的 23 个属性。(不包含对于所有生物群系都相同的属性和部分特有属性,参见后文)
接下来的表格中 SE 代表 specialEffects
相同的属性
下面这些属性对于所有原版生物群系都有相同的值。
属性 | 值 |
---|---|
SE.ambientMoodSettings.blockSearchExtent | 8 |
SE.ambientMoodSettings.soundPositionOffset | 2 |
SE.ambientMoodSettings.tickDelay | 6000 |
下界特有属性
下面这些属性为下界生物群系特有。
下界生物群系 |
---|
nether_wastes |
soul_sand_valley |
crimson_forest |
warped_forest |
basalt_deltas |
相同的属性
下面这些属性对于所有下界生物群系都有相同的值。
属性 | 值 |
---|---|
SE.ambientAdditionsSettings.tickChance | 0.0111 |
SE.backgroundMusic.minDelay | 12000 |
SE.backgroundMusic.maxDelay | 24000 |
SE.backgroundMusic.replaceCurrentMusic | false |
相似的属性
下面这些属性对于所有下界生物群系都有相似的值。
将表中[biome]替换成下界生物群系的名字即为该下界生物群系该属性的值。
属性 | 值 |
---|---|
SE.ambientAdditionsSettings.soundEvent | ambient.[biome].additions |
SE.ambientLoopSoundEvent | ambient.[biome].loop |
SE.backgroundMusic.event | music.nether.[biome] |
其他属性
这些属性为部分下界生物群系特有。
生物群系 | SE.ambientParticleSettings.probability | SE.ambientParticleSettings.options |
---|---|---|
soul_sand_valley | 0.00625 | minecraft:ash |
crimson_forest | 0.025 | minecraft:crimson_spore |
warped_forest | 0.01428 | minecraft:warped_spore |
basalt_deltas | 0.118093334 | minecraft:white_ash |
生物群系 | mobSettings.mobSpawnCosts |
---|---|
soul_sand_valley | skeleton:{energyBudget=0.15,charge=0.7}, ghast:{energyBudget=0.15,charge=0.7}, enderman:{energyBudget=0.15,charge=0.7}, strider:{energyBudget=0.15,charge=0.7} |
warped_forest | enderman:{energyBudget=0.12,charge=1.0} |
变种生物群系表
下表列出的是 1.16 所有有变种的生物群系的变种。
实际上这些变种都是 1.7 时代遗留下来的,之后再无新的变种。特列出 ID = id + 128
以供对照。
id | 名称 | 变种 | ID |
---|---|---|---|
1 | plains | sunflower_plains | 129 |
2 | desert | desert_lakes | 130 |
3 | mountains | gravelly_mountains | 131 |
4 | forest | flower_forest | 132 |
5 | taiga | taiga_mountains | 133 |
6 | swamp | swamp_hills | 134 |
12 | snowy_tundra | ice_spikes | 140 |
21 | jungle | modified_jungle | 149 |
23 | jungle_edge | modified_jungle_edge | 151 |
27 | birch_forest | tall_birch_forest | 155 |
28 | birch_forest_hills | tall_birch_hills | 156 |
29 | dark_forest | dark_forest_hills | 157 |
30 | snowy_taiga | snowy_taiga_mountains | 158 |
32 | giant_tree_taiga | giant_spruce_taiga | 160 |
33 | giant_tree_taiga_hills | giant_spruce_taiga_hills | 161 |
34 | wooded_mountains | modified_gravelly_mountains | 162 |
35 | savanna | shattered_savanna | 163 |
36 | savanna_plateau | shattered_savanna_plateau | 164 |
37 | badlands | eroded_badlands | 165 |
38 | wooded_badlands_plateau | modified_wooded_badlands_plateau | 166 |
39 | badlands_plateau | modified_badlands_plateau | 167 |
废稿
这里收录了一些 2.2 节 舍不得删掉的废稿,是本文在 1.15 -> 1.16 更新之后中失效的内容。
相似生物群系
这里我们引入相似生物群系的概念。
出于易于理解的目的,相似可以近似解释为:
-
相同的生物群系相似
-
类别相同且均不为 none 的生物群系相似
但是 Mojang 为平顶山生物群系做了特化,导致判断相似的方法是一个不对称的方法(即存在 a
和 b
两个生物群系使得,areSimilar(a, b) != areSimilar(b, a)
),因此下面给出我整理后的等效方法:
boolean areSimilar(Biome biome1, Biome biome2) {
if (biome1 == biome2) return true;
return biome1 == WOODED_BADLANDS_PLATEAU || biome1 == BADLANDS_PLATEAU
? biome2 == WOODED_BADLANDS_PLATEAU || biome2 == BADLANDS_PLATEAU
: biome1.getCategory() != Category.NONE
&& biome2.getCategory() != Category.NONE
&& biome1.getCategory() == biome2.getCategory();
}
可以看出,当 biome1
为恶地高原或者繁茂恶地高原时,biome2
也必须是恶地高原或者繁茂恶地高原才能相似。但是 biome2
为恶地高原或者繁茂恶地高原却不需要 biome1
是恶地高原或者繁茂恶地高原,(根据后面的分支)只需要类别是恶地即可相似。
我不想用左相似、右相似这样奇怪的说法来区分这两种情况,下文所述的
a 相似于 b
指的就是 areSimilar(a, b)
。
相似于 b 的生物群系
指的是 areSimilar(/*这些生物群系*/, b) == true
边缘
失落的属性:温度类别
温度类别也是世界生成时用到的属性之一,而且仅在世界生成中使用。它不是_指定_的,而是根据类别、温度这两个属性_计算_出来的。分为海洋、冷、中和暖。
然而不幸的是,唯一使用它的地方不是别处,正是决定是否生成山地边缘的代码。结果你也知道了,经过化简得出:山地边缘一定不会生成。是具体是什么原因导致了山地边缘的消失呢?
山地边缘作为一个 1.7.2 开始就不再生成的边缘生物群系,在世界生成的代码里,并非真的消失——实际上 MC 第一个试图生成的边缘生物群系就是它!但结果总是失败,为什么呢?
这里直接上代码可能会好理解一些,但是不借助代码也可以讲清楚。对 山地生物群系(常量)和四周(車)每一个区域的生物群系调用下面的函数,如果全部为 true,说明自己不能变成山地边缘。
boolean cannotBecomeMtEdge(Biome biome1, Biome biome2) {
if (areSimilar(biome1, biome2)) return true;
TemperatureCategory type1 = biome1.getTemperatureCategory();
TemperatureCategory type2 = biome2.getTemperatureCategory();
return type1 == type2
|| type1 == TemperatureCategory.MEDIUM
|| type2 == TemperatureCategory.MEDIUM;
}
这个函数会在这些情况下为 true:
- 两个生物群系相似,即相似于 山地生物群系
- 温度类别相同,或其中一个是中
如果表面上观察,一切似乎很合理。不过,山地生物群系的温度类别是什么?相信读者读到这里,答案呼之欲出了:中。没错,无论另一个生物群系的温度类别是什么,最后一个语句都相当于一个return true
。进而得到这个函数相当于areSimilar(biome1, biome2) ? true : true
:这两个生物群系相似也是 true,不相似也是 true,说白了永远是 true——因此直接排除了所有的区域!山地边缘自此成了笑话:后文其实仍有关于山地边缘的判断、尝试,我于是一并忽略、删去。
终章
这一部分原本是绪言,经过再三考虑被改成了后记...
前作
写前作我我完全是一时兴起——我当时毫无这方面的经验。我一直以来对世界生成有着敬畏之心,因为世界生成总是给人一种宏大、复杂、难以研究的感觉。我试过寻找一些世界生成的文章,Switefaster 给我推荐了土球的那一篇知乎回答。
探索没有终结,之后我也写过一些世界生成的模组,是在我那个练手的模组里。当时我的目标是“写遍所有的 ForgeRegistries”,其中有一个便是 Biome,顺藤摸瓜,我也看到了许多关于世界生成的事实。
1.13 原本是技术更新,代码大重写。在此之前我也开发过 1.12 Forge 的模组,对于 1.13 的代码可以说是耳目一新。这样的代码是 Mojang 写出来的?对我来说在以前想都不敢想。1.13 Forge 刚出的时候,正好是寒假,我马不停蹄开始了创作,那几天几乎是不分昼夜,终于在令我自己都难以相信的短时间内完成了文章。我硬着头皮发布了,不假思索。
为什么是「浅析」呢,因为我几乎是囫囵吞枣地把每个环节都过了一遍,造成差点把我噎死——我的理解不尽完善,文章漏洞百出,在这里再次感谢提出指正意见的读者。也有反馈说感觉内容不够详细,我想大概也是事实,但这也是不可避免的。海螺向我抛出了橄榄枝,我们互相在自己的帖子里加了对方帖子的链接。我当时还未意识到,海螺实际上也是国内在这条路上行走的、为数不多的先驱。海螺曾向我们生动地描述过世界生成所用的那个数组,而那个数组从未走远。
后来我又进行了再版,是根据新版和新版教程进行的修订。最重要的删改可能是删去了生物群系有关的内容,这也意味着有关这一部分内容的中心全部转向这个教程。无论如何,旧的教程应该走入历史的垃圾桶了。
续作
因为疫情,在家的我又重新研究起了世界生成。
这次本来没想写这篇文章的——我最初的想法并不复杂,就是看看:原版如何生成生物群系?在这之前,其实已经有了不少模拟世界生成的网站、软件,或无扩展性可言,或依赖于 minecraft.jar。于是我萌生了做一个独立的生物群系生成模拟 api。把原版生物群系生成逻辑剥离出来,我相当于就研究了这整个过程,既然都研究了,索性就记录了下来。这个模拟项目被命名为 DigHog。
我在写这篇文章的过程中,正经历着 1.16 下界更新,下界不再单调,最后一个单生物群系的维度跟我们说再见了。好在 1.14 - 1.16 的主世界生成代码都是一样的,我的前期努力没有白费;可是不断变化的代码,不断变化的名字,总是让我应接不暇,这让我走了不少弯路,放慢了教程的进度。在不断探索的过程之中,我更进一步的加入了许多相关的内容,也许最初文章的目标仅仅只是主世界的生物群系,但一不做二不休,既然来都来了,那我就干脆莽到底!
Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover. - Unknown
20年之后,更令你失望的不是你做了什么,而是你没做什么。所以解开帆脚索,离开安全的港湾,赶着航程中的信风,去探索,去梦想,去发现。
——佚名,《终末之诗》鸣谢名单的末尾附言
事与愿违。
动笔的时候,已经是我高二下学期了。不久后我便返校,投入了高考的总复习之中。我再没有网课期间那样充裕的时间了。时间过得飞快,我是否会淡忘这个教程呢?
答案是不会,因为 Mojang 很能带给我“惊喜”。1.16.2 横空出世,给我的教程带来了毁灭性的打击。还没等我喘过气,1.17 1.18 的更新主题已经昭示我的教程必然只能止步于此了。说来讽刺,我在返校前特意预测了下一次更新的内容和未来一年 Mojang 的时间表,没想到一语成谶。
待我高考结束后,面对着陌生的文字,陌生的代码,我倒吸一口凉气。我给我自己定了一个目标,要在前作发布三周年之前发布新作。
令我感慨的是,我的教程起与 1.13 世界生成重写,终于 1.17 1.18 世界生成重写。
正如我在正文所说,是时候说再见了:我决定就此封笔——我想这是我关于 MC 的最后一篇文章。
呜乎!胜地不常,盛筵难再;兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。(——王勃《滕王阁序》)
我很难想象我亲历了这一切;此后之事,就留待后人罢。(——Yaossg《再析世界生成:生物群系》)
结束了?结束了。
I love you. All of you, Thank you for turning Minecraft into what it has become...In one sense, it belongs to Microsoft now. In a much bigger sense, it’s belonged to all of you for a long time, and that will never change.
——Notch, interviewed when Minecraft was sold to Microsoft
And the game was over and the player woke up from the dream. And the player began a new dream. And the player dreamed again, dreamed better. And the player was the universe. And the player was love.
You are the player.
曲终人散,黄粱一梦。玩家开始了新的梦境。玩家再次做起了梦,更好的梦。玩家就是宇宙。玩家就是爱。
你就是那个玩家。
——Julian Gough《终末之诗》
昔我往矣,杨柳依依;今我来思,雨雪霏霏;行道迟迟,载渴载饥;我心伤悲,莫知我哀。
——《诗经·小雅·采薇》
鸣谢 Acknowledge
Supporters
- 土球 资格最老、影响最深远的中文教程作者,曾做过世界生成相关的探索。提供层的例子。
- 海螺 曾做过大量世界生成相关的探索。提供关于区块的票的内容的重要帮助。
- WaterKing 对文章指出了许多纰漏,并提出了宝贵的修改意见。
Previewers
- 3TUSK @1 @4
- 纪华裕 @2 @4
- Switefaster @3
- WaterKing @3 @4
- 土球 海螺 @4
- 仅主世界基本完成
- 前半段大致完善
- 在艰难的大后期
- 文章完稿终审