🇨🇳 中文 (Chinese - China)
🇨🇳 中文 (Chinese - China)
外观
🇨🇳 中文 (Chinese - China)
🇨🇳 中文 (Chinese - China)
外观
本页面基于这个版本编写:
1.21.4
方块实体是一种存储额外数据的方法,这些数据不是方块状态的一部分:库存内容、自定义名称等。 Minecraft 将方块实体用于箱子、熔炉和命令方块等方块。
例如,我们将创建一个方块,计算它被右键单击的次数。
为了让 Minecraft 识别并加载新的方块实体,我们需要创建一个方块实体类型。 这是通过扩展 'BlockEntity' 类并将其注册到一个新的 'ModBlockEntities' 类来完成的。
public class CounterBlockEntity extends BlockEntity {
public CounterBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.COUNTER_BLOCK_ENTITY, pos, state);
}
}
注册一个 'BlockEntity' 会产生一个 'BlockEntityType',就像我们上面使用的 'COUNTER_BLOCK_ENTITY' 一样:
public static final BlockEntityType<CounterBlockEntity> COUNTER_BLOCK_ENTITY =
register("counter", CounterBlockEntity::new, ModBlocks.COUNTER_BLOCK);
private static <T extends BlockEntity> BlockEntityType<T> register(
String name,
FabricBlockEntityTypeBuilder.Factory<? extends T> entityFactory,
Block... blocks
) {
Identifier id = Identifier.of(FabricDocsReference.MOD_ID, name);
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, FabricBlockEntityTypeBuilder.<T>create(entityFactory, blocks).build());
}
TIP
注意 'CounterBlockEntity' 的构造函数需要两个参数,而 'BlockEntity' 的构造函数需要三个参数:'BlockEntityType'、'BlockPos' 和 'BlockState'。 如果我们没有对 'BlockEntityType' 进行硬编码,那么 'ModBlockEntities' 类就不会编译! 这是因为 'BlockEntityFactory' 是一个函数式接口,它描述了一个只接受两个参数的函数,就像我们的构造函数一样。
接下来,要实际使用方块实体,我们需要一个实现 'BlockEntityProvider' 的方块。 让我们创建一个并将其命名为 'CounterBlock'。
TIP
有两种方法来实现这个:
BlockWithEntity
的方块并实现 createBlockEntity
方法BlockEntityProvider
的方块并重写 createBlockEntity
方法我们将在本例中使用第一种方法,因为 BlockWithEntity
也提供了一些不错的实用程序。
public class CounterBlock extends BlockWithEntity {
public CounterBlock(Settings settings) {
super(settings);
}
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {
return createCodec(CounterBlock::new);
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new CounterBlockEntity(pos, state);
}
}
使用 BlockWithEntity
作为父类意味着我们还需要实现 createCodec
方法,这相当容易。
与单例方块不同,每个方块实例都会创建一个新的方块实体。 这是通过 createBlockEntity
方法完成的,该方法采用位置和 BlockState
,并返回 BlockEntity
,如果不应该有则返回 null
。
不要忘记在 ModBlocks
类中注册该方块,就像在创建你的第一个方块指南中一样:
public static final Block COUNTER_BLOCK = register(
"counter_block",
CounterBlock::new,
AbstractBlock.Settings.create(),
true
);
现在我们有了一个方块实体,我们可以用它来存储该方块被右键单击的次数。 我们通过向 CounterBlockEntity
类添加 clicks
字段来实现这一点:
private int clicks = 0;
public int getClicks() {
return clicks;
}
public void incrementClicks() {
clicks++;
markDirty();
}
incrementClicks
中使用的 markDirty
方法告诉游戏该实体的数据已更新;当我们添加方法来序列化计数器并从保存文件加载回来时,这将很有用。
接下来,我们需要在每次右键单击方块时增加该字段。 这是通过重写 CounterBlock
类中的 onUse
方法来实现的:
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (!(world.getBlockEntity(pos) instanceof CounterBlockEntity counterBlockEntity)) {
return super.onUse(state, world, pos, player, hit);
}
counterBlockEntity.incrementClicks();
player.sendMessage(Text.literal("You've clicked the block for the " + counterBlockEntity.getClicks() + "th time."), true);
return ActionResult.SUCCESS;
}
由于没有将 BlockEntity
传递到方法中,我们使用 world.getBlockEntity(pos)
,如果 BlockEntity
无效,则从方法返回。
现在我们有了一个功能方块,我们应该使计数器在游戏重启时不会重置。 这是通过在游戏保存时将其序列化为 NBT,并在加载时反序列化来实现的。
序列化是通过 writeNbt
方法完成的:
@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
nbt.putInt("clicks", clicks);
super.writeNbt(nbt, registryLookup);
}
在这里,我们添加应该保存到传递的 NbtCompound
中的字段:在计数器方块的情况下,这就是 clicks
字段。
读取类似,但不是保存到 NbtCompound
,而是获取之前保存的值,并将它们保存在 BlockEntity 的字段中:
@Override
protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.readNbt(nbt, registryLookup);
clicks = nbt.getInt("clicks");
}
现在,如果我们保存并重新加载游戏,计数器方块应该从保存时停止的地方继续。
BlockEntityProvider
接口还定义了一个名为 getTicker
的方法,该方法可用于为方块的每个实例每次运行代码。 我们可以通过创建一个用作 BlockEntityTicker
的静态方法来实现这一点:
getTicker
方法还应检查传递的 BlockEntityType
是否与我们正在使用的相同,如果相同,则返回每刻时调用的函数。 值得庆幸的是,有一个实用函数可以在 BlockWithEntity
中执行检查:
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
return validateTicker(type, ModBlockEntities.COUNTER_BLOCK_ENTITY, CounterBlockEntity::tick);
}
CounterBlockEntity::tick
是我们应该在 CounterBlockEntity
类中创建的静态方法 tick
的引用。 像这样构建它不是必需,但它是一种保持代码干净和有序的很好的做法。
假设我们想让计数器每 10 刻(每秒 2 次)只能增加一次。 我们可以通过向 CounterBlockEntity
类添加 ticksSinceLast
字段并在每刻时增加它来实现这一点:
public static void tick(World world, BlockPos blockPos, BlockState blockState, CounterBlockEntity entity) {
entity.ticksSinceLast++;
}
别忘了序列化和反序列化这个字段!
现在我们可以使用 ticksSinceLast
来检查计数器是否可以在 incrementClicks
中增加:
if (ticksSinceLast < 10) return;
ticksSinceLast = 0;
TIP
如果区块实体似乎没有打钩,请尝试检查关于注册到上下文的代码! 它应将对该实体有效的块传入到 BlockEntityType.Builder
中,否则会在控制台中发出警告:
[13:27:55] [Server thread/WARN] (Minecraft) Block entity fabric-docs-reference:counter @ BlockPos{x=-29, y=125, z=18} state Block{fabric-docs-reference:counter_block} invalid for ticking: