方块本身通过预定义的有限 BlockState 集合保存数据;通过默认的方块渲染行为渲染模型;通过更新系统、计划系统等决定执行逻辑。
但是,这样的模式具有局限性, 无法直接满足在游戏交互中一些方块的功能。
因此,我们需要一种专门帮助特定方块存储数据,实时处理逻辑,进行额外渲染的组件。我们叫它们方块实体。
方块实体相为普通方块提供了三个重要的额外功能: 使用 NBT 存储数据、自定义的渲染行为 和 在每一次 tick 中进行更新。
方块实体通过 NBT 来存储数据,和实体一样。而且,与方块状态不同,
方块实体的数据不会自动同步到客户端: 方块实体必须自己声明何时同步以及同步哪些数据。
方块实体是因方块而异的,但一个抽象的,基本的方块实体含有以下基本信息与功能:
方块实体与世界绑定,每个方块位置仅有一个方块实体实例。
在 Minecraft 中,漏斗是一种重要的方块实体,主要用于物品的自动化传输。
一个可爱的漏斗方块实体携带以下基本信息:
inventory 储存空间(5 格)transferCooldown 冷却(默认为 - 1)lastTickTime 上一 tick 的世界时间facing 朝向每个游戏刻(tick),漏斗都会执行以下步骤:
减少冷却时间(cd, Cooldown)
执行物品传输逻辑
现在,让我们移步到尝试拉取和输出的具体的逻辑。
当漏斗确定自己需要做些什么时,它会先尝试向指向的容器输出。随后,它会尝试从上方吸取物品。最后,如果吸取和输出中有任意一方成功了,它就会设置冷却,并更新同步自己的数据。
我们分别来看输出和拉取的逻辑。
先看输出...
输出输出操作由 insert() 方法完成,它的作用是将物品从漏斗传输到目标容器,并返回一个布尔值来表示是否成功传输。如果传输成功,则漏斗进入冷却状态。
输出的基本逻辑:
确认目标容器是否可用
尝试输出物品
当调用 transfer() 方法尝试传输物品时,transfer() 方法会返回一个物品组:
到此,输出的部分已经完成,接下来,我们来看看拉取的部分。
拉取物品的逻辑与输出类似,但方向相反:
transfer() 物品至目标容器。transfer() 物品至漏斗。由于 transfer() 方法的逻辑马上会详细解释,因此这里不再赘述。
transfer() 是漏斗实现物品传输的核心方法。它接收以下参数:
执行流程如下:
检查目标槽位的状态
itemStack 放入,并清空 itemStack。itemStack。判断是否传输成功
itemStack 为空,则表示成功传输,否则表示失败。特殊处理漏斗间传输的时序
8gt的冷却时间。to 已先于源漏斗 from 计算,则目标漏斗 to 仅会得到 7gt 的冷却时间。值得注意的是,这只在目标漏斗为空时发生;
此外,就算from后于to计算, 并给予8gt的冷却时间,由于随后totick 时会将冷却立刻 - 1,所以结果上还是只有7gt冷却。
到此,你已经完全了解了漏斗的基本运作机制。相信这将会对之后的篇章理解提供很大的帮助。
另外一个及其重要的方块叫 moving_piston,移动的活塞,俗称 b36。
一个移动活塞方块实体携带一下基本信息:
pushedBlock 存储的方块facing 朝向extending 是否在伸出(与动画播放的方向有关)source 是否是收回中的活塞本体progress 当前的移动进度savedWorldTime 上次执行逻辑时的世界时间progress>=1,则将 MOVING_PISTON 替换为pushedBlock中存储的方块详情见5.7。
方块实体tick过程执行由 LevelChunk 中以下两个列表维护:
pendingBlockEntityTickers: 在tickBlockEntities过程中动态加入的方块实体。blockEntityTickers: 将要tick的方块实体这两个列表都是 ArrayList,因此它们的遍历顺序完全取决于元素的加入顺序。具体流程如下:
tickBlockEntities() 会首先将 pendingBlockEntityTickers 中的元素添加到 blockEntityTickers 末尾,然后清空 pendingBlockEntityTickers。blockEntityTickers 时,如果某个 TickingBlockEntity 已被移除(isRemoved() 返回 true),则从列表中移除;否则会调用其 tick() 方法进行更新。新增方块实体时,会调用 addBlockEntityTicker() 方法,将其加入列表。若当前正处于 tick 过程中(tickingBlockEntities == true),则加入 pendingBlockEntityTickers;否则直接加入 blockEntityTickers。
卸载时, 同一区块内的方块实体会被保存在 Map<BlockPos, BlockEntity> 中,加载时遍历这个 Map,并依次调用 addBlockEntityTicker() 加入 blockEntityTickers。因此它们的执行顺序取决于 BlockPos 的 hashCode 计算顺序。
不同区块之间的方块实体加载顺序则取决于区块的加载顺序,即哪个区块先加载,它的方块实体就会先加入 blockEntityTickers。
一句话总结:
卸载前, 方块实体的计算顺序取决于放置的顺序. 重新加载后。一个区块内的方块实体计算取决与其 pos 的 hash, 不同区块的方块实体则取决于区块加载的顺序。
相关代码如下 (删减了部分无关内容)