- ブループリントを使用した AI の紹介
- ビヘイビア ツリーの理論
- 初回ビヘイビアツリーを作成する
- プレイヤーを追跡する
- ビヘイビアツリーをテストする
- 最初の EQS クエリを作成する
- 飢えに基づいて決定する
- 2番目のEQSクエリを作成する
- EQS Testing Pawn を使用する
- EQS ゲームプレイ デバッガ
- 最後に
- メモ
ブループリントを使用した AI の紹介
ビヘイビア ツリーの理論
- AIの最も重要な部分
- AIによる意思決定がどのように行われるかを簡単に制御及び視覚化する事ができる
- 意思決定を視覚的にモデル化する方法
- これにより簡単にフローが把握できる
- 実行順序はトップダウンで左から右に実行される
ビヘイビアツリーを実装する
- タスクとコンポジットはビヘイビア(振る舞い)に対するAIの反応方法、実行方法の制御を可能にする
タスク
- タスクノードはビヘイビアツリー内のノードであり、子を持つことができない。
- 末端ノード
- リーフノードとも呼ばれる
- タスクは3つの状態のいずれかになる。
- 成功、失敗、実行中
- 成功、失敗はそのまま次のノードへの実行へと移り、左から右へのフローで続く
- 実行中は現在のタスクの結果を待機する
- ただしあまり複雑なことはできない。複雑なことはコンポジットの出番となる
コンポジット
- コンポジットはAIの実行ブランチのルート
- 構造化を行い、その配下にあるノードの成功または失敗のステータスに応じて動作を制御する事ができる
- SELECTOR コンポジットとSEQUENCER コンポジットがある
- 組み合わせることで複雑なことも可能になる
SELECTOR コンポジット
- 下にあるノードを実行する
- いずれかのノードが成功を返した場合、ノードは成功ステータスであるとみなす
- 全てのノードが失敗を返すと失敗ステータスが返される
- 🤔status = OR(ノードステータス) ってことですかね
- 最もふさわしいタスクを選択する場合を考える
- ビヘイビアツリーでは左から右に動作が実行されるので、優先的なタスクを左に配置すると良い
- ドアを開ける動作を例にすると、鍵を使う、錠をこじ開ける、ドアを壊すといったことが考えられる
- どれが実行されるのかが問題ではなく、1つが実行されるという事が重要
SEQUENCER コンポジット
- サブノートをシーケンシャルな順番で移動し、全てのサブノートが精巧である場合にのみ成功ステータスを返す
- 🤔status = AND(ノードステータス)ってことですかね
- このノードの動作は人間が障害に出会ったときの振る舞いに似ている
- 多くの場合、物事を順序に従って行う必要があり、前に進めなくなってもそのステップをスキップする事はできない。
- ドアを開ける場合、ドアを開け、通過してから、ドアを閉じる。順番は変えられない
初回ビヘイビアツリーを作成する
- ビヘイビアツリーの概要の学習が終わったので作ってみる
- 流れ
- 新しいアセット作成
- 機能をAIコントローラーについか
- ビヘイビアツリーを自動的に実行する
- 上記によって最初に作成した RandomWander よりも柔軟なロジックに置き換える
アセット作成
- Blueprints フォルダ直下にフォルダ作成。名前を BT_Assets とする
- BT_Assetsフォルダーに入る
- ビヘイビアツリーを新規作成する
- 名前を BT_EnemyAI とする
- ブラックボードを新規作成する
- 名前を BB_EnemyAI とする
作成完了。
AIが作成したビヘイビアツリーを使用するようにする
このステップはAIがビヘイビアツリーを使用するようにする
- AIC_ThirdPersonCharacter を開く
- イベントグラフを表示させるためブループリントエディタで開くをクリック
- イベントグラフに Event On Possessを配置
- Run Behavior Tree を配置しEvent On Possessと接続する
- BTAsset には BT_EnemyAI を選択
- コンパイル→保存してエディターを閉じる
- 🤔ここでは作業内容以外の解説がなかった Possess とは何なのか気になる。後の方でも解説がなければ調べるとしよう
ブラックボードがビヘイビアツリーに関連付けされているか確認する
- コンテンツブラウザから BT_EnemyAI を開く
- 詳細パネルのBehaviorTree の Blackboard Asset の後で BB_EnemyAI が選択されている事を確認する
- 🤔選択した覚えがないけど選択されている。
- 次はブラックボード内に位置情報を作成してAIが目的地とする位置を格納する必要がある
- 上記はこれは新しいRandomWander機能の一部となる
ブラックボード内に位置情報を作成する
- ブラックボードを開く
- Vector Keyを作成。名前はTargetLocation にする
- ビヘイビアツリーに戻る
- 詳細パネルに TargetLocation が生成されている事が確認できる
新しいタスクを作成する
このステップは新しいタスクを作成し、前に作ったRandomWander関数の全てのロジックをカプセル化する。このステップで作成するものも RandomWander同様に有効な移動先を見つけるもの
- 新規タスクをクリック
- 🤔動画ではBTTask_BlueprintBaseを選択とあるがそのような項目はなくデフォルトでそれが選択されている模様
- 上記によって EventGraph の画面に移行する
フォルダ構成を整理する
このステップでは EventGraph の編集に移る前にフォルダ構成を整理するみたい。
- 前の手順によって開かれている EventGraph を最小化する
- コンテンツブラウザに BTTask_BlueprintBase_New が作成されている。これの名前を BTT_FindNavigableLocation とする
- 同じ階層に Tasks フォルダを作成
- 上記フォルダに BTT_FindNavigableLocation を移動
- フォルダ内に移動して BTT_FindNavigableLocation をダブルクリックしてエディターを開く
新しいイベントの作成
- ここで作成するイベントはAIの実行を受け取るために呼び出される
- これはビヘイビアツリーがタスクを実行しようとした時に、ビヘイビアツリーからトリガーされるイベント
- Event Receive Execute AI を配置
- GetRandomReachablePointInRadius を配置
- このノードには Origin の情報が必要
- Event Receive Execute AIの Controlled Pawn ピンはコントロール中のPawnへの参照が入っている
- Controlled Pawn から位置情報を取る
- このノードには Origin の情報が必要
- Get Actor Location を配置
- GetRandomReachablePointInRadius の Radius は 1000に設定
- 図の通り接続
- GetRandomReachablePointInRadius より返される RandomLocation をビヘイビアツリーとブラックボードに移行する必要がある
- ブラックボードに移行するためにSet Blackboard Value as Vector andom Location を配置する
- どのブラックボードに移行するかの Key が必要
- Key をドラッグして変数へ昇格させる
- 名前を TargetLocationKey とする
- MyBlueprintパネルの変数にある上記変数を Public にする(目が開いた状態にする)
- ビヘイビアツリーがら参照する必要があるため
- 現在のノードセットアップ
- Receive Execute AIの実行ピンを Set Blackboard Value as Vector に接続
- 残りの作業はビヘイビアツリー自体にこのタスクの完了を通知する
- この通知を忘れるミスはよくある。その場合タスクが終了したとみなされずタスク内でスタックする
- 残りの作業はビヘイビアツリー自体にこのタスクの完了を通知する
- タスクが完了した事を示す為、 Finish Execute を配置し、Set Blackboard value as Vector と接続
- このノードにはタスクが成功したかどうかの Boolean を設定するピンがある
- GetRandomReachablePointInRadiusは実行が成功したかどうかを Return Value で出力している
- GetRandomReachablePointInRadius の Return Value を Finish Execute の Success に接続
- コンパイル
ビヘイビアツリーを作成する
- 🤔ここで動画は急にビヘイビアツリーが完成している画面に移行しビヘイビアツリーの作成が終了しました。と言っている。なので以下はサンプルファイルを参考に自分で作業した内容です。間違っている可能性がありますがご了承ください
- Sequence を配置しルートに接続、名前をRandom Wanderをする
- Tasks → BTT_FindNavigableLocation を配置し接続
- 詳細パネルより名前を Find Suitable Location とする
- Tasks → Move to を配置し右隣に置き接続
- 右上に番号があるが、評価順序のようなので位置には気をつける必要がある
- 右上に番号があるが、評価順序のようなので位置には気をつける必要がある
- Tasks → Wait を配置し右隣に置き接続。詳細パネルより 3秒に設定する
- 🤔見様見真似で作成しました。プレイしてみると3秒おきにランダムにAIが動いている事が確認できるのでおそらくOKでしょう。
- 🤔ビヘイビアツリーのとおりですが一応理解のためにまとめておきます。シーケンスなので全て順番に行っていく事を前提としていると言うことですね。
ビヘイビアツリー(BT)とビヘイビアツリー(BTT)とブラックボード(BB)の連携の概要
プレイヤーを追跡する
- ビヘイビアツリーを使用する主な理由の1つは、AIが意思決定を行う方法を制御する為
- しかし現状では何も意思決定は行わず常に同じことを繰り返す(ランダムに動く)
- ここでは意思決定を行えるようにする。そのためには↓
- 知覚からAIに関連情報を提供する
- ビヘイビアツリーを修正し、その情報に基づいて実行フローを確定できるようにする
BBに新しいキーを作成しビヘイビアツリー内にこの情報を格納できるようにする
- BT_EnemyAI を開く
- 右上のボタンからブラックボードに切り替える
- 新規キー Object を作成、名前は TargetActor、Base Class は Actor とする
- 新規キー Bool を作成、名前は HasLineOfSight
- 保存してウィンドウを閉じる
AIコントローラーを変更
AIコントローラーに変更を加え、知覚システム内の変更に基づいてこれらのキーを更新するようにする
- AIC_ThirdPersonCharacter を開く
ヘルパ関数の作成
ブラックボードキーを使用した作業を簡単にする為のヘルパ関数を作成する
UpdateTargetActorKey関数を作成
最初の関数はアクタを入力として取得し、その入力内容でブラックボードキーの情報を更新する
- 関数のとこの
+
ボタンを押す - 名前は UpdateTargetActorKey とする
- 新規インプットを作成
- 名前は TargetActor で、タイプは Actor にする
- この関数は現在使用しているブラックボードを取得する必要がある
- Get Blackboard を配置する
- Set Value as Object を配置し接続
- 次に ビヘイビアツリーに関連付けれらた Blackboard内のキーを指すキー名を取得する必要がある
- Set Value as Object の Key Name をドラッグし変数に昇格させ、名前を TargetActorName とする
- TargetActorName を選択したまま、詳細パネルのカテゴリに Key names と入力
- MyBlueprintパネルに Key Names というカテゴリが出来上がっている事が確認できる
- コンパイルする
- コンパイルするとデフォルト値が設定可能となる
- Blackboard の TargetActorキーのキー名を正確にタイプする必要がある
- 最も安全な方法は BB_EnemyAI を開いて キー名 をコピーし貼り付ける事。とのことなのでそのとおりにした
- TargetActorName のデフォルト値を設定
- 最後にこの関数に渡されたパラメータをそのまま設定する為、Object Value へ接続。実行ピンも接続する
UpdateHasLineOfSightKey関数を作成
知覚システムを通じてAIから現在プレイヤーが見えているかどうかを格納するブラックボードキーを設定する
- 前の手順同様に関数を作成。名前は UpdateHasLineOfSightKey という名前にする
- Input を作成し型を Bool、名前を HasLineOfSight とする
- Get Blackboard を配置する
- Set Value as Bool を配置し接続
- Set Value as Bool の Key Name をドラッグし変数に昇格させ、名前を HasLineOfSightName とする
- Key names カテゴリにするため、HasLineOfSightName をドラッグして移動する
- コンパイルしてデフォルト値が設定できるようにする
- HasLineOfSightName のデフォルト値を設定
- タイポを防ぐためBB_EnemyAI から HasLineOfSight というキー名をコピーして貼り付けている
- 全て接続し整理する。
- 整理のためにコードに頂点を追加するのはダブルクリックにて可能
- コンパイルしてエディターを閉じる
On Target Perception Updated イベントの修正
- ヘルパ関数の作成が完了したため、キャラクター内の On Target Perception Updated イベントの修正に移る。
- 上記のイベントからブラックボードキーを更新する
- こうすることで知覚のたびに関連するブラックボードキーも更新されるようになる
- まずこのイベントで起こりうるケースを考える
- TargetLostイベント発生時には、アクティブブラックボードキーを null にする
- TargetLostイベントは消失後5秒後に発生する
- これはAIが忘れて追うことを諦める事を意味する
- TargetLostイベントは消失後5秒後に発生する
- On Target Perception Updated の False は知覚できなくなった事を意味する為、HasLineOfSightもFalseにする
- On Target Perception Updated の True は知覚した時のイベントを修正する
- TargetLostイベント発生時には、アクティブブラックボードキーを null にする
TargetLostイベント発生時には、アクティブブラックボードキーを null にする
- AI_ThircPersonCharacter を開く
- ヘルパ関数にアクセスするために、Get Controllerを配置
- Contoller から先程の関数にアクセスするには明示的に Cast する必要がある
- Cast To AIC_ThirdPersonCharacter を配置し接続
- ノードをきれいに保つため、Cast To AIC_ThirdPersonCharacterで右クリック純粋キャストに変更をクリック
- Cast…の出力ピンから Update Target Actor Key を選択して配置
- 接続する
On Target Perception Updated の False は知覚できなくなった事を意味する為、HasLineOfSightもFalseにする
- Get Controller と Cast to AIC_ThirdPersonCharacter をコピーする
- Cast…の出力ピンから Update Has LIne Of Sight Key を選択して配置
- 接続する
On Target Perception Updated の True は知覚した時のイベントを修正する
- Get Controller、Cast To AIC_ThirdPersonCharacter、UpdateTargetActorKeyをコピーして貼り付け
- 接続する
- True 時は知覚したアクタを設定する必要がある
- これはすでにPerceivedActor変数として持っている
- True 時は知覚したアクタを設定する必要がある
- PerceivedActor変数をCtrlキーを押しながらドラッグして配置し接続する
- Get Controller、Cast To AIC_ThirdPersonCharacter、Update Has LIne Of Sight Keyをコピーして貼り付け
- 接続し True 時は見えている為、変数もチェックを入れておく
- コンパイルして閉じる
追いかけるかランダムに動くか切り替えられるようにする
- BT_EnemyAIを開く
- 追跡またはランダムに行動する選択をする為、Root から Sequence の線を削除し間に Selector を配置
- Sequence以下を右にずらす
- 今から左側に追跡のためのツリーを作成するため
- 左のほうが優先順位が高く、右は優先順位が低い
- 今から左側に追跡のためのツリーを作成するため
- 左側に新規Sequence を作成し、名前を Chase Player とする
- Sequence の子として Move to を配置
- 自キャラを追跡してもらうため、Move toを選択した状態で、詳細パネルの Blackboard Key を Target Actor にする
- Move to の右に Wait を配置し、詳細パネルより 1秒で設定する
- Wait は行動の最後においておくと高速で循環することで不自然に見える事をなくせるのでおすすめとのこと
基本的な Chase Player シーケンスの定義はこれで完了。
RandomWander実行前に常にChase Playerしないようにする
- 新しく配置した Chase Player は常に実行される。これを防止するためにデコレーターを使用する。
- Chase Player を実行してよいのは、ターゲットアクタがNullではない場合のみ
- Chase Player で右クリック → デコレータを追加 → Blackboard をクリックし、デコレータを追加する
- デコレータの説明をわかりやすくする為、デコレータを選択し詳細パネルから変更
- Is a target actor set? としている
- 講師は説明文に ? を入れることを好むとのこと。そうすると馴染みがない人が見てもツリーが理解しやすくなるとのこと
- 🤔重要ですね
デコレータを設定する
- デコレータを適切に設定する必要がある
- AIはTargetActorが存在しなくなるまでChasePlayerを実行する必要がある
- Nullになったら RandomWander を実行する必要がある
- RandomWander実行中でもアクタが設定されたら直ちにChasePlayerを実行する必要がある
- デコレータの詳細パネルのBlackboardセクションは求めている結果を定義可能
- デコレータの詳細パネルのFlow Controlセクションは求めている結果が観察された時、発生することを実行フローの観点から制御可能
- 2つのオプションが有る
- Notify Observer(観察者に通知)
- On Result Change(結果変化時)に通知
- On Value Change(値変化時)
- これはターゲットキーが別のアクタに変わった時に通知される
- Observer aborts(観察者による中止)
- Notifyされた時に中止にする物があればここで設定
- Notify Observer(観察者に通知)
- 2つのオプションが有る
- デコレータに関心があるのは TargetActor のため、Blackboard KeyはTarget Actorを選択する
- 次に必要なのはキーについて何が知りたいのかと言うこと
- これは キークエリ と言われる
- ここで行いたいのはターゲットアクタが設定されたら、Chase Playerを実行したいという事
- キークエリの内容を見ると Is Set となっている。これはセットされているかを問い合わせている為、このままで良い
- Flow Control で観察された時にどうするか設定可能
- Notify Observer は結果が変わった時にという設定になっているのでこのまま
- Observer aborts は結果が変わった時に何かを中止するか?という問でNoneになっているので変更する
- 次に必要なのはキーについて何が知りたいのかと言うこと
- ChasePlayerもRandomWanderもキーの変更によって直ちに中止してもらうため、Observer abortsを bothに設定する
- ツリーを保存して閉じる
ビヘイビアツリーをテストする
- ビヘイビアツリーが複雑になる前にツリー全体の実行フローをたどる最も迅速かつ効果的な方法を知っておく事が重要。
- ゲームプレイデバッガを見てビヘイビアツリーカテゴリを詳しく見ていく
- ツリー内でブレークポイントを使用してステップ実行して何が起きているかを見る方法も紹介する
- 🤔いいですね
- 実行前にプレイヤーがすぐにAIによって発見されないように設置し直す
- プレイする
- ビヘイビアツリーをコンテンツブラウザーから開く
- 上のコンボボックスからAIC_ThirdPersonCharacter を選択する
- ビヘイビアツリーが今何を実行しているのか線がアニメーションされ、把握できるようになっている
- 赤枠の部分ではTargetActorやTargetLocatinなどキーが更新された内容がランタイムで確認できる
ステップ実行してみる
- RandomWanderのWaitを選択しF9を押すとブレークポイントが設定できる
- 次にWait に処理が移った時に一時停止される事が確認できる
- イベントグラフ右上には一時停止中と記載されている
- 右下のブラックボードパネルは更新が停止され同じ値のままになる事も確認できる
- メニューバーの戻るをクリックすると直前のイベントに戻ることもできる
- 🤔ある程度は履歴を記録してるんですね
- 🤔ある程度は履歴を記録してるんですね
- デコレータにマウスカーソルを合わせると失敗している旨表示される
- 失敗しているから現在RandomWanderが実行されていると判断できる
- デバッグ機能を使うと現在実行されているツリーがどのような経緯で来たかが簡単に把握できる
ゲームプレイデバッガーで確認する
- ゲームプレイデバッガを表示させる
- ビヘイビアツリーの結果のみを表示する
- 内容を見るとツリーの経緯が確認できる 0 → 2 → 4(現在)
- 0 Selector
- 2 Chase Player
- 4 Wait
- 右側のAIの頭上付近にはブラックボードの情報が確認できる
- HasLineOfSight は Trueなど
- 内容を見るとツリーの経緯が確認できる 0 → 2 → 4(現在)
🤔便利ですねデバッガ。
最初の EQS クエリを作成する
発見したら向かってくるAIを作れたがもっと複雑なもの。
例えば自身とターゲット間に固定のオブジェクトがあったら隠れようとするなど。
作りたい場合はどうすればよいか?このような複雑な例なら環境クエリシステム(EQS)を使用する必要がある。
少し単純な例でEQSを使っていく。
- AIが望む別のオブジェクトとして食料源アクタを作成する
- レベルに食料源アクタを配置
- EQSシステムを有効にし
- EQSクエリを作成して
- AIがレベル内にある食料源を見つけられるようにする
食料源アクタを作成する
- BlueprintフォルダにEQSという名前のフォルダを作成する
- 作成したフォルダに移動
- Blueprintを作成する。 親: actor 名前: BP_FoodSource
- 🤔動画では FoodSourceという名前でしたが先頭にBP_をつけることにしました
- BP_FoodSource を開く
- キューブコンポーネントを追加 Collisionのプリセットを No Collision にする。Location Z を 70にする
- コンパイルしてから閉じる
- レベルにいくつかランダムに配置する
EQS システムを有効にする
現時点ではまだ実験機能とのこと。
- 有効にするために エディタの環境設定 → 環境クエリシステム を有効化
- エディタの環境設定を閉じる
EQSクエリを作成する
- 環境クエリを作成。 名前: EQS_FindClosestFoodSource とする
- ダブルクリックで開く
- Actor of Class を配置
- 詳細パネルの Searched Actor Classで何を探すかを指定する。ここでは BP_FoodSource を設定
- Generate Only Actor In Radius をオフにする
- これは検索結果を特定の半径に制限するものだが、今はそれを行う理由が無いためオフにする
- このチュートリアルで扱うレベルは小さく全てを容易に見つけられる為。
- このチュートリアルで扱うレベルは小さく全てを容易に見つけられる為。
- クエリの作成は完了だが、見つけた全てを返す様になっているため、最も近いものを1つを返すようにする
- これは検索結果を特定の半径に制限するものだが、今はそれを行う理由が無いためオフにする
- Actor of Class を右クリックし、テストを追加、Distanceを作成
- 詳細パネルより Test Purpose を Score Only。Scoring Factor を -1 にする
- これでより近いものでソートされる状態。未だ複数検出はしている
- セーブして閉じる
作成した環境クエリをビヘイビアツリーから使用する
最も近い食料源の情報を格納するキーをブラックボードに作成する
その後でこのキーを使用してデコレーターを動作させプレイヤーの発見、追跡そして食料の探索を識別可能にする
- BT_EnemyAI を開きブラックボードを表示する
- 新しいキーを作成。 型: オブジェクト 名前: TargetFoodSource
- Base Classを BP_FoodSource にする
- ビヘイビアツリーの画面に切り替える
- 2つのSequenceの間に配置する
- スペースを開け、Sequenceを配置。 名前: Find Food
- Run EQS Query を配置
- 詳細パネルより Query Template で EQS_FindClosestFoodSource を選択。Run ModeがSingle Best Item である事を確認。BlackboardのKeyで TargetFoodSourceを選択
- Single Best Item となっていると一番近いもの1つに絞れる
- これで食料を見つけたらキーを更新するタスクが完了した。
食料アクタに向かって行動するようにする
- Move to を追加して詳細パネルの Blackboard key を TargetFoodSource にする
- Wait を追加し、5秒から1秒にする
飢えに基づいて決定する
食料源に向かうAIはできたが、ここでは食料源を探し求める理由を作成する。
これを行うためにビヘイビアツリーで飢えを表すブラックボードキーを変更するサービスを作成する
- 🤔サービス??初めて出てきましたね。
このサービスを使用して飢えを徐々に上げていく。最後にこの飢えのレベルに応じて既存のシーケンスのどれが実行されるかを制御する
サービスを作成する
- この動画の目的はAIに飢えを表す手段を作成する事
- 上記のため飢えを表すブラックボードキーをまず作成する
- 次に飢えのレベルを変更するサービスを作成する
- BT_EnemyAI を開きブラックボードを表示する
- 新しいキーを作成。 型: Float 名前: Hunger
- ビヘイビアツリーの画面に切り替える
- 新規サービスを作成する
- サービスの編集画面になる。その前にフォルダを整理する
フォルダを整理する
- サービス編集画面を最小化する
- コンテンツブラウザには新しいサービスが作成されている事が確認できる
- 名前を BTS_Hunger にする
- Services という名前のフォルダを作成し、BTS_Hunger を移動させる
- サービスをクリックしてサービスの編集画面に戻る
サービスを編集する
- MyBlueprintパネルの変数から新規変数作成 名前: HungerKey 型: Blackboard Key Selector パブリックにする
- Event Receive Tick AI ノードを配置
- Hunger Key 変数を ctrl を押しながら配置(Getでは位置するため)
- Get Blackboard as Float を配置
- これで値の取得はできる
- 次に行うのはこの値に値を加算する
- Float + Float を配置
- 上記ノードの空いているピンを変数にする。名前: HungerIncreateRate
- 現状のノードセットアップ
- 上記の変数もパブリックにする
- 現状では無限に大きくなるだけなため天井を設ける為 Clamp を使う
- Clamp(float)を配置する
- 次に行うのはこの値をブラックボードへ返すこと
- Hunger Key ノードをコピーしてClamp(float)の後ろの方へ移動させる
- Set Blackboard Value as Float を配置
- Clamp(float)のReturn ValueをSet Blackboard Value as Float に接続する
- 最後に最初に配置したイベントノードの実行ピンを Set Blackboard value as float に接続
- ノードも少し見やすく再配置
新しく追加したサービスをビヘイビアツリーに組み込む
サービスはノードが実行中ずっと実行されている為、ブランチの中でも最上位に配置しなければならない
🤔Windowsにもサービスがありますが、PC起動したらログインしていなくても動作していますね。サービスの位置付けはプロセス以上のなにかなのでしょう。
- BT_EnemyAI を開きビヘイビアツリーを表示する
- Root に接続されている Selector にサービスを追加する
- 詳細パネルで Hunger Key: Hunger, Hunger Increate Rate: 0.1 にする
- Service Intervalはサービスが実行される間隔。1秒にして Random Deviation は0にする
- Random Deviation はきっかり間隔毎に行うのではなく 0.1なら 1~1.1の間隔で行う模様
- Random Deviation はきっかり間隔毎に行うのではなく 0.1なら 1~1.1の間隔で行う模様
Hungerの値に応じて優先順位を変える方法を実装する必要がある。
それはデコレーターを使って実装する
Hungerの値に応じて優先順位を変える方法を実装する
- Find Food にデコレーターを追加する
- 詳細パネルで設定。 Blackboard key: Hunger, Key Value: 1.0, Description: Is Hunger at Maxmum? にする
- Key Value 1.0 にしたことで、1.0になるまでの間、このアクションは取らない
- Key Value 1.0 にしたことで、1.0になるまでの間、このアクションは取らない
- 保存する
プレイして確認する
- その前に自キャラをすぐに見つからない場所に退避させる
- といっても前回やったままだったため何も行わず
- ビヘイビアツリーを表示し、プレイ
- すぐに一時停止ボタンを押して停止
- ビヘイビアツリーで AIキャラクターが選択されている事を確認
- 右下のBlackboardを確認する
- Hunger は0.1 でそこまでお腹が減っていない事がわかる
- そしてビヘイビアツリーを見るとプレイヤーも見つけてない上に食料も探していない為、RandomWanderをしていることがわかる
- 戻るボタンを押す
- どのような経路をたどったかがわかる
- 最初に Chase Player を実行しようとしたが TargetActor がNullのためデコレータによって実行を阻止される
- TargetActor は知覚システムが設定するものだが、自キャラを見ていないため Null のまま
- 次に FindFood シーケンスを試行するが、Hungerが1.0でなければデコレーターは阻止する
- 再開を押す
- Hunger が時間とともに上がっていく様子がわかる
- ここからはいかにシーケンスが変わっていくかを詳細に説明してくれている。
- 🤔これは実際に見たほうがわかりやすいですね
確認作業でHungerは上がりっぱなしで1に到達するとずっと1のままであることがわかった。
これを修正する
Hunger Blackboard key をリセットするタスクを追加する
- 新規タスクを追加する。 BTTask_BlueprintBase
- タスクが作成されエディタ画面になる
フォルダを整理する
- コンテンツブラウザを見るとタスクが作成されているので名前を BTT_SetKeyFloat にする
- Tasksフォルダに格納する
タスクを作成
- フォルダ内の BTT_SetKeyFloat をクリックして編集画面を開く
- MyBlueprintパネルの変数から新規変数作成 名前: FloatKey 型: Blackboard Key Selector パブリックにする
- MyBlueprintパネルの変数から新規変数作成 名前: FloatValue 型: Float パブリックにする
- コンパイルする
- Receive Execute AI イベントを追加
- Float Key変数を Ctrl を押しながらドラッグ
- Float Value 変数を Ctrl を押しながらドラッグ
- Finish Execute を配置。Success にチェックを入れる
- 全てを頭のとおり接続。
- コンパイルして保存
ビヘイビアツリーにタスクを追加
waitまで処理が行われたものは食料への移動が完了し1秒経過していると考えられる為、このとなりにリセットするタスクを追加する。
- wait の隣に追加するため、RandomWanderを少し横にずらす
- BTT_SetKeyFloatノードを配置。詳細パネルのFloat Key はHunger に設定
- 保存して閉じる
プレイしながらビヘイビアツリーを確認すると Hunger は1になるとFind foodが実行され最後に0にリセットされ、再度カウントアップを開始するのがわかる。
2番目のEQSクエリを作成する
どのように使用すればよいかがわかったのでもう少し複雑なタスクを実行する。
- ここではできるだけ近く有効な位置を見つけるクエリを作成する
- 同時にそれらを使用して視線を回避する
- これは後ほど待ち伏せビヘイビアへと発展させるビヘイビアの一種
- EQSクエリ内でユーザー作成コンテキストがどれだけ強力か確認できる
- 新しいクエリを作成する。
- このクエリはプレイヤーキャラクターの位置からいくつかの意思決定を行う必要がある
- しかし現時点ではそれに関する有用なコンテキストがない
- まずコンテキストを作成し
- 新しいEQSクエリに組み込む
- 新しいクエリを作成する。
新しいコンテキストを作成する
- コンテンツブラウザより Blueprintを作成、親クラスは ENVQueryContext_BlueprintBase。
- 名前は EQSC_Player とする
- EQS フォルダ内に Contexts というフォルダを作成する
- Contexts 直下に EQSC_Player を移動させてダブルクリックで開く
コンテキストの目的はプレイヤーを見つけて、それを新しいアクタとして提供する事
- 🤔?
上記の目的のため、既存の関数をオーバーライド(上書き)する
関数をオーバーライドする
- オーバーライドから Provide Single Actor をクリックする
- レベル内のキャラクターを発見する何かを追加する必要がある
- Get Player Character を配置し接続する
- コンパイルし保存し閉じる
これで作成した新しいコンテキストを通じてプレイヤーキャラクターにアクセスできるようになった
🤔何をしたのか全くわからない
新しいクエリを作成する
- EQSフォルダで新しく環境クエリを作成する
- EQS_HideNearPlayer という名前にする
- このクエリの出発点はAIを取り囲む数々のポイント
- 一連のテスト実行後にポイントを減らす
- 最終的にはポイントを一つにする
- 幸いポイントをグリッドにするジェネレータをすでに存在している
- EQS_HideNearPlayerをクリックし編集画面にする
- Root から線をのばし Points: Grid を追加する
- グリッドを調整する。より大きく、よりまばらな方がこのタスクにはあっている.
- 🤔そうなのか?完全に迷子だけど最後に答えはわかるかな
- 詳細パネルからグリッドサイズ、グリッド間スペースを修正
グリッドのフィルタリングをする
グリッドのフィルタリングが開始できるので、移動した時にAIから見える全てのポイントを削除する
- SimpleGrid を選択して右クリックからテストを追加。Trace を追加する
- 詳細パネルから Test Purpose を Filter Only にする(有効ではなく無効なポイントを探すため)
- Context から EQSC_Player を選択する
- プレイヤーに最も近いポイントを選択する為、Distanceテストを追加する
- 詳細パネルから Test Pourpose、Scoreing Factorを変える
- 最も近いものを基準としてスコアが付けられる
- 最後にポイントに到達できるかテストが必要
- 更に PathFinding テストを追加する
- 最後のテストで移動可能なポイント一つが帰ってきているはず
- 保存して閉じる
ビヘイビアツリーに組み込む
- BT_EnemyAI を開きビヘイビアツリーを表示する
- Chase Player にタスクを追加するため横にずらす
- Chase Player という名前を Hide From Player に変える
- 最初に行う必要があるのは、クエリーを実行してプレイヤーから見つからない場所を見つけること
- Run EQS Query を左端に配置する
- もはやアクタの場所を追うのではなく別の場所に移動する為、そのための設定をする
- Run EQS Query を選択し、詳細パネルで設定を行う
- Move to を選択し Blackboard Key を TargetLocation に設定する
- 現状ではデコレータは編集していないため、Hide From Playerが実行されるのはプレイヤーを知覚したときのみ
EQS Testing Pawn を使用する
- EQSクエリ作成時に毎回ゲームを実行する必要があるとすれば、設計時に長い時間がかかる
- UE4には EQS Testing Pawn があり、エディタ内でクエリを素早く繰り返してテストできるようになる
- EQS Testing Pawn を作成して、それを使用して前の手順で作成したEQSクエリの結果を表示しましょう
EQS Testing Pawnを作成してレベルに追加する
- EQSフォルダでブループリントを新規作成する。 親クラス: EQSTestingPawn
- 名前を EQS_MyTestingPawn とする
- ドラッグしてレベルに配置する
- 青い矢印を上にドラッグして十分宙に浮かせたあと、Endキーをおしてきれいに地面に設置するようにする
EQS_FindClosestFoodSource のテストする
- EQS_MyTestingPawn を選択した状態で詳細パネルのEQS の QueryTemplate より EQS_FindClosestFoodSource を選択する
- Pawnが俯瞰的に見やすい位置に視界を移動する
- FoodSource に数字が表示されている事がわかる
- これらの数値は EQS_FindClosestFoodSource クエリによって与えられるスコアを表している
- 最も近い食料は 1 であり、離れているところは 0.24 であることがわかる
- この Pawn を動かすたびにこのスコアは更新される
EQS_HideNearPlayer のテストする
- EQS_HideNearPlayer に変える
- グリッドが表示される
- それぞれのポイントにはスコアがある
- しかし有効なポイントが大量にあり現状は正しい値ではない
- なぜならプレイ中ではなくエディタからクエリを確認しているためとのこと
- 現状ではAIはプレイヤーを探そうとしているが、これを Pawn に変更する必要がある
- Contexts フォルダ内の EQSC_Player を開く
- Alt + クリックで接続を解除する
- 後でもとに戻せるようにノードは消さずに残しておく
- Querier Actor からドラッグして Cast To EQSTestingPawn を配置する
- Cast to … ノードを右クリックし純粋キャストに変更をクリック
- Return node に接続する
- コンパイル+保存して閉じる
- レベルエディタに戻り Pawn を動かすと表示が変わっている
- 青い球は移動先候補ではなく除外されている事を示す
- 緑のポイントはAIが立つことのできる全てのポイントを示している
- 緑の中のどのポイントが実際に選択されるのか?
- 詳細パネルの EQS の Querying Mode を Single Best Item に変更する
- Pawn に最も近いが、死角となっているポイントが選ばれている
- 🤔これスゴ…
- 更に詳細が必要な場合はどうすればよいだろうか?
- 各テストを個別に調べる必要がある場合でもそれは可能
- ここで言う各テストとはEQS_HideNearPlayerにあるテストで、Trace,Distance,PathExistを指す
- テストは3つある、これを個別に行いたい場合 Step to Debug Draw 設定を変える
- 例えば最初の Trace のみ行いたい場合、一番上にあるので 1番でこの1を設定する
- 例えば最初の Trace のみ行いたい場合、一番上にあるので 1番でこの1を設定する
- 赤い部分は1番目のテストである Trace が失敗している事を示す
- 動画ではそのままテストしてこの表示になっていたが、私の場合は Querying Mode を All Matches に変更している
- 2番目のテスト。こちらも動画と異なり動画では Trace → Distance の結果となっているが、私の方は純粋に Distance のみのテスト結果となっている。しかし確認はできるのでこのままにしておく
- Pawn と AI の距離が近いほどスコアが高いことがわかる
変更をもとに戻す
- Contexts フォルダの EQSC_Player を開く
- Cast to.. に置き換えた部分を元のとおりに戻す
- コンパイルして保存する
EQS ゲームプレイ デバッガ
EQSTestingPawnでデバッグができることを見てきたが、ゲームが実行中の場合はどうだろうか?
この動画ではゲームプレイデバッガを使い EQSカテゴリを有効にする
- すぐに見つからないところからプレイ初めてデバッガを表示させる
- EQSのみを有効化する
- Tab を押して Spectator ビューにする
/
を押すとFoodSourceのScoreの一覧などが見える- プレイヤーがAIから見える位置に移動し
*
を押すとクエリの結果が動的に表示される
最後に
ビヘイビアツリーのSelectorとSequencerは特に難しいこともなく、わかりやすい設計ですね。
pluntuml の勉強もしながらブログを書いてるけど、シーケンス図の矢印に書く内容って、目的、手段、データ引き渡し色々あって何を書けばいいのか迷う。
しかしこのコンテンツ日曜日に半分以上進めたと思ってたらあとの動画が長くなっていくというトラップがあった。結果、日曜日は 1/3 程度しか終わらせられていなかったようだ。
気をつけなければ。しかしAIはとてもおもしろい設計。ちゃんと理解すれば必ずゲームプログラミング以外でも流用できる。
自分用メモ(pumlソース)
@startuml start :Find Suitable Locaion 移動先を探す; :Move To 移動先に移動する; :Wait 待機3秒; stop @enduml
@startuml BT -> BTT: Event Triger BTT -> BTT: Get Random Location BTT -> BB: Set Blackboard Value as Vector andom Location BTT -> BT: Finish Execution @enduml
メモ
- 接頭辞
- モジュールという言葉は学習する1セクションを意味している。例:このコンテンツでは最初から2番目に「はじめに」というモジュールがあり、3番目には 「Box-リンクについて」というモジュールがある
- Test の目的
- Score Only 得点だけつける。後で何かしらの優先順位などの選考基準にする
- Filter Only フィルターだけする。リストの中でいらないものを除外または抽出したい
英語メモ
- Spectator 見物人 ゲームプレイデバッガの tab モード
ショートカット
- コンテンツブラウザで ctrl + shift + n で新しいフォルダ作成(Windowsと同じ)