初心者modderの備忘録

マイクラのmodを作りたくて、初めて見たのですが難しくて忘れそうなので自分用の備忘録も兼ねてブログにしようと思います

10日目 GUIの追加

前回に引き続きTileEntityの編集をしていきたいと思います。
今回はGUIを追加します。GUIを追加するとバニラのチェストみたいに画面上でTileEntityのイベントリにアクセスできます。

前回までに作った2つのクラス、BlockTomatoとTileEntityTomatoに加えて今回はさらに3つのクラスを追加します。

・BlockTomatoContainer
これは文字通り、コンテナーとなるようにスロットを追加していきます。Containerクラスを継承します。
・guiBlockTomatoContainer
ここでは、BlockTomatoContainerに対応する画像を登録したり、その壁画に関する詳細を設定したりします。
・GUIHandler
ここでguiを登録してTileEntityなどと結びつけます。

では、まずはBlockTomatoContainerから作成していきます。

BlockTomatoContainer

package test4mod;

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

public class BlockTomatoContainer extends Container{

	
	//-----------------------------PART 1---------------------------------------
	public BlockTomatoContainer(IInventory playerInv, final TileEntityTomato te) 
	{
		IItemHandler inventory = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.NORTH);

		for(int i=0; i<3;i++)
		{
			for(int j=0; j<3; j++)
			{
				addSlotToContainer(new SlotItemHandler(inventory, i*3+j, 62+i*18, 17+j*18)
				{
					@Override
					public void onSlotChanged() {
						te.markDirty();
					}
				});
			}
		}

		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 9; j++) {
				addSlotToContainer(new Slot(playerInv,  i * 9+j + 9, 8 + j * 18, 84 + i * 18));
			}
		}

		for (int k = 0; k < 9; k++) {
			addSlotToContainer(new Slot(playerInv, k, 8 + k * 18, 142));
		}
	}

	//--------------------------------PART 2-----------------------------------
	
	@Override
	public boolean canInteractWith(EntityPlayer playerIn) {

		return !playerIn.isSpectator();
	}


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

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

			int containerSlots = inventorySlots.size() - player.inventory.mainInventory.size();

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

			if (itemstack1.getCount() == 0) {
				slot.putStack(ItemStack.EMPTY);
			} else {
				slot.onSlotChanged();
			}

			if (itemstack1.getCount() == itemstack.getCount()) {
				return ItemStack.EMPTY;
			}

			slot.onTake(player, itemstack1);
		}

		return itemstack;
	}
}

まずPART1についてですが、この部分がこのクラスのコンストラクタで、この中でイベントリの設定をします。3つのfor分はそれぞれタイルエンティティのスロット、プレイヤーの見えないスロット、プレイヤーの下に見えているスロットを表しています。SlotHandlerを使った場合はpublic void onSlotChanged()をオーバーライドしておく必要があるみたいです。それぞれ、Slotの第2引数はスロットの番号、第3,4引数が画像のピクセルのx座標とy座標ですね。

f:id:json_fileman:20190111004626p:plain

今回はディスペンサーの画像をそのまま使っているので、細かい値は自分で設定していませんが、自作の画像を使う場合にはスロット位置の左上か中央の座標を指定するようにすればいいのではないでしょうか。
また、プレイヤーのイベントリのスロット番号は下に表示されているアイテムの左端が0のようです。

続いて、PART 2の最初の
public boolean canInteractWith(EntityPlayer playerIn)
はプレイヤーがこのコンテナを使えるかどうかで、基本的には !playerIn.isSpectator()またはtrueでいいんじゃないかと思います。
次のメソッドは大事なんですけど理解はしていません。ただこれを入れておかないとShift+クリックとか右クリックしたときにクラッシュとかフリーズを引き起こしてしまいます。
逆にこれを入れておくことで、Shift+クリックでスタックを全部移動できたり、右クリックで半分だけ拾えたり、バニラのチェストと同じ操作ができるようになります。



次にguiBlockTomatoContainerを作って行きます。

package test4mod;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.inventory.IInventory;
import net.minecraft.util.ResourceLocation;

public class guiBlockTomatoContainer extends GuiContainer{

	public guiBlockTomatoContainer(IInventory playerinv, TileEntityTomato te) {
		super(new BlockTomatoContainer(playerinv, te));

		this.xSize = 176;
		this.ySize = 166;

	}

	@Override
	protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
		GlStateManager.color(1, 1, 1,1);
		this.mc.getTextureManager().bindTexture(new ResourceLocation("testmod","textures/gui/tomatocontainer.png"));
		this.drawTexturedModalRect(this.guiLeft, this.guiTop, 0, 0, this.xSize,this.ySize);
	}
}

ここは、xSizeとySizeにそれぞれ画像のサイズに対応した値をいれて、下のResourceLocationに画像のPathを書いておけば大丈夫だと思っています。
guiのテクスチャは
assets.testmod.textures.gui
に入れています。


次はGUIHandlerです。

package test4mod;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.IGuiHandler;

public class GuiHandler implements IGuiHandler{
	
	public static final int BlockTomatoContainer = 1;

	@Override
	public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if(ID == BlockTomatoContainer) {
			return new BlockTomatoContainer(player.inventory,(TileEntityTomato) world.getTileEntity(new BlockPos(x,y,z)));
		}
		return null;
	}

	@Override
	public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if(ID == BlockTomatoContainer){
			return new guiBlockTomatoContainer(player.inventory,(TileEntityTomato) world.getTileEntity(new BlockPos(x,y,z)));
		}
		return null;
	}
}


ここはGUIのIDを決めて、サーバーおよびクライアントでそれぞれif分またはswitch分でケース処理して、対応したguiを返すようにしておけば大丈夫でしょう。
サーバーにはコンテナーを、クライアントにはGUIを返すようです。


これで、新しいファイルは終わったのですが、これに応じてTileEntityおよびBlockクラスを少し変更します。

TileEntityTomato.java

public class TileEntityTomato extends TileEntity{
	private ItemStackHandler inventory = new ItemStackHandler(9);

//省略

}

ItemStackHandlerをコンテナーで設定したイベントリの数に対応させておきます。
ここの数がずれていると、フリーズしたりしました。


続いて、右クリックでGUIが開くように変更します。

BlockTomato.java

public class BlockTomato extends BlockContainer{

//省略

	@Override
	public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
		if (!world.isRemote) {
			TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
			IItemHandler itemHandler = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side);
			if (!player.isSneaking()) {
				player.openGui(testmod.instance, GuiHandler.BlockTomatoContainer, world, pos.getX(), pos.getY(), pos.getZ());
			} else {
				ItemStack stack = itemHandler.getStackInSlot(0);
				if (!stack.isEmpty()) {
					System.out.println(stack.getItem().getRegistryName()+" "+stack.getCount());
				} else {
					System.out.println("Empty");
				}
			}
		}
		return true;
	}


	@Override
	public void breakBlock(World world, BlockPos pos, IBlockState state) {
	TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
	IItemHandler handler =te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
	for(int slot = 0; slot < handler.getSlots();slot++) 
	{
		ItemStack stack = handler.getStackInSlot(slot);
		InventoryHelper.spawnItemStack(world, pos.getX(), pos.getY(),pos.getZ(), stack);
	}
		super.breakBlock(world, pos, state);
	}

//省略
}

openGuiでGUIを開きます。
スニークのときの処理は変えていないので0番のスロットの中身をコンソールに表示できます。

最後にメインのクラスで、

NetworkRegistry.INSTANCE.registerGuiHandler(this, new GuiHandler());

で登録をします。


これで起動してみます。

f:id:json_fileman:20190111005427p:plain


f:id:json_fileman:20190111005439p:plain


ちゃんと機能しました!
最後にファイルをアップしておきます。

次回は、ディメンションの追加か、WorldGeneratorによる自然生成をしようと思います。


testmod.java
BlockTomato.java
TileEntityTomato.java
guiBlockTomatoContainer.java
BlockTomatoContainer.java
GuiHandler.java