前文
実際に解説に入る前に、なぜこの記事を書いたのかについて書いておく。
誰得?
既に1.12系以前のGUIに関する知識を有していて、1.13系に移行したい人向け。
なぜGUI?
実際に1.12.2から1.13.2に移行した際、GUI関連、特にregisterExtensionPoint<T>
に関して適当な記事がまだなかったので。
「NetworkHooks.openGui
を使え」という記事はたくさんあるのに。。。
解説
GUIファクトリの登録
前文で触れたNetworkHooks.openGui
でGUIを表示するためには、事前にModLoadingContext#registerExtensionPoint<T>
を呼び出してGUIファクトリを登録しておく必要があります。
public ExampleMod() {
/* 省略 */
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
bus.addListener(this::doClientStuff);
/* 省略 */
}
private void doClientStuff(final FMLClientSetupEvent event) {
registerGuiFactory();
}
@OnlyIn(Dist.CLIENT)
private static void registerGuiFactory() {
ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.GUIFACTORY, () -> (openContainer) -> {
switch (openContainer.getId().toString()) {
case "examplemod:foo":
return new GuiContainerFoo(new ContainerFoo());
case "examplemod:bar":
return new GuiContainerBar(new ContainerBar());
/* 必要に応じてGuiScreenの派生オブジェクトを返せるようにしておく */
}
return null;
});
}
ModLoadingContext#registerExtensionPoint<T>
はExtensionPoint<T>
とSupplier<T>
を引数に取る。
第1引数に指定するExtensionPoint.GUIFACTORY
の型はExtensionPoint<Function<FMLPlayMessages.OpenContainer, GuiScreen>>
なので、第2引数にはSupplier<Function<FMLPlayMessages.OpenContainer, GuiScreen>>
型のオブジェクトを渡す必要がある。
実際には、FMLPlayMessages.OpenContainer
を受け取ってGuiScreen
を継承したクラスのオブジェクトを返すFunction
を返すSupplier
を渡す。ここで、FMLPlayMessages.OpenContainer
のread系のメソッドで、GUI呼び出し側のPacketBuffer
に書き込んだデータを受け取ることができる。
GuiScreen
を継承したクラスについては1.12系と変わらないので割愛する。
また、SSPではなくSMPを対象にしたMOD制作ではregisterExtensionPoint<T>
の呼び出し箇所に注意が必要となる。
サーバ側でも呼ばれる箇所(例えばコンストラクタ等)でregisterExtensionPoint<T>
を呼ぶと、「GuiScreen
がないよ」とお叱りを受けてクラッシュするので、クライアント側でのみ呼び出されるFMLClientSetupEvent
を受け取るイベントハンドラ(前述の例ではdoClientStuff
がこれに相当する)で呼び出すのがよい。
GUIの呼び出し
アイテムを右クリックした場合
NetworkHooks.openGui
を呼び出してGUIを表示する。
第2引数のインタラクションオブジェクト(後述する)で、GUIの表すコンテンツのコンテナオブジェクトを作成したり、どのGUIを表示するかを指定したりする。
第3引数のConsumer<PacketBuffer>
内で、GUIファクトリとの間でデータの受け渡しを行う。
このPacketBuffer
のwrite系のメソッドで書き込んだデータは、GUIファクトリ側ではFMLPlayMessages.OpenContainer
のread系のメソッドで読み取ることができる。
public class ItemFoo extends Item {
/* 省略 */
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
ItemStack foo = player.getHeldItem(hand);
if (!world.isRemote && player instanceof EntityPlayerMP) {
NetworkHooks.openGui((EntityPlayerMP)player, new InteractionObjectFoo(foo, hand), (buffer) -> {
/* bufferを通じて、必要な情報をGUIファクトリに受け渡す */
});
}
return ActionResult.newResult(EnumActionResult.SUCCESS, foo);
}
}
ブロックを右クリックした場合
こちらも同様にNetworkHooks.openGui
を呼び出してGUIを表示する。
ただ、第2引数のインタラクションオブジェクトは個別に実装する必要はなく、TileEntity
の派生クラスにIInteractionObject
インタフェースを実装することで対応する。
public class BlockFoo extends Block {
/* 省略 */
@Override
public boolean onBlockActivated(IBlockState state, World world, BlockPos pos, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
if (world.isRemote) {
return true;
}
TileEntity tile = world.getTileEntity(pos);
if (tile instanceof TileEntityFoo && player instanceof EntityPlayerMP) {
NetworkHooks.openGui((EntityPlayerMP)player, (TileEntityFoo)tile, (buffer) -> {
/* bufferを通じて、必要な情報をGUIファクトリに受け渡す */
});
}
return true;
}
}
インタラクションオブジェクト
NetworkHooks.openGui
の第2引数に渡すIInteractionObject
インタフェースを実装するオブジェクトについて解説する。
public class InteractionObjectFoo implements IInteractionObject {
private final ItemStack foo;
private final EnumHand hand;
public InteractionObjectFoo(ItemStack foo, EnumHand hand) {
this.foo = foo;
this.hand = hand;
}
@Override
public ITextComponent getCustomName() {
return null;
}
@Override
public ITextComponent getName() {
return null;
}
@Override
public boolean hasCustomName() {
return false;
}
@Override
public Container createContainer(InventoryPlayer arg0, EntityPlayer arg1) {
return new ContainerFoo(this.foo, arg0, this.hand);
}
@Override
public String getGuiID() {
return "examplemod:foo";
}
}
createContainer
はクライアント・サーバの両側で用いられるContainer
クラスを継承したオブジェクトを返す。
getGuiID
の返す文字列が、GUIファクトリ側でFMLPlayMessages.OpenContainer.getId
として参照できる文字列となる。
コンテナオブジェクト
コンテナオブジェクトについては1.12系から特段変わった点は少ない。
変更点といえばaddSlotToContainer
がaddSlot
に名称変更されたくらい。
public class ContainerFoo extends Container {
public ContainerFoo(ItemStack foo, InventoryPlayer inventoryPlayer, EnumHand hand) {
addSlot(new Slot(/* 省略 */));
}
}
まとめ
既に1.14系のForgeも出ているので1.13系の記事は今更感がありますが、何かの役に立てればと思います。