zh_cn:tutorial:extendedscreenhandler

​使用扩展的 ScreenHandler 同步数据 ​

在本教程中,我们将使用 ExtendedScreenHandler ​在 ScreenHandler 打开时将任意数据从服务器传输到客户端 ScreenHandler。

在我们的示例中,我们将用方块的位置作为容器的标题。

方块实体

由于 Block 类根本不需要更改,我们将其留在这里。

我们的方块实体现在实现了 ExtendedScreenHandlerFactory,这个接口为我们提供了 writeScreenOpeningData 方法,当它请求客户端(client)打开一个 ScreenHandler 时,将在服务器(server)上调用该方法。 您写入 PacketByteBuf 的数据将通过网络传输到客户端(client)。

BoxBlockEntity.java
  1. public class BoxBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, ImplementedInventory {
  2. private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(9, ItemStack.EMPTY);
  3.  
  4. public BoxBlockEntity() {
  5. super(Test.BOX_BLOCK_ENTITY);
  6. }
  7.  
  8.  
  9. //来自 ImplementedInventory 接口
  10.  
  11. @Override
  12. public DefaultedList<ItemStack> getItems() {
  13. return inventory;
  14.  
  15. }
  16.  
  17. //这些方法来自 NamedScreenHandlerFactory 接口
  18.  
  19. @Override
  20. public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
  21. //我们将它提供给 screenHandler 作为我们的类实现 Inventory
  22. //一开始只有服务器(Server)有 Inventory ,这将在 ScreenHandler 中同步到客户端(Cilent)
  23. return new BoxScreenHandler(syncId, playerInventory, this);
  24. }
  25.  
  26. @Override
  27. public Text getDisplayName() {
  28. return new TranslatableText(getCachedState().getBlock().getTranslationKey());
  29. }
  30.  
  31. //此方法来自 ExtendedScreenHandlerFactory
  32.  
  33. //当它请求客户端(client)打开 screenHandler 时,在服务器(server)上调用此方法
  34. //您写入 packetByteBuf 的内容将自动以(数据)包的形式传输到客户端
  35. //并在客户端(client)调用带有 packetByteBuf 参数的 ScreenHandler 构造函数
  36. //
  37. //您在此处插入内容的顺序与您需要提取它们的顺序相同。您不需要颠倒顺序!
  38. @Override
  39. public void writeScreenOpeningData(ServerPlayerEntity serverPlayerEntity, PacketByteBuf packetByteBuf) {
  40. //pos 字段是 BlockEntity 的公共字段
  41. packetByteBuf.writeBlockPos(pos);
  42. }
  43. }

新的 ExtendedScreenHandler (实例)

BoxScreenHandler.java
  1. public class BoxScreenHandler extends ScreenHandler {
  2. //我们保存从服务器获得的 blockPos 并为其提供一个 getter,以便 BoxScreen 可以读取该信息
  3. private BlockPos pos;
  4. private final Inventory inventory;
  5.  
  6. //当服务器(Server)希望它打开 screenHandler 时,在客户端(Cilent)上调用此构造函数,
  7. //客户端将使用一个空的 Inventory 调用超级构造函数,并且 screenHandler 将自动将此空库存与服务器上的库存同步。
  8.  
  9. //新:客户端的构造函数现在获取我们在 BlockEntity 中填充的 PacketByteBuf
  10. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, PacketByteBuf buf) {
  11. this(syncId, playerInventory, new SimpleInventory(9));
  12. pos = buf.readBlockPos();
  13. }
  14.  
  15. //此构造函数从服务器上的 BlockEntity 调用,服务器知道容器的库存(你可以将它理解为物品栏),因此可以直接将其作为参数提供。 然后,此库存(物品栏)将同步到客户端。
  16. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory) {
  17. //[...]
  18. //有关其余代码,请参阅第一个 Screenhandler 教程
  19.  
  20. //想一想,为什么我们在这里使用 BlockPos.ORIGIN?
  21. //This is because the packetByteBuf with our blockPosition is only availible on the Client, so we need a placeholder
  22. //value here. This is not a problem however, as the Server version of the ScreenHandler does not really need this
  23. //information.
  24. //(机翻警告)这是因为带有我们 blockPosition 的 packetByteBuf 仅在 Client 上可用,所以我们这里需要一个占位符值。 然而,这不是问题,
  25. //因为 ScreenHandler 的服务器版本并不真正需要此信息。
  26. pos = BlockPos.ORIGIN;
  27.  
  28. [...]
  29. }
  30.  
  31. //这个 getter 将被我们的 Screen 类使用
  32. public BlockPos getPos() {
  33. return pos;
  34. }
  35.  
  36. @Override
  37. public boolean canUse(PlayerEntity player) {
  38. return this.inventory.canPlayerUse(player);
  39. }
  40.  
  41. // 参阅 Screenhandler 教程
  42. // Shift + Player Inv Slot
  43. @Override
  44. public ItemStack transferSlot(PlayerEntity player, int invSlot);
  45. }

在 Screen 中使用 ExtendedScreenHandler 信息

BoxScreen.java
  1. public class BoxScreen extends HandledScreen<ScreenHandler> {
  2. private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");
  3.  
  4. public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {
  5. super(handler, inventory, getPositionText(handler).orElse(title));
  6. //我们尝试获取方块位置以将其用作我们的标题,如果由于某种原因失败,我们将使用默认标题
  7. }
  8.  
  9. //此方法将尝试从 ScreenHandler 获取位置,因为 ScreenRendering 仅发生在客户端上,
  10. //我们在此处获取具有正确 BlockPos 的 ScreenHandler 实例!
  11. private static Optional<Text> getPositionText(ScreenHandler handler) {
  12. if (handler instanceof BoxScreenHandler) {
  13. BlockPos pos = ((BoxScreenHandler) handler).getPos();
  14. return pos != null ? Optional.of(new LiteralText("(" + pos.toShortString() + ")")) : Optional.empty();
  15. } else {
  16. return Optional.empty();
  17. }
  18. }
  19.  
  20.  
  21. @Override
  22. protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) { [...] }
  23.  
  24. @Override
  25. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { [...] }
  26.  
  27. @Override
  28. protected void init() { [...] }
  29. }

注册我们的 ScreenHandler

ExampleMod.java
  1. public class ExampleMod implements ModInitializer {
  2.  
  3. [...]
  4. public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER;
  5.  
  6. static {
  7. [...]
  8.  
  9. //我们现在使用 registerExtended 作为我们的 screenHandler 现在在其构造函数中接受一个 packetByteBuf
  10. BOX_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(BOX, BoxScreenHandler::new);
  11. }
  12.  
  13. @Override
  14. public void onInitialize() {
  15.  
  16. }
  17. }

结果

您现在已经了解了如何在 ScreenHandler 打开时传输数据。 在图像中您可以看到结果:方块的标题现在是它的位置。 请注意,这只是一个演示,还有更简单的方法可以将位置设置为标题。

您可能想知道:即使在屏幕打开后,我可以再次传输这些数据吗? 这可以通过在屏幕打开后发送自定义数据包来实现。 (see: Networking Tutorial)
您可能还想看看BlockEntityClientSerializable, 来自Fabric API 的界面.

如果您只想同步整数值,您可以使用PropertyDelegate: propertydelegates.

zh_cn/tutorial/extendedscreenhandler.txt · Last modified: 2022/02/10 13:32 by timothy_starman