AIで色々悩んでみた:EQS編
EQSは位置情報またはアクタ情報(これらをItemと呼ぶ)を生成し、それらに重み付けを行って最も数値の高いItemの情報をBlackboardに書き込む、ということを行う機能です。書いただけだとなんじゃそら、と思うかも知れませんが…
始めに言っておくことがあります。
EQSは決して難しくありません。
実験的な機能ではありますが、視覚化もしやすいですし、機能の理解自体はそれほど難しくないです。Materialノードなどで数列の扱い方がなんとなくでも分かっていれば、拍子抜けするぐらい簡単に、しかし複雑な挙動をAIにさせることができます。
まずEQSを扱うための下準備をします。EditorPreferences>General>Experimental>AI>EnvironmentQueryingSystemにチェックを入れます。
◎EQSTestingPawn
EQSTestingPawnというPawnをBlueprintから作成してViewportに設置してみましょう。
どうでしょう?まだ何も起こりませんね。これはEQSを視覚的に分かりやすくするためだけのものなのでそれで問題ありません。ゲームにも一切影響は与えないPawnです。次に進みます。
◎EQS
ではEQSを作成しましょう。ContentBrowserを右クリックしてメニューを開き、CreateAdvancedAsset>ArtificialIntelligenceの中身を見るとEnvironmentQueryという項目があります。これを選択することでEQSを作成することができます。
EQSの中身は初期ではこのようになっています。BehaviorTreeのエディタとかなり似ていますね。
◎Generator
見ての通りRootからノードを繋ぐ形式なわけですが、繋げるのはGeneratorsというカテゴリのノードのみとなっています。
これらはItem(位置情報またはアクタ情報)を生成する機能です。試しにですが、個人的には最も分かりやすいPoints:GridをViewport上で視覚化してみましょう。
追加したところです。Detailsは今はそのままで大丈夫です。これを先程Viewport上に配置したEQSTestingPawnのDetails>EQS>QueryTempleteに追加します。
さてViewportを見てみましょう。
EQSTestingPawnを中心に沢山の水色Sphereが生成されています。これがItemsが視覚化されたものです。基本的にPoints:~という名前のGeneratorは位置情報を生成します。それぞれの違いは生成の仕方です。紹介していきます。
・Points:Grid
中心からGridHalfSize*2の大きさの辺を持つ正方形内に、SpaceBetween間隔でグリッド状にItemsを生成します。
・Points:Circle
放射状にItemsを生成します。障害物に当たるとそこで止まります。生成間隔はSpaceBetweenで間隔指定かNumberOfPointsで数指定かPointOnCircleSpacingMethodで選択できます。またArcAngleとArcDirectionの組み合わせで敵などに向かって扇状にItemを飛ばすこともできます。(活用方法はちょっと思いつきませんが…)
Points:Cone
X軸方向に扇状にItemsを生成します。AlignedPointsDistanceで各々の軸(扇で言う所の"骨")に生成するItemsの間隔、ConeDegreeで角度指定、AngleStepで"骨"そのものの数の指定をします。
Points:Donut
ドーナツ状にItemsを生成します。これはパラメーターは比較的分かりやすいですね。UseSpiralPatternはチェックを入れると
こうなります。
・Points:PathingGrid
基本的にはPoints:Gridと変わらないのですが、こちらはNavMeshに沿ってグリッド状に生成します。Points:Gridだと段差のある所などに生成する時、生成されなかったり歪んだ形になってしまったりするのですが、こちらでは比較的綺麗に配置されていることが分かります。ただNavModifierVolumeを使用しても生成をコントロールすることはできないようです。
・CurrentLocation
これはPoints:~という名前は付いていませんが位置情報を扱うGeneratorです。生成の中心位置を返します。それだけです。
アクタ情報を扱うGeneratorは現状一つだけです。
・ActorsOfClass
Radius内のActorの情報を取得します。取得するActorの種類はSearchedActorClassで指定できます。
・Composite
2つ以上のGeneratorを組み合わせる時に使うノードです。Generatorsパラメーターで配列を追加して使用します。
◎Test
現状ではItemsが生成されましたが、これだけでは何の意味もありません。今度はこのItemsに"重み"を付けていきます。それにはTestを使用します。これはBehaviorTreeにおけるDecoratorまたはServiceのように、Generatorにくっつけるような形で機能するノードです。もちろん複数個くっつけることもできます。
いくつか種類が並んでいますが全てについて書いていくと長くなるのでやめにして、ドキュメントに投げます。
EQS ノードのリファレンス:テスト | Unreal Engine ドキュメント
個々のTestについて詳しくは上で見ていただくとして、共通パラメーターで重要なものがいくつかあります。まずはTestPurposeというパラメーターです。
これにはFilterとScore、あるいはその両方を選択するようになっています。
Scoreは言わば"重み付け"です。例えば0~1の間で条件に最も合うItemを1とし、最も合わないItemを0とします。それ以外のものはその間の値で推移します。
Filterは"分類"です。こちらはもっと単純に条件に合うItemは1とし、合わないItemは0とします。
もう一つはScoreカテゴリにあるパラメーター郡です。これはTestの種類やTestPurposeパラメーターによって変更できるものが変わってくるのですが、下の画像はGeneratorはPoints:Cone、Testの種類はDistance、TestPurposeはScoreOnlyで設定した時において表示されるパラメーターです。
ScoringEquationは補完の種類です。今はLinearなので発生源からの距離に対しての線形補完になっています。
ScoringFactorは複数のTest間で重みの調整を行う時に弄ることになります。
では具体的にViewport上でこれはどう見えるのでしょうか?それがこちらです。
見ての通り発生源からの距離に応じて0~1の数値が付与されているのが分かります。最も数値が高いのが扇状の外縁部なので、この場合EQSを呼び出すと外縁のItemsがBlackboardに書き込まれます。
Points:PathingGrid(Generator)とTrace(Test)とFilterOnly(TestPurpose)の組み合わせではこうなります。
赤くなっている部分が"使用可能な"Itemsです。青いItemsはこれ以降強制的に0になり無視されます。
EQSTestingPawnからLineTraceがヒットする部分は青く、ヒットしない部分は赤くなっているのが分かります。これを反転させるにはBoolMatchパラメーターのチェックを外します。
複数のTestを組み合わせると、例えば「プレイヤーから逃げる敵」「プレイヤーから隠れる敵」などを実装できます。もっと工夫すると「プレイヤーが見えている時にプレイヤーから逃げる敵」や「自分から見えている範囲内でプレイヤーから隠れる敵」なども作ることができます。
◎EnvironmentQueryContext
ここまで見てきた中で例えばPoints:GridのGenerateAroundというパラメーターに注目してみましょう。これはその名の通り生成の中心点を決めるパラメーターですが、なんだか見慣れないリストが出てきます。
これはEQS内でのこれらがEnvironmentQueryContext(以下EQC)というシステムによって返された位置あるいはActorの情報で指定されることになっているからです。このパラメーターではそのEQCの種類を指定しています。ここでは2つプリセットとしてEQSが登録されています。EnvQueryContext_QuerierはこのEQSを呼び出した本体の情報を返します。今まで何の気無しにItemsを生成していましたが、常にEQSTestingPawnを中心としてそれらが生成されていたのは、生成位置を決めるパラメーターにこのEQCが初期設定されていたからです。EnvQueryContext_Itemはそのまま各々のItemの情報を返します。しかしこれ以外にもEQCは自作拡張が容易にできます。ではこのEQCをどのように扱うのかを見ていきましょう。
まずはEnvQueryContext_BlueprintBaseというObjectを作成します。
作成した時点で既にEQCのリストには加えられています。早速中身を見てみましょう。
EventGraphには何もありません。このObjectはOverrideすることで活用します。OverrideできるFunctionは以下の通りです。
上からActor型配列、Vector型配列、Actor型単体変数、Vector型単体変数をそれぞれ返すためのFunctionです。いずれもInputノードとReturnノードを内包しており、これを繋げることでEQCを表現します。
例えば、ゲームに存在する全てのActorからPoints:ConeでItemsを生成させたい時はProvideActorSetをこのようにOverrideし…
Points:ConeのCenterActorパラメーターをこのEQCに変更すればそのように動きます(重いのでおすすめはしません)。
◎BehaviorTreeからEQSを呼び出す
さていよいよBehaviorTreeからのEQSを呼び出しを実装します。これにはBehaviorTreeのRunEQSQueryというTaskを使用します。
EQSカテゴリのQueryTemplateパラメーターに作成したEQSを設定します。次にRunModeの設定ですが…まあ書いてある通りなので迷うことはないでしょう。
最後にBlackboardのKeyを設定します。これでこのノードが走ったタイミングでEQSが呼び出され、Blackboardの値が書き換わります。
無事EQSが動作します。以上です。お疲れさまでした。
EQSはEQSTestingPawnのおかげで視覚化しやすく、触った時点では簡単な印象がありましたが、文章にすると結構かかりますね。まあ一つ一つの要素の関係性は分かりやすいのでそんなに難しさは感じないのではないかと思います。
あと書いている内に気付いてしまったんですがGeneratorも自作で作れそうですね…これはもうちょっと暇な時に調べようかな?(^_^;)
ご指摘、ご質問があれば是非お願いいたします。