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 [2024/08/25 23:51] – update translations 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 之间的序列化和反序列化、提供物品栏等。本教程提供方块实体功能的最常见一些实现。 方块实体支持一系列方法以支持一些功能,例如与 NBT 之间的序列化和反序列化、提供物品栏等。本教程提供方块实体功能的最常见一些实现。
  
-===== 注册你的方块实体 =====+===== 注册方块和方块实体 =====
  
-一旦创建了 ''BlockEntity'' 类,您将需要对其进行注册以使其起作用。 过程的第一步是创建一个 ''BlockEntityType''将 ''Block'' 和 ''BlockEntity'' 接在一起。创建 ''Block'' 并保存到静态常量字段 ''DEMO_BLOCK'',则将在下面的行中创建匹配的 ''BlockEntityType''。在本教程中,方块实体的ID是 ''tutorial:demo_block''+一旦创建了 ''BlockEntity'' 类,需要注册它才起作用。这个过程的第一步是在我们的 ''TutorialBlockEntities'' 类中创建 ''BlockEntityType'' 对象,将 ''Block'' 和 ''BlockEntity'' 接在一起。创建 ''Block'' 并保存到 ''TutorialBlocks'' 类的静态常量字段 ''DEMO_BLOCK'' 中。在本教程中,方块实体的 ID 是 ''tutorial:demo_block''
  
-''BlockEntityType'' 应在类的初始化或 ''onInitialize'' 方法中注册,以确保在正确的时候注册。+''BlockEntityType'' 应在类的初始化或 ''onInitialize'' 方法中注册,以确保在正确的时候注册。在这个例子中,我们在单独的类中注册(见 [[blocks]])
  
 +<code java TutorialBlocks.java>
 +public final class TutorialBlocks {
 +    [...]
 +    
 +    // 对于 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 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() {
 +  }
 +}
 +
 +</code>
 +
 +记得要在 ''ModInitializer'' 里面引用 ''initialize'' 方法:
 <code java ExampleMod.java> <code java ExampleMod.java>
-public class ExampleMod extends ModInitializer { +public class ExampleMod implements ModInitializer { 
-    public static final DemoBlock DEMO_BLOCK = Registry.register( +    [...]
-        Registries.BLOCK, +
-        Identifier.of("tutorial", "demo_block"), +
-        new DemoBlock(AbstractBlock.Settings.create()) +
-    ); +
-    public static final BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY = Registry.register( +
-        Registries.BLOCK_ENTITY_TYPE, +
-        Identifier.of("tutorial", "demo_block"), +
-        BlockEntityType.Builder.create(DemoBlockEntity::new, DEMO_BLOCK).build() +
-    );+
          
     @Override     @Override
     public void onInitialize() {     public void onInitialize() {
         [...]         [...]
 +        
 +        TutorialBlockEntityTypes.initialize();
     }     }
 } }
 </code> </code>
- 
-> :!: 在这个例子中,为了简体,我们只把方块和方块实体置于 ''ModInitializer'' 里面。实际开发模组时,考虑将这些放到单独的类里面。 
  
 对于旧版本,如果无法访问 ''BlockEntityType.Builder.create'',尝试 ''FabricBlockEntityTypeBuilder.create'' 对于旧版本,如果无法访问 ''BlockEntityType.Builder.create'',尝试 ''FabricBlockEntityTypeBuilder.create''
  
-这个方块实体类型定义了只有 ''DEMO_BLOCK'' 可以拥有这个方块实体类型。如果你想要让方块实体类型支持更多方块,只需要将其添加到 ''FabricBlockEntityTypeBuilder.create'' 的参数中即可。如果方法引用 ''DemoBlockEntity::new'' 无法解析,检查 ''DemoBlockEntity'' 的构造方法的参数是否正确。+这个方块实体类型定义了只有 ''TutorialBlocks.DEMO_BLOCK'' 可以拥有这个方块实体类型。如果你想要让方块实体类型支持更多方块,只需要将其添加到 ''FabricBlockEntityTypeBuilder.create'' 的参数中即可。如果方法引用 ''DemoBlockEntity::new'' 无法解析,检查 ''DemoBlockEntity'' 的构造方法的参数是否正确。 
 + 
 +> **注意:**和其他方块一样,这个方块也需要方块模型和物品模型,可能也需要战利品表,关于如何创建请参见 [[blocks]]。对于战利品表,有[[blockentity_sync_itemstac|后续教程]]会提到如何改进战利品表表以包含方块实体数据
  
 ==== 将方块实体连接到方块 ==== ==== 将方块实体连接到方块 ====
Line 73: Line 98:
         return new DemoBlockEntity(pos, state);         return new DemoBlockEntity(pos, state);
     }     }
-} 
-</code> 
  
-===== 序列化数据 ===== 
- 
-如果您想在 ''BlockEntity'' 存储任何数据,必须保存和加载它,否则数据只会在''BlockEntity''被加载时保持,而且每次你重进游戏时会重置。幸运的是,保存和加载非常简单——只需要覆盖 ''writeNbt()'' 和 ''readNbt()'' 即可。 
- 
-''writeNbt()'' 将会修改其参数 ''nbt'' 的内容,这个 ''nbt'' 包含了方块实体中的所有数据。该方法通常不会修改方块实体本身。方块实体数据将会存储在磁盘中,并且如果您需要将 ''BlockEntity'' 数据与客户端同步,则会通过封包发送。调用 ''super.writeNbt()'' 非常重要,因为方块实体的坐标及其方块实体类型 id 保存到 nbt 中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 ''BlockEntityType'' 不相关。 
- 
-知道了这一点,下面的示例演示了如何将 ''BlockEntity'' 中的整数保存到标签中。在此示例中,整数保存在键 ''number'' 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个项,并且需要记住该键以便以后读取数据。 
- 
-<code java> 
-public class DemoBlockEntity extends BlockEntity { 
- 
-    // 储存数字的当前值 
-    private int number = 7; 
-    
-    public DemoBlockEntity(BlockPos pos, BlockState state) { 
-        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state); 
-    } 
-    
-    // 序列化方块实体 
     @Override     @Override
-    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) { +    protected BlockRenderType getRenderType(BlockState state) { 
-        // 将数字的当前值保存到 nbt +        return BlockRenderType.MODEL;
-        nbt.putInt("number", number); +
- +
-        super.writeNbt(nbt, wrapper);+
     }     }
 } }
 </code> </code>
  
-为了以后读取数据,您还需要覆盖 ''readNBT''。此方法与 ''writeNBT'' 相反——不会将数据保存到 ''NBTCompound'',而是您已经有了之前保存的 nbt 数据,使您可以检索所需的任何数据。该方法会修改方块实体本身,不会修改这个 ''nbt'' 参数。与 ''writeNbt'' 一样,必须调用 ''super.readNbt'',并且您将需要使用相同的键来检索保存的数据。要检索我们之前保存的数字,请参见下面的示例。 +覆盖''getRenderType''因为 ''BlockWithEntity''默认让它不可
- +
-<code java> +
-    // 反序列化方块实体 +
-    @Override +
-    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) { +
-        super.readNbt(nbt, wrapper); +
-         +
-        number = nbt.getInt("number"); +
-    } +
-</code> +
- +
-一旦实现了 ''writeNbt'' 和 ''readNbt'' 方法,您只需要确保在合适的时候调用即可。每当您的 ''BlockEntity'' 数据发生更改并需要保存时,请调用 ''markDirty()''。这会将方块所在的区块标记dirty,在世界下次保存时强制调用 ''writeNbt'' 方法。原则上,只要修改了 ''BlockEntity'' 类中的任何一个自定义变量,就只需要简单调用 ''markDirty'',否则当你退出并重进世界后,这个方块实体依然是没有修改过的。 +
- +
-===== 将服务器数据同步至客户端 ===== +
-数据通常是在服务器世界读取的。大多数数据都是客户端需要知道的,例如客户端并不需要知道箱子和熔炉里面有什么,除非打开它。但对于某些方块实体,例如告示牌和旗帜,你需要将所有或者部分数据告知客户端,比如用于渲染。 +
- +
-对于 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);'' 来触发数据的同步,否则客户端不会知道方块实体已经改变+
  
 ===== 方块实体刻 ===== ===== 方块实体刻 =====
Line 156: Line 125:
 在你的 ''BlockEntity'' 类中: 在你的 ''BlockEntity'' 类中:
 <code java DemoBlockEntity.java> <code java DemoBlockEntity.java>
-public class DemoBlockEntity extends BlockEntity {+public class DemoBlockEntity extends BlockEntity implements BlockEntityTicker<DemoBlockEntity> {
     [...]     [...]
          
-    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity be) {+    @Override 
 +    public static void tick(World world, BlockPos pos, BlockState state, DemoBlockEntity blockEntity) {
         [...]         [...]
     }     }
Line 165: 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.1724629861.txt.gz · Last modified: 2024/08/25 23:51 by solidblock