MOD製作チュートリアル > GUIの実装

概要

MOD製作チュートリアル/TileEntityの追加で追加したTileEntityにGUI(Graphical User Interface)を実装する。

動作確認済バージョン:1.7.10-10.13.4.1558

ソースコード

  • AluminiumMod.java
package tutorial.aluminiummod;
 
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.block.Block;
 
@Mod(modid = AluminiumMod.MODID, name = AluminiumMod.MODNAME, version = AluminiumMod.VERSION)
public class AluminiumMod {
 
	public static final String MODID = "AluminiumMod";
	public static final String MODNAME = "Aluminium Mod";
	public static final String VERSION = "1.0.0";
 
	@Instance(MODID)
	public static AluminiumMod instance;
 
	public static Block chestAluminium;
 
	@EventHandler
	public void perInit(FMLPreInitializationEvent event) {
		chestAluminium = new BlockAluminiumChest()
				.setBlockName("chestAluminium")
				.setBlockTextureName("aluminiummod:Aluminium_Chest");
		GameRegistry.registerBlock(chestAluminium, "chestAluminium");
		GameRegistry.registerTileEntity(TileEntityAluminiumChest.class, "TileEntityAluminiumChest");
 
		NetworkRegistry.INSTANCE.registerGuiHandler(this.instance, new AluminiumModGuiHandler());
	}
 
}
 

  • BlockAluminiumChest.java
package tutorial.aluminiummod;
 
import java.util.Random;
 
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
 
public class BlockAluminiumChest extends Block implements ITileEntityProvider {
 
	private Random random = new Random();
 
	public BlockAluminiumChest() {
		super(Material.rock);
		this.setCreativeTab(CreativeTabs.tabDecorations);
		this.setHardness(5.0F);
		this.setResistance(1.0F);
		this.setStepSound(soundTypeMetal);
		isBlockContainer = true;
	}
 
	@Override
	public TileEntity createNewTileEntity(World world, int meta) {
		return new TileEntityAluminiumChest();
	}
 
	@Override
	public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) {
		// GUIを開く。
		player.openGui(AluminiumMod.instance, 1, world, x, y, z);
		return true;
	}
 
	@Override
	public void breakBlock(World world, int x, int y, int z, Block block, int meta) {
		// TileEntityの内部にあるアイテムをドロップさせる。
		TileEntityAluminiumChest tileentity = (TileEntityAluminiumChest) world.getTileEntity(x, y, z);
		if (tileentity != null) {
			for (int i = 0; i < tileentity.getSizeInventory(); i++) {
				ItemStack itemStack = tileentity.getStackInSlot(i);
 
				if (itemStack != null) {
					float f = random.nextFloat() * 0.6F + 0.1F;
					float f1 = random.nextFloat() * 0.6F + 0.1F;
					float f2 = random.nextFloat() * 0.6F + 0.1F;
 
					while (itemStack.stackSize > 0) {
						int j = random.nextInt(21) + 10;
 
						if (j > itemStack.stackSize) {
							j = itemStack.stackSize;
						}
 
						itemStack.stackSize -= j;
						EntityItem entityItem = new EntityItem(world, x + f, y + f1, z + f2,
								new ItemStack(itemStack.getItem(), j, itemStack.getItemDamage()));
 
						if (itemStack.hasTagCompound()) {
							entityItem.getEntityItem()
									.setTagCompound(((NBTTagCompound) itemStack.getTagCompound().copy()));
						}
 
						float f3 = 0.025F;
						entityItem.motionX = (float) random.nextGaussian() * f3;
						entityItem.motionY = (float) random.nextGaussian() * f3 + 0.1F;
						entityItem.motionZ = (float) random.nextGaussian() * f3;
						world.spawnEntityInWorld(entityItem);
					}
				}
			}
			world.func_147453_f(x, y, z, block);
		}
		super.breakBlock(world, x, y, z, block, meta);
	}
 
}
 

  • TileEntityAluminiumChest.java
package tutorial.aluminiummod;
 
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
 
public class TileEntityAluminiumChest extends TileEntity implements IInventory {
 
	protected ItemStack[] itemStacks = new ItemStack[54];
 
	@Override
	public void writeToNBT(NBTTagCompound nbt) {
		super.writeToNBT(nbt);
		NBTTagList nbttaglist = new NBTTagList();
		for (int i = 0; i < itemStacks.length; i++) {
			if (itemStacks[i] == null)
				continue;
			NBTTagCompound nbt1 = new NBTTagCompound();
			nbt1.setByte("Slot", (byte) i);
			itemStacks[i].writeToNBT(nbt1);
			nbttaglist.appendTag(nbt1);
		}
		nbt.setTag("Items", nbttaglist);
	}
 
	@Override
	public void readFromNBT(NBTTagCompound nbt) {
		super.readFromNBT(nbt);
		NBTTagList nbttaglist = nbt.getTagList("Items", 10);
		itemStacks = new ItemStack[54];
		for (int i = 0; i < nbttaglist.tagCount(); i++) {
			NBTTagCompound nbt1 = nbttaglist.getCompoundTagAt(i);
			byte b0 = nbt1.getByte("Slot");
			if (0 <= b0 && b0 < itemStacks.length) {
				itemStacks[b0] = ItemStack.loadItemStackFromNBT(nbt1);
			}
		}
	}
 
	@Override
	public int getSizeInventory() {
		return 54;
	}
 
	@Override
	public ItemStack getStackInSlot(int slot) {
		return itemStacks[slot];
	}
 
	@Override
	public ItemStack decrStackSize(int slot, int amount) {
		if (itemStacks[slot] == null)
			return null;
		ItemStack itemstack;
		if (itemStacks[slot].stackSize <= amount) {
			itemstack = itemStacks[slot];
			itemStacks[slot] = null;
			return itemstack;
		}
		itemstack = itemStacks[slot].splitStack(amount);
		if (itemStacks[slot].stackSize < 1) {
			itemStacks[slot] = null;
		}
		return itemstack;
	}
 
	@Override
	public ItemStack getStackInSlotOnClosing(int slot) {
		return null;
	}
 
	@Override
	public void setInventorySlotContents(int slot, ItemStack itemStack) {
		itemStacks[slot] = itemStack;
		if (itemStack != null && itemStack.stackSize > this.getInventoryStackLimit()) {
			itemStack.stackSize = this.getInventoryStackLimit();
		}
	}
 
	@Override
	public boolean hasCustomInventoryName() {
		return false;
	}
 
	@Override
	public String getInventoryName() {
		return "container.AluminiumMod.AluminiumChest";
	}
 
	@Override
	public int getInventoryStackLimit() {
		return 64;
	}
 
	@Override
	public boolean isUseableByPlayer(EntityPlayer player) {
		return worldObj.getTileEntity(xCoord, yCoord, zCoord) != this ? false : player.getDistanceSq(xCoord + 0.5D, yCoord + 0.5D, zCoord + 0.5D) <= 64.0D;
	}
 
	@Override
	public void openInventory() {}
 
	@Override
	public void closeInventory() {}
 
	@Override
	public boolean isItemValidForSlot(int slot, ItemStack itemStack) {
		return true;
	}
 
}
 

  • AluminiumModGuiHandler.java
package tutorial.aluminiummod;
 
import cpw.mods.fml.common.network.IGuiHandler;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
 
public class AluminiumModGuiHandler implements IGuiHandler {
 
	@Override
	public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
		if (!world.blockExists(x, y, z))
			return null;
		TileEntity tileentity = world.getTileEntity(x, y, z);
		if (tileentity instanceof TileEntityAluminiumChest) {
			return new ContainerAluminiumChest(player, (TileEntityAluminiumChest) tileentity);
		}
		return null;
	}
 
	@Override
	public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
		if (!world.blockExists(x, y, z))
			return null;
		TileEntity tileentity = world.getTileEntity(x, y, z);
		if (tileentity instanceof TileEntityAluminiumChest) {
			return new GuiAluminiumChest(player, (TileEntityAluminiumChest) tileentity);
		}
		return null;
	}
 
}
 

  • ContainerAluminiumChest.java
package tutorial.aluminiummod;
 
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
 
public class ContainerAluminiumChest extends Container {
 
	private TileEntityAluminiumChest tileEntity;
	/** アルミニウムチェストのインベントリの第一スロットの番号 */
	private static final int index0 = 0;
	/** プレイヤーのインベントリの第一スロットの番号 */
	private static final int index1 = 54;
	/** クイックスロットの第一スロットの番号 */
	private static final int index2 = 81;
	/** このコンテナの全体のスロット数 */
	private static final int index3 = 90;
 
	public ContainerAluminiumChest(EntityPlayer player, TileEntityAluminiumChest tileEntity) {
		// スロットを設定する。
		this.tileEntity = tileEntity;
		for (int iy = 0; iy < 6; iy++) {
			for (int ix = 0; ix < 9; ix++) {
				this.addSlotToContainer(new Slot(tileEntity, ix + (iy * 9), 8 + (ix * 18), 18 + (iy * 18)));
			}
		}
		for (int iy = 0; iy < 3; iy++) {
			for (int ix = 0; ix < 9; ix++) {
				this.addSlotToContainer(new Slot(player.inventory, ix + (iy * 9) + 9, 8 + (ix * 18), 140 + (iy * 18)));
			}
		}
		for (int ix = 0; ix < 9; ix++) {
			this.addSlotToContainer(new Slot(player.inventory, ix, 8 + (ix * 18), 198));
		}
	}
 
	@Override
	public boolean canInteractWith(EntityPlayer player) {
		return tileEntity.isUseableByPlayer(player);
	}
 
	@Override
	public ItemStack transferStackInSlot(EntityPlayer player, int slotNumber) {
		ItemStack itemStack = null;
		Slot slot = (Slot) inventorySlots.get(slotNumber);
		if (slot != null && slot.getHasStack()) {
			ItemStack itemStack1 = slot.getStack();
			itemStack = itemStack1.copy();
			if (index0 <= slotNumber && slotNumber < index1) {
				// アルミニウムチェストのインベントリならプレイヤーのインベントリに移動。
				if (!this.mergeItemStack(itemStack1, index1, index3, true)) {
					return null;
				}
			} else {
				// プレイヤーのインベントリならアルミニウムチェストのインベントリに移動。
				if (!this.mergeItemStack(itemStack1, index0, index1, false)) {
					return null;
				}
			}
 
			if (itemStack1.stackSize == 0) {
				slot.putStack((ItemStack) null);
			} else {
				slot.onSlotChanged();
			}
			if (itemStack1.stackSize == itemStack.stackSize) {
				return null;
			}
			slot.onPickupFromSlot(player, itemStack1);
		}
		return itemStack;
	}
 
}
 

  • GuiAluminiumChest.java
package tutorial.aluminiummod;
 
import org.lwjgl.opengl.GL11;
 
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StatCollector;
 
public class GuiAluminiumChest extends GuiContainer {
 
	private TileEntityAluminiumChest tileEntity;
	// GUIのテクスチャの場所を指定する。今回はバニラのラージチェストと同じものを使う。
	private static final ResourceLocation GUITEXTURE = new ResourceLocation("textures/gui/container/generic_54.png");
 
	public GuiAluminiumChest(EntityPlayer player, TileEntityAluminiumChest tileEnttiy) {
		super(new ContainerAluminiumChest(player, tileEnttiy));
		this.tileEntity = tileEnttiy;
		ySize = 222;
	}
 
	@Override
	protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
		fontRendererObj.drawString(StatCollector.translateToLocal(tileEntity.getInventoryName()), 8, 6, 4210752);
		fontRendererObj.drawString(StatCollector.translateToLocal("container.inventory"), 8, ySize - 96 + 2, 4210752);
	}
 
	@Override
	protected void drawGuiContainerBackgroundLayer(float tick, int mouseX, int mouseY) {
		GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
		mc.getTextureManager().bindTexture(GUITEXTURE);
		int k = (width - xSize) / 2;
		int l = (height - ySize) / 2;
		this.drawTexturedModalRect(k, l, 0, 0, xSize, ySize);
	}
 
}
 

解説

Instance

String modid
コアクラスのインスタンスにつけるアノテーション。
登録やインスタンスの生成・代入はForge側がやってくれるので何もしなくてよい。

NetworkRegistry

GUIやパケットなどを管理するenum。

void registerGuiHandler(Object mod, IGuiHandler handler)

MODのコアクラスのインスタンスと、それに対応したGuiHandlerを登録する処理。

EntityPlayer

プレイヤーのEntity継承クラス。

void openGui(Object mod, int modGuiId, World world, int x, int y, int z)

第一引数はコアクラスのインスタンス、第二引数はMOD内でのGUIのID。
プレイヤーにGUIを開かせる。

IGuiHandler

GuiHandlerのためのインターフェース。

Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z)

サーバー側で呼ばれる。
基本的にはIDに対応したContainerを返す。
Worldのインスタンスと座標も引数に含まれているので、TileEntityを取得して判別してもよい。

Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z)

クライアント側で呼ばれる。
基本的にはIDに対応したGuiを返す。

Container

Slot addSlotToContainer(Slot slot)

新たなSlotをコンテナに登録する処理。

boolean canInteractWith(EntityPlayer player)

プレイヤーがコンテナを使えるかどうかの判定。

ItemStack transferStackInSlot(EntityPlayer player, int slotNumber)

スロットがShift+クリックされた時の処理。
クリックされたスロットの中身を移動する。

boolean mergeItemStack(ItemStack itemStack, int beginIndex, int endIndex, boolean doReverse)

引数のItemStackを指定された範囲内の空のスロットに移動する。
範囲は、beginIndexを含み、endIndexは含まない。
doReverseがtrueの場合は、検索方向がend→beginになる。

GuiContainer

コンテナを持つGUIのクラス。
クライアントのみで使用される。

public GuiContainer(Container container)

引数は対応するコンテナ。

void drawGuiContainerForegroundLayer(int mouseX, int mouseY)

Guiの前面レイヤーを描画する処理。
文字の描画などをする。

void drawGuiContainerBackgroundLayer(float tick, int mouseX, int mouseY)

Guiの背景レイヤーを描画する処理。
Guiのテクスチャの描画などをする。

ResourceLocation

リソースの場所を保持するクラス。

public ResourceLocation(String path)

コンストラクタ。
引数でリソースの場所を指定する。
"modid:textures/..."というように、すべて小文字にしたMODIDと、textures以下のファイルのパスを指定する。
例えば、assets/aluminiummod/textures/gui/gui.pngだとすると、private static final ResourceLocation GUITEXTURE = new ResourceLocation("aluminiummod:textures/gui/gui.png");と記述する。
なお、GUIのテクスチャは画像ファイルを正方形にしないと縦横比がおかしくなる可能性があるので、カンバスサイズを256x256や512x512などにしておいて、余白を透明で残しておくと良い。

使用例

オファレンプレゼントボックスを追加している部分。
+ オファレンMOD

コメント

この項目に関する質問などをどうぞ。
  • GUIのテクスチャをオリジナルのものにする場合どう記述すれば、よいのでしょうか? - 新人もっだー 2015-12-20 12:20:13
    • 解説/public ResourceLocation(String path)に追記しましたのでご確認ください。自分で指定したパスにバニラのラージチェストのテクスチャを改変したものを保存すればできます。 - Tom Kate 2015-12-21 16:52:24
  • コピペでもコンストラクター ContainerAluminiumChest(EntityPlayer, TileEntityChest) は未定義ですとなります。 - 新人もっだー 2015-12-26 11:26:55
    • それは、Eclipse上でコンパイルエラーの検出がされたということでしょうか?どの行でのエラーかを教えていただけないでしょうか。 - 赤砂蛇凪浜 2015-12-26 13:48:01
  • ContainerAluminiumChestのreturn new ContainerChest(player, (TileEntityChest) tileentity);no - 新人もっだー 2015-12-28 08:51:55
  • の行です - 新人もっだー 2015-12-28 08:52:37
    • このチュートリアルの中に該当する行がありません。一度コピー&ペーストしなおしてみてください。 - 赤砂蛇凪浜 2015-12-28 09:36:26
  • import cpw.mods.fml.common.Mod.Instance;が書き込んでから保存するとなぜかそれだけ消えてしまうのですが、なんで何でしょうか? - san 2016-09-11 08:20:49
    • IDE(Eclipse)のimport文自動編集が消しているのだと思われます。@Instanceの部分にエラーが出ていなければ特に問題はないと思います。 - 赤砂蛇凪浜 2016-09-16 07:35:27
  • 醸造台やエンチャント台、装備スロットのように、スロット内に一部のアイテムのみ入れられるようにするにはどうすれば良いのでしょうか? - 名無しさん 2016-12-02 01:33:32
    • addSlotToContainerでSlotを継承した自作のクラスを渡せばよいです。
      その際、isItemValidをオーバーライドすれば入れられるアイテムを、canTakeStackをオーバーライドすれば取り出せるアイテムを制御できます。
      分かりにくかったらすみません。後ほど本文への掲載も検討します。 - 赤砂蛇凪浜 2016-12-04 10:50:12
名前: