Графический интерфейс пользователя с блоком хранения

Перевод Графический интерфейс пользователя с блоком хранения

Версия(и) Minecraft
1.12.х
Источник
https://wiki.mcjty.eu/modding/index.php?title=GUI-1.12
В этом уроке мы объясняем, как вы можете сделать сундук, который будет содержать девять слотов для хранения предметов (это же всё, что тебе нужно, верно?), и как сделать графический интерфейс, чтобы вы могли получить доступ к этим предметам.
g5yIlgc.png


Вот код блока:
TestContainerBlock.java:
public class TestContainerBlock extends Block implements ITileEntityProvider {

    public static final int GUI_ID = 1;

    public TestContainerBlock() {
        super(Material.ROCK);
        setUnlocalizedName(ModTut.MODID + ".testcontainerblock");
        setRegistryName("testcontainerblock");
    }

    @SideOnly(Side.CLIENT)
    public void initModel() {
        ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0, new ModelResourceLocation(getRegistryName(), "inventory"));
    }

    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta) {
        return new TestContainerTileEntity();
    }

    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, ItemStack heldItem, EnumFacing side, float hitX, float hitY, float hitZ) {
        //Выполнять только на стороне сервера:
        if (world.isRemote) {
            return true;
        }
     
        TileEntity te = world.getTileEntity(pos);
        if (!(te instanceof TestContainerTileEntity)) {
            return false;
        }
     
        player.openGui(ModTut.instance, GUI_ID, world, pos.getX(), pos.getY(), pos.getZ());
        return true;
    }
}
В этом коде мы также регистрируем TileEntity. В методе onBlockActivation () мы фактически открываем графический интерфейс. Обратите внимание, что это делается на сервере, потому что графический интерфейс для TileEntity должен быть открыт с обеих сторон, чтобы был механизм для синхронизации содержимого. Вызов player.openGui () происходит на сервере, и он будет синхронизироваться с клиентом с помощью IGuiHandler (чуть поподробнее о нём позже). Сделано это для отображения содержимого в режиме реального времени когда открыт графический интерфейс.

Не забудьте правильно зарегистрировать блок в CommonProxy:
CommonProxy.java:
@Mod.EventBusSubscriber
public class CommonProxy {

    ...
 
    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().register(new TestContainerBlock());
        GameRegistry.registerTileEntity(TestContainerTileEntity.class, ModTut.MODID + "_testcontainerblock");
     
        ...
     
    }

    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register<Item> event) {
        event.getRegistry().register(new ItemBlock(ModBlocks.testContainerBlock).setRegistryName(ModBlocks.testContainerBlock.getRegistryName()));
   
       ...
     
    }

}

И добавьте ObjectHolder c вызовом initModel () для ModBlocks:
ModBlocks.java:
public class ModBlocks {

    @GameRegistry.ObjectHolder("modtut:testcontainerblock")
    public static TestContainerBlock testContainerBlock;

    ...

    @SideOnly(Side.CLIENT)
    public static void initModels() {
     
        ...

        testContainerBlock.initModel();
    }
}

TileEntity - это то, что на самом деле хранит предметы. В этом уроке мы не будем использовать ванильную систему IInventory, а вместо этого реализуем наш инвентарь, используя новую рекомендованную систему возможностей IItemHandler.
TestContainerTileEntity.java:
public class TestContainerTileEntity extends TileEntity {

    public static final int SIZE = 9;

    //Этот обработчик предметов будет coдержать наши девять слотов инвентаря
    private ItemStackHandler itemStackHandler = new ItemStackHandler(SIZE) {
        @Override
        protected void onContentsChanged(int slot) {
            //Нам нужно сообщить тайлу, что что-то изменилось, чтобы содержимое сундука сохранилось
            TestContainerTileEntity.this.markDirty();
        }
    };

    @Override
    public void readFromNBT(NBTTagCompound compound) {
     
        super.readFromNBT(compound);
     
        if (compound.hasKey("items")) {
            itemStackHandler.deserializeNBT((NBTTagCompound) compound.getTag("items"));
        }
    }

    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
     
        super.writeToNBT(compound);
     
        compound.setTag("items", itemStackHandler.serializeNBT());
     
        return compound;
    }

    public boolean canInteractWith(EntityPlayer playerIn) {
        //Если мы слишком далеко от блока тайла, мы не сможем с ним взаимодействовать
        return !isInvalid() && playerIn.getDistanceSq(pos.add(0.5D, 0.5D, 0.5D)) <= 64D;
    }

    @Override
    public boolean hasCapability(Capability<?> capability, EnumFacing facing) {
     
        if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
         
            return true;
        }
     
        return super.hasCapability(capability, facing);
    }

    @Override
    public <T> T getCapability(Capability<T> capability, EnumFacing facing) {
     
        if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
         
            return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.cast(itemStackHandler);
        }
     
        return super.getCapability(capability, facing);
    }
}

Вот состояние блока в .json (blockstates/testcontainerblock.json):
testcontainerblock.json:
{
  "forge_marker": 1,
  "defaults": {
    "model": "cube_all",
    textures: { "all":"modtut:blocks/testcontainer"}
  },
  "variants": {
    "normal": [{}],
    "inventory": [{}]
  }
}
Обратите внимание, что в этом примере у нас нет собственной модели блока, и вместо этого используется стандартная модель cube_all

Однако этого недостаточно. Для работы графического интерфейса нам необходим реальный код этого самого графического интерфейса, а также реализация контейнера. Несмотря на то, что объект TileEntity содержит фактическое содержимое, именно контейнер используется для связи между графическим интерфейсом пользователя и фактическим содержимым на сервере. Помните, что когда вы открываете графический интерфейс допустим для сундука, вы видите не только слоты из самого сундука, но и слоты из инвентаря игрока. Таким образом, контейнер представляет собой как минимум две инвентаря в этом случае.

Когда графический интерфейс открывается в игре, это обычно происходит на стороне сервера. Там реализация контейнера используется, чтобы указать, какой инвентарь открывается. На стороне клиента также используется синхронизированная копия этого контейнера и передается фактическому графическому интерфейсу.

Здесь вы видите контейнер для этого примера. Обратите внимание, что добавляются слоты как для нашего собственного TileEntity, так и для инвентаря игрока.

Крайне важно, чтобы вы реализовали TransferStackInSlot. Если вы этого не сделаете, ваш блок вызовет вылет из игры, если игрок нажмет Shift на предмете. В этом примере мы скопировали реализацию, которая также используется ванильным сундуком. Он в основном передает слоты между хотбаром/инвентарем/сундуком игрока в зависимости от того, где начался предмет и где есть место.
TestContainer.java:
public class TestContainer extends Container {

    private TestContainerTileEntity te;

    public TestContainer(IInventory playerInventory, TestContainerTileEntity te) {
     
        this.te = te;

           //Этот контейнер ссылается на предметы из нашего инвентаря блока (9 слотов), а также слоты из инвентаря игрока, чтобы пользователь мог перемещать предметы между обоими инвентарями.
        //Два вызова снижу гарантируют, что слоты определены для обоих инвентарёв:
        addOwnSlots();
        addPlayerSlots(playerInventory);
    }

    private void addPlayerSlots(IInventory playerInventory) {
        //Слоты основного инвентаря:
        for (int row = 0; row < 3; ++row) {
            for (int col = 0; col < 9; ++col) {
                int x = 9 + col * 18;
                int y = row * 18 + 70;
                this.addSlotToContainer(new Slot(playerInventory, col + row * 9 + 10, x, y));
            }
        }

        //Слоты хотбара:
        for(int row = 0; row < 9; ++row) {
         
            int x = 9 + row * 18;
            int y = 58 + 70;
            this.addSlotToContainer(new Slot(playerInventory, row, x, y));
        }
    }

    private void addOwnSlots() {
     
        IItemHandler itemHandler = this.te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
     
        int x = 9;
        int y = 6;

        //Добавить наши новые собственные слоты:
        int slotIndex = 0;
        for (int i = 0; i < itemHandler.getSlots(); i++) {
         
            addSlotToContainer(new SlotItemHandler(itemHandler, slotIndex, x, y));
            slotIndex++;
            x += 18;
         
        }
    }

    @Override
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index) {
     
        ItemStack itemstack = ItemStack.EMPTY;
        Slot slot = this.inventorySlots.get(index);

        if (slot != null && slot.getHasStack()) {
         
            ItemStack itemstack1 = slot.getStack();
            itemstack = itemstack1.copy();

            if (index < TestContainerTileEntity.SIZE) {
             
                if (!this.mergeItemStack(itemstack1, TestContainerTileEntity.SIZE, this.inventorySlots.size(), true)) {
                    return ItemStack.EMPTY;
                }
             
            } else if (!this.mergeItemStack(itemstack1, 0, TestContainerTileEntity.SIZE, false)) {
             
                return ItemStack.EMPTY;
             
            }

            if (itemstack1.isEmpty()) {
             
                slot.putStack(ItemStack.EMPTY);
             
            } else {
             
                slot.onSlotChanged();
             
            }
        }

        return itemstack;
    }

    @Override
    public boolean canInteractWith(EntityPlayer playerIn) {
     
        return te.canInteractWith(playerIn);
     
    }
}

Теперь нам нужен графический интерфейс. Графические интерфейсы для контейнеров обычно берутся из GuiContainer. В этом простом случае нам не нужно много кода, чтобы этот графический интерфейс работал. Конечно, вы можете добавить свой собственный рендеринг, чтобы сделать этот графический интерфейс более привлекательным, а также добавить пользовательские компоненты и тому подобное:
TestContainerGui.java:
public class TestContainerGui extends GuiContainer {
 
    public static final int WIDTH = 180;
    public static final int HEIGHT = 152;

    private static final ResourceLocation background = new ResourceLocation(ModTut.MODID, "textures/gui/testcontainer.png");

    public TestContainerGui(TestContainerTileEntity tileEntity, TestContainer container) {
     
        super(container);

        xSize = WIDTH;
        ySize = HEIGHT;
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
   
        mc.getTextureManager().bindTexture(background);
     
        drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize);
     
    }
}

Теперь осталась последняя вещь которую вам нужно сделать, это сказать игре, что вы создали свой графический интерфейс и реализовали для него контейнер. Для этого необходим IGuiHandler, который содержит серверный и клиентский код для обработки графического интерфейса. В приведенном ниже примере мы жестко закодировали имена TestContainerTileEntity и TestContainer. Я рекомендую не делать этого в своем собственном коде, а использовать простую структуру. Например, у вас может быть GenericTileEntity, для которого вы можете сделать instanceof, а затем использовать методы для создания графического интерфейса и/или контейнера. Или вы также можете использовать данный идентификатор, который передается через player.openGui (), как он был назван выше GUI_ID. Этот пример просто демонстрирует, принцип работы:
GuiProxy.java:
public class GuiProxy implements IGuiHandler {

    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
     
        BlockPos pos = new BlockPos(x, y, z);
     
        TileEntity te = world.getTileEntity(pos);
     
        if (te instanceof TestContainerTileEntity) {
         
            return new TestContainer(player.inventory, (TestContainerTileEntity) te);
        }
        return null;
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
   
        BlockPos pos = new BlockPos(x, y, z);
     
        TileEntity te = world.getTileEntity(pos);
     
        if (te instanceof TestContainerTileEntity) {
         
            TestContainerTileEntity containerTileEntity = (TestContainerTileEntity) te;
            return new TestContainerGui(containerTileEntity, new TestContainer(player.inventory, containerTileEntity));
        }
        return null;
    }
}

И, наконец, вам нужно зарегистрировать этот класс GuiProxy в вашем CommonProxy:
CommonProxy.java:
    public static class CommonProxy {

        ...

        public void init(FMLInitializationEvent e) {
         
            NetworkRegistry.INSTANCE.registerGuiHandler(instance, new GuiProxy());
         
        }

        ...

Конец!
Автор
Garik
Просмотры
2,639
Первый выпуск
Обновление
Оценка
4.00 звёзд 1 оценок

Другие ресурсы пользователя Garik

Последние рецензии

Текст ресурса неплох, с учётом, что это перевод, но код в блоках не имеет общего стиля. Куча пустых строк, отступы в самых неожиданных местах...
Сверху