User Tools

Site Tools


zh_cn:tutorial:blockentity

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
zh_cn:tutorial:blockentity [2022/12/16 02:14] – [注册你的方块实体] solidblockzh_cn:tutorial:blockentity [2025/04/01 12:20] (current) – [方块实体刻] solidblock
Line 2: Line 2:
  
 ===== 介绍 ===== ===== 介绍 =====
-方块实体主要用于在方块内存储数据。创建之前,您需要一个[[zh_cn:tutorial:blocks|方块]]。本教程将介绍BlockEntity类的创建及其注册。+**方块实体**主要用于在方块内存储数据。创建之前,您需要一个[[blocks|方块]]。本教程将介绍 BlockEntity 类的创建及其注册。
  
-===== 创建一个方块实体 =====+===== 创建方块实体 =====
  
-最简单的方块实体仅继承 ''BlockEntity'',并使用默认构造函数。 这是完全有效的,但不会给予方块任何特殊功能。+最简单的方块实体仅继承 ''BlockEntity'',并使用默认构造函数。这是完全有效的,但不会给予方块任何特殊功能。
  
-<code java>+<code java DemoBlockEntity.java>
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
     public DemoBlockEntity(BlockPos pos, BlockState state) {     public DemoBlockEntity(BlockPos pos, BlockState state) {
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);+        super(TutorialBlockEntityTypes.DEMO_BLOCK, pos, state);
     }     }
 } }
 </code> </code>
  
-请确保这个构造方法只接收这两个参数,否则我们后面写的方法引用 ''DemoBlockEntity::new'' 将会无效。这个 ''ExampleMod.DEMO_BLOCK_ENTITY'' 字段稍后再写。 +请确保这个构造方法只接收这两个参数,否则我们后面写的方法引用 ''DemoBlockEntity::new'' 将会无效。这个 ''TutorialBlockEntityTypes.DEMO_BLOCK'' 字段稍后再写。
-  +
-您可以简单地向此准系统类添加变量,或实现诸如 ''Tickable'' 和 ''Inventory'' 之类的接口以添加更多功能。''Tickable'' 提供了一个单独的 ''tick()'' 方法,该方法将会在世界加载的每一刻调用一次。而 ''Inventory'' 则允许您的方块实体自动进行交互,例如漏斗——稍后可能会有专门针对此接口的单独教程+
  
-===== 注册你的方块实体 =====+方块实体支持一系列方法以支持一些功能,例如与 NBT 之间序列化和反序列化、提供物品栏等。本教程提供方块实体功能的最常见一些实现。
  
-一旦创建了 ''BlockEntity'' 类,您将需要对其进行注册以使其起作用。 此过程的第一步是创建一个 ''BlockEntityType'',它将 ''Block'' 和 ''BlockEntity'' 链接在一起。假设您的 ''Block'' 已创建并保存到了静态常量字段 ''DEMO_BLOCK'' 中,则将在下面的行中创建匹配的 ''BlockEntityType''。在本教程中,方块实体的ID是 ''tutorial:demo_block_entity''+===== 注册方块和方块实体 =====
  
-''BlockEntityType'' 应在类的初始化或 ''onInitialize'' 方法注册以确正确时候注册+一旦创建了 ''BlockEntity'',就需要注册它才起作用。这个过程的第一步是在我们的 ''TutorialBlockEntities'' 创建 ''BlockEntityType'' 对象将 ''Block'' 和 ''BlockEntity'' 连接在一起。创建 ''Block''存到 ''TutorialBlocks'' 类的静态常量字段 ''DEMO_BLOCK'' 中。本教程中,方块实体的 ID 是 ''tutorial:demo_block''
  
-<code java> +''BlockEntityType'' 应在类的初始化或 ''onInitialize'' 方法中注册,以确保在正确的时候注册。在这个例子中,我们在单独的类中注册(见 [[blocks]]) 
-    public static final BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY Registry.register+ 
-        Registries.BLOCK_ENTITY_TYPE, +<code java TutorialBlocks.java> 
-        new Identifier("tutorial", "demo_block_entity"), +public final class TutorialBlocks { 
-        FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, DEMO_BLOCK).build() +    [...] 
-    );+     
 +    // 对于 1.21.2 之前的版本 
 +    // public static final DemoBlock DEMO_BLOCK = register("demo_block", new DemoBlock(AbstractBlock.Settings.create())); 
 +     
 +    // 对于 1.21.2 及之后的版本 
 +    public static final DemoBlock DEMO_BLOCK = register("demo_block", DemoBlock::new, AbstractBlock.Settings.create()); 
 +     
 +    [...] 
 +}
 </code> </code>
  
-这个方块实体类型定义了只有 ''DEMO_BLOCK'' 可以拥有这个方块实体类型。如果你想要让方块实体类型支持更多方块,只需要将其添加到 ''FabricBlockEntityTypeBuilder.create'' 的参数中即可。如果方法引用 ''DemoBlockEntity::new'' 无法解析,检查 ''DemoBlockEntity'' 的构造方法的参数是否正确。+<code java TutorialBlockEntityTypes.java> 
 +public class TutorialBlockEntityTypes { 
 +  public static <T extends BlockEntityType<?>> T register(String path, T blockEntityType) { 
 +    return Registry.register(Registries.BLOCK_ENTITY_TYPE, Identifier.of("tutorial", path), blockEntityType); 
 +  }
  
-==== 将方块实体连接到方块 ====+  public static final BlockEntityType<DemoBlockEntity> DEMO_BLOCK register( 
 +      "demo_block", 
 +       
 +      // 对于 1.21.2 之前的版本,请使用 BlockEntityType.Builder。 
 +      FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, TutorialBlocks.DEMO_BLOCK).build() 
 +  ); 
 +   
 +  public static void initialize() { 
 +  } 
 +}
  
-一旦创建并注册了 ''BlockEntityType'',就可以在 ''Block'' 类中简单地实现 ''BlockEntityProvider''。每次放置方块,就会产生对应的方块实体。+</code>
  
-<code java> +记得要在 ''ModInitializer'' 里面引用 ''initialize'' 方法: 
-public class DemoBlock extends Block implements BlockEntityProvider { +<code java ExampleMod.java> 
- +public class ExampleMod implements ModInitializer {
     [...]     [...]
- +    
     @Override     @Override
-    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { +    public void onInitialize() { 
-        return new DemoBlockEntity(pos, state);+        [...] 
 +         
 +        TutorialBlockEntityTypes.initialize();
     }     }
 } }
 </code> </code>
  
-===== 序列化数据 =====+对于旧版本,如果无法访问 ''BlockEntityType.Builder.create'',尝试 ''FabricBlockEntityTypeBuilder.create''
  
-如果要将任何数据存储在 ''BlockEntity'' 需要保存并加载,或者仅在载 ''BlockEntity'' 时保留,并且只要您返回到它。幸运是,保存和加载非常简单——只需要覆盖 ''writeNbt()'' 和 ''readNbt()'' 即可+这个方块实体类型定义了只有 ''TutorialBlocks.DEMO_BLOCK'' 可以拥有这个方块实体类型。如果你想要让方块实体类型支持更多方块需要将其添到 ''FabricBlockEntityTypeBuilder.create''参数中即可。如果方法引用 ''DemoBlockEntity::new'' 无法解析,检查 ''DemoBlockEntity'' 的构造方法的参数是否正确
  
-''writeNbt()'' 将会修改参数 ''nbt'' 的内容,这个 ''nbt'' 包含了方块实体中的所有数据。该方法通常不会修改方块实体本身。方块实体数据将会存储在磁盘中并且如果您需要将 ''BlockEntity'' 数据与客户端同步通过封发送。调用 ''super.writeNbt()'' 非常重要,因为方块实体的坐标及其方块实体类型 id 保存到 nbt 中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 ''BlockEntityType'' 不相关+> **注意:**和他方块一样,这个方块也需要方块模型和物品模型可能也需要战利品表关于如何创建请参见 [[blocks]]。对于战利品表,有[[blockentity_sync_itemstac|后续教程]]提到如何改进战利品表表以方块实体数据。
  
-知道了这一点,下面的示例演示了如何将 ''BlockEntity'' 中的整数保存标签中。在此示例中,整数保存在键 ''number'' 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个项,并且需要记住该键以便以后读取数据。+==== 方块实体连接方块 ====
  
-<code java> +一旦创建并注册了 ''BlockEntityType'',就需要与之关联的方块。你可以继承 ''BlockWithEntity'',或者简单地实现 ''BlockEntityProvider'' 并覆盖 ''createBlockEntity''。每次放置方块,就会产生对应的方块实体。
-public class DemoBlockEntity extends BlockEntity {+
  
-    // 储存数字的当前值 +<code java DemoBlock.java> 
-    private int number = 7; +public class DemoBlock extends BlockWithEntity { 
-    +    public DemoBlock(Settings settings) { 
-    public DemoBlockEntity(BlockPos pos, BlockState state) { +        super(settings);
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state);+
     }     }
-    +
-    // 序列化方块实体+
     @Override     @Override
-    public void writeNbt(NbtCompound nbt) { +    protected MapCodec<? extends DemoBlock> getCodec() { 
-        // Save the current value of the number to the tag +        return createCodec(DemoBlock::new);
-        nbt.putInt("number", number); +
-         +
-        super.writeNbt(nbt)+
     }     }
-} 
-</code> 
  
-为了以后读取数据,您还需要覆盖 ''readNBT''。此方法与 ''writeNBT'' 相反——不会将数据保存到 ''NBTCompound'',而是您已经有了之前保存的 nbt 数据,使您可以检索所需的任何数据。该方法会修改方块实体本身,不会修改这个 ''nbt'' 参数。与 ''writeNbt'' 一样,必须调用 ''super.readNbt'',并且您将需要使用相同的键来检索保存的数据。要检索我们之前保存的数字,请参见下面的示例。+    @Override 
 +    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { 
 +        return new DemoBlockEntity(pos, state); 
 +    }
  
-<code java> +    @Override 
-// 反序列化方块实体 +    protected BlockRenderType getRenderType(BlockState state) { 
-@Override +        return BlockRenderType.MODEL
-public void readNbt(NbtCompound nbt) { +    }
-    super.readNbt(nbt)+
-     +
-    number = nbt.getInt("number");+
 } }
 </code> </code>
  
-一旦实现了 ''writeNbt'' 和 ''readNbt'' 方法,您只需要确保在合适的时候调用即可。每当您的 ''BlockEntity'' 数据发生更改并需要保存时,请调用 ''markDirty()''。这会将方块所在的区块标记dirty,在世界下次保存时强制调用 ''writeNbt'' 方法。原则上,只要修改了 ''BlockEntity'' 类中的任何一个自定义变量,就只需要简单调用 ''markDirty'',否则当你退出并重进世界后,这个方块实体依然是没有修改过的+覆盖''getRenderType''因为 ''BlockWithEntity''默认让它不可见
  
-===== 将服务器数据同步至客户端 ===== 
-数据通常是在服务器世界读取的。大多数数据都是客户端不需要知道的,例如客户端并不需要知道箱子和熔炉里面有什么,除非打开它。但对于某些方块实体,例如告示牌和旗帜,你需要将所有或者部分数据告知客户端,比如用于渲染。 
- 
-对于 1.17.1 及以下版本,请实现 Fabric API 中的 ''BlockEntityClientSerializable''。此接口提供了 ''fromClientTag'' 和 ''toClientTag'' 方法,其作用与前面讨论的 ''readNbt'' 和 ''writeNbt'' 方法基本相同,只是专门用于发送和接收客户端上的数据。你可以简单地在 ''fromClientTag'' 和 ''toClientTag'' 两个方法中调用 ''readNbt'' 和 ''writeNbt'' 
- 
-对于 1.18 及以上版本,请覆盖 ''toUpdatePacket'' 和 ''toInitialChunkDataNbt'' 
-<code java> 
-  @Nullable 
-  @Override 
-  public Packet<ClientPlayPacketListener> toUpdatePacket() { 
-    return BlockEntityUpdateS2CPacket.create(this); 
-  } 
-  
-  @Override 
-  public NbtCompound toInitialChunkDataNbt() { 
-    return createNbt(); 
-  } 
-</code> 
-**警告**: 需要调用 ''world.updateListeners(pos, state, state, Block.NOTIFY_LISTENERS);'' 来触发数据的同步,否则客户端不会知道方块实体已经改变。 
 ===== 方块实体刻 ===== ===== 方块实体刻 =====
-1.17 添加了静态的刻,然后你就可以实现 ''Tickable'' 接口对于需计划刻的方块,你只需要使用 ''Block'' 中的 ''getTicker'',链接回到 ''Block Entity''。参考下面关于刻的一个常见的实现。+刻是指方块在每一刻(每 1/20 秒)运行些什么。要方块有刻通常需要使用 ''Block'' 中的 ''getTicker'',链接回到 ''BlockEntity'' 中的静态方法 ''tick''。参考下面关于刻的常见的实现。
  
 在你的 ''Block'' 类中: 在你的 ''Block'' 类中:
-<code java>+<code java DemoBlock.java>
 public class DemoBlock extends BlockWithEntity { public class DemoBlock extends BlockWithEntity {
     [...]     [...]
-    @Override +
-    public BlockRenderType getRenderType(BlockState state) { +
-        // 由于继承了BlockWithEntity,这个默认为INVISIBLE,所以我们需要更改它! +
-        return BlockRenderType.MODEL; +
-    }+
     @Override     @Override
     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {     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));+        // 如果只需要在服务器上有刻,确保检查 world.isClient 
 +        return validateTicker(type, ExampleMod.DEMO_BLOCK_ENTITY, DemoBlockEntity::tick);
     }     }
 </code> </code>
 +
 在你的 ''BlockEntity'' 类中: 在你的 ''BlockEntity'' 类中:
-<code java> +<code java DemoBlockEntity.java> 
-public class DemoBlockEntity extends BlockEntity { +public class DemoBlockEntity extends BlockEntity implements BlockEntityTicker<DemoBlockEntity> 
-    public DemoBlockEntity(BlockPos pos, BlockState state) { +    [...] 
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state); +     
-    } +    @Override 
-    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity be) {+    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity blockEntity) {
         [...]         [...]
     }     }
Line 143: Line 135:
 </code> </code>
  
-===== 概览 =====+===== 下一步 ===== 
 + 
 +现在,您应该拥有自己的 ''BlockEntity'',可以以各种方式扩展以适应您的需求。注册了 ''BlockEntityType'',并用它来将 ''Block'' 和 ''BlockEntity'' 类连接在一起。然后,继承了 ''BlockWithEntity'' 并使用了接口 ''BlockEntityProvider'' 以提供 ''BlockEntity'' 的新实例。
  
-现在,您应该拥有自己的 ''BlockEntity'',可以以各种式扩展以适应您需求。 您注册了 ''BlockEntityType''并用它来将 ''Block'' 和 ''BlockEntity'' 类连接在一起。 然后,您在 ''Block'' 类中实现了 ''BlockEntityProvider'',并使用该接口提供了新 ''BlockEntity'' 的实。 最后,您学习了何将数据到 ''BlockEntity'' 中,以及如何检索以备后+你也学习了如何为它添加课刻。下一步,可以尝试对块实体进行一些复杂操作,例如: 
 +  * [[blockentity_modify_data|修改方块实体数据]] 
 +  * [[inventory|作为物品栏在方块实体储物品]] 
 +  * [[blockentityrenderers|使方块实体渲染器动态渲染]] 
 +  * [[screenhandler|创建容器方块]]
zh_cn/tutorial/blockentity.1671156847.txt.gz · Last modified: 2022/12/16 02:14 by solidblock