AIの追加
ここではレッドストーンをもったプレイヤーに突進するAIを搭載した、微改変牛を追加するサンプルコードでAIの追加方法を説明します。
以下のサンプルコードはMinecraft1.5.2、Minecraft Forge7.8.0.684で動作することを確認しました。
MODのエントリポイントを作成
package redbull; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.registry.EntityRegistry; import cpw.mods.fml.common.registry.LanguageRegistry; @Mod(name = RedBull.modID, modid = RedBull.modID) public class RedBull { public static final String modID = "redbull"; // 追加するmobのentityID private final int entityRedBullId = 28; @Mod.Init public void init(FMLInitializationEvent event) { LanguageRegistry.instance().addStringLocalization("entity.RedBull.name", "en_US", "RedBull"); // サンプルのため手っ取り早くregisterGlobalEntityIDを利用している // 良い子のみんなはregisterModEntityを使いましょう EntityRegistry.registerGlobalEntityID(EntityRedBull.class, "RedBull", entityRedBullId, 0xCCCCCC, 0xFFFFFF); } }
微改変牛を作成
package redbull; import net.minecraft.entity.passive.EntityCow; import net.minecraft.world.World; /* * 赤いものを持ったプレイヤーに突進するAIを追加した改変牛 */ public class EntityRedBull extends EntityCow { public EntityRedBull(World par1World) { super(par1World); this.tasks.addTask(2, new EntityAIRush(this, 0.45F)); } }
AIを作成
package redbull; import net.minecraft.entity.EntityCreature; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; /* * 最寄りのプレイヤーが赤い物を手に持っていたら突進するAI */ public class EntityAIRush extends EntityAIBase { // このAIを搭載したentity EntityCreature owner; // 突進する対象となるプレイヤー EntityPlayer target; // 突進するスピード float speed; public EntityAIRush(EntityCreature entity, float f) { owner = entity; speed = f; setMutexBits(3); } /* * このAIによる行動を開始すべきかどうかの判定を行うメソッド * EntityAITasksのonUpdateTasksメソッドから呼び出され、戻り値によりこのAIによる行動を実行するかどうかの判定が行われる * trueを返却すると、EntityAITasksはstartExecuting、updateTask、continueExecutingを呼び出す */ @Override public boolean shouldExecute() { // 一番近くのプレイヤーを探す target = owner.worldObj.getClosestPlayerToEntity(owner, 15.0D); if(target != null) { if(owner.getDistanceSqToEntity(target) < 2.0D) { // 既に接近してたら何もしない return false; } // プレイヤーが持っているアイテム ItemStack itemStack = target.getCurrentEquippedItem(); if(itemStack != null) { if(itemStack.itemID == Item.redstone.itemID) { // レッドストーンを持ってたら突進する return true; } // 赤い染料やレッドストーントーチも赤いけどこのサンプルでは省略 } } return false; } /* * このAIによる行動を続行すべきかどうかの判定を行うメソッド * shouldExecuteが開始判定を行うのに対し、こちらはすでに開始された行動を続行すべきかどうかの判定を行う */ @Override public boolean continueExecuting() { // EntityAIBaseで定義済み。オーバーライドしなければshouldExecuteをそのまま利用する return super.continueExecuting(); } /* * このAIによる行動が起動したときの初回の行動を定義する * つまり、shouldExecuteがtrueの場合の行動 * ここで記述した行動は1回しか実行されないので注意 * shouldExecute→startExecuting→continueExecuting→updateTask→continueExecuting→updateTask→… * のように実行される */ @Override public void startExecuting() { } /* * このAIによる行動を終了するときの処理 */ @Override public void resetTask() { target = null; } /* * このAIによる行動を続行する場合の行動を定義する * つまり、continueExecutingがtrueの場合の行動であり、1回の行動(startExecutingで定義された行動)で全て終わる場合は不要 * 例えば、飼い主にずっと追従するようなAIの場合、継続して飼い主の位置に向かって移動するように、updateTaskで移動先を更新するような実装をする */ @Override public void updateTask() { if(target != null) { owner.getNavigator().tryMoveToEntityLiving(target, speed); } } }
解説
微改変牛では牛(EntityCow)を継承し、突進するAI(EntityAIRush)を追加しています。
this.tasks.addTask(2, new EntityAIRush(this, 0.45F));
tasksというメンバー変数がそのEntityが使用するAIリストを管理するクラスのインスタンスです。
MinecraftにおけるEntityのAIは、比較的単純な行動を規定した複数のAIが周囲の環境とAI間の優先度によって主導権を争い、主導権を獲得したAIがそのEntityを動かすようなモデルを採用しています。
例えば、EntityCowでは泳ぐ、パニクって駆けまわる、繁殖する、小麦を持ったプレイヤーに近づく等8個のAIが搭載されています。
上記のコードは突進するAIを優先度2として登録しています。
優先度は値が小さいほど優先度が高くなります。
優先度の低いAIは行動中であっても、優先度の高いAIに行動を"上書き"されることがあります。
この判定は、tick毎にEntityのonUpdate()が呼ばれ、そのtickでの行動を決定する際に行われます。
このような切り替えができないAIもありますがここでは説明を省略します。
詳しくはEntityAITasks.javaを確認してください。
AIを追加するときは、EntityAIBaseまたはそのサブクラスを継承したクラスを作成してください。
そして、shouldExecuteをオーバーライドし、追加したいAIの動作を開始する条件判定を実装します。
今回追加する「レッドストーンをもったプレイヤーに突進するAI」では、
「15m以内にプレイヤーがいること」
「プレイヤーとの距離が2m以上であること」
「プレイヤーがレッドストーンを手に持っていること」
の3つの条件全てを満たす場合にtrue(動作開始)を返却するようにshouldExecuteメソッドを実装しています。
このAIは1tickで完結するものではなく、tick毎のプレイヤーの移動に追従するようにしています。
もしAIの行動が1tickで完結する場合は、startExecutingメソッドにEntityに実行させる行動を実装してください。
tick毎に行動を変更する場合は、updateTaskメソッドに実装してください。
今回は以下のようにtarget(=プレイヤー)の位置に指定の速度で移動する行動を実装しています。
@Override public void updateTask() { if(target != null) { owner.getNavigator().tryMoveToEntityLiving(target, speed); } } }
なお、以下のようにコンストラクタにEntityCreatureをとっているため、このAIは今回追加した微改変牛に限らずEntityCreatureを継承したクラスならば汎用的に使用することができます。
// このAIを搭載したentity EntityCreature owner; public EntityAIRush(EntityCreature entity, float f) {
ただし、汎用的であるがゆえに特化した処理を行うことはできません。
例えば、村人が取引を終えたら逃げるように自宅に帰るAIを追加したい!という場合にはEntityVillagerのメソッドにアクセスする必要がでてくるでしょう。
その場合はコンストラクタの引数とownerの型をEntityVillagerに変更して特化したAIを作成してみてください。
他に注意すべき点としてはAI間の排他制御が挙げられます。
以下のコードで指定している「3」という値が排他制御のための値です。
この値が同じAIは同時に実行することはできません。
setMutexBits(3);
例えば、弓を打つEntityAIArrowAttackと近くの敵を殴るEntityAIAttackOnCollideではこの値は「3」であり、同時に実行することはできませんが、敵から遠ざかるEntityAIAvoidEntityではこの値は「1」です。
つまり、敵から遠ざかりながら弓を打つという行動は可能ですが、弓を打ちながら殴るという行動はできないようになっています。