User Tools

Site Tools


zh_cn:tutorial:blockentity_modify_data

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_modify_data [2024/08/27 02:32] solidblockzh_cn:tutorial:blockentity_modify_data [2025/06/11 16:02] (current) – [使用数据组件] solidblock
Line 3: Line 3:
 在之前的教程,我们创建了[[blockentity|方块实体]],但是这些方块实体太无聊了,没有任何数据。所以我们尝试给它添加一些数据,并定义了序列化和反序列化数据的方块。 在之前的教程,我们创建了[[blockentity|方块实体]],但是这些方块实体太无聊了,没有任何数据。所以我们尝试给它添加一些数据,并定义了序列化和反序列化数据的方块。
  
 +===== 关于 NbtCompound 的注意事项 =====
 +
 +自从 1.21.5 开始,''NbtCompound'' 的相关方法被更改了。各 getter 方法返回的都是 ''Optional'' 对象,除非再指定一个参数以表示该项不存在时的默认值。例如:
 +
 +<code java>
 +// 没有指定默认值,将返回 Optional
 +Optional<Integer> value = nbt.getInt("value"); // 注意不是 OptionalInt
 +
 +// 指定默认值,当对应字段不存在时返回这个默认值
 +int value = nbt.getInt("value", 1000);
 +</code>
 +
 +对于集合类型,如复合标签和列表,可以返回一个空的对象作为默认值,例如:
 +<code java>
 +// 没有指定默认值,将返回 Optional
 +Optional<NbtCompound> config = nbt.getCompound("config");
 +
 +// 当对应字段不存在时,返回空复合标签
 +NbtCompound config = nbt.getCompoundOrEmpty("config");
 +</code>
 +
 +而在 1.21.5 之前,默认都会返回零值或者空白,如果需要指定默认值需要先自行用 ''contains'' 方法进行判断,例如:
 +<code java>
 +// 当对应字段不存在时返回 0。
 +int value = nbt.getInt("value");
 +
 +// 当对应字段不存在时返回空复合标签。
 +NbtCompound config = nbt.getCompound("config");
 +</code>
 +
 +此外,自从 1.21.5 开始,整个 NBT 复合标签以及里面的字段,可以直接使用 [[codec]] 进行解码,例如:
 +<code java>
 +NbtCompound nbt = new NbtCompound();
 +nbt.put("Id", Identifier.CODEC, Identifier.of("tutorial", "example_id"));
 +Optional<Identifier> id = nbt.get("Id", Identifier.CODEC);
 +</code>
 +
 +<code java>
 +NbtCompound nbt = new NbtCompound();
 +// 需要使用 RegistryOps,因为物品堆在编码解码时需要访问注册表内容
 +nbt.copyFromCodec(ItemStack.MAP_CODEC, wrapperLookup.getOps(NbtOps.INSTANCE), new ItemStack(Items.WHEAT));
 +Optional<ItemStack> stack = nbt.decode(ItemStack.MAP_CODEC, wrapperLookup.getOps(NbtOps.INSTANCE));
 +</code>
 +
 +此外,从 1.21.5 开始,列表支持混合不同类型的元素,而在之前的版本中,混入不同类型的元素会导致报错。
 +
 +关于 NBT 变化的更多信息,请参见 [[https://fabricmc.net/2025/03/24/1215.html|Fabric for Minecraft 1.21.5]]。
 ===== 序列化数据 ===== ===== 序列化数据 =====
  
-如果想在 ''BlockEntity'' 存储任何数据,必须保存和加载它,否则数据只会在 ''BlockEntity'' 被加载时保持,而且每次你重进游戏时会重置。幸运的是,保存和加载非常简单——只需要覆盖 ''writeNbt()'' 和 ''readNbt()'' 即可。+如果想在 ''BlockEntity'' 存储任何数据,必须保存和加载它,否则数据只会在 ''BlockEntity'' 被加载时保持,而且每次你重进游戏时会重置。幸运的是,保存和加载非常简单——只需要覆盖 ''writeNbt()''/''writeData()'' 和 ''readNbt()''/''readData()'' 即可。
  
-''writeNbt()'' 将会修改其参数 ''nbt'' 的内容,这个 ''nbt'' 包含了方块实体中的所有数据。该方法通常不会修改方块实体本身。方块实体数据将会存储在磁盘中,并且如果您需要将 ''BlockEntity'' 数据与客户端同步,则会通过封包发送。+''writeNbt()''/''writeData()'' 将会修改其参数 ''nbt'' 的内容(或通过 ''writeView'' 参数修改相应的 NBT 内容),这个 ''nbt'' 包含了方块实体中的所有数据。该方法通常不会修改方块实体本身。方块实体数据将会存储在磁盘中,并且如果您需要将 ''BlockEntity'' 数据与客户端同步,则会通过封包发送。
  
 在旧版本中,调用 ''super.writeNbt()'' 非常重要,因为方块实体的坐标及其方块实体类型 id 保存到 nbt 中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 ''BlockEntityType'' 不相关。但是,最新版本中不是必要的,因为这些数据会通过 ''createNbt()'' 方法处理。 在旧版本中,调用 ''super.writeNbt()'' 非常重要,因为方块实体的坐标及其方块实体类型 id 保存到 nbt 中。否则,您尝试保存的所有其他数据都将丢失,因为它与位置和 ''BlockEntityType'' 不相关。但是,最新版本中不是必要的,因为这些数据会通过 ''createNbt()'' 方法处理。
Line 13: Line 60:
 知道了这一点,下面的示例演示了如何将 ''BlockEntity'' 中的整数保存到标签中。在此示例中,整数保存在键 ''number'' 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个项,并且需要记住该键以便以后读取数据。 知道了这一点,下面的示例演示了如何将 ''BlockEntity'' 中的整数保存到标签中。在此示例中,整数保存在键 ''number'' 下——您可以将其替换为任何字符串,但是标签中的每个键只能有一个项,并且需要记住该键以便以后读取数据。
  
 +//在 1.21.5 及之前的版本://
 <code java DemoBlockEntity.java> <code java DemoBlockEntity.java>
 public class DemoBlockEntity extends BlockEntity { public class DemoBlockEntity extends BlockEntity {
Line 25: Line 73:
     // 序列化方块实体     // 序列化方块实体
     @Override     @Override
-    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) {+    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) {
         // 将数字的当前值保存到 nbt         // 将数字的当前值保存到 nbt
 +        super.writeNbt(nbt, registries);
         nbt.putInt("number", number);         nbt.putInt("number", number);
- 
-        super.writeNbt(nbt, wrapper); 
     }     }
 } }
 </code> </code>
  
-为了以读取数据,您还需要覆盖 ''readNBT''。此方法与 ''writeNBT'' 相反——不会将数保存到 ''NBTCompound'',而是您已经有了之前保存的 nbt 数据,使您可以检索所需的任何数据。该方法会修改方块实体本身,不会修改这个 ''nbt'' 参数。要检索我们之前保存的数字,请参见下面的示例。+//在 1.21.6 及之的版本:// 
 +<code java> 
 +    @Override 
 +    public void writeData(WriteView view) { 
 +        super.writeData(view); 
 +        // 将数字的当前值保存到 nbt 
 +        view.putInt("number", number); 
 +    }
  
 +</code>
 +
 +为了以后读取数据,您还需要覆盖 ''readNBT''/''readData''。此方法与 ''writeNBT''/''writeData'' 相反——不会将数据保存到 ''NBTCompound'',而是您已经有了之前保存的 nbt 数据,使您可以检索所需的任何数据。该方法会修改方块实体本身,不会修改这个 ''nbt'' 参数。要检索我们之前保存的数字,请参见下面的示例。
 +
 +//在 1.21.5 及之前的版本://
 <code java> <code java>
     // 反序列化方块实体     // 反序列化方块实体
     @Override     @Override
-    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) { +    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
-        super.readNbt(nbt, wrapper);+        super.readNbt(nbt, registries); 
 +         
 +        number = nbt.getInt("number", 0); 
 +    } 
 +</code> 
 + 
 +//在 1.21.6 及之后的版本:// 
 +<code java> 
 +    @Override 
 +    public void readData(ReadView view) { 
 +        super.readData(ReadView view);
                  
-        number = nbt.getInt("number");+        number = view.getInt("number", 0);
     }     }
 </code> </code>
  
-要获取方块实体的 NBT 数据,调用 ''createNbt(registryLookup)'',这会自动地处理一些数据,例如位置和组件。+要获取方块实体的 NBT 数据,调用 ''createNbt(registries)'',这会自动地处理一些数据,例如位置和组件。
  
 旧有版本中必须调用 ''super.readNbt()'' 和 ''super.writeNbt()'',因为有必要处理坐标等数据。在旧版本中,如果 ''createNbt'' 不存在,尝试 ''writeNbt(new NbtCompound())'' 旧有版本中必须调用 ''super.readNbt()'' 和 ''super.writeNbt()'',因为有必要处理坐标等数据。在旧版本中,如果 ''createNbt'' 不存在,尝试 ''writeNbt(new NbtCompound())''
Line 64: Line 133:
    
   @Override   @Override
-  public NbtCompound toInitialChunkDataNbt() { +  public NbtCompound toInitialChunkDataNbt(RegistryWrapper.WrapperLookup registries) { 
-    return createNbt();+    return createNbt(registries);
   }   }
 </code> </code>
Line 78: Line 147:
     }     }
    
 +    // 以下两个方法,在 1.21.6 中有所改变,参见上面的示例。
     @Override     @Override
-    public void writeNbt(NbtCompound nbt) {+    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
 +        super.writeNbt(nbt, registries);
         nbt.putInt("number", number);         nbt.putInt("number", number);
-  
-        super.writeNbt(nbt); 
     }     }
          
     @Override     @Override
-    public void readNbt(NbtCompound nbt) { +    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
-        super.readNbt(nbt);+        super.readNbt(nbt, registries);
    
-        number = nbt.getInt("number");+        // 对于 1.21.5 之前的版本,请使用 nbt.getInt("number"); 
 +        number = nbt.getInt("number", 0);
     }     }
 } }
Line 177: Line 247:
   }   }
  
 +  // 仅限 1.21.5 及之前的版本:
   @Override   @Override
   public void removeFromCopiedStackNbt(NbtCompound nbt) {   public void removeFromCopiedStackNbt(NbtCompound nbt) {
     nbt.remove("number");     nbt.remove("number");
 +  }
 +  
 +  // 对于 1.21.6 及之后的版本:
 +  @Override
 +  public void removeFromCopiedStackData(WriteView view) {
 +    view.remove("number");
   }   }
 </code> </code>
  
-''removeFromCopiedStackNbt'' 的用途是,复制物品堆时,因为数据组件已经被复制,所以 NBT 就不再需要了。如果拾取物品(按下 ''Ctrl'' 的同时按下鼠标中键),组件会转移到物品堆。如果需要在不按下 ''Ctrl'' 的情况下就转移这些组件(就像原版旗帜的行为),请看 [[blockentity_sync_itemstack]]。+''removeFromCopiedStackNbt''/''removeFromCopiedStackData'' 的用途是,复制物品堆时,因为数据组件已经被复制,所以 NBT 就不再需要了。如果拾取物品(按下 ''Ctrl'' 的同时按下鼠标中键),组件会转移到物品堆。如果需要在不按下 ''Ctrl'' 的情况下就转移这些组件(就像原版旗帜的行为),请看 [[blockentity_sync_itemstack]]。
zh_cn/tutorial/blockentity_modify_data.1724725966.txt.gz · Last modified: 2024/08/27 02:32 by solidblock