.nbt 结构文件存储的是调色板索引,而非方块名称——这正是它如此之小的原因
整个建筑仅由三个关键部分构成:尺寸、调色板和方块。每个方块都是一个指向调色板的整数,因此一个 32³ 的石头方块体积仅需一个字符串和 32,768 个数字。
一个 .nbt 结构文件——也就是结构方块保存并数据包分发的文件——其格式远比表面看起来要简单。一个建筑所需的一切都包含在少数几个顶层键中,而其中一个设计决策解释了这些文件为何如此之小。
承载建筑的三个关键部分
size [x, y, z] — a list of exactly three integers
palette [ { Name, Properties } … ] — every DISTINCT block state, once
blocks [ { pos, state, nbt } … ] — one entry per placed block
palette 仅存储每个独特的方块状态一次。 每个条目都是一个 Name——方块 ID,例如 minecraft:oak_stairs——外加一个可选的 Properties 复合标签,其中包含其方块状态,例如 facing: north 和 half: bottom。
blocks 从不重复名称。 每个条目都包含一个由三个整数组成的 pos 和一个 state,而 state 是一个指向调色板的索引,而非方块 ID。朝北的橡木楼梯(Oak Stairs)数据只存储一次字符串,之后每一个这样的方块都只是一个小整数。
这就是全部诀窍。一个 32 × 32 × 32 的纯石头(Stone)体积,只对应一个调色板条目和 32,768 个索引数字——而不是 32,768 份 minecraft:stone 这个词。调色板的大小取决于你使用了多少不同的方块,而方块列表的大小则取决于你建造的规模。这两个数字截然不同,通常只有一个会很大。
两个值得注意的设计结果
方块状态会增加你的调色板大小,而非方块列表大小。 橡木楼梯(Oak Stairs)并非一个调色板条目——它会根据你实际使用的朝向而增加。朝向四个方向、上半部分和下半部分、直角以及内外角:同一个方块可以占据十几个调色板槽位。因此,一个细节丰富的建筑会有一个很大的调色板,而一个简单的建筑则不会,这与尺寸无关。
只有放置的方块才会有条目。 方块列表是一个列表,而非密集的 3D 数组,一个不存在的条目就是不存在——此页面上的查看器会用 -1 填充其网格,并将缺失的坐标视为未放置任何东西。一个空心建筑的存储成本确实比相同尺寸的实心建筑要低。
nbt 是按方块可选的
blocks 中的每个条目都可以携带自己的 nbt 复合标签,但大多数都没有。这里存储着箱子(Chest)的内容、告示牌的文本、刷怪笼的生物和旗帜的图案——它们附属于单个方块,而非调色板。
这种区别很重要:调色板数据是共享的,而方块数据不是。 两个箱子(Chest)是一个调色板条目,因为它们具有相同的方块状态——但每个箱子都在自己的 nbt 中保留自己的物品栏。改变方块状态会触及调色板;改变方块内部的内容则会触及方块列表中的一个条目。
逐层读取
这里的查看器是特意设计成 2D 的。要绘制 y 层,它会遍历方块列表,保留 pos[1] 等于 y 的条目,并在调色板中查找每个条目的 state。这就是整个算法,值得了解,因为它也是你自己检查这些文件的方式:除了 gzip 封装之外,没有空间索引需要构建,也没有解压缩。该格式是一个你可以过滤的扁平列表。
这也意味着结构文件不会告诉你它在世界中的哪个位置。size 是一个边界框,pos 值是相对于其角落的。放置是结构方块(Structure Block)的事情,而不是文件的事情。