Javaは分からないけどマイクラMODを作りたい #6 村人AIをいじってみる

前回 EntityPlayer を継承して痛い目に合ったので、今回からは村人ベースで進めていこうと思います。機能はたくさん必要になると思いますが、仕方ないですね。

1. EntityVillager クラスを見てみる

まず目についたのは村人の行動パターンメソッド、すなわちAIにあたる部分ですね。

protected void initEntityAI()
{
    this.tasks.addTask(0, new EntityAISwimming(this));
    this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityZombie.class, 8.0F, 0.6D, 0.6D));
    this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityEvoker.class, 12.0F, 0.8D, 0.8D));
    this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityVindicator.class, 8.0F, 0.8D, 0.8D));
    this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityVex.class, 8.0F, 0.6D, 0.6D));
    this.tasks.addTask(1, new EntityAITradePlayer(this));
    this.tasks.addTask(1, new EntityAILookAtTradePlayer(this));
    this.tasks.addTask(2, new EntityAIMoveIndoors(this));
    this.tasks.addTask(3, new EntityAIRestrictOpenDoor(this));
    this.tasks.addTask(4, new EntityAIOpenDoor(this, true));
    this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 0.6D));
    this.tasks.addTask(6, new EntityAIVillagerMate(this));
    this.tasks.addTask(7, new EntityAIFollowGolem(this));
    this.tasks.addTask(9, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
    this.tasks.addTask(9, new EntityAIVillagerInteract(this));
    this.tasks.addTask(9, new EntityAIWanderAvoidWater(this, 0.6D));
    this.tasks.addTask(10, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F));
}

行動はタスク tasks で管理されているようです。そのタスクを addTask で追加することでやれることを増やすのでしょうか。引数にはプライオリティ(優先度)と行動内容を入れるようです。

2. AIタスクをいじってみる

タスクを1つにしてみます。まずはドアを開けることだけをさせて、優先度を 0 の一番高く設定します。私の予想が正しければ、ドアを設置するとドアの開け閉めを繰り返すのではと思います。

public class HumanUnitBase extends EntityVillager {
    public HumanUnitBase(World worldIn) {
        super(worldIn, 0);
    }

    @Override
    protected void initEntityAI()
    {
        this.tasks.addTask(0, new EntityAIOpenDoor(this, true));
    }
}

実際にやってみた。

f:id:takunology:20200313174519p:plain

まず、ドアを開けてくれないどころか振り向いてもくれません。悲しいですw
そもそも、なぜドアを開けてくれないのかが謎ですね。もしかしたら動作させるためのタスクが必要なのかもしれません。

ということで、動作に関するタスク?を追記します。動作っぽいものを入れてみるとどうなるでしょうか。

@Override
protected void initEntityAI()
{
    this.tasks.addTask(0, new EntityAIOpenDoor(this, true));
    this.tasks.addTask(2, new EntityAIMoveIndoors(this));
    this.tasks.addTask(5, new EntityAIMoveTowardsRestriction(this, 0.6D));
}

やっぱり動かないですね。もしかして村判定されないと動かないかと思いましたがどうやら違うようです。仕方ないので、元に戻してコメントアウトしながら確認していきます。

ちなみに、もともとタスクとしてあった EntityAISwimming をなしにした状態で水に入水させると動かないまま沈んでいきました。溺死です。

2.1 EntityAITradePlayer メソッドを無効化

私の予想ではアイテム取引ができなくなると思います。

f:id:takunology:20200313184515p:plain

普通に取引できるじゃあねぇかw
何を制御しているのでしょうかね...?

2.2 EntityAILookAtTradePlayer を無効化

これはプレイヤーのほうを見なくなると思います。

f:id:takunology:20200313184958p:plain

普通に見てくるじゃねーかw

と思いましたが、取引するときに振り向くかどうかでした。このあと普通の村人と比べてみたら、違いがありました。こちらを見ていない状態で取引すると、取引後にこちらを向いています。

2.3 EntityAIMoveIndoors メソッドを無効化

あれですかね。ドアを開けて家に入る処理ですかね。EntityAIOpenDoor もあるのですが、これは逆に家から出るときに使うと予想しました。

f:id:takunology:20200313185826p:plain

夜になっても家に入らなかったのでそのようです。これは夜になったら家に入る というロジックですね。

2.4 EntityAIOpenDoor メソッドを無効化

これは家から出るかどうかの処理っぽいですね。

f:id:takunology:20200313190241p:plain

違いました!まさかの ドアを開閉するためだけのロジックでした!

EntityAIMoveIndoors をもとにもどして夜にしたところ、家のドアに群がっているようでしたがドアを開けることはありませんでした。昼夜にかかわらずドアの開閉をさせるにはこのメソッドが必要らしいです。

2.5 EntityAIRestrictOpenDoor メソッドの無効化

上記の2つだけでなくもう一つドアのメソッドがありました。これは何ですかね。Restrictって直訳だと制限ですが...

どうやら夜の間の行動を制限するようです。無効化する前は、夜になると家に入った後一切出てきませんでした。しかし、無効化すると頻繁に出たり入ったりを繰り返します。

EntityAIMoveIndoors メソッドによって夜間は家に戻るようなロジックにしておき、EntityAIRestrictOpenDoor によって夜間の行動を制限するようなロジックなのでしょう。これらは相互作用してこそ意味がありそうです。

人間ユニットにはそのような制限はかけなくてもいいかなとは考えていますが、この辺は色々やってみないと分かりませんね。

2.6 EntityAIMoveTowardsRestriction メソッドの無効化

まぁ Move と書いてあれば動きだとおもうのですが...。英語からして何かに対しての動きを制限しているようですね。

普通に動いているので何を処理するメソッドなのか分かりません。

2.7 EntityAIVillagerMate メソッドの無効化

これは子供を産むか否かの機能ですが、今は必要ないですね。

よく分からなかったです。目を合わせているだけなのか喋っているのかよく分かりませんでした。ただ、話すというタスクは人間ユニットにも必要な動作だと思うので、残しておこうと思います。

2.8 EntityAIWatchClosest メソッドの無効化

これと同じく EntityAIWatchClosest2 もありました。ただ、どっちがどっちかは良く分かりませんが、恐らく視線を合わせてくれるかどうかの機能ですね。

引数を見ればわかるのですが EntityAIWatchClosest2 の引数には EntityPlayer.class が代入されています。EntityAIWatchClosest の引数には EntityLiving.class が代入されています。優先順位は 2 のほうが高いことから、プレイヤーに対して視線を合わせることを優先しているようです。後者はクラス名からしてすべての生物に対して視線を合わせるようですね。

f:id:takunology:20200313200403p:plain

はい。振り向いてくれなくなりました。

2.9 EntityAIAvoidEntity メソッドについて

これはこちらに記事がありました。

www.tntmodders.com

どうやら引数に代入した Entity から逃げるようです。逆に襲うようなメソッドもあるようなので、試してみたいと思います。

3. プレイヤーから逃げるユニットをつくる

EntityAIAvoidEntity メソッドを用いてプレイヤーから逃げるようなユニットを作ってみます。

次のような1行を追記してみました。

this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityPlayer.class, 10.0F, 1.0D, 1.5D));
引数 代入値
第1引数 this 修飾子
第2引数 逃げる対象となる Entity のクラス
第3引数 対象との間をとる距離
第4引数 対象から十分に離れている際の逃げる速度倍率
第5引数 対象に近いときの逃げる速度倍率

結果、プレイヤーから逃げるような Enitiy に仕上がりました。

youtu.be

4. プレイヤーを襲うユニットをつくりたかった

EntityAIAttackOnCollide メソッドを実装すると追いかけまわしてくるそうですw

しかし、バージョン 1.12.2 では使用できませんでした。代わりになりそうなものを探してみたら EntityAIAttackMelee を見つけたので、これを実装してみます。 が、これだけでは意味がなかったです。他にも2つのメソッドを追記しています。

先ほどの逃げるメソッドは消しました。(共存させるとどうなるか面白そうですが...)

this.tasks.addTask(3, new EntityAIAttackMelee(this, 1.0D, false));

this.targetTasks.addTask(1, new EntityAINearestAttackableTarget(this, EntityPlayer.class, true)); //攻撃されなくても追跡される
this.targetTasks.addTask(2, new EntityAIHurtByTarget(this, false, new Class[0])); //攻撃されると追跡される

f:id:takunology:20200314000221p:plain

こんな感じで囲まれますが、攻撃はされないみたいですね...w

人間ユニットに道具を使わせてみたいですね。