MOD製作チュートリアル > イベントの利用

概要

MinecraftForgeのイベント機能を利用して既存の処理に追加動作を施す。
2017/6/14 全体を確認し、修正や変更を行いました。

仕組み

今までMinecraft内のソースを見てみて、「ForgeHooks」や「EVENT_BUS」という記述を見たことはあるだろうか。
例えば、EntityLivingBase/onUpdateのメソッドを見てみよう。
public void onUpdate()
	{
		if (ForgeHooks.onLivingUpdate(this)) return;
		super.onUpdate();
	/* 略 */
	}
/* 略 */
 
このように、Forgeがシステムを書き換えてイベントを起こしている場所がある。
ここで、@SubscribeEventのアノテーションを付け、適切な処理をしてあるメソッドが呼ばれる。
クラス書き換えと違って使えるものは限られるものの、競合が起きにくくかつ実装しやすい手段である。

ソースコード

  • AluminiumMod.java
package tutorial.aluminiummod;
 
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.EntityIronGolem;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.util.DamageSource;
import net.minecraft.world.ChunkPosition;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ExplosionEvent;
 
import java.util.Iterator;
 
@Mod(modid = "AluminiumMod", name = "Aluminium Mod", version = "1.0.0")
public class AluminiumMod {
	@EventHandler
	public void preInit(FMLPreInitializationEvent event) {
		// Event処理を記述しているクラスをForgeに登録する。
		// Forgeのイベントを受け取る。
		MinecraftForge.EVENT_BUS.register(this);
		// FMLのイベントを受け取りたい場合は以下のように追加で登録する。
		//		FMLCommonHandler.instance().bus().register(this);
	}
 
	/** EntityLivingBaseがダメージを負った時のイベント。 */
	@SubscribeEvent
	public void onLivingHurt(LivingHurtEvent event) {
		// プレイヤーがアイアンゴーレムに対して攻撃した時、プレイヤーに同量のダメージを与える。
		Entity sourceEntity = event.source.getEntity();
		if (event.entityLiving instanceof EntityIronGolem && sourceEntity != null && sourceEntity instanceof EntityPlayer)
			sourceEntity.attackEntityFrom(DamageSource.causeMobDamage(event.entityLiving), event.ammount);
	}
 
	/** プレイヤーがEntityを攻撃した時のイベント。 */
	@SubscribeEvent
	public void onPlayerAttackEntity(AttackEntityEvent event) {
		// ダイヤモンドを持っていたら、HPをハート10個分回復する。
		if (event.entityPlayer.getHeldItem() != null && event.entityPlayer.getHeldItem().getItem() == Items.diamond)
			event.entityPlayer.heal(20);
	}
 
	/** EntityLivingBaseが更新される時のイベント。 */
	@SubscribeEvent
	public void onLivingUpdate(LivingEvent.LivingUpdateEvent event) {
		// クリーパーなら雷雨時に巨匠化させる。
		if (event.entityLiving instanceof EntityCreeper && event.entityLiving.worldObj.isThundering())
			event.entityLiving.getDataWatcher().updateObject(17, (byte) 1);
	}
 
	/** 爆発が起こった時のイベント。 */
	@SubscribeEvent
	public void onExplosionDetonate(ExplosionEvent.Detonate event) {
		System.out.println("onExplosionDetonate");
		// クリーパーが起こした爆発なら、EntityMob継承のEntityを全て爆発対象から除外する。(ダメージやノックバックがなくなる。)
		if (event.explosion.exploder != null && event.explosion.exploder instanceof EntityCreeper) {
			Iterator<Entity> iterator = event.getAffectedEntities().iterator();
			while (iterator.hasNext()) {
				if (iterator.next() instanceof EntityMob)
					iterator.remove();
			}
		}
		// ブロック破壊と炎上があるなら、氷と氷塊を破壊対象から除外する。
		if (event.explosion.isSmoking && event.explosion.isFlaming) {
			Iterator<ChunkPosition> iterator = event.getAffectedBlocks().iterator();
			while (iterator.hasNext()) {
				ChunkPosition position = iterator.next();
				Block block = event.world.getBlock(position.chunkPosX, position.chunkPosY, position.chunkPosZ);
				if (block == Blocks.ice || block == Blocks.packed_ice)
					iterator.remove();
			}
		}
	}
 
	/** プレイヤーがブロックを設置した時のイベント。 */
	@SubscribeEvent
	public void onPlayerBlockPlace(BlockEvent.PlaceEvent event) {
		// 土にカーソルを合わせてダイヤモンドブロックを置こうとしたらキャンセルする。
		if (event.placedBlock == Blocks.diamond_block && event.placedAgainst == Blocks.dirt)
			event.setCanceled(true);
	}
}
 

解説

EventBus

イベントを管理するためのクラス。
ForgeとFMLが別でインスタンスを持っているため、利用したいイベントのパッケージを確認して対応したものに登録する。
このチュートリアルではForgeのものしか使っていないが、FMLにもTickEventやKeyInputEventなど多くのイベントが用意されている。

void register(Object target)

イベントを処理するメソッドがあるクラスのインスタンスを渡す。
このクラス内にある、@SubscribeEventがついており引数がEventを継承するクラス一つのみのメソッドが、イベントリスナーとして登録される。

Event

Minecraft内にフックされているイベントの元クラス。
これを継承したクラスがインスタンス化され、イベントが発生する。
親子関係があるイベントでは、親クラスを引数とするメソッドは、その全ての子クラスのイベントを受け取る。
例:ExplosionEventを引数とするメソッドはExplosionEvent.StartとExplosionEvent.Detonateの双方のイベントを受け取る。(一度の爆発につき二回呼ばれる。)

void setResult(Result value)

Eventに@HasResultがついている場合、これを利用するとフック元の残りの動作をキャンセルしたり別のものにしたりできる。

setCanseled(boolean cansel)

Eventに@Canselableがついている場合、ここにtrueを入れるとフック元の残りの動作をなくせる。

LivingHurtEvent

EntityLivingBaseを継承したEntityがダメージを負った時のイベント。

DamageSource source

ダメージの種類や性質を持つクラス。

float ammount

ダメージ量

EntityLivingBase entityLiving

親クラスであるLivingEventで定義。
LivingHurtEventの場合はダメージを受けたEntity。

AttackEntityEvent

プレイヤーがEntityを攻撃した時のイベント。

Entity target

攻撃対象のEntity。

EntityPlayer entityPlayer

親クラスであるPlayerEventで定義。
攻撃したプレイヤー。

LivingUpdateEvent

EntityLivingBaseを継承したEntityが更新される時のイベント。
EntityLivingBase.onUpdate()の最初で発生している。

EntityLivingBase entityLiving

親クラスであるLivingEventで定義。
LivingUpdateEventの場合は更新されるEntity。

Detonate

ExplosionEventの子クラス。
爆発が起こった時のイベント。
ExplosionEventの別の子クラスであるStartは爆発処理の開始時だが、こちらは影響対象のリストアップとその処理の間で発生する。
そのため、影響対象の操作が可能となる。

List<ChunkPosition> getAffectedBlocks()

爆発の影響を受けるブロックのリストを返す。
Explosionのインスタンスから直接取得しても同じ。

List<Entity> getAffectedEntities()

爆発の影響を受けるEntityのリストを返す。
操作すると、爆発処理(ダメージや移動)に反映される。
Explosionはインスタンス変数として持っていないが、DetonateがentityListとして保持している。

World world

親クラスであるExplosionEventで定義。
爆発の発生したWorldのインスタンス。

Explosion explosion

親クラスであるExplosionEventで定義。
爆発の処理を行うクラス。
性質などをインスタンス変数として持っている。

boolean isSmoking
煙パーティクルを発生させるか。
ブロックの破壊もこれで管理されている。

boolean isFlaming
炎を発生させるか。

PlaceEvent

プレイヤーがブロックを設置した時のイベント。
実際の設置処理は一通り終わっているので、座標とWorldからは設置されたブロックを得られる。

EntityPlayer player

ブロックを設置したプレイヤー。

ItemStack itemInHand

プレイヤーが手に持っているItemStackのインスタンス。
ブロック設置の処理を行う前の状態になっている。

BlockSnapshot blockSnapshot

ブロック置き換え処理の途中で、置き換え前のブロックを一時的に保持するために使われている。
置き換えられたブロック(通常は空気)を得たい場合はこれを利用する。
また、座標やディメンションIDなども得られる。

Block placedBlock

設置されたブロック。

Block placedAgainst

設置の際に視線の先にあった(設置の支えとなった)ブロック。

コメント

この項目に関する質問などをどうぞ。
  • メニューの「イベントの追加」のリンクがおかしいです。 - mod初心者 2017-05-20 21:26:17
    • ご指摘ありがとうございます。見事に製作の「製」の字を「制」にしておりました。修正しましたのでご確認ください。 - Tom Kate 2017-05-20 21:45:11
  • BlockPlacedEventですが、「== Blocks.air」ではなく「!= Blocks.air」だと思います。 - mod初心者 2017-05-30 17:23:56
    • 「設置前のブロックが」空気ブロックであることを判定してますので問題ありません。 - Tom Kate 2017-05-30 19:23:53
    • ではこのイベントは何をするイベントなんですか? - mod初心者 2017-06-02 18:26:18
    • 返信が遅くなってしまい申し訳ありません。
      PlaceEvent.placedAgainstは置き換えられたブロックではなく、ブロック設置直前に視線の先にあったブロックでした。
      本来、ダイヤモンドブロックを設置した時にキャンセルする、という処理を行いたかったため、正しいご指摘でした。
      少し処理を変更しましたので、ご確認ください。
      ご迷惑をおかけしてしまい、申し訳ありませんでした。 - 赤砂蛇凪浜 2017-06-14 19:15:35
      • こちらも説明不足ですみませんでした。大幅なコード修正ありがとうございました! - mod初心者 2017-06-16 19:03:04
  • List remover = new ArrayList();の型の設定やremover.addの括弧が多いような気がします。 - 名無しさん 2017-05-30 18:23:33
    • Entity型へのキャストを行っておりますので問題ありません。EclipseやIDEAなどでエラーが出ないかどうかご確認ください。 - Tom Kate 2017-05-30 19:25:56
      • remover.addのEntityMobの括弧足りなくてエラーになります。 - 名無しさん 2017-06-01 22:25:35
      • 上のは解決しましたが、ListとArrayListの型は設定しなくて良いのでしょうか? - 名無しさん 2017-06-02 18:10:51
        • 返信が遅くなってしまい申し訳ありません。
          ジェネリクスを利用しないとビルド時に警告が出ます。
          該当部分の処理方法を変更しましたが、同様のエラーは出ないよう修正しておきました。 - 赤砂蛇凪浜 2017-06-14 19:16:23
  • いつもこちらで勉強させていただいています。質問なのですが、下にあるブロックを掘ってアイテム化する(ドリルのように)というのは、どうすればいいのでしょうか。onDestroyByPlayerなどいろいろ試しましたが、壊れるのではなくて消えるようになってしまって困っています。 - 美羽 2017-06-04 09:47:06
    • 返信が遅くなってしまい申し訳ありません。
      どのMODのドリルを指しているのかわかりませんが、アイテムを手に持ちブロックを右クリックした時の処理は、Itemを継承したクラスonItemUseを利用できます。
      また、ツルハシなどのように左クリック長押しによるブロック破壊の速度を上げたいのであれば、「ツール類の追加」を参考にしていただけると思います。
      onDestroyByPlayerという名のメソッドは見当たりませんでしたが、Block.onBlockDestroyedByPlayer(World, int, int, int, int)のことでしたら、ブロックがプレイヤーに破壊された時に呼ばれるメソッドです。
      ブロック破壊時のアイテムのドロップは、Block.harvestBlock(World, EntityPlayer, int, int, int, int)などを見ると分かるかもしれません。 - 赤砂蛇凪浜 2017-06-14 19:17:42
    • ありがとうございます。単に、下にあるブロックを破壊してアイテム化するブロックが作りたいのです - 美羽 2017-06-22 14:28:21
    • すみません、途中で書き込んでしまいました - 美羽 2017-06-22 14:29:02
    • Setblocktoairのように消えてしまうのではなく、つるはしで掘ったときのように徐々にひびが入って、破砕、アイテム化させたいです。 - 美羽 2017-06-22 14:35:06
      • 返信が遅くなってしまい申し訳ありません。
        コメントのツリーを編集させていただきました。
        私も挑戦したことがないため、軽く調べてみましたが、間違っていたらすみません。
        ブロックのひび割れのレンダリングはRenderGlobalを通して行っているようです。
        drawBlockDamageTextureメソッドでレンダリングしているようですが、対象となるブロックはプレイヤーのEntityIDをキーとしたマップのdamagedBlocksで保持しており、ブロックやTileEntityからの介入は容易ではないと思います。 - 赤砂蛇凪浜 2017-07-08 18:29:55
最終更新:2017年06月14日 19:31