01 基础概念与结构
Minecraft 的世界在水平方向上近乎无限——如果每次读写方块都要在数千万个位置中定位,性能根本无法接受。
Mojang 的解决方案是把世界切成一个个 16×16 的区块(chunk):只把玩家附近的区块加载到内存中,远离玩家的区块从内存移除。当区块被加载或卸载时,远处的地形就会 "一片一片出现"——这是正常现象,不是 bug。
这一章从区块的数学定义出发,建立子区块、坐标打包、ProtoChunk 与 WorldChunk 等基本概念。后半部分为进阶内容,面向需要接触源码的读者。
1 区块与子区块的定义
1.1 什么是区块?
在游戏内按下F3+G,读者应该能看到如下场景的出现:

每一个这样的框都代表了游戏的一个区块,每一个区块的大小都是水平方向上的。
严格来说,一个区块可以被如下定义:
这里的m,n指的就是一个区块的区块坐标。一个区块的坐标可以被唯一固定的整数对(m,n)确认。
因此,一个区块在水平方向上有界,但高度方向上情况稍复杂:方块只能放置在 Y=-64 到 Y=319 之间,覆盖 24 个子区块。实体不受此限制——它们可以移动到比 -64 更低或比 319 更高的位置,但超出方块放置范围后不再有方块交互。
1.2 什么是子区块?
注意到在上图中,水平方向上有许多蓝色的横线。这些横线将区块分成了一个个 的区域。我们称这样分隔的区域为子区块。在方块可放置的高度范围内(Y=-64 到 Y=319),一个区块包含 24 个子区块。
子区块坐标比区块坐标额外增加了 Y 这一维度,从 -4 到 19 分布。在游戏内使用 F3 即可清晰地看到当前子区块坐标。

1.3 :advance 子区块计数器
子区块内部维护三个计数器:非空气方块数、需要随机刻的方块数、非空流体方块数。它们对玩家不可观测,但对性能至关重要——在进行区块刻、随机刻和流体计划刻时,游戏检查这些计数器,完全为空(计数器全零)的子区块可以直接跳过,无需遍历整个 16³ 的范围。
1.4 :advance 子区块的坐标哈希
在 16³ 的子区块中,游戏频繁需要表示 "第几行第几列第几层" 的局部坐标。Mojang 的做法是将三个维度的坐标压缩为一个 16 位的 short——每个维度只需 4 位(可表示 0~15),三个维度共 12 位,余下 4 位闲置。
选择 short 而非 int 是内存优化:在 ChunkHolder 中按子区块维护变更位置的集合时,每个位置占用 2 字节而非 4 字节——对于有数千个活跃区块的服务器,累积差异显著。
2 :advance 区块的数据结构
Chunk,即前文提到的区块,是所有区块对象的抽象基类。它定义了每个区块共有的数据结构和访问方式。
3 :advance 已生成区块和准生成区块
区块在代码中有两种主要实现,对应生命周期中的两个阶段:
为什么需要准生成区块? 世界生成是在独立于主线程的生成线程上进行的。如果生成线程直接操作 WorldChunk,就可能在主线程同时读写这个区块时造成竞争条件。ProtoChunk 提供了一个隔离的 "生成沙盒"——只有生成完成的区块才能转换为 WorldChunk,正式进入服务端的运行逻辑。
WorldChunk 在构造时挂载了一个加载等级提供器(levelTypeProvider),它让 WorldChunk 能随时查询自己当前的运行资格——例如 "这个区块是否应该执行实体运算"。加载等级的具体含义见第 02 章末尾的等级表。
4 :advance 客户端区块管线
客户端和服务端对区块的管理是两个完全独立的体系。客户端是否渲染与区块是否运算是互相独立的。
4.1 客户端视角
客户端的区块数据完全来自服务端同步。流程为:
- 服务端决定发送:当区块进入玩家视距时,服务端判断是否需要向该玩家发送区块数据。
- 区块数据网络包:包含该区块内所有非空子区块的方块状态、生物群系、方块实体和光照数据。
- 客户端接收与重建:客户端解析数据包,构建对应的 已生成区块(客户端自己的版本),用于渲染和本地预测。
4.2 服务端视角
服务端的 WorldChunk 是权威数据——即使客户端已经收到了区块数据包并完成了渲染,服务端可能仍在后台安排这个区块的后续生成步骤。
