User Tools

Site Tools


zh_cn:tutorial:blockentity

This is an old revision of the document!


添加一个方块实体

介绍

BlockEntity主要用于在方块内存储数据。创建之前,您需要一个方块。本教程将介绍BlockEntity类的创建及其注册。

创建一个方块实体

最简单的方块实体仅继承BlockEntity,并使用默认构造函数。 这是完全有效的,但不会给予方块任何特殊功能。

public class DemoBlockEntity extends BlockEntity {
    public DemoBlockEntity(BlockPos pos, BlockState state) {
        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
    }
}

下面将向您展示如何创建ExampleMod.DEMO_BLOCK_ENTITY字段。

您可以简单地向此准系统类添加变量,或实现诸如TickableInventory之类的接口以添加更多功能。Tickable提供了一个单独的tick()方法,该方法将会在世界加载的每一刻调用一次。而Inventory则允许您的BlockEntity与自动化进行交互,例如漏斗——稍后可能会有专门针对此接口的单独教程。

注册你的方块实体

一旦创建了BlockEntity类,您将需要对其进行注册以使其起作用。 此过程的第一步是创建一个BlockEntityType,它将BlockBlockEntity链接在一起。 假设您的Block已创建并保存到本地变量DEMO_BLOCK,则将在下面的行中创建匹配的BlockEntityType。在本教程中,方块实体的ID是tutorial:demo_block_entity

BlockEntityType应在您的onInitialize方法中注册,以确保它在正确的时候被注册。

public static BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY;
 
@Override
public void onInitialize() {
    DEMO_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, "tutorial:demo_block_entity", FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, DEMO_BLOCK).build(null));
}

将方块实体连接到方块

一旦创建并注册了BlockEntityType,就可以在Block类中简单地实现BlockEntityProvider。每次放置方块,就会产生对应的方块实体。

public class DemoBlock extends Block implements BlockEntityProvider {
 
    [...]
 
    @Override
    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
        return new DemoBlockEntity(pos, state);
    }
}

序列化数据

如果要将任何数据存储在 BlockEntity 中,则需要保存并加载,或者仅在加载 BlockEntity 时保留,并且只要您返回到它。幸运的是,保存和加载非常简单——只需要覆盖 writeNbt()readNbt() 即可。

writeNbt() 返回一个 NBTCompound,其中应包含 BlockEntity 中的所有数据。如果您需要将 BlockEntity 数据与客户端同步,则此数据将保存到磁盘并通过数据包发送。调用 writeNbt 的默认实现非常重要,因为它将“标识数据(Identifying data)(位置和ID)保存到标签中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 BlockEntityType 不相关。知道了这一点,下面的示例演示了如何将 BlockEntity 中的整数保存到标签中。在此示例中,整数保存在键 number 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个条目(entry),并且需要记住该键以便以后检索数据。

public class DemoBlockEntity extends BlockEntity {
 
    // 储存数字的当前值
    private int number = 7;
 
    public DemoBlockEntity(BlockPos pos, BlockState state) {
        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
    }
 
    // 序列化方块实体
    @Override
    public voidwriteNbt(NbtCompound tag) {
        writeNbt(tag);
 
        // Save the current value of the number to the tag
        tag.putInt("number", number);
    }
}

为了以后检索数据,您还需要覆盖 readNBT。 此方法与 writeNBT 相反——不会将数据保存到 NBTCompound,而是您已经有了之前保存的标签,使您可以检索所需的任何数据。与 writeNbt 一样,必须调用 super.readNbt,并且您将需要使用相同的键来检索保存的数据。要检索我们之前保存的数字,请参见下面的示例。

// 取消序列化方块实体
@Override
public void readNbt(NbtCompound tag) {
    super.readNbt(tag);
    number = tag.getInt("number");
}

一旦实现了 writeNbtreadNbt 方法,您只需要确保在合适的时候调用即可。每当您的 BlockEntity 数据发生更改并需要保存时,请调用 markDirty()。这会将方块所在的区块标记为dirty,在世界下次保存时强制调用 writeNbt 方法。原则上,只要改变了 BlockEntity 类中的任何一个自定义变量,就只需要简单调用 markDirty

将服务器数据同步至客户端

数据通常是在服务器世界读取的。有时候你需要将所有或者部分数据同步到客户端,比如用于渲染。

对于 1.17.1 及以下版本,请实现 Fabric API 中的BlockEntityClientSerializable。此接口提供了 fromClientTagtoClientTag 方法,其作用与前面讨论的 readNbtwriteNbt 方法基本相同,只是专门用于发送和接收客户端上的数据。你可以简单地在 fromClientTagtoClientTag 两个方法中调用 readNbtwriteNbt

对于 1.18 及以上版本,请覆盖 toUpdatePackettoInitialChunkDataNbt

  @Nullable
  @Override
  public Packet<ClientPlayPacketListener> toUpdatePacket() {
    return BlockEntityUpdateS2CPacket.create(this);
  }
 
  @Override
  public NbtCompound toInitialChunkDataNbt() {
    return createNbt();
  }

警告: 需要调用world.updateListeners(pos, state, state, Block.NOTIFY_LISTENERS);来触发数据的同步。

方块实体刻

1.17 添加了静态的刻,然后你就可以实现 Tickable 接口。对于需要计划刻的方块,你只需要使用 Block 中的 getTicker,链接回到 Block Entity。参考下面关于刻的一个常见的实现。

在你的 Block 类中:

public class DemoBlock extends BlockWithEntity {
    [...]
    @Override
    public BlockRenderType getRenderType(BlockState state) {
        // 由于继承了BlockWithEntity,这个默认为INVISIBLE,所以我们需要更改它!
        return BlockRenderType.MODEL;
    }
    @Override
    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
        return checkType(type, ExampleMod.DEMO_BLOCK_ENTITY, (world1, pos, state1, be) -> DemoBlockEntity.tick(world1, pos, state1, be));
    }

在你的 Block Entity 中:

public class DemoBlockEntity extends BlockEntity {
    public DemoBlockEntity(BlockPos pos, BlockState state) {
        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);
    }
    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity be) {
        [...]
    }
}

概览

现在,您应该拥有自己的 BlockEntity,可以以各种方式扩展以适应您的需求。 您注册了 BlockEntityType,并用它来将 BlockBlockEntity 类连接在一起。 然后,您在 Block 类中实现了 BlockEntityProvider,并使用该接口提供了新 BlockEntity 的实例。 最后,您学习了如何将数据保存到 BlockEntity 中,以及如何检索以备后用。

zh_cn/tutorial/blockentity.1653557052.txt.gz · Last modified: 2022/05/26 09:24 by moheng