イベントの利用(1.7.10)

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

仕組み

今までMinecraft内のソースを見てみて、「ForgeHooks」や「EVENT_BUS」という記述を見たことはあるだろうか。
例えば、EntityLivingBase/onUpdateのメソッドを見てみよう。

public void onUpdate()
	{
		if (ForgeHooks.onLivingUpdate(this)) return;
		super.onUpdate();
	/* 略 */
	}
/* 略 */

このように、Forgeがシステムを書き換えてイベントを起こしている場所がある。
ここで、@SubscribeEventのアノテーションを付け、適切な処理をしてあるメソッドが呼ばれる。
クラス書き換えと違って使えるものは限られるものの、競合が起きにくくかつ実装しやすい手段である。

ソースコード

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 getAffectedBlocks()

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

List 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

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

#実際のコード
匠Craftでイベントを多く利用している。
匠Craft

Share this...

コメントはこちらです。(スパム対策の為コメントは手動承認になっています。未承認のコメントは表示されないので連投はお控え下さい。)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

[最終更新日]2017/11/25 20:50