藍と淡々

物作り冒険譚


【Unity】タイルマップで作る敵AI

こんにちは(ؓؒؒؑؑؖؔؓؒؐؐ⁼̴̀ωؘؙؖؕؔؓؒؑؐؕ⁼̴̀ )

先日ラジオを聞いていたら「1から10の数字であなたが好きなのは?」という質問がありました。

自分は、6月生まれというのもあってか、「6」という数字が好きです。曲線が可愛くて、温かみがある気がします。

しかしリスナーの投票ランキングでは不人気な数字でした。。なんでや・・・_:('Θ' 」 ∠):_


さて、余談からになりましたが、前回の記事では「タイルマップの自動生成アルゴリズムの解説をしました。(先に読んでおくと今回の内容を理解しやすいです)
→「【Unity】「Trocco」で使った、タイルマップの自動生成アルゴリズム【基礎編】 - 藍と淡々

今回はそれに関連して、これまた「Trocco」で使用した敵AIの作り方を紹介していこうと思います。


0、実際の敵の挙動

まず簡単に説明すると、前回解説した通り、1平方メートルを1マスとしてマップが作られるので、マップ上を移動するキャラクターは、碁盤のようなマス目を基本として移動するようにします。

たとえば、クロッシーロードのプレイヤーキャラの移動方法みたいな感じです。


Crossy Road - Gameplay Launch Trailer (By Hipster Whale)


しかし、移動したいマスに障害物やアイテムがあった場合には、別の移動可能なマスに移動するようにさせる必要があります。

また、プレイヤーが障害物を破壊した場合には、もうそのマスには移動可能になるので、それを敵AIが判断できるような仕組みを作る必要があります。


実際に、ピンクの敵キャラ「うさぎソックス」が、プレイヤーが宝箱を壊した後にそのマスに移動している様子がこちらです。

f:id:MegumiSoft:20160623155559g:plain
http://gph.is/28P31vP

宝箱を壊す前は、宝箱と反対のマスに移動し、破壊後は宝箱があったマスに移動したのが確認できたと思います。

これを実装するには、どんな風に考えていけばいいでしょうか。



1、考え方

まず前提として、基本的な敵の動きは、「プレイヤーに近づくこと」です。

以下のようなマップがあるとします。

f:id:MegumiSoft:20160623162910p:plain:w400

この場合敵がプレイヤーに近づくための最短ルートは下のマスに移動することですが、マップ上ではないため、移動できません。

なので、次に最短ルートを移動するために左右のマスに移動できるか判断します。

左はマップがありませんが、右はマップ上なので移動可能です。

f:id:MegumiSoft:20160623163204p:plain:w400

こんな感じでプレイヤーへ近づくための最短ルートを基本にして、そのマスに移動可能かどうかを調べ、可能なら移動、もしダメなら次のルートを調べる、といった考え方になります。

なので、もし右側に障害物があった場合には、最短ルートは上に移動することになります。

f:id:MegumiSoft:20160623163535p:plain:w400

というわけで、移動先のマスが「マップ上で障害物等の移動を遮るものがない」ことがわかればOKです。

それではどうやって調べれば良いでしょう・・・?


早速ネタばらしをしてしまうと、これを実現するには配列を使用します。


ーーーーー 補足 ーーーーー

敵の移動は「最短ルート」を選ぶようにすると説明しましたが、これを遵守するとあまり賢くないAIができてしまいます笑

たとえば以下のようなマップの場合、2つのマスの間を行ったり来たりするだけの挙動になってしまいます。

f:id:MegumiSoft:20160623230020p:plain:w400

なので、「一個前にいたマスは他に移動できるマスがない限り移動しない」といった処理などを書く必要があります。

本当に賢いAIを作りたいのであればこれでも不十分ですが、Troccoではそれほど敵を強くする必要はないと感じたので、これさえ実装していません。

なのでこれ以上のことは説明できません、、、いずれ経路探索のアルゴリズムも学習してみたいところです。

ーーーーーーーーーーーー


2、実装

先ほどの図の例で考えていきます。

f:id:MegumiSoft:20160623214646p:plain:w400

まずこの図をXが5、Zが6の二次元配列にすると、

int[,] array_MapList = new int[5, 6];  // int[X, Z]

になり、すべてに「0」が格納された状態です。

そして次にマップが生成されるので、マップチップがある場所に「1」を入れていきます。

// Xが1、Zが0の位置にマップが存在することを表す
array_MapList[1, 0] = 1;

とすれば良いわけですが、実際にはマップが生成された時にやるので、前回の記事を参考にすると、

// マップチップを置いてから、その位置情報を配列に反映
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = new Vector3(xPos, 0, zPos);
array_MapList[xPos, zPos] = 1;

と書く感じになりますね。

これで上の図を反映させた「array_MapList」の中身はこんな風になります。

// 0=地面なし、1=地面あり
01111
01111
01111
00011
01111
01100

あとは敵が移動先を決める時に、この配列の中身を見てあげるようにすれば良いだけです。

「0」が返ってきたら移動できないので、「1」が返ってくるまで次のルートを探索するようにすればいいですね!


ここまでわかれば障害物の確認も簡単です。

障害物設置時に「array_MapList」に「2」を入れておきます。

たとえば、

f:id:MegumiSoft:20160623220752p:plain:w400

この場合の配列の中身は、

// 0=地面なし、1=地面あり、2=障害物あり
01111
01111
01211
00012
01111
01100

となります。

プレイヤーが障害物を破壊した時には、この配列にその障害物のXとZ位置の値を渡して、「1」を入れてあげれば、敵もそのマスに移動ができるようになります。

また敵どうしが同じマスに移動しないようにするのも、敵の移動時にこの配列に適当な値を入れてあげて、そのマスから去った時にまた「1」に戻してあげれば簡単に実装できます。



3、敵AI以外の利用

この方法はすごく便利で、敵の移動以外にも利用できます。

Troccoではレールを敷いてプレイヤーキャラを動かしていくのですが、レールを敷いたマスに障害物がある場合はレールの色が赤くなって、障害物があることをわかりやすく示すようにしてます。

f:id:MegumiSoft:20160623231257g:plain
http://giphy.com/gifs/26BRs32mvJY82zlFS

これも配列を参照して、障害物がある場合にはレールを赤くしておき、障害物が消えたら元の色に戻す、といった使い方をしています。


他にも、破壊した時にたまに周りにちびキノコを増殖させる障害物のキノコも、周りのマップ状況を配列で確認して、他に障害物がなければ、増やすかどうかの判断をしています。

f:id:MegumiSoft:20160623224350g:plain
http://giphy.com/gifs/l46C8EZqscIfQ3R3G



といった感じで、配列でマップの情報を簡単に確認できるので、タイルマップはとても便利です。

キャラクターのアニメーションも、単純にジャンプで移動という方法でも許されます(オイ)

個人的にとてもお気に入りな方法なので、今後も他も3Dゲームで使っていきたいですね ʕ•̫͡•ʕ*̫͡*ʕ•͓͡•ʔ-̫͡-ʕ•̫͡•ʔ*̫͡*ʔ-̫͡-ʔ


あと最後になりましたが、説明しやすいので配列を使いましたが、エンドレスなマップ生成をする場合はListを使ったほうが良いかと思います。

それではではばいちー・*・:≡( ε:)