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