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 [2021/05/18 05:05] – [介绍] solidblockzh_cn:tutorial:blockentity [2023/09/03 01:37] (current) – [序列化数据] wjz_p
Line 1: Line 1:
-====== 添加一个方块实体 ======+====== 添加方块实体 ======
  
 ===== 介绍 ===== ===== 介绍 =====
- +方块实体主要用于在方块内存储数据。创建之前,您需要一个[[zh_cn:tutorial:blocks|方块]]。本教程将介绍BlockEntity类的创建及其注册。
-BlockEntity主要用于在方块内存储数据。创建之前,您需要一个[[zh_cn:tutorial:blocks|方块]]。本教程将介绍BlockEntity类的创建及其注册。+
  
 ===== 创建一个方块实体 ===== ===== 创建一个方块实体 =====
  
-最简单的方块实体仅扩展''BlockEntity'',并使用默认构造函数。 这是完全有效的,但不会给予方块任何特殊功能。+最简单的方块实体仅继承 ''BlockEntity'',并使用默认构造函数。 这是完全有效的,但不会给予方块任何特殊功能。
  
 <code java> <code java>
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
-   public DemoBlockEntity() { +    public DemoBlockEntity(BlockPos pos, BlockState state) { 
-      super(ExampleMod.DEMO_BLOCK_ENTITY); +        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state); 
-   }+    }
 } }
 </code> </code>
  
-面将向您展示如何创建''ExampleMod.DEMO_BLOCK_ENTITY''字段。 +请确保这个构造方法只接收这两个参数,否则我们后写的方法引用 ''DemoBlockEntity::new'' 会无效。这个 ''ExampleMod.DEMO_BLOCK_ENTITY'' 字段稍后再写。 
- +  
-您可以简单地向此准系统类添加变量,或实现诸如''Tickable''和''Inventory''之类的接口以添加更多功能。''Tickable''提供了一个单独的''tick()''方法,该方法将会在世界加载的每一刻调用一次。而''Inventory''则允许您的BlockEntity与自动进行交互,例如漏斗——稍后可能会有专门针对此接口的单独教程。+您可以简单地向此准系统类添加变量,或实现诸如 ''Tickable'' 和 ''Inventory'' 之类的接口以添加更多功能。''Tickable'' 提供了一个单独的 ''tick()'' 方法,该方法将会在世界加载的每一刻调用一次。而 ''Inventory'' 则允许您的方块实体自动进行交互,例如漏斗——稍后可能会有专门针对此接口的单独教程。
  
 ===== 注册你的方块实体 ===== ===== 注册你的方块实体 =====
  
-一旦创建了''BlockEntity''类,您将需要对其进行注册以使其起作用。 此过程的第一步是创建一个''BlockEntityType'',它将''Block''和''BlockEntity''链接在一起。 假设您的''Block''已创建并保存到本地变量''DEMO_BLOCK'',则将在下面的行中创建匹配的''BlockEntityType''。 ''modid:demo''应替换为您Mod ID和您要在其下注册''BlockEntity''的名称+一旦创建了 ''BlockEntity'' 类,您将需要对其进行注册以使其起作用。 此过程的第一步是创建一个 ''BlockEntityType'',它将 ''Block'' 和 ''BlockEntity'' 链接在一起。假设您的 ''Block'' 已创建并保存到了静态常字段 ''DEMO_BLOCK'' ,则将在下面的行中创建匹配的 ''BlockEntityType''在本教程中,方块实体的ID是 ''tutorial:demo_block_entity''
  
-''BlockEntityType''应在的''onInitialize''方法中注册,以确保它在正确的时候注册。+''BlockEntityType'' 应在初始化或 ''onInitialize'' 方法中注册,以确保它在正确的时候注册。
  
 <code java> <code java>
-public static BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY; +    public static final BlockEntityType<DemoBlockEntity> DEMO_BLOCK_ENTITY = Registry.register( 
- +        Registries.BLOCK_ENTITY_TYPE, 
-@Override +        new Identifier("tutorial", "demo_block_entity"), 
-public void onInitialize() { +        FabricBlockEntityTypeBuilder.create(DemoBlockEntity::new, DEMO_BLOCK).build() 
-   DEMO_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, "modid:demo", BlockEntityType.Builder.create(DemoBlockEntity::new, DEMO_BLOCK).build(null)); +    );
-}+
 </code> </code>
 +
 +这个方块实体类型定义了只有 ''DEMO_BLOCK'' 可以拥有这个方块实体类型。如果你想要让方块实体类型支持更多方块,只需要将其添加到 ''FabricBlockEntityTypeBuilder.create'' 的参数中即可。如果方法引用 ''DemoBlockEntity::new'' 无法解析,检查 ''DemoBlockEntity'' 的构造方法的参数是否正确。
  
 ==== 将方块实体连接到方块 ==== ==== 将方块实体连接到方块 ====
  
-一旦创建并注册了''BlockEntityType'',就可以在''Block''类中简单地实现''BlockEntityProvider''。每次放置方块,就会产生对应的方块实体。+一旦创建并注册了 ''BlockEntityType'',就可以在 ''Block'' 类中简单地实现 ''BlockEntityProvider''。每次放置方块,就会产生对应的方块实体。
  
 <code java> <code java>
-public class MyBlock extends Block implements BlockEntityProvider {+public class DemoBlock extends Block implements BlockEntityProvider {
    
-   [...]+    [...]
    
-   @Override +    @Override 
-   public BlockEntity createBlockEntity(BlockView blockView) { +    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { 
-      return new DemoBlockEntity(); +        return new DemoBlockEntity(pos, state); 
-   }+    }
 } }
 </code> </code>
Line 54: Line 54:
 ===== 序列化数据 ===== ===== 序列化数据 =====
  
-如果要将任何数据存储在''BlockEntity''则需要保存加载,或者仅加载''BlockEntity''时保只要您返回到它。幸运的是,保存和加载非常简单——只需要覆盖''toTag()''和''fromTag()''即可。+如果您想在 ''BlockEntity'' 存储任何数据必须保存加载否则数据只会在''BlockEntity''被加载时保每次你重进游戏时会重置。幸运的是,保存和加载非常简单——只需要覆盖 ''writeNbt()'' 和 ''readNbt()'' 即可。
  
-''toTag()''返回一个''CompoundTag''其中应包含''BlockEntity''中的所有数据。如果您需要将''BlockEntity''数据与客户端同步,则此数据将保存到磁盘并通过数据包发送。调用''toTag''的默认实现非常重要,因为它将“识数据(Identifying data)''(位置和ID)''保存到标签中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和''BlockEntityType''不相关。知道了这一点,下面的示例演示了如何将''BlockEntity''中的整数保存到标签中。在此示例中,整数保存在键''number''下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个条目(entry),并且需要记住该键以便以后检索数据。+''writeNbt()'' 将会修改其参数 ''nbt'' 的内容这个 ''nbt'' 包含了方块实体中的所有数据。该方法通常不会修改方块实体本身。方块实体数据将会存储在磁盘中,并且如果您需要将 ''BlockEntity'' 数据与客户端同步,则通过包发送。调用 ''super.writeNbt()'' 非常重要,因为方块实体的坐及其方块实体类型 id 保存到 nbt 中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 ''BlockEntityType'' 不相关。 
 + 
 +知道了这一点,下面的示例演示了如何将 ''BlockEntity'' 中的整数保存到标签中。在此示例中,整数保存在键 ''number'' 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个,并且需要记住该键以便以后读取数据。
  
 <code java> <code java>
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
  
-   // 储存数字的当前值 +    // 储存数字的当前值 
-   private int number = 7;+    private int number = 7;
        
-   public DemoBlockEntity() { +    public DemoBlockEntity(BlockPos pos, BlockState state) { 
-      super(ExampleMod.DEMO_BLOCK_ENTITY); +        super(ExampleMod.DEMO_BLOCK_ENTITY, pos, state); 
-   }+    }
        
-   // 序列化方块实体 +    // 序列化方块实体 
-   @Override +    @Override 
-   public CompoundTag toTag(CompoundTag tag) { +    public void writeNbt(NbtCompound nbt) { 
-      super.toTag(tag); +        // Save the current value of the number to the tag 
-       +        nbt.putInt("number", number); 
-      // Save the current value of the number to the tag +         
-      tag.putInt("number", number); +        super.writeNbt(nbt) 
-       +    }
-      return tag; +
-   }+
 } }
 </code> </code>
  
-为了以后检索数据,您还需要覆盖''fromTag''。 此方法与''toTag''相反-不会将数据保存到''CompoundTag'',而是提供了之前保存的标签,使您可以检索所需的任何数据。 与''toTag''一样,必须调用''super.fromTag'',并且您将需要使用相同的键来检索保存的数据。 要检索我们之前保存的号码,请参见下面的示例。+为了以后读取数据,您还需要覆盖 ''readNBT''。此方法与 ''writeNBT'' 相反——不会将数据保存到 ''NBTCompound'',而是您已经有了之前保存的 nbt 数据,使您可以检索所需的任何数据。该方法会修改方块实体本身,不会修改这个 ''nbt'' 参数。与 ''writeNbt'' 一样,必须调用 ''super.readNbt'',并且您将需要使用相同的键来检索保存的数据。要检索我们之前保存的数字,请参见下面的示例。
  
 <code java> <code java>
-// 取消序列化方块实体+// 序列化方块实体
 @Override @Override
-public void fromTag(BlockState state, CompoundTag tag) { +public void readNbt(NbtCompound nbt) { 
-   super.fromTag(state, tag); +    super.readNbt(nbt); 
-   number = tag.getInt("number");+     
 +    number = nbt.getInt("number");
 } }
 </code> </code>
  
-一旦实现了''toTag''和''fromTag''方法,您只需要确保在正确的时调用它们即可。每当您的''BlockEntity''数据发生更改并需要保存时,请调用''markDirty()''。这会通过标记方块所在的区块为dirty,在世界下次保存时强制调用''toTag''方法。原则上,只要改了''BlockEntity''类中的任何一个自定义变量,就只需要简单调用''markDirty''+一旦实现了 ''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);'' 来触发数据的同步,否则客户端不会知道方块实体已经改变。 
 +===== 方块实体刻 ===== 
 +1.17 添加了静态的刻,然后你就可以实现 ''Tickable'' 接口。对于需要计划刻的方块,你只需要使用 ''Block'' 中的 ''getTicker'',链接回到 ''Block Entity''。参考下面关于刻的一个常见的实现。 
 + 
 +在你的 ''Block'' 类中: 
 +<code java> 
 +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)); 
 +    } 
 +</code> 
 +在你的 ''BlockEntity'' 类中: 
 +<code java> 
 +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) { 
 +        [...] 
 +    } 
 +
 +</code>
  
-如果您需要将某些''BlockEntity''数据同步到客户端,出于渲染等目的,则应从Fabric API中实现''BlockEntityClientSerializable''。此类提供了''fromClientTag''和''toClientTag''方法,其作用与前面讨论的''fromTag''和''toTag''方法基本相同,只是专门用于发送和接收客户端上的数据。 +===== 概览 =====
-===== Overview =====+
  
-现在,您应该拥有自己的''BlockEntity'',可以按照各种方式扩展以适应您的需求。 您注册了''BlockEntityType'',并用它来将''Block''和''BlockEntity''类连接在一起。 然后,您在''Block''类中实现了''BlockEntityProvider'',并使用该接口提供了新''BlockEntity''的实例。 最后,您学习了如何将数据保存到''BlockEntity''中,以及如何检索以备后用。+现在,您应该拥有自己的 ''BlockEntity''可以各种方式扩展以适应您的需求。 您注册了 ''BlockEntityType'',并用它来将 ''Block'' 和 ''BlockEntity'' 类连接在一起。 然后,您在 ''Block'' 类中实现了 ''BlockEntityProvider'',并使用该接口提供了新 ''BlockEntity'' 的实例。 最后,您学习了如何将数据保存到 ''BlockEntity'' 中,以及如何检索以备后用。
zh_cn/tutorial/blockentity.1621314313.txt.gz · Last modified: 2021/05/18 05:05 by solidblock