前面有人提到TileEntity是不是带GUI的Block必要条件,这节就来说说无TileEntity的GUI Block。
大家常用的方块中除了熔炉就是工作台了,工作台就是一个无TileEntity的方块,但是却也能够放置物品到槽位中,通过查看工作台的相关源码就可以知道工作台关于放置物品的运算都放在了Container的Slot中,而提供给Slot存储物品的是Container的内部InventoryCrafting和InventoryCraftResult,这两个类都是基于IInventory接口的,在类中集成了ItemStack,整个Slot操作全部都在这些临时的ItemStack中,由于不需要储存用的TileEntity NBT节点,工作台就被设计成玩家关闭GUI时将这些物品弹出(刷新在世界中),这就是无TileEntity的基本原理。现在就利用手头上的源码来实现这个功能吧。
拿出之前的源码,复制一份RepairTable的Block、Container、Gui,重新命名为rtBlockRepairTableLite、rtContainerRepairTableLite、rtGuiRepairTableLite,然后去掉有关TileEntity的代码,包括构造函数中的TileEntity,还有给GUI代理添加上打开GUI的参数,修正好了之后应该可以得到这样的代码:
rtBlockRepairTableLite.java
public class rtBlockRepairTableLite extends Block {
protected rtBlockRepairTableLite(Material p_i45386_1_) {
super(p_i45386_1_);
// TODO Auto-generated constructor stub
}
@Override
public boolean onBlockActivated(World par1World, int par2, int par3,
int par4, EntityPlayer par5EntityPlayer, int par6, float par7,
float par8, float par9) {
// TODO Auto-generated method stub
par5EntityPlayer.openGui(mod_RepairTable.instance, 11, par1World, par2, par3, par4);
return true;
}
}
rtContainerRepairTableLite.java
public class rtContainerRepairTableLite extends Container {
public rtContainerRepairTableLite(InventoryPlayer par1InventoryPlayer) {
this.addSlotToContainer(new Slot(input, 0, 49, 19));
this.addSlotToContainer(new Slot(input, 1, 112, 19) {
@Override
public boolean isItemValid(ItemStack par1ItemStack) {
// TODO Auto-generated method stub
return false;
}
});
this.addSlotToContainer(new Slot(input, 2, 80, 54));
int var3;
for (var3 = 0; var3 < 3; ++var3) {
for (int var4 = 0; var4 < 9; ++var4) {
this.addSlotToContainer(new Slot(par1InventoryPlayer, var4 + var3 * 9 + 9, 8 + var4 * 18, 84 + var3 * 18));
}
}
for (var3 = 0; var3 < 9; ++var3) {
this.addSlotToContainer(new Slot(par1InventoryPlayer, var3, 8 + var3 * 18, 142));
}
}
@Override
public void onCraftMatrixChanged(IInventory par1iInventory) {
// TODO Auto-generated method stub
super.onCraftMatrixChanged(par1iInventory);
}
@Override
public boolean canInteractWith(EntityPlayer var1) {
// TODO Auto-generated method stub
return true;
}
@Override
public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int par2) {
ItemStack var3 = null;
Slot var4 = (Slot) this.inventorySlots.get(par2);
if (var4 != null && var4.getHasStack()) {
ItemStack var5 = var4.getStack();
var3 = var5.copy();
// 点击到Slot的ID为0-2之间的时候,将物品送回玩家的背包中,这个地方是
if (par2 >= 0 && par2 <= 2) {
if (!this.mergeItemStack(var5, 3, 30, false)) {
return null;
}
var4.onSlotChange(var5, var3);
}
// 点击到玩家的背包的时候将物品送到玩家的快捷栏中
else if (par2 > 3 && par2 < 30) {
if (!this.mergeItemStack(var5, 30, 39, false)) {
return null;
}
}
// 点击到玩家的快捷栏的时候将物品送到背包中
else if (par2 >= 30 && par2 < 39) {
if (!this.mergeItemStack(var5, 3, 30, false)) {
return null;
}
}
if (var5.stackSize == 0) {
var4.putStack((ItemStack) null);
} else {
var4.onSlotChanged();
}
if (var5.stackSize == var3.stackSize) {
return null;
}
var4.onPickupFromSlot(par1EntityPlayer, var5);
}
return var3;
}
}
rtGuiRepairTableLite.java
public class rtGuiRepairTableLite extends GuiContainer {
public rtGuiRepairTableLite(InventoryPlayer inventory, World worldObj) {
super(new rtContainerRepairTableLite(inventory, worldObj));
// TODO Auto-generated constructor stub
this.doesGuiPauseGame();
}
@Override
protected void drawGuiContainerForegroundLayer(int par1, int par2) {
// TODO Auto-generated method stub
super.drawGuiContainerForegroundLayer(par1, par2);
this.fontRendererObj.drawString(StatCollector.translateToLocal("RepairTable Lite"), 65, 6, 4210752);
this.fontRendererObj.drawString(StatCollector.translateToLocal("container.inventory"), 8, this.ySize - 96 + 2, 4210752);
}
@Override
protected void drawGuiContainerBackgroundLayer(float var1, int var2,
int var3) {
// TODO Auto-generated method stub
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
this.mc.renderEngine.bindTexture(new ResourceLocation("newmod","textures/gui/RepairTableLite.png"));
int var5 = (this.width - this.xSize) / 2;
int var6 = (this.height - this.ySize) / 2;
this.drawTexturedModalRect(var5, var6, 0, 0, this.xSize, this.ySize);
}
}
这是一个基本的无TileEntity结构,然后需要实现的是关闭GUI弹出物品,首先需要一个储存物品的地方,新建一个文件,命名为rtInputInventory.java,引用IInventory,这个和TileEntity引用的方法是一样的。为这个类增加一个ItemStack的数组用于临时储存物品,临时就是因为GUI关闭后,这个类会被释放掉。这类的接口方法的实现和TileEntity有点不一样,因为部分处理需要通知Container,所以需要准备好一个Container的指针,整个的时间基本照抄InventoryCrafting就可以了,处理完之后可以得到这样的代码:
rtInputInventory.java
public class rtInputInventory implements IInventory {
private ItemStack itemStack[] = new ItemStack[3];
private Container eventHandler;
public rtInputInventory(Container container) {
eventHandler = container;
}
@Override
public int getSizeInventory() {
// TODO Auto-generated method stub
return itemStack.length;
}
@Override
public ItemStack getStackInSlot(int var1) {
// TODO Auto-generated method stub
return var1 >= this.getSizeInventory() ? null : this.itemStack[var1];
}
/**
* Removes from an inventory slot (first arg) up to a specified number (second arg) of items and returns them in a
* new stack.
*/
@Override
public ItemStack decrStackSize(int par1, int par2)
{
if (this.itemStack[par1] != null)
{
ItemStack itemstack;
if (this.itemStack[par1].stackSize <= par2)
{
itemstack = this.itemStack[par1];
this.itemStack[par1] = null;
this.eventHandler.onCraftMatrixChanged(this);
return itemstack;
}
else
{
itemstack = this.itemStack[par1].splitStack(par2);
if (this.itemStack[par1].stackSize == 0)
{
this.itemStack[par1] = null;
}
this.eventHandler.onCraftMatrixChanged(this);
return itemstack;
}
}
else
{
return null;
}
}
@Override
public ItemStack getStackInSlotOnClosing(int var1) {
// TODO Auto-generated method stub
if (this.itemStack[var1] != null)
{
ItemStack itemstack = this.itemStack[var1];
this.itemStack[var1] = null;
return itemstack;
}
else
{
return null;
}
}
@Override
public void setInventorySlotContents(int var1, ItemStack var2) {
// TODO Auto-generated method stub
this.itemStack[var1] = var2;
this.eventHandler.onCraftMatrixChanged(this);
}
@Override
public String getInventoryName() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean hasCustomInventoryName() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getInventoryStackLimit() {
// TODO Auto-generated method stub
return 64;
}
@Override
public void markDirty() {
// TODO Auto-generated method stub
}
@Override
public boolean isUseableByPlayer(EntityPlayer var1) {
// TODO Auto-generated method stub
return true;
}
@Override
public void openInventory() {
// TODO Auto-generated method stub
}
@Override
public void closeInventory() {
// TODO Auto-generated method stub
}
@Override
public boolean isItemValidForSlot(int var1, ItemStack var2) {
// TODO Auto-generated method stub
return true;
}
}
然后rtContainerRepairTableLite中需要重写onContainerClosed,这个方法看名字就知道是关闭GUI的时候会回调的函数了。依然是照搬ContainerWorkbench的,不过需要注意的是ContainerWorkbench有⑨个Slot,而我们的RepairTable只有3个这里需要修改for循环的最大值
@Override
public void onContainerClosed(EntityPlayer par1EntityPlayer)
{
super.onContainerClosed(par1EntityPlayer);
if (!this.worldObj.isRemote)
{
for (int i = 0; i < 3; i++)
{
ItemStack itemstack = this.input.getStackInSlotOnClosing(i);
if (itemstack != null)
{
par1EntityPlayer.dropPlayerItemWithRandomChoice(itemstack, false);
}
}
}
}
在这里它会调用rtInputInventory中的getStackInSlotOnClosing,并从中取出指定索引的物品,但是感觉好像和getStackInSlot没什么太大的区别……
然后主文件注册一下方块就好了
repairTableLite = new rtBlockRepairTableLite(Material.rock)
.setBlockTextureName(MODID + ":" + "RepairTableLite")
.setBlockName("RepairTableLite")
.setCreativeTab(CreativeTabs.tabBlock);
GameRegistry.registerBlock(repairTableLite, "RepairTableLite");
这个时候就可以实现关闭GUI弹出物品了.
然后就开始制作方块的逻辑了,这个方块我给它设计的任务是将沙子通过活塞制作成砂岩,将沙砾制作成原石(原本想做成放武器和煤可以修复武器,调试耐久度太麻烦了,于是就省事这么干了,名字什么的不用太在意……)
在rtContainerRepairTableLite.java的onCraftMatrixChanged函数中加入如下代码:
ItemStack repairItem = input.getStackInSlot(0);
ItemStack repairMeta = input.getStackInSlot(2);
ItemStack repairOut = input.getStackInSlot(1);
if (repairItem != null && repairMeta != null && repairOut == null) {
if(Block.getBlockFromItem(repairMeta.getItem()) == Blocks.piston) {
if(repairItem.getItem() == Item.getItemFromBlock(Blocks.sand) ||
repairItem.getItem() == Item.getItemFromBlock(Blocks.gravel)) {
if(repairItem.getItem() == Item.getItemFromBlock(Blocks.sand)) {
input.setInventorySlotContents(1, new ItemStack(Blocks.sandstone));
} else if(repairItem.getItem() == Item.getItemFromBlock(Blocks.gravel)) {
input.setInventorySlotContents(1, new ItemStack(Blocks.cobblestone));
}
if(repairItem.stackSize - 1 > 0)
repairItem.stackSize -= 1;
else
input.setInventorySlotContents(0, null);
}
}
}
onCraftMatrixChanged函数是在Slot动作的时候返回的函数,Slot动作需要注册到Inventory,这里边就用之前的rtInputInventory,在rtInputInventory中我们已经重写了相关的方法,那些方法都用到eventHandler,这个eventHandler是从构造函数中传来的Container指针,在rtInputInventory中相关功能动作就会回调Container的函数,由函数来处理这些动作的事件,这里回调的都是onCraftMatrixChanged,我们就把逻辑部分都写到了这里,这个代码中,就是每次玩家点击Slot,按Shift键点击Slot等会使Slot内容改变的时候检查rtInputInventory中的物品是否符合要求,当输入时沙子并安放好活塞的时候,马上会消耗一个沙子生成一个砂岩。
然后就完成了一个简单的无TileEntity的GUI。
Hi baby
The article is very helpful for me,i like it,thanks!