=====使用 PropertyDelegates 同步整形数据===== **PropertyDelegate**:''PropertyDelegate'' 是一种包含多个可读写的整型值的容器。 在这章教程中,我们将会同步客户端和服务器之间的整型值,比如原版熔炉熔炼进度。 要理解这篇教程,你需要先阅读 [[zh_cn:tutorial:screenhandler|ScreenHandler]] 教程,其中提到的方法代码本页不再提及。 为了减小复杂性,这章教程不再使用 [[zh_cn:tutorial:extendedscreenhandler|ExtendedScreenHandler]]。 ===== BlockEntity ===== 由于 Block 类完全不需要更改,所以我们把它放在这里。 我们的 ''BlockEntity'' 现在实现了 ''Tickable'',这将提供 ''tick()'' 方法,每刻都会调用该方法,我们用这个方法来增加想要同步的整数值。 public class BoxBlockEntity extends BlockEntity implements NamedScreenHandlerFactory, ImplementedInventory, Tickable { private final DefaultedList inventory = DefaultedList.ofSize(9, ItemStack.EMPTY); // 这是我们希望同步的整型,每刻会增加 1。 private int syncedInt; // PropertyDelegate 是一个接口,我们将在这里使用内联实现。 // 它通常可以包含多个整型作为索引标志的数据,但是在本例中只有一个整型 private final PropertyDelegate propertyDelegate = new PropertyDelegate() { @Override public int get(int index) { return syncedInt; } @Override public void set(int index, int value) { syncedInt = value; } // 这里应该返回你的 delegate 中整型的数量,在本例中只有一个 @Override public int size() { return 1; } }; public BoxBlockEntity() { super(Test.BOX_BLOCK_ENTITY); } // 来自 ImplementedInventory @Override public DefaultedList getItems() { return inventory; } //这些方法来自 NamedScreenHandlerFactory 接口 @Override public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) { // 当我们的类执行完物品栏时,我们将此提供给 screenHandler // 只有服务器在开始时拥有物品栏,这将会在 ScreenHandler 中同步到客户端 // 类似于物品栏:服务器获得 PropertyDelegate 并将其直接给 screen handler 的服务器实例 return new BoxScreenHandler(syncId, playerInventory, this,propertyDelegate); } @Override public Text getDisplayName() { // 对于 1.18.2 或更低版本,请使用 return new TranslatableText(getCachedState().getBlock().getTranslationKey()); return Text.translatable(getCachedState().getBlock().getTranslationKey()); } // 每刻增加一个同步的整型,我们仅在服务器上这么做,从而用于演示。 @Override public void tick() { if(!world.isClient) syncedInt++; } } =====我们的新的 ScreenHandler===== public class BoxScreenHandler extends ScreenHandler { private final Inventory inventory; PropertyDelegate propertyDelegate; // 当服务器想要打开 screenHandler 时,客户端就会调用这个构造函数 // 客户端将调用父级构造函数,其中物品栏是空的。screenHandler 将自动将这个空的物品栏同步到服务器上 // 类似于物品栏,客户端将分配一个空的 propertyDelegate 并且它将自动与服务器同步 public BoxScreenHandler(int syncId, PlayerInventory playerInventory) { this(syncId, playerInventory, new SimpleInventory(9),new ArrayPropertyDelegate(1)); } // 该构造函数从服务器上的 BlockEntity 调用,服务器知道容器(Container)中的物品栏,因此可以直接将其作为参数提供。这个物品栏以及 propertyDelegate 将被同步到客户端 public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory, PropertyDelegate propertyDelegate) { super(Test.BOX_SCREEN_HANDLER, syncId); checkSize(inventory, 9); this.inventory = inventory; this.propertyDelegate = propertyDelegate; // 有些物品栏会在玩家打开时作自定义逻辑操作(Custom Logic) inventory.onOpen(playerInventory.player); // 我们需要告诉 screenHandler 关于 propertyDelegate 里的数据,否则它不会同步这里面的数据 this.addProperties(propertyDelegate); // 这将会把物品槽(Slot)放在 3x3 网格中正确的位置,物品槽(Slot)在服务端和客户端都存在! [...] } // 我们为已同步的整型提供了 getter, 所以 Screen 可以访问它并且在屏幕上显示它。 public int getSyncedNumber(){ return propertyDelegate.get(0); } @Override public boolean canUse(PlayerEntity player) { return this.inventory.canPlayerUse(player); } @Override public ItemStack transferSlot(PlayerEntity player, int invSlot) {[...]} } =====使用 Screen 显示信息===== 当屏幕在其构造函数中获得 ScreenHandler 时,我们可以从上面访问 property delegates,并可以在屏幕上渲染这个整数。 public class BoxScreen extends HandledScreen { private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png"); BoxScreenHandler screenHandler; public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) { super(handler, inventory, title); // 我们保存了对 screenhandler 的引用,这样我们就可以在屏幕上呈现 propertyDelegate 中的数字 screenHandler = (BoxScreenHandler) handler; } @Override protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) {[...]} @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { // 我们只是在容器的某个地方渲染我们同步的数字,这毕竟是一个演示 // 最后一个参数是一个颜色代码,使字体亮绿色 textRenderer.draw(matrices, Integer.toString(screenHandler.getSyncedNumber()), 0, 0, 65280); renderBackground(matrices); super.render(matrices, mouseX, mouseY, delta); drawMouseoverTooltip(matrices, mouseX, mouseY); } @Override protected void init() { super.init(); // 居中标题 titleX = (backgroundWidth - textRenderer.getWidth(title)) / 2; } } =====最后===== 由于 ''ScreenHandler'' 的注册与第一个教程相同,我们已经可以看到结果了!放置这个 ''BlockEntity'' 时,它将每游戏刻增加一个 ''syncedInt'';当我们查看容器内部时,这个整数将自动同步到客户端并呈现在左上角。 [[https://streamable.com/7aic8q | 示例视频]] 如果您想要一个更现实的例子,您可以去看看 ''Minecraft'' 代码中的 ''AbstractFurnaceEntity'' 和 ''AbstractFurnaceScreenHandler''。