LoginSignup
2
2

More than 1 year has passed since last update.

Eclipse+Forgeでマイクラのmod開発~イベントハンドリングしてみようず

Last updated at Posted at 2021-05-06

image.png
今回はイベントハンドリングの方法を書いていきます。だんだん、準備段階から開発段階に進んできた感じがしますね。イベントハンドリングができればプレイヤーが何かをしたときに爆発させたり建築ができたり色々と夢広がりんぐです。ちなみにこの記事の執筆時点でmunecraftもまだ勉強中なので今後大いに修正する可能性あり。。

環境

  • OS: Windows10
  • Jdk: openjdk version "15.0.2" 2021-01-19
    ※16.xでもいいかもだけどgradleやらなにやら色々変わってしまって心配だったので今回は15で。
  • IDE: Eclipse IDE for Java Developers (Version 4.19.0)
  • Buildship(Eclipseのgradleプラグイン): 3.0
  • Minecraft: Java版 1.16.5
  • Forge: 1.16.5

何はともあれイベントハンドリングをしてみる

仕組みの話とか色々ありますがまずは動かしてみたいよね~ということでサンプルコード。これを中心に解説していきます。前回の記事の空のmodをベースに少しコードを書き加えてみます。

EmptyMod.java
package jp.munecraft.mod.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent.ItemPickupEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod("emptymod")
public class EmptyMod {

    private static final Logger LOGGER = LogManager.getLogger();

    public EmptyMod() {
        LOGGER.info("Welcome to munecraft mod!!!");
        MinecraftForge.EVENT_BUS.register(this);
    }

    @SubscribeEvent
    public void onItemPickUp(ItemPickupEvent event) {
        LOGGER.info("Item Pickup!");
    }   
}

実行してみましょう。いかにもアイテムをとった時にログを出すイベント処理のように見えますが、実際にアイテムを拾ってみると。。。

[22:29:02] [Render thread/INFO] [minecraft/AdvancementList]: Loaded 7 advancements
[22:29:06] [Server thread/WARN] [minecraft/MinecraftServer]: Can't keep up! Is the server overloaded? Running 11568ms or 231 ticks behind
[22:29:16] [Server thread/INFO] [jp.mu.mo.te.EmptyMod/]: Item Pickup!
[22:29:16] [Render thread/INFO] [minecraft/AdvancementList]: Loaded 9 advancements
[22:29:19] [Server thread/INFO] [minecraft/IntegratedServer]: Saving and pausing game...

ログが出ましたね。ほら、簡単。

なんて話で終わりだとなんの説明にもならないのでここで少し解説。さらに詳細を後述していきます。
まずはコンストラクタの中のMinecraftForge.EVENT_BUS.register(this);。これは要するにForgeのイベントハンドラをイベントバス、という仕組みに登録する処理です。これをすることによって、今後EmptyModがイベントハンドラとして認識されます。
次に@SubscribeEventアノテーションがついたpublic void onItemPickUp(ItemPickupEvent event)。これは、イベントバスに対して、イベント発生時に呼び出してほしいメソッドにマークする処理になります。この合わせ技で、マイクラの中でプレーヤがアイテムを拾った時にpublic void onItemPickUp(ItemPickupEvent event)を呼び出してもらえるようになるという寸法です。

イベントバス

Forgeではイベントバスという仕組みでイベントハンドリングの仕組みを提供しています。これはある意味名前の通りですが、イベントが発生するとイベントバスを通じてイベントバスに登録されているイベントハンドラを発火させていく、というものです。
Forgeには次の二つのイベントバスがあります。

  • Forgeのイベントバス
    これは前述のサンプルコードにあるMinecraftForge.EVENT_BUSが該当します。こちらはメインのイベントバスで、基本的にゲームが開始(ワールドが読み込まれ始めるタイミング)以降のイベントを拾うためのイベントバスです。
  • FML(Forge Mod Loader)イベントバス
    こちらはゲームを開始する前、modを読み込んでいる段階で発火されるイベント群のイベントバスの模様。今のところ、アイテムやブロックを追加する用途で使用する以外の用途はわかっておりません。。アイテム追加についての記事はこちら

イベントハンドラの登録方法

明示的に各イベントバスのregister()を呼び出して登録する方法と、アノテーションを使う方法があります。正確にはほかにもaddListener()にラムダ式で渡す方法がありますがそちらは今度追記します。

明示的にregister()を呼び出す方法

    public EmptyMod() {
        LOGGER.info("Welcome to munecraft mod!!!");
        // こっちがForgeのイベントバスにイベントハンドラとしてthisを登録する方法
        MinecraftForge.EVENT_BUS.register(this);
    }
    public EmptyMod() {
        LOGGER.info("Welcome to munecraft mod!!!");
        // こっちはFMLのイベントバスにイベントハンドラとしてthisを登録する方法
        FMLJavaModLoadingContext.get().getModEventBus().register(this);
    }

同じクラスをForgeとFMLの両方のイベントバスに登録しないようにしましょう。じゃないと後述する@SubscribeEentがどちらのイベントハンドラなのか混乱してランタイムエラーを引き起こすことになります。
ところでイベントハンドラとして登録しているthisは何のクラスもインターフェイスも継承/実装していません。上述したregister()メソッドはObject型を受け取るのでなんでも登録できちゃうんですね。じゃあどうやってイベントハンドラのメソッドを呼ぶんだ。。。と謎に思ってしまったけど、その種明かしが@SubscribeEventアノテーションにありました。これもまた後述。

クラスをアノテーションでイベントハンドラとして登録する方法

2つクラスを追加しました。

ForgeEventHandlerTest.java
package jp.munecraft.mod.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.minecraftforge.event.entity.player.PlayerEvent.ItemPickupEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.FORGE)
public class ForgeEventHandlerTest {

    private static final Logger LOGGER = LogManager.getLogger();

    @SubscribeEvent
    public static void onItemPickupEvent(ItemPickupEvent event) {
        LOGGER.info("ForgeEventHandlerTest::onItemPickupEvent called.");
    }
}
ModEventHandlerTest.java
package jp.munecraft.mod.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;

@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public class ModEventHandlerTest {

    private static final Logger LOGGER = LogManager.getLogger();

    @SubscribeEvent
    public static void onFMLCommonSetupEvent(FMLCommonSetupEvent event) {
        LOGGER.info("ModEventHandlerTest::onFMLCommonSetupEvent called");
    }   
}

起動してみるとまずゲームを開始する前にModEventHandlerTest::onFMLCommonSetupEvent calledがログに出力され、次にゲームを開始してアイテムを拾うとorgeEventHandlerTest::onItemPickupEvent called.がログに出力されます。うん、期待通り。
コードを見ると大体想像がつくと思いますが、@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.FORGE)アノテーションと@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)アノテーションがミソですな。前者は、このクラスがForgeのイベントバスに登録したいイベントハンドラであることを示し、後者はFMLのイベントバスに登録したいイベントハンドラであることを示しています。
注意してほしいのは、今回はメソッドがstaticな点。インスタンス化してイベントバスに渡していないので、このアノテーションで登録する場合はメソッドはstaticである必要があります。

イベントハンドラのメソッド

ここが正直悩みました。。。なんのイベントが発生したときにどのメソッドが呼ばれるのか。。。メソッド名から識別している!?なら指定できるメソッドがわかるようにスーパークラスがあればいいけどイベントハンドラはObject型で受け取っていて、任意のクラスを渡せるし。。。
答えは引数でした。

先ほどのサンプルコードでonItemPickupEvent(ItemPickupEvent event)は引数にItemPickupEventを指定してます。Forgeは@SubscriveEventのメソッドの引数だけを確認して、該当するイベントが発生したときに発火してくれます。なので、メソッド名はhogeでもpiyoでも@SubscriveEventアノテーションを付けてItemPickupEventを引数につければアイテムを拾ったときに発火されます。

イベントハンドリングのポリモーフィズム

実はイベントハンドラのメソッドにはポリモーフィズムな仕組みが備わっています。例えば先ほどのItemPickupEventPlayerEventというクラスを継承しています。例えば先ほどのコードを次のように書き換えてみる。

ForgeEventHandlerTest.java
package jp.munecraft.mod.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.minecraftforge.event.entity.player.PlayerEvent.ItemPickupEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.FORGE)
public class ForgeEventHandlerTest {

    private static final Logger LOGGER = LogManager.getLogger();

    @SubscribeEvent
    public static void onItemPickupEvent(PlayerEvent event) {
        LOGGER.info("ForgeEventHandlerTest::onItemPickupEvent called.");
    }
}

すると、ItemPickupEventも含めた、PlayerEventを継承したすべてのイベントをハンドリングするようにコードが変わります(ログが膨大に出るので注意)。極端な話、最上位クラスのEventクラスを引数に指定するとゲーム中の(おそらく)すべてのイベントでハンドラが発火することになります(こちらもログが大量に出るので注意)。

FMLのイベントバスへの登録時の注意点

FMLのイベントバスに登録するイベントハンドラのメソッドには注意が要ります。ForgeのイベントハンドラはEventクラスを継承していればなんでも受け取れますが、FMLのイベントバスではIModBusEventを継承したイベントしか@SubscribeEventできません。

主なイベント

を書こうと思ったけど。。。膨大にあるし全部はわからないので挫折。。いずれ使いでのある重要なイベントだけでもリファレンス化しようと思いますが。。とりあえずゲーム中のイベントとしてはnet.minecraftforge.eventパッケージは以下をあさってみてくださいな。クラス名から直感的にいつ発火するかわかりやすいイベントも多く存在しているので。特にプレイヤーが何かをしたときに発火させたくなることが多いと思うのでnet.minecraftforge.event.entity.playerの下を掘ると色々出てくると思います。
あと、今度記事にしようと思いますがコマンド追加関連で重要なのはnet.minecraftforge.event.RegisterCommandsEventですね。コマンド追加関連はネット上に転がっているドキュメントが軒並み古くて苦労したので次回記事にしようと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2