概述

还记得这张图吗?

gui_analysis

本节将介绍这张图中的⑤部分。

此外,本节将在之间的章节中制作的熔炉添加一个GUI,也就是说将TileEntity中的数据应用到GUI中。

准备工作

我们先准备一张GUI的贴图,并将其起名为gui_metal_furnace.png,放入assets.fmltutor.textures.gui.container目录下:

src/main/resources/assets/fmltutor/textures/gui/container/gui_metal_furnace.png:

gui_metal_furnace

(好像没有燃料槽?不要在意这些细节)

实际上,用于熔炉的GUI的代码和之间使用的示例GUI的代码是十分相近的。我们先在包com.github.ustc_zzzz.fmltutor.inventory下新建类ContainerMetalFurnace

src/main/java/com/github/ustc_zzzz/fmltutor/inventory/ContainerMetalFurnace.java:

package com.github.ustc_zzzz.fmltutor.inventory;

import com.github.ustc_zzzz.fmltutor.tileentity.TileEntityMetalFurnace;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.SlotItemHandler;

public class ContainerMetalFurnace extends Container
{
    private IItemHandler upItems;
    private IItemHandler downItems;

    protected TileEntityMetalFurnace tileEntity;

    protected int burnTime = 0;

    public ContainerMetalFurnace(EntityPlayer player, TileEntity tileEntity)
    {
        super();

        this.upItems = tileEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.UP);
        this.downItems = tileEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.DOWN);

        this.addSlotToContainer(new SlotItemHandler(this.upItems, 0, 56, 30));
        this.addSlotToContainer(new SlotItemHandler(this.downItems, 0, 110, 30)
        {
            @Override
            public boolean isItemValid(ItemStack stack)
            {
                return false;
            }
        });

        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 9; ++j)
            {
                this.addSlotToContainer(new Slot(player.inventory, j + i * 9 + 9, 8 + j * 18, 74 + i * 18));
            }
        }

        for (int i = 0; i < 9; ++i)
        {
            this.addSlotToContainer(new Slot(player.inventory, i, 8 + i * 18, 132));
        }

        this.tileEntity = (TileEntityMetalFurnace) tileEntity;
    }

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

        if (slot == null || !slot.getHasStack())
        {
            return null;
        }

        ItemStack newStack = slot.getStack(), oldStack = newStack.copy();

        boolean isMerged = false;

        if (index == 0 || index == 1)
        {
            isMerged = mergeItemStack(newStack, 2, 38, true);
        }
        else if (index >= 2 && index < 29)
        {
            isMerged = mergeItemStack(newStack, 0, 1, false) || mergeItemStack(newStack, 29, 38, false);
        }
        else if (index >= 29 && index < 38)
        {
            isMerged = mergeItemStack(newStack, 0, 1, false) || mergeItemStack(newStack, 2, 29, false);
        }

        if (!isMerged)
        {
            return null;
        }

        if (newStack.stackSize == 0)
        {
            slot.putStack(null);
        }
        else
        {
            slot.onSlotChanged();
        }

        slot.onPickupFromSlot(playerIn, newStack);

        return oldStack;
    }

    @Override
    public boolean canInteractWith(EntityPlayer playerIn)
    {
        return playerIn.getDistanceSq(this.tileEntity.getPos()) <= 64;
    }
}
  • 第一点区别,是我们在这个类的构造方法中加入了一个TileEntityMetalFurnace的参数,用于传入我们之前写的金属熔炉的TileEntity
  • 第二点区别,就是这里的IItemHandler,是直接通过TileEntity获取,而不是新创建的
  • 第三点区别,就是canInteractWith方法被设计为了玩家距离熔炉不超过64格,这往往也是用于方块的GUI的惯例

剩下的代码和之前的GUI类似,这里也就不过多解释了。这里还添加了一个burnTime变量,用于存储同步的数据。

然后我们着手写GuiContainer。在包com.github.ustc_zzzz.fmltutor.client.gui,并在其中新建类GuiMetalFurnace

src/main/java/com/github/ustc_zzzz/fmltutor/client/gui/GuiMetalFurnace.java:

package com.github.ustc_zzzz.fmltutor.client.gui;

import com.github.ustc_zzzz.fmltutor.FMLTutor;
import com.github.ustc_zzzz.fmltutor.inventory.ContainerMetalFurnace;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

@SideOnly(Side.CLIENT)
public class GuiMetalFurnace extends GuiContainer
{
    private static final String TEXTURE_PATH = FMLTutor.MODID + ":" + "textures/gui/container/gui_metal_furnace.png";
    private static final ResourceLocation TEXTURE = new ResourceLocation(TEXTURE_PATH);

    protected ContainerMetalFurnace inventory;

    public GuiMetalFurnace(ContainerMetalFurnace inventorySlotsIn)
    {
        super(inventorySlotsIn);
        this.xSize = 176;
        this.ySize = 156;
        this.inventory = inventorySlotsIn;
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)
    {
        GlStateManager.color(1.0F, 1.0F, 1.0F);

        this.mc.getTextureManager().bindTexture(TEXTURE);
        int offsetX = (this.width - this.xSize) / 2, offsetY = (this.height - this.ySize) / 2;

        this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, this.ySize);

        // TODO: draw progress bar
    }
}

这里还有一点,就是绘制进度条尚未完成,不过不要着急,进度条数据的处理也是本部分的重点。

最后我们调整GuiElementLoader类:

src/main/java/com/github/ustc_zzzz/fmltutor/inventory/GuiElementLoader.java(部分):

    public static final int GUI_DEMO = 1;
    public static final int GUI_METAL_FURNACE = 2;

    public GuiElementLoader()
    {
        NetworkRegistry.INSTANCE.registerGuiHandler(FMLTutor.instance, this);
    }

    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        switch (ID)
        {
        case GUI_DEMO:
            return new ContainerDemo(player);
        case GUI_METAL_FURNACE:
            return new ContainerMetalFurnace(player, world.getTileEntity(new BlockPos(x, y, z)));
        default:
            return null;
        }
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        switch (ID)
        {
        case GUI_DEMO:
            return new GuiContainerDemo(new ContainerDemo(player));
        case GUI_METAL_FURNACE:
            return new GuiMetalFurnace(new ContainerMetalFurnace(player, world.getTileEntity(new BlockPos(x, y, z))));
        default:
            return null;
        }
    }

然后我们重新覆写对应方块的onBlockActivated方法:

src/main/java/com/github/ustc_zzzz/fmltutor/block/BlockMetalFurnace.java(部分):

    @Override
    public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn,
            EnumFacing side, float hitX, float hitY, float hitZ)
    {
        if (!worldIn.isRemote)
        {
            int id = GuiElementLoader.GUI_METAL_FURNACE;
            playerIn.openGui(FMLTutor.instance, id, worldIn, pos.getX(), pos.getY(), pos.getZ());
        }
        return true;
    }

现在除了进度条,一切都已经就绪了。

数据同步

本节的重点也就在于此,即自定义数据从服务端传输至客户端的同步。

每一个gametick服务端都会试图找到数据变化,并将其更新到客户端,而找到并更新的方法,就是Container类的detectAndSendChanges方法。

为方便起见,我们先为TileEntity加两个方法:

src/main/java/com/github/ustc_zzzz/fmltutor/tileentity/TileEntityMetalFurnace.java(部分):

    public int getBurnTime()
    {
        return this.burnTime;
    }

    public int getTotalBurnTime()
    {
        IBlockState state = this.worldObj.getBlockState(this.pos);
        switch (state.getValue(BlockMetalFurnace.MATERIAL))
        {
        case GOLD:
            return 100;
        case IRON:
            return 150;
        default:
            return 200;
        }
    }

然后我们覆写ContainerMetalFurnace类的detectAndSendChanges方法:

src/main/java/com/github/ustc_zzzz/fmltutor/inventory/ContainerMetalFurnace.java(部分):

    @Override
    public void detectAndSendChanges()
    {
        super.detectAndSendChanges();

        this.burnTime = tileEntity.getBurnTime();

        for (ICrafting i : this.crafters)
        {
            i.sendProgressBarUpdate(this, 0, this.burnTime);
        }
    }

Container类提供了一个名为crafters的列表,用于表示监听该Container的所有对象,如玩家等。我们遍历这一列表,并调用其sendProgressBarUpdate方法发送数据。

  • sendProgressBarUpdate方法的第一个参数用于指定同步数据的Container,这里传入this就可以了
  • sendProgressBarUpdate方法的第二个参数用于指定数据的标识符,一般情况下硬编码
  • sendProgressBarUpdate方法的第三个参数用于指定一个整数数据

一个典型的同步数据的代码如下:

        int data1;
        int data2;
        int data3;
        // ...
        for (ICrafting i : this.crafters)
        {
            i.sendProgressBarUpdate(this, 0, data1);
            i.sendProgressBarUpdate(this, 1, data2);
            i.sendProgressBarUpdate(this, 2, data3);
            // ...
        }

然后客户端的Container类会在每次接收到服务端发送的数据后,调用updateProgressBar方法接收数据(记得加上@SideOnly(Side.CLIENT)注解,因为其只作用于客户端):

一个典型的接收数据的代码如下:

    @SideOnly(Side.CLIENT)
    @Override
    public void updateProgressBar(int id, int data)
    {
        super.updateProgressBar(id, data);

        int data1;
        int data2;
        int data3;
        // ...
        switch (id)
        {
        case 0:
            data1 = data;
            break;
        case 1:
            data2 = data;
            break;
        case 2:
            data3 = data;
            break;
        // ...
        default:
            break;
        }
    }

这是我们的示例中的代码:

src/main/java/com/github/ustc_zzzz/fmltutor/inventory/ContainerMetalFurnace.java(部分):

    @SideOnly(Side.CLIENT)
    @Override
    public void updateProgressBar(int id, int data)
    {
        super.updateProgressBar(id, data);

        switch (id)
        {
        case 0:
            this.burnTime = data;
            break;
        default:
            break;
        }
    }

然后我们提供两个方法用于GuiMetalFurnace获取数据,以确定进度条如何绘制:

src/main/java/com/github/ustc_zzzz/fmltutor/inventory/ContainerMetalFurnace.java(部分):

    public int getBurnTime()
    {
        return this.burnTime;
    }

    public int getTotalBurnTime()
    {
        return this.tileEntity.getTotalBurnTime();
    }

然后我们就可以绘制进度条了:

src/main/java/com/github/ustc_zzzz/fmltutor/client/gui/GuiMetalFurnace.java(部分):

    private int totalBurnTime;

    public GuiMetalFurnace(ContainerMetalFurnace inventorySlotsIn)
    {
        super(inventorySlotsIn);
        this.xSize = 176;
        this.ySize = 156;
        this.inventory = inventorySlotsIn;
        this.totalBurnTime = inventorySlotsIn.getTotalBurnTime();
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)
    {
        GlStateManager.color(1.0F, 1.0F, 1.0F);

        this.mc.getTextureManager().bindTexture(TEXTURE);
        int offsetX = (this.width - this.xSize) / 2, offsetY = (this.height - this.ySize) / 2;

        this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, this.ySize);

        int burnTime = this.inventory.getBurnTime();
        int textureWidth = 1 + (int) Math.ceil(22.0 * burnTime / this.totalBurnTime);
        this.drawTexturedModalRect(offsetX + 79, offsetY + 30, 0, 156, textureWidth, 17);
    }

大功告成!可以打开游戏试试了。

results matching ""

    No results matching ""