UE4 ガンシューティングゲームを作ろう!その6 AI(ビヘイビアツリー)

f:id:YumaDogidogi:20151226173627p:plain

はい!今回は敵に自動で行動させるために、敵のAIを作っていきたいと思います。

UE4には敵のAIを作るための便利な機能があります。今回はそれを使って、簡単なAIを作っていきましょう。最初に、前回作った敵が、近くにプレイヤーが来たら弾を撃つようにしてみます。

 

1.敵弾

まず、下準備に弾を作ります。いつものようにBluePrintを作ります。今回はActorを選択します。名前はEnemy1Projectileにします。

f:id:YumaDogidogi:20151225180149p:plainコンポーネントを追加をクリック。Sphere Collisionを追加します。これは当たり判定です。ほかにも四角やカプセル型の当たり判定があります。状況に応じて形を選びましょう!

次に球(Sphere)を追加します。これを当たり判定(Collision)の中に納まるように大きさを調整します。

f:id:YumaDogidogi:20151225181131p:plain好きなマテリアルを選びましょう!

f:id:YumaDogidogi:20151225181531p:plain当たり判定はすでにあるので、こちらのコリジョンはNoCollisionにしましょう。

f:id:YumaDogidogi:20151226164042p:plain

このままでは弾は動かないので、動くようにしていきます。Projectile Movementを追加します。

f:id:YumaDogidogi:20151225191329p:plain

これは弾が飛ぶためのコンポーネントです。弾の速度や重力、反射やバウンドなどの動きをシミュレーションしてくれる優れものです!今回はこれを使っていきます。

Initial Speed(初期速度を)200に、Projectile Gravity Scaleを0にします。これは速さ200の重力の影響を受けない弾といった感じです。

f:id:YumaDogidogi:20151225192322p:plain

画像の通りに画面左上のMyEnemy1Projectileをクリックすると、詳細にInitial Life Spanが出てくるので、その値を20にします。これは発生してから何秒存在できるかを表しており、0ならずっと消えません。今回は弾が発生してから20秒後に消えるようになりました。

f:id:YumaDogidogi:20151225192618p:plain

 

Event Graphに進み、先ほど追加したSphere(当たり判定のほう)を右クリックし、イベントを追加からOnComponentBeginOverLapを選択します。これは当たり判定が何かほかの当たり判定に重なったときに呼び出されるノードです。

f:id:YumaDogidogi:20151225200919p:plain

このブログの「その3」で、敵を倒せるようにしたときのように、弾がプレイヤーに当たったときに爆発を発生させ、爆発を発生させてから弾を消すようにしましょう。このままではプレイヤーは何も食らわないので、プレイヤーの方にもプログラムを打っていきます。FirstPersonCharacterのブループリントを開きます。

f:id:YumaDogidogi:20151225182725p:plain

今回はカスタムイベントというものを使っていきます。これは他のイベントや他のブループリントから呼び出して使うイベントです。引数(今回は「入力」と呼ぶ)という、イベントを呼び出すときに送る値の種類や数も変えることができます。

f:id:YumaDogidogi:20151225182942p:plaincustomと検索すると出てきやすいです。カスタムイベントを追加を選択します。

イベント名をDamageにし入力の新規をクリックFloat型の変数damageを作ります

f:id:YumaDogidogi:20151225185021p:plainFloat型の変数HPを作ります。

f:id:YumaDogidogi:20151225185322p:plain

コンパイルし、HPの初期値を10にします。

f:id:YumaDogidogi:20151225185433p:plainそしてこのようにプログラムを打ちます。HPをdamage分減らし、0以下になったらマップ選択画面に飛ぶ、というものです。もちろんゲームオーバー画面を作ってもいいですし、OpenLevelで今のレベルを指定して、最初からプレーさせるようにしてみてもいいでしょう。

f:id:YumaDogidogi:20151225185858p:plainでは先ほどのMyEnemyProjectileに戻り、キャストノードの青からから線を引っ張りDamageイベントを探して選択します。

f:id:YumaDogidogi:20151225190334p:plain線をつなぎなおし、入力damageに1を入力します。

f:id:YumaDogidogi:20151225190726p:plainこのことにより、弾に1回当たったらプレイヤーのHPが1減るようになります。この入力を2にするともちろん2減ります。ほかの種類の弾を作って、プレイヤーと当たったときにこのDamageイベントを呼び出して入力damageを変えると威力の違う弾が作れます。カスタムイベントは非常に便利ですね!

 

 2.敵のAI(1)

次に今回のメインである、ビヘイビアツリーを作ります。BluePrintフォルダを選択し、新規追加をクリックします。そこから未分類を選び、ビヘイビアツリーを作成します。

f:id:YumaDogidogi:20151225154140p:plain

名前はMyEnemy1Behaviorにします。

次にブラックボードを作ります。名前はMyEnemy1BlackBoardにします。

f:id:YumaDogidogi:20151225155351p:plain

これはAIの行動のプログラムで使う変数を保存するためのものです。実際に使って慣れていきましょう!

では先ほど作成したビヘイビアツリーをダブルクリックし、エディタ画面を開きます。

f:id:YumaDogidogi:20151225160310p:plainこれがビヘイビアツリーのエディタ画面です。真ん中にあるルートがAIの思考の始まりになります。難しいことは考えず、まずは先ほど作ったブラックボードを選択します。

では変数を作っていきます。画面右上のブラックボードをクリックします。

f:id:YumaDogidogi:20151225165808p:plainこれがブラックボードの画面です。変数を作るときは、新規キーをクリックします。

f:id:YumaDogidogi:20151225170226p:plain今回は弾を撃つかどうかを決める変数を作るので、TrueかFalseの値を持つ、Boolean型の変数を作ります。ブランチノードで使ったやつですね。変数名はDoShotにしておきます。

これで準備が完了したので、実際にAIの行動を作っていきます。まず「プレイヤーが近くにいるかを判定し、近くにいるならDoShotをTrueにし、そうでなければFalseにする」ような行動を作ります。新規タスクをクリックします。

f:id:YumaDogidogi:20151225161130p:plainそうするとブループリントが開かれます。ここにプログラムを打っていきます。ブラックボードで作った変数を使うための変数を作ります変数の型BlackBoardKeySelectorにしますここで必ず変数を編集可能にしてください

f:id:YumaDogidogi:20170218230555p:plain

プログラムの開始点を作ります。Event Receive Executeノードを出します。

f:id:YumaDogidogi:20151225171620p:plain

そして以下のようにノードを組みます。赤色で囲っている部分はこのAIを使っているアクター、つまりEnemy1の位置を取得しています。なかなかに複雑になっていますが、意味は単純です。緑色で囲っている部分は、プレイヤーの位置を取得しています。青色で囲っている部分は、その二つの位置の距離を取得し、その長さが2000以下かどうかを判定しています。

f:id:YumaDogidogi:20151225225609p:plainその結果をブランチにつなぐことで、Trueなら近くにいて、Falseなら遠くにいるということが分かります。

ブランチのTrueとFalseの両方にSet BlackBoard Value as Booをつなぎます。Trueの方はチェックをつけて、Falseの方はチェックを外してください。KeyにはDoShotをつなぐのを忘れずに!

f:id:YumaDogidogi:20151225230258p:plain最後にFinishExecuteノードをつないでください。その際に、Successにチェックを入れておいてください(あとで解説します)。このノードはAIの行動が終わったということを指し示すノードです。

ビューポートに戻って、タスクの名前を決めましょう。タスクのブループリントを右クリックして、名前を変更を選択します。

f:id:YumaDogidogi:20151226114554p:plain

デフォルトではBTTask_BluePrintBase_Newになっているので、BTTask_MeasureDistanceにします。

 

では作ったタスクをビヘイビアツリーで呼び出してみます。

ビヘイビアツリーを開き、ルートの下部分をドラッグし、適当なところで離します。すると、ブループリントの時のように選択肢が出てくるので、今回はSequenceを選びます。

f:id:YumaDogidogi:20151226115202p:plainSequenceやSelectorはコンポジットと呼ばれ、ノードの流れを制御するものです(これも後で解説します)。

同じようにSequenceノードの下を引っ張ります。先ほど作ったタスクを選択します。

f:id:YumaDogidogi:20151226120802p:plain

このタスクで使う変数を指定します。DefaultでDoShotの欄をDoShotにします。おそらく初めから指定されていると思いますが、よく忘れるので確認をしておきましょう。

 

では、先延ばしにしていた解説をします。

Sequenceノードはタスクを左から順番に評価(実行していきます)。例えば以下の写真の場合、BTTask_MeasureDistanceを実行し次にBTTask_BluePrintBase_Newを評価します。

f:id:YumaDogidogi:20151226121122p:plain

しかし、先ほど作ったタスクのSuccessフラグによって動きが変わります。もしBTTask_MeasureDistanceがSccessにチェックが入ったFinishExecuteノード終わればそのまま次のタスクを評価するので、BTTask_BluePrintBase_Newが評価されます。逆にチェックが入っていなければ、評価は終わり、BTTask_BluePrintBase_Newは評価されません。

f:id:YumaDogidogi:20151225230258p:plain

 

Selectorの場合は、左から評価していくのはSequenceと変わりません。

f:id:YumaDogidogi:20151226121539p:plainもし上の画像の時、BTTask_MeasureDistanceがSuccessにチェックが入ったFinishExecuteノードで終了した場合、Selectorノードは左からの処理を終了します。なので、BTTask_BluePrintBase_Newは実行されません。

逆にBTTask_MeasureDistanceがSuccessにチェックが入っていないFinishExecuteノードで終了した場合、BTTask_BluePrintBase_Newが実行されます。

 

 

次は弾を撃つタスクを作りましょう。名前はBTTask_Shotにしましょう。

f:id:YumaDogidogi:20151225161130p:plainまたEnemy1の位置プレイヤーの位置を取得します。そして緑色に囲まれたノードなのですが、これはStartにつながれた位置からTargetにつながれた位置の"方向"を指す回転の値(Rotator)を返すノードです。

f:id:YumaDogidogi:20151226123902p:plainつまり今回の場合、Enemy1からプレイヤーの方向を指すRotatorが出てきます。これはどの方向に弾を撃ちだすかというのに必要な値になります。

それらが繋げましたら、次は弾を撃った時の音を鳴らします。Play Sound at Locationノードです。これは選択した音声ファイル指定した位置から流すというものです。

f:id:YumaDogidogi:20151226124826p:plain今回はFirstPerson_WeaponFireを選択しEnemy1の位置を指定します。

次は実際に弾を発射するノードを作ります。spawnと検索すると、「クラスからアクタをスポーンします」というのが出てくるかと思います。これはアクタをワールドに作りだすというものです。敵などはビューポートで直接設置しましたが、弾などはこうしてプログラム内でスポーンさせます。

f:id:YumaDogidogi:20151226130157p:plainこのノードではどのクラス(ブループリント)をアクタとして生成するかを選びます。今回は弾を作り出したいので、MyEnemy1Projectileを選択します。

f:id:YumaDogidogi:20151226125948p:plain次はTransformを指定します。これはアクタに必要な

位置(Location)、回転(Rotation)、大きさ(Scale)、をまとめたものです。スポーン時には最低でも位置だけは指定する必要があります。しかし今回は回転もほしいので、Transformを作ります。Spawn Transformから線を引っ張り、MakeTransformノードを出します。

f:id:YumaDogidogi:20151226130840p:plain今回は位置をEnemy1の位置にして回転を先ほど作ったEnemy1からプレイヤーの方向にします

f:id:YumaDogidogi:20151226131340p:plain最後に忘れずにFinishExecuteノードを作ります。Successにチェックしておきましょう

f:id:YumaDogidogi:20151226131636p:plainこれで弾の発射のタスクは完成です。ではDoShotがTrueの時にBTTask_Shotを呼び出してみましょう。

Sequenceノードから新しくSequenceノードを作ります新しく作ったSequenceノードを右クリックして、デコレーターを追加からBlackboardを選択します。デコレーターとはブランチ(if文)のような条件文です。今回はBlackboardのDoShotの値がTrueなら弾を撃ちたいので、Blackboardを選択しました。

Blackboard Based Conditionをクリックし、Blackboardの欄のKey QueryをIs SetにBlackboardKeyをDoShotにします。Is Setは選ばれた変数がTrueの時に処理をするという意味です。

f:id:YumaDogidogi:20151226132914p:plainでは先ほど作ったBTTask_Shotを出します。

f:id:YumaDogidogi:20151226135231p:plain

このままでは弾を1フレームに撃ちまくるので、ゲームとしても面白ありません。なので弾を撃った後にしばらく待ってもらおうと思います。

ビヘイビアツリーにはデフォルトで"待つ"というタスクがあります。Waitタスクです。

f:id:YumaDogidogi:20151226135608p:plain

このタスクは、入力されたWaitTime分待つというタスクです。そのままですねw

f:id:YumaDogidogi:20151226135741p:plainこれにてAIは完成です!最後にAIをEnemy1に適応していきましょう。

キャラクターにAIを搭載するときは新規のブループリントからAIControllerを作ります。名前はEnemy1AIControllerにします。

f:id:YumaDogidogi:20151226141331p:plainEventGraphのイベントBeginPlayからRun Behavior Treeをつなぎます。

f:id:YumaDogidogi:20151226141531p:plain先ほど作ったMyEnemy1Behaviorを選択します。

f:id:YumaDogidogi:20151226141729p:plain

これでAIControllerは完成です。MyEnemy1を開きましょう。

f:id:YumaDogidogi:20151226141907p:plain

AI Controller Classを先ほど作ったEnemy1AIControllerにします。

f:id:YumaDogidogi:20151226142143p:plain

これにて完成です!!敵に近づくと、弾を撃ってきます。

 

3.敵のAI(2)

どうせならプレイヤーに向かって歩いてくる敵も作ってみましょう!以下のブループリントをコピーします。コントロールキーを押しながらクリックして、複数選択し、右クリックから複製を選んでください。

f:id:YumaDogidogi:20151226143226p:plain

今回はたくさんの要素を使ったので、復習を兼ねて以下のように変更していきましょう。

f:id:YumaDogidogi:20151226143616p:plain

 

f:id:YumaDogidogi:20151226143738p:plainこれはプレイヤーの位置を保存する変数です。

 

f:id:YumaDogidogi:20151226143922p:plain

 

f:id:YumaDogidogi:20151226144050p:plain

 

ではプレイヤーの位置を感知するタスクを作っていきます。名前はBTTask_FindTargetにします。

f:id:YumaDogidogi:20151226144328p:plain以下のようにプログラムを打ちます。プレイヤーの位置を取得して、それを変数を使ってブラックボードの変数に格納しています。

f:id:YumaDogidogi:20151226144718p:plainビヘイビアツリーで呼び出します。このShotの前に移動したいので、いったん下の画像のようにALTキーを押しながらタスクの上の部分をクリックして線をなくします。

f:id:YumaDogidogi:20151226151916p:plainここで呼び出します。画面右上のTargetPointをTargetPointに変えます。(僕が良く忘れるポイントです…)

f:id:YumaDogidogi:20151226152206p:plainここで実際に移動するタスクを呼び出します。Waitタスクのようにこれもデフォルトで用意されているタスクです。Move Toタスクをつなぎます。

f:id:YumaDogidogi:20151226152439p:plainこのようにVector型の変数を与えることによってその場所まで移動してくれるものです。障害物があったりしてもよけてくれるという優れものです!

f:id:YumaDogidogi:20151226152643p:plainこうなりました。

f:id:YumaDogidogi:20151226152913p:plain

実はこれだけでは動いてくれません。動かすにはフィールドのどこまでが動ける範囲なのかを指定する必要があります。

ビューポートでNav Mesh Bounds Volume(ナビゲーションメッシュ)を見つけて、マップに設置します。

f:id:YumaDogidogi:20151226153159p:plainこのようにマップ全体を覆うといいでしょう。

f:id:YumaDogidogi:20151226153448p:plainここでキーボードのPキーを押すと、以下のように移動可能なところが緑になります。これでAIが動いてほしい場所が緑になっているかを確認しましょう。坂が急であったりすると移動不可なことが分かります。

f:id:YumaDogidogi:20151226154007p:plainこれで動いてくれるようになりました!

敵がムーンウォークしてくるのが嫌であれば、アニメーションを使いましょう。ビヘイビアツリーではアニメーションも再生することができます。

f:id:YumaDogidogi:20151226155120p:plainPlayAnimationを使います。画面右側に出てくる詳細では、Animation to Play(どのアニメーションを使うか)、Looping(アニメーションを繰り返させるか)、Non Blocking(再生が終わるまで次の処理をするか)を決めることができます。

f:id:YumaDogidogi:20151226155425p:plain今回は歩くアニメーションであるWalk_Fwd_Rifle_Ironsightsにします。歩くアニメーションはループさせ、アニメーションを再生させながら移動させたいので、両方にチェックを入れます。(なんか画像がおかしくなっていますが気にしないでください…)。

f:id:YumaDogidogi:20151226160444p:plain次は弾を撃つアニメです。Fire_Rifle_Ironsightsにします。ループせず、再生完了後に次に進ませずにしたいので、チェックを外します。

 これで障害物を避けながらプレイヤーを追いかけてきて、止まった地点でプレイヤーと近かったときに弾を撃ってくるようになりました!

 

はい!今回はこれで以上です!!今回は敵のAIを主に作成しました。敵が自動で動くようになると一気にクオリティが高く見えますね。敵だけでなく、味方を作ることもできます。ぜひいろんなAIを作成してみてください!!

 

4.まとめ

・弾を自作しました(ProjectileMovement)

・カスタムイベントを作り、使いました

・敵のAIを作りました。キーワード:(ビヘイビアツリー、ブラックボード、AIController、タスク、コンポジット、デコレーター、ナビゲーションメッシュ)

 

今回は久しぶりの投稿というのもあって、がっつりボリュームになったかと思います。しかしゲームではほぼ必須であるAIなので長くなるのは仕方ないですね。AIはいろんな行動を追加していくにつれて、ゲーム自体を多様で想像もつかないものにしてくれます。つまり面白くなるということですねwどんどん応用して面白いゲームにしていきましょう!!

では、今回はこの辺で!