Table of Contents

使用 PropertyDelegates 同步整形数据

PropertyDelegatePropertyDelegate 是一种包含多个可读写的整型值的容器。

在这章教程中,我们将会同步客户端和服务器之间的整型值,比如原版熔炉熔炼进度。

要理解这篇教程,你需要先阅读 ScreenHandler 教程,其中提到的方法代码本页不再提及。

为了减小复杂性,这章教程不再使用 ExtendedScreenHandler

BlockEntity

由于 Block 类完全不需要更改,所以我们把它放在这里。

我们的 BlockEntity 现在实现了 Tickable,这将提供 tick() 方法,每刻都会调用该方法,我们用这个方法来增加想要同步的整数值。

BoxBlockEnity.java
  1. public class BoxBlockEntity extends BlockEntity implements NamedScreenHandlerFactory, ImplementedInventory, Tickable {
  2. private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(9, ItemStack.EMPTY);
  3. // 这是我们希望同步的整型,每刻会增加 1。
  4. private int syncedInt;
  5.  
  6. // PropertyDelegate 是一个接口,我们将在这里使用内联实现。
  7. // 它通常可以包含多个整型作为索引标志的数据,但是在本例中只有一个整型
  8. private final PropertyDelegate propertyDelegate = new PropertyDelegate() {
  9. @Override
  10. public int get(int index) {
  11. return syncedInt;
  12. }
  13.  
  14. @Override
  15. public void set(int index, int value) {
  16. syncedInt = value;
  17. }
  18.  
  19. // 这里应该返回你的 delegate 中整型的数量,在本例中只有一个
  20. @Override
  21. public int size() {
  22. return 1;
  23. }
  24. };
  25.  
  26. public BoxBlockEntity() {
  27. super(Test.BOX_BLOCK_ENTITY);
  28. }
  29.  
  30.  
  31. // 来自 ImplementedInventory
  32.  
  33. @Override
  34. public DefaultedList<ItemStack> getItems() {
  35. return inventory;
  36.  
  37. }
  38.  
  39. //这些方法来自 NamedScreenHandlerFactory 接口
  40.  
  41. @Override
  42. public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
  43. // 当我们的类执行完物品栏时,我们将此提供给 screenHandler
  44. // 只有服务器在开始时拥有物品栏,这将会在 ScreenHandler 中同步到客户端
  45.  
  46. // 类似于物品栏:服务器获得 PropertyDelegate 并将其直接给 screen handler 的服务器实例
  47. return new BoxScreenHandler(syncId, playerInventory, this,propertyDelegate);
  48. }
  49.  
  50. @Override
  51. public Text getDisplayName() {
  52. // 对于 1.18.2 或更低版本,请使用 return new TranslatableText(getCachedState().getBlock().getTranslationKey());
  53. return Text.translatable(getCachedState().getBlock().getTranslationKey());
  54. }
  55.  
  56. // 每刻增加一个同步的整型,我们仅在服务器上这么做,从而用于演示。
  57. @Override
  58. public void tick() {
  59. if(!world.isClient)
  60. syncedInt++;
  61. }
  62. }

我们的新的 ScreenHandler

BoxScreenHandler.java
  1. public class BoxScreenHandler extends ScreenHandler {
  2. private final Inventory inventory;
  3. PropertyDelegate propertyDelegate;
  4.  
  5. // 当服务器想要打开 screenHandler 时,客户端就会调用这个构造函数
  6. // 客户端将调用父级构造函数,其中物品栏是空的。screenHandler 将自动将这个空的物品栏同步到服务器上
  7.  
  8. // 类似于物品栏,客户端将分配一个空的 propertyDelegate 并且它将自动与服务器同步
  9.  
  10. public BoxScreenHandler(int syncId, PlayerInventory playerInventory) {
  11. this(syncId, playerInventory, new SimpleInventory(9),new ArrayPropertyDelegate(1));
  12. }
  13.  
  14. // 该构造函数从服务器上的 BlockEntity 调用,服务器知道容器(Container)中的物品栏,因此可以直接将其作为参数提供。这个物品栏以及 propertyDelegate 将被同步到客户端
  15. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory, PropertyDelegate propertyDelegate) {
  16. super(Test.BOX_SCREEN_HANDLER, syncId);
  17. checkSize(inventory, 9);
  18. this.inventory = inventory;
  19. this.propertyDelegate = propertyDelegate;
  20. // 有些物品栏会在玩家打开时作自定义逻辑操作(Custom Logic)
  21. inventory.onOpen(playerInventory.player);
  22.  
  23. // 我们需要告诉 screenHandler 关于 propertyDelegate 里的数据,否则它不会同步这里面的数据
  24. this.addProperties(propertyDelegate);
  25.  
  26. // 这将会把物品槽(Slot)放在 3x3 网格中正确的位置,物品槽(Slot)在服务端和客户端都存在!
  27. [...]
  28.  
  29. }
  30.  
  31. // 我们为已同步的整型提供了 getter, 所以 Screen 可以访问它并且在屏幕上显示它。
  32. public int getSyncedNumber(){
  33. return propertyDelegate.get(0);
  34. }
  35.  
  36. @Override
  37. public boolean canUse(PlayerEntity player) {
  38. return this.inventory.canPlayerUse(player);
  39. }
  40.  
  41. @Override
  42. public ItemStack transferSlot(PlayerEntity player, int invSlot) {[...]}
  43. }

使用 Screen 显示信息

当屏幕在其构造函数中获得 ScreenHandler 时,我们可以从上面访问 property delegates,并可以在屏幕上渲染这个整数。

  1. public class BoxScreen extends HandledScreen<ScreenHandler> {
  2. private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");
  3. BoxScreenHandler screenHandler;
  4.  
  5. public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {
  6. super(handler, inventory, title);
  7. // 我们保存了对 screenhandler 的引用,这样我们就可以在屏幕上呈现 propertyDelegate 中的数字
  8. screenHandler = (BoxScreenHandler) handler;
  9.  
  10. }
  11.  
  12. @Override
  13. protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) {[...]}
  14.  
  15. @Override
  16. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  17. // 我们只是在容器的某个地方渲染我们同步的数字,这毕竟是一个演示
  18. // 最后一个参数是一个颜色代码,使字体亮绿色
  19. textRenderer.draw(matrices, Integer.toString(screenHandler.getSyncedNumber()), 0, 0, 65280);
  20. renderBackground(matrices);
  21. super.render(matrices, mouseX, mouseY, delta);
  22. drawMouseoverTooltip(matrices, mouseX, mouseY);
  23. }
  24.  
  25. @Override
  26. protected void init() {
  27. super.init();
  28. // 居中标题
  29. titleX = (backgroundWidth - textRenderer.getWidth(title)) / 2;
  30. }
  31. }

最后

由于 ScreenHandler 的注册与第一个教程相同,我们已经可以看到结果了!放置这个 BlockEntity 时,它将每游戏刻增加一个 syncedInt;当我们查看容器内部时,这个整数将自动同步到客户端并呈现在左上角。

示例视频

如果您想要一个更现实的例子,您可以去看看 Minecraft 代码中的 AbstractFurnaceEntityAbstractFurnaceScreenHandler