跳转到主要内容
GTMC
  • 总序
    • 00引言
    • 01更新的概念与不同类型的更新
    • 02连续的方块更新及其分析方法
    • 01刻与刻间时序
    • 02初窥刻内时序
    • 03计划刻与计划刻元件
    • 04树电与脚电
    • 05方块实体
    • 06方块事件
    • 07活塞:方块事件与方块实体
    • 01Masa Gadget
    • 02投影模组教程
    • 00绪论
    • 01实体运动基础
    • 02实体移动过程及碰撞机制
    • 03实体自由运动相关公式推导
    • 04外界因素对运动的影响
    • 05LivingEntity运动机制
    • 06几类具体实体运动分析
    • 07实体运动应用举例
    • 08参考文献
    • 附录
    • I.附录
    • 01铁轨
    • 02铜灯
  • 附录
  • A.栈的概念
  • B.术语表

文章目录

  • 总序
    • 00引言
    • 01更新的概念与不同类型的更新
    • 02连续的方块更新及其分析方法
    • 01刻与刻间时序
    • 02初窥刻内时序
    • 03计划刻与计划刻元件
    • 04树电与脚电
    • 05方块实体
    • 06方块事件
    • 07活塞:方块事件与方块实体
    • 01Masa Gadget
    • 02投影模组教程
    • 00绪论
    • 01实体运动基础
    • 02实体移动过程及碰撞机制
    • 03实体自由运动相关公式推导
    • 04外界因素对运动的影响
    • 05LivingEntity运动机制
    • 06几类具体实体运动分析
    • 07实体运动应用举例
    • 08参考文献
    • 附录
    • I.附录
    • 01铁轨
    • 02铜灯
  • 附录
  • A.栈的概念
  • B.术语表
Loading article content
Loading
Loading article
GTMC

Graduate Texts in Minecraft

求知于方寸之间。

前言

文章
25
作者
12
最近修订
2026年8月

社区

  • 关于
  • 贡献者
  • 团队
  • QQ 群

参与

  • 参与贡献
  • 编辑政策
  • 议题

源码

  • 代码仓库
  • CC BY-NC-SA 4.0
  • Apache-2.0

本站为非官方 Minecraft 产品,与 Mojang Studios 无任何关联。

贡献者保留各自作品的版权。文章内容以 CC BY-NC-SA 4.0 协议发布,署名信息保存在文章元数据与源代码历史记录中。

© 2024–2026 GTMC Collectiveec529be

GTMC

  • 文章
  • PDF
  • 术语表
  • 关于
  • 贡献者
  • 总序
    • 00引言
    • 01更新的概念与不同类型的更新
    • 02连续的方块更新及其分析方法
    • 01刻与刻间时序
    • 02初窥刻内时序
    • 03计划刻与计划刻元件
    • 04树电与脚电
    • 05方块实体
    • 06方块事件
    • 07活塞:方块事件与方块实体
    • 01Masa Gadget
    • 02投影模组教程
    • 00绪论
    • 01实体运动基础
    • 02实体移动过程及碰撞机制
    • 03实体自由运动相关公式推导
    • 04外界因素对运动的影响
    • 05LivingEntity运动机制
    • 06几类具体实体运动分析
    • 07实体运动应用举例
    • 08参考文献
    • 附录
    • I.附录
    • 01铁轨
    • 02铜灯
  • 附录
  • A.栈的概念
  • B.术语表

文章目录

  • 总序
    • 00引言
    • 01更新的概念与不同类型的更新
    • 02连续的方块更新及其分析方法
    • 01刻与刻间时序
    • 02初窥刻内时序
    • 03计划刻与计划刻元件
    • 04树电与脚电
    • 05方块实体
    • 06方块事件
    • 07活塞:方块事件与方块实体
    • 01Masa Gadget
    • 02投影模组教程
    • 00绪论
    • 01实体运动基础
    • 02实体移动过程及碰撞机制
    • 03实体自由运动相关公式推导
    • 04外界因素对运动的影响
    • 05LivingEntity运动机制
    • 06几类具体实体运动分析
    • 07实体运动应用举例
    • 08参考文献
    • 附录
    • I.附录
    • 01铁轨
    • 02铜灯
  • 附录
  • A.栈的概念
  • B.术语表
Loading article content
Loading
Loading article
CH 02微时序
路径: MicroTiming/方块实体.zh.md
Molforte+4
字数: 1,971
预计阅读时间: 7 分钟
最后编辑: 2026-08-29
贡献者:
  • MMolforte
  • BBFladderbeanawa
  • hhotpad100c
  • SSDU-Little-River
  • ttanhHeng
创建时间:
2024-12-28
最后编辑:
2026-08-29
链接:
https://www.techmc.wiki/zh/articles/micro-timing/block-entities
复用
CC BY-NC-SA 4.0
带有方块实体的方块们
带有方块实体的方块们
代码来源MC 1.20.1
YARN
1 个代码引用

05 方块实体

1 方块实体的概念与内容

方块本身通过预定义的有限 BlockState 保存数据;通过默认的方块渲染行为渲染模型;通过更新系统、计划系统等决定执行逻辑。
但是,这样的模式具有局限性, 无法直接满足在游戏交互中一些方块的功能。

方块实体为此而生。它为普通方块提供了三个重要的额外功能:

  • 使用 NBT 存储数据
  • 自定义的渲染行为
  • 在每一次 tick 中进行更新

1.1 方块实体的内容

方块实体是因方块而异的,但一个抽象的,基本的方块实体含有以下基本信息与功能:

  • type 类型
  • world 所在的世界
  • pos 位置
  • removed 是否已被移除
  • cachedState 缓存的,对应方块的状态

1.2 方块实体与方块

方块实体与世界中的方块绑定。一个世界里,每个方块位置仅可有一个方块实体实例。

仅有经过特殊声明的方块可以合法地与指定种类的方块实体共存。
方块实体随着方块一同添加与移除。但通过更新抑制等特殊手段可以在方块移除时保留其方块实体。

2 重要的带有方块实体的方块

2.1 漏斗

在 Minecraft 中,漏斗是一种重要的方块实体,主要用于物品的自动化传输。

2.1.1 漏斗方块实体携带的信息

一个可爱的漏斗方块实体携带以下基本信息:

  • inventory 储存空间(5 格)
  • transferCooldown 冷却(默认为 - 1)
  • lastTickTime 上一 tick 的世界时间
  • facing 朝向

2.1.2 漏斗方块实体的功能

  • 将物品传输到目标容器
  • 从容器中吸取物品
  • 尝试吸取物品实体

2.1.3 漏斗方块实体的工作流程

每个游戏刻(tick),漏斗都会执行以下步骤:

  1. 减少冷却时间(cd, Cooldown)

    • 若 cd 仍大于 0,漏斗不会执行任何拉取或输出操作,而是直接结束运算。
    • 若 cd 归零,漏斗会开始尝试执行输出和拉取操作。
  2. 执行物品传输逻辑

    • 首先尝试输出(将物品放入目标容器)
    • 随后尝试拉取(从上方容器吸取物品)
    • 如果吸取或输出成功,则漏斗会重置冷却时间,并同步更新自身数据。
正在渲染图表

现在,让我们移步到_尝试拉取和输出的具体的逻辑_。

当漏斗确定自己需要做些什么时,它会先尝试向指向的容器输出。随后,它会尝试从上方吸取物品。最后,如果吸取和输出中有任意一方成功了,它就会设置冷却,并更新同步自己的数据。

正在渲染图表

我们分别来看输出和拉取的逻辑。

先看_输出_...
_输出_输出操作由 insert() 方法完成,它的作用是将物品从漏斗传输到目标容器,并返回一个布尔值来表示是否成功传输。如果传输成功,则漏斗进入冷却状态。

输出的基本逻辑:

  1. 确认目标容器是否可用

    • 如果目标容器已满,则输出失败。
    • 如果目标容器有可用槽位,则尝试传输物品。
  2. 尝试输出物品

    • 遍历漏斗中的物品槽,逐个尝试传输物品到目标容器。
    • 只要有一次传输成功,就返回 “成功”;否则返回 “失败”。
正在渲染图表

当调用 transfer() 方法尝试传输物品时,transfer() 方法会返回一个物品组:

  • 若返回值为空,则说明物品已完全传输,输出成功。
  • 若返回值不为空,则说明物品未能全部传输,输出失败。
正在渲染图表

到此,输出的部分已经完成,接下来,我们来看看_拉取_的部分。
拉取物品的逻辑与输出类似,但方向相反:

  • 输出 时:漏斗 transfer() 物品至目标容器。
  • 拉取 时:某个容器 transfer() 物品至漏斗。

由于 transfer() 方法的逻辑马上会详细解释,因此这里不再赘述。

正在渲染图表

transfer() 是漏斗实现物品传输的核心方法。它接收以下参数:

  • from:物品来源(可以是漏斗或其他容器)
  • to:传输目标(可以是容器或另一个漏斗)
  • itemStack:希望传输的物品组
  • slot:目标容器中的目标槽位

执行流程如下:

  1. 检查目标槽位的状态

    • 如果该槽为空,直接将 itemStack 放入,并清空 itemStack。
    • 如果该槽已有相同类型的物品,并且仍有剩余空间,则合并物品并清空 itemStack。
  2. 判断是否传输成功

    • 传输后,若 itemStack 为空,则表示成功传输,否则表示失败。
  3. 特殊处理漏斗间传输的时序

    • 如果目标是另一个漏斗,则会给目标漏斗增加 8gt的冷却时间。
    • 若目标漏斗 to 已先于源漏斗 from 计算,则目标漏斗 to 仅会得到 7gt 的冷却时间。

      值得注意的是,这只在目标漏斗为空时发生;
      此外,就算from后于to 计算, 并给予8gt的冷却时间,由于随后 totick 时会将冷却立刻 - 1,所以结果上还是只有7gt冷却。

正在渲染图表

到此,你已经完全了解了漏斗的基本运作机制。相信这将会对之后的篇章理解提供很大的帮助。

2.1.4 移动中的活塞

另外一个及其重要的方块叫 moving_piston,移动的活塞,俗称 b36。

2.1.5 移动的活塞方块实体携带的信息

一个移动活塞方块实体携带一下基本信息:

  • pushedBlock 存储的方块
  • facing 朝向
  • extending 是否在伸出(与动画播放的方向有关)
  • source 是否是收回中的活塞本体
  • progress 当前的移动进度
  • savedWorldTime 上次执行逻辑时的世界时间

2.1.6 移动的活塞方块实体的功能

  • 处理活塞推送实体
  • 更新动画进度
  • 如果progress>=1,则将 MOVING_PISTON 替换为pushedBlock中存储的方块
  • 在客户端渲染动画

2.1.7 移动的活塞方块实体的工作流程

详情见5.7。

进阶深入底层机制细节跳过

3 方块实体间的时序

方块实体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, 不同区块的方块实体则取决于区块加载的顺序。

相关代码如下 (删减了部分无关内容)

java
MC 1.20.1
YARN
// 用于暂存在 tickBlockEntities 过程中动态加入的方块实体private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();//将要 tick 的方块实体列表protected final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList();private void tickBlockEntities() { this.tickingBlockEntities = true; if (!this.pendingBlockEntityTickers.isEmpty()) { this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); this.pendingBlockEntityTickers.clear(); } // 遍历正在 tick 的方块实体列表 Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator(); while (iterator.hasNext()) { TickingBlockEntity tickingBlockEntity = iterator.next(); if (tickingBlockEntity.isRemoved()) { // 如果方块实体已经被移除,则从列表中剔除 iterator.remove(); } else if (this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { // 如果所在位置应被 tick,则调用 tick () 方法进行更新 tickingBlockEntity.tick(); } } this.tickingBlockEntities = false;}// 添加一个方块实体的 tickerpublic void addBlockEntityTicker(TickingBlockEntity tickingBlockEntity) { (this.tickingBlockEntities ? this.pendingBlockEntityTickers : this.blockEntityTickers) .add(tickingBlockEntity);}// 区块内所有方块实体protected final Map<BlockPos, BlockEntity> blockEntities = Maps.newHashMap();// 区块加载完成后,注册所有已有的方块实体(将它们加入 tick 系统)public void registerAllBlockEntitiesAfterLevelLoad() { this.blockEntities.values().forEach(blockEntity -> { // 为该方块实体添加 ticker,即加入到 blockEntityTickers 中 this.updateBlockEntityTicker(blockEntity); });}
// SYNTAX_HIGHLIGHT
进阶内容结束
上一篇
树电与脚电
下一篇
方块事件
CH 02微时序
路径: MicroTiming/方块实体.zh.md
Molforte+4
字数: 1,971
预计阅读时间: 7 分钟
最后编辑: 2026-08-29
贡献者:
  • MMolforte
  • BBFladderbeanawa
  • hhotpad100c
  • SSDU-Little-River
  • ttanhHeng
创建时间:
2024-12-28
最后编辑:
2026-08-29
链接:
https://www.techmc.wiki/zh/articles/micro-timing/block-entities
复用
CC BY-NC-SA 4.0
带有方块实体的方块们
带有方块实体的方块们
代码来源MC 1.20.1
YARN
1 个代码引用

05 方块实体

1 方块实体的概念与内容

方块本身通过预定义的有限 BlockState 保存数据;通过默认的方块渲染行为渲染模型;通过更新系统、计划系统等决定执行逻辑。
但是,这样的模式具有局限性, 无法直接满足在游戏交互中一些方块的功能。

方块实体为此而生。它为普通方块提供了三个重要的额外功能:

  • 使用 NBT 存储数据
  • 自定义的渲染行为
  • 在每一次 tick 中进行更新

1.1 方块实体的内容

方块实体是因方块而异的,但一个抽象的,基本的方块实体含有以下基本信息与功能:

  • type 类型
  • world 所在的世界
  • pos 位置
  • removed 是否已被移除
  • cachedState 缓存的,对应方块的状态

1.2 方块实体与方块

方块实体与世界中的方块绑定。一个世界里,每个方块位置仅可有一个方块实体实例。

仅有经过特殊声明的方块可以合法地与指定种类的方块实体共存。
方块实体随着方块一同添加与移除。但通过更新抑制等特殊手段可以在方块移除时保留其方块实体。

2 重要的带有方块实体的方块

2.1 漏斗

在 Minecraft 中,漏斗是一种重要的方块实体,主要用于物品的自动化传输。

2.1.1 漏斗方块实体携带的信息

一个可爱的漏斗方块实体携带以下基本信息:

  • inventory 储存空间(5 格)
  • transferCooldown 冷却(默认为 - 1)
  • lastTickTime 上一 tick 的世界时间
  • facing 朝向

2.1.2 漏斗方块实体的功能

  • 将物品传输到目标容器
  • 从容器中吸取物品
  • 尝试吸取物品实体

2.1.3 漏斗方块实体的工作流程

每个游戏刻(tick),漏斗都会执行以下步骤:

  1. 减少冷却时间(cd, Cooldown)

    • 若 cd 仍大于 0,漏斗不会执行任何拉取或输出操作,而是直接结束运算。
    • 若 cd 归零,漏斗会开始尝试执行输出和拉取操作。
  2. 执行物品传输逻辑

    • 首先尝试输出(将物品放入目标容器)
    • 随后尝试拉取(从上方容器吸取物品)
    • 如果吸取或输出成功,则漏斗会重置冷却时间,并同步更新自身数据。
正在渲染图表

现在,让我们移步到_尝试拉取和输出的具体的逻辑_。

当漏斗确定自己需要做些什么时,它会先尝试向指向的容器输出。随后,它会尝试从上方吸取物品。最后,如果吸取和输出中有任意一方成功了,它就会设置冷却,并更新同步自己的数据。

正在渲染图表

我们分别来看输出和拉取的逻辑。

先看_输出_...
_输出_输出操作由 insert() 方法完成,它的作用是将物品从漏斗传输到目标容器,并返回一个布尔值来表示是否成功传输。如果传输成功,则漏斗进入冷却状态。

输出的基本逻辑:

  1. 确认目标容器是否可用

    • 如果目标容器已满,则输出失败。
    • 如果目标容器有可用槽位,则尝试传输物品。
  2. 尝试输出物品

    • 遍历漏斗中的物品槽,逐个尝试传输物品到目标容器。
    • 只要有一次传输成功,就返回 “成功”;否则返回 “失败”。
正在渲染图表

当调用 transfer() 方法尝试传输物品时,transfer() 方法会返回一个物品组:

  • 若返回值为空,则说明物品已完全传输,输出成功。
  • 若返回值不为空,则说明物品未能全部传输,输出失败。
正在渲染图表

到此,输出的部分已经完成,接下来,我们来看看_拉取_的部分。
拉取物品的逻辑与输出类似,但方向相反:

  • 输出 时:漏斗 transfer() 物品至目标容器。
  • 拉取 时:某个容器 transfer() 物品至漏斗。

由于 transfer() 方法的逻辑马上会详细解释,因此这里不再赘述。

正在渲染图表

transfer() 是漏斗实现物品传输的核心方法。它接收以下参数:

  • from:物品来源(可以是漏斗或其他容器)
  • to:传输目标(可以是容器或另一个漏斗)
  • itemStack:希望传输的物品组
  • slot:目标容器中的目标槽位

执行流程如下:

  1. 检查目标槽位的状态

    • 如果该槽为空,直接将 itemStack 放入,并清空 itemStack。
    • 如果该槽已有相同类型的物品,并且仍有剩余空间,则合并物品并清空 itemStack。
  2. 判断是否传输成功

    • 传输后,若 itemStack 为空,则表示成功传输,否则表示失败。
  3. 特殊处理漏斗间传输的时序

    • 如果目标是另一个漏斗,则会给目标漏斗增加 8gt的冷却时间。
    • 若目标漏斗 to 已先于源漏斗 from 计算,则目标漏斗 to 仅会得到 7gt 的冷却时间。

      值得注意的是,这只在目标漏斗为空时发生;
      此外,就算from后于to 计算, 并给予8gt的冷却时间,由于随后 totick 时会将冷却立刻 - 1,所以结果上还是只有7gt冷却。

正在渲染图表

到此,你已经完全了解了漏斗的基本运作机制。相信这将会对之后的篇章理解提供很大的帮助。

2.1.4 移动中的活塞

另外一个及其重要的方块叫 moving_piston,移动的活塞,俗称 b36。

2.1.5 移动的活塞方块实体携带的信息

一个移动活塞方块实体携带一下基本信息:

  • pushedBlock 存储的方块
  • facing 朝向
  • extending 是否在伸出(与动画播放的方向有关)
  • source 是否是收回中的活塞本体
  • progress 当前的移动进度
  • savedWorldTime 上次执行逻辑时的世界时间

2.1.6 移动的活塞方块实体的功能

  • 处理活塞推送实体
  • 更新动画进度
  • 如果progress>=1,则将 MOVING_PISTON 替换为pushedBlock中存储的方块
  • 在客户端渲染动画

2.1.7 移动的活塞方块实体的工作流程

详情见5.7。

进阶深入底层机制细节跳过

3 方块实体间的时序

方块实体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, 不同区块的方块实体则取决于区块加载的顺序。

相关代码如下 (删减了部分无关内容)

java
MC 1.20.1
YARN
// 用于暂存在 tickBlockEntities 过程中动态加入的方块实体private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();//将要 tick 的方块实体列表protected final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList();private void tickBlockEntities() { this.tickingBlockEntities = true; if (!this.pendingBlockEntityTickers.isEmpty()) { this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); this.pendingBlockEntityTickers.clear(); } // 遍历正在 tick 的方块实体列表 Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator(); while (iterator.hasNext()) { TickingBlockEntity tickingBlockEntity = iterator.next(); if (tickingBlockEntity.isRemoved()) { // 如果方块实体已经被移除,则从列表中剔除 iterator.remove(); } else if (this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { // 如果所在位置应被 tick,则调用 tick () 方法进行更新 tickingBlockEntity.tick(); } } this.tickingBlockEntities = false;}// 添加一个方块实体的 tickerpublic void addBlockEntityTicker(TickingBlockEntity tickingBlockEntity) { (this.tickingBlockEntities ? this.pendingBlockEntityTickers : this.blockEntityTickers) .add(tickingBlockEntity);}// 区块内所有方块实体protected final Map<BlockPos, BlockEntity> blockEntities = Maps.newHashMap();// 区块加载完成后,注册所有已有的方块实体(将它们加入 tick 系统)public void registerAllBlockEntitiesAfterLevelLoad() { this.blockEntities.values().forEach(blockEntity -> { // 为该方块实体添加 ticker,即加入到 blockEntityTickers 中 this.updateBlockEntityTicker(blockEntity); });}
// SYNTAX_HIGHLIGHT
进阶内容结束
上一篇
树电与脚电
下一篇
方块事件