• Forge论坛

导航页

  • 主页
  • 向文档做出贡献
  • 入门
    • 概述
    • 模组文件
    • 规划你的模组结构
    • 版本号
  • 核心概念
    • 注册表
    • 端位(Sides)
    • 事件
    • 模组生命周期
    • 资源
    • 国际化与本地化
  • 方块
    • 概述
    • 方块状态
  • 物品
    • 概述
    • BlockEntityWithoutLevelRenderer
  • 网络
    • 概述
    • SimpleImpl
    • 实体的同步
  • 方块实体
    • 概述
      • 注册
      • 创建一个BlockEntity
      • 将数据存储到你的BlockEntity
      • 计时的BlockEntity
      • 向客户端同步数据
    • BlockEntityRenderer
  • 游戏特效
    • 粒子效果
    • 音效
  • 数据储存
    • Capabilities
    • Saved Data
    • 编解码器(Codecs)
  • 图形用户界面
    • 菜单(Menus)
    • 屏幕(Screens)
  • 渲染
    • 模型扩展
      • 概述
      • 根变换
      • 渲染类型
      • 部分可见度
      • 面数据
    • 模型加载器
      • 概述
      • 烘焙模型
      • 变换
      • 物品重载
  • 资源
    • 客户端资源(Assets)
      • 概述
      • 模型
        • 概述
        • 纹理色调
        • 物品属性
    • 服务端数据(Data)
      • 概述
      • 配方
        • 概述
        • 自定义配方
        • 原料
        • 非数据包配方
      • 战利品表
      • 全局战利品修改器
      • 标签
      • 进度
      • 条件性加载数据
  • 数据生成
    • 概述
    • 客户端资源(Assets)
      • 模型提供者
      • 语言提供者
      • 音效提供者
    • 服务端数据(Data)
      • 配方提供者
      • 战利品表提供者
      • 标签提供者
      • 进度提供者
      • 全局战利品修改器提供者
      • 数据包注册表对象提供者
  • 杂项功能
    • 配置
    • 键盘布局
    • 游戏测试
    • Forge更新检查器
    • 调试分析器
  • 进阶主题
    • 访问转换器
  • 向Forge做出贡献
    • 概述
    • Pull Request准则
  • 旧版本
    • 概述
    • 移植到当前版本

方块实体

BlockEntities类似于绑定到某一方块的简化的Entities。 它们能用于存储动态数据、执行基于游戏刻的任务和动态渲染。 原版Minecraft中的一些例子是处理箱子的物品栏、熔炉的熔炼逻辑或信标的区域效果。 模组中存在更高级的示例,例如采石场(如BC)、分拣机(如IC2)、管道(如BC)和显示器(如OC)。(括号内容为译者注。)

注意

BlockEntities并不是万能的解决方案,如果使用错误,它们可能会导致游戏卡顿。 如果可能的话,尽量避免使用。

注册

方块实体是动态创建和删除的,因此它们本身不是注册表对象。

为了创建BlockEntity,你需要继承BlockEntity类。这样,另一个对象被替代性地注册以方便创建和引用动态对象的类型。对于BlockEntity,这些对象被称为BlockEntityType。

BlockEntityType可以像任何其他注册表对象一样进行注册。若要构造BlockEntityType,可以通过BlockEntityType$Builder#of使用其Builder形式。这需要两个参数:BlockEntityType$BlockEntitySupplier,它接受BlockPos和BlockState来创建关联BlockEntity的新实例,以及该BlockEntity可以附加到的Block的可变参数。构建该BlockEntityType是通过调用BlockEntityType$Builder#build来完成的。其接受一个Type,表示用于引用某个DataFixer中的此注册表对象的类型安全引用。由于DataFixer是用于模组的可选系统,因此其也可用null代替。

// 对于某个类型为DeferredRegister<BlockEntityType<?>>的REGISTER
public static final RegistryObject<BlockEntityType<MyBE>> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null));

// 在MyBE(一个BlockEntity的子类)中
public MyBE(BlockPos pos, BlockState state) {
  super(MY_BE.get(), pos, state);
}

创建一个BlockEntity

要创建BlockEntity并将其附加到Block,EntityBlock接口必须在你的Block子类上实现。方法EntityBlock#newBlockEntity(BlockPos, BlockState)必须实现并返回一个你的BlockEntity的新实例。

将数据存储到你的BlockEntity

为了保存数据,请重写以下两个方法:

BlockEntity#saveAdditional(CompoundTag tag)

BlockEntity#load(CompoundTag tag)
每当包含BlockEntity的LevelChunk从标签加载/保存到标签时,都会调用这些方法。 使用它们以读取和写入你的方块实体类的字段。

注意

每当你的数据发生改变时,你需要调用BlockEntity#setChanged;否则,保存存档时可能会跳过包含你的BlockEntity的LevelChunk。

重要

调用super方法非常重要!

标签名称id、x、y、z、ForgeData和ForgeCaps均由super方法保留。

计时的BlockEntity

如果你需要一个计时的BlockEntity,例如为了跟踪冶炼过程中的进度,则必须在EntityBlock中实现并重写另一个方法:EntityBlock#getTicker(Level, BlockState, BlockEntityType)。这可以根据用户所处的逻辑端实现不同的计时器,或者只实现一个通用计时器。无论哪种情况,都必须返回BlockEntityTicker。由于这是一个功能性的接口,因此它可以转而采用一个表示计时器的方法:

// 在某个Block子类内
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
  return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null;
}

// 在MyBlockEntity内
public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) {
  // 处理一些事情
}

注意

这个方法在每个游戏刻都会调用;因此,你应该避免在这里进行复杂的计算。如果可能的话,你应该每X个游戏刻进行更复杂的计算。(一秒钟内的游戏刻数量可能低于20(二十),但不会更高)

向客户端同步数据

有三种方法可以将数据同步到客户端:在区块加载时同步、在方块更新时同步以及使用自定义网络消息同步。

在LevelChunk加载时同步

为此你需要重写

BlockEntity#getUpdateTag()

IForgeBlockEntity#handleUpdateTag(CompoundTag tag)
同样,这非常简单,第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的BlockEntity不包含太多数据,你可以使用将数据存储到你的BlockEntity小节之外的方法。

重要

为方块实体同步过多/无用的数据可能会导致网络拥塞。你应该通过在客户端需要时仅发送客户端需要的信息来优化网络使用。例如,在更新标签中发送方块实体的物品栏通常是没有必要的,因为这可以通过其AbstractContainerMenu进行同步。

在方块更新时同步

这个方法有点复杂,但同样,你只需要重写两个或三个方法。 下面是它的一个简易的实现示例:

@Override
public CompoundTag getUpdateTag() {
  CompoundTag tag = new CompoundTag();
  //将你的数据写入标签
  return tag;
}

@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
  // 将从#getUpdateTag得到标签
  return ClientboundBlockEntityDataPacket.create(this);
}

// 可以重写IForgeBlockEntity#onDataPacket。默认地,其将遵从#load。
静态构造器ClientboundBlockEntityDataPacket#create接受:

  • 该BlockEntity。
  • 从该BlockEntity中获取CompoundTag的可选函数。默认情况下,其使用BlockEntity#getUpdateTag。

现在,要发送数据包,必须在服务端上发出更新通知。

Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags)
pos应为你的BlockEntity的位置。 对于oldState和newState,你可以传递那个位置的BlockState。 flags是一个应含有2的位掩码(bitmask),其将向客户端同步数据。有关更多信息以及flags的其余信息,参见Block。flag 2与Block#UPDATE_CLIENTS相同。

使用自定义网络消息同步

这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有需要同步的数据才是真正同步的。在尝试之前,你应该先查看Networking部分,尤其是SimpleImpl。一旦你创建了自定义网络消息,你就可以使用SimpleChannel#send(PacketDistributor$PacketTarget, MSG)将其发送给所有加载了该BlockEntity的用户。

警告

进行安全检查很重要,当消息到达玩家时,BlockEntity可能已经被销毁/替换!你还应该检查区块是否已加载(Level#hasChunkAt(BlockPos))。

Built with MkDocs using a custom theme. Hosted by Read the Docs.
Enable Dark Theme