02 区块
按下 F3 + G 后,地面上会出现一张整齐的彩色网格。无论地形怎样起伏,这些边界始终沿 X、Z 方向每隔 16 格出现一次。
这些网格展示的就是区块边界。
1 为什么需要区块
Minecraft 的维度在水平方向上非常大。如果游戏把整个维度当成一个不可分割的整体,那么读取一个方块、保存一次修改,甚至只加载玩家附近的地形都会变得十分困难。
因此,游戏将维度沿水平方向切分成许多 16 × 16 的竖直区域。每一个这样的区域就是一个区块(Chunk)。
一个区块的 X、Z 尺寸固定为 16 格,但它不是一个 16 × 16 × 16 的小立方体。区块会覆盖所在维度的完整可用高度。主世界从 Y=-64 到 Y=319 的同一列方块,都属于同一个区块。
2 从方块坐标到区块坐标
区块只有 X、Z 坐标,没有单独的 Y 坐标。
源码通过 ChunkSectionPos#getSectionCoord 将方块坐标换算为区块坐标:
右移 4 位相当于向下取整后除以 16。区块内坐标则通过:
得到 0 至 15 的值。
例如:
负坐标最容易算错。方块 X=-1 不在区块 0,而在区块 -1 的最东侧。
3 子区块
区块本身很高,但游戏不需要每次都遍历整根竖直柱体。它会继续沿 Y 方向把区块切成 16 × 16 × 16 的子区块(Chunk Section)。
每个子区块包含 4096 个坐标。Chunk 根据维度高度创建一个 ChunkSection[];主世界高度为 384 格,因此一个完整的主世界区块拥有 24 个子区块。
子区块坐标的换算方式与区块坐标相同:
主世界最底部的子区块坐标为 -4,因为 -64 >> 4 = -4。但是在 ChunkSection[] 中,它的数组下标仍然是 0。HeightLimitView#sectionCoordToIndex 会用 “子区块坐标减去最低子区块坐标” 完成转换。
4 区块里有什么
从技术研究的角度看,区块远不只是 “4096 的很多倍个方块”。
Chunk 中保存或关联了:
- 一组子区块;
- 多种高度图;
- 方块实体及其待加载的 NBT;
- 结构起点与结构引用;
- 生物群系信息;
- 已居住时间、升级数据等附加信息。
子区块使用 PalettedContainer<BlockState> 保存方块状态。它不会为 4096 个坐标都写一份完整的方块名称和属性,而是使用调色板与紧凑数据记录重复出现的状态。空气很多、方块种类较少的子区块因此可以更节省空间。
子区块还记录非空气方块、需要随机刻的方块和非空流体的数量。游戏可以用这些计数快速跳过完全为空、也没有流体需要处理的子区块。
5 生成完成不等于正在运算
你可能观察过这样的现象:远处地形已经在客户端出现,但那里的红石装置或实体并不一定正常运算。
这是因为 “区块存在” 不是一个简单的是非状态。
维度生成阶段使用 ProtoChunk 逐步完成结构、生物群系、噪声地形、地表、洞穴、地物、光照和初始生物等步骤。达到 ChunkStatus.FULL 后,它可以转换为正常游戏使用的 WorldChunk。
完整区块还会根据加载等级处于不同用途:
FULL:区块数据可用;BLOCK_TICKING:方块相关运算可以进行;ENTITY_TICKING:实体也可以正常运算;INACCESSIBLE:尚未达到完整可访问状态。
因此,一个区块 “已经生成”“已经加载”“正在进行方块运算”“正在进行实体运算” 是四个不同的问题。加载票如何决定这些状态,将在章节中讨论。
6 小实验
- 在创造模式中按下
F3 + G。 - 找到 X=
15与 X=16两列方块,观察它们是否位于边界两侧。 - 再前往 X=
-1与 X=0,观察负坐标侧的边界。 - 使用调试界面记录这四个位置的区块坐标。
这个实验足以验证区块坐标采用向下取整,而不是简单地去掉小数部分。
7 小结
- 区块是水平尺寸为
16 × 16、覆盖维度完整高度的区域。 - 区块坐标由方块 X、Z 坐标向下取整除以 16 得到。
- 区块内部再按高度划分为
16 × 16 × 16的子区块。 - 方块状态实际保存在子区块的调色板容器中。
- 区块还包含高度图、方块实体、生物群系和结构等信息。
- 生成完成、完整加载、方块运算和实体运算不是同一件事。
