そうだ、ゲームを作ろう

現状や学んだことなど記録するブログ。間違ってたらごめんね

AIで色々悩んでみた:EQS編

f:id:wvigler:20191114111324p:plain

EQSは位置情報またはアクタ情報(これらをItemと呼ぶ)を生成し、それらに重み付けを行って最も数値の高いItemの情報をBlackboardに書き込む、ということを行う機能です。書いただけだとなんじゃそら、と思うかも知れませんが…

始めに言っておくことがあります。

EQSは決して難しくありません。

実験的な機能ではありますが、視覚化もしやすいですし、機能の理解自体はそれほど難しくないです。Materialノードなどで数列の扱い方がなんとなくでも分かっていれば、拍子抜けするぐらい簡単に、しかし複雑な挙動をAIにさせることができます。

 

まずEQSを扱うための下準備をします。EditorPreferences>General>Experimental>AI>EnvironmentQueryingSystemにチェックを入れます。

f:id:wvigler:20191128140550p:plain

 

◎EQSTestingPawn

EQSTestingPawnというPawnをBlueprintから作成してViewportに設置してみましょう。

f:id:wvigler:20191126175059p:plain

f:id:wvigler:20191126175433p:plain

どうでしょう?まだ何も起こりませんね。これはEQSを視覚的に分かりやすくするためだけのものなのでそれで問題ありません。ゲームにも一切影響は与えないPawnです。次に進みます。

 

◎EQS

ではEQSを作成しましょう。ContentBrowserを右クリックしてメニューを開き、CreateAdvancedAsset>ArtificialIntelligenceの中身を見るとEnvironmentQueryという項目があります。これを選択することでEQSを作成することができます。

f:id:wvigler:20191128141317p:plain

f:id:wvigler:20191128182500p:plain

EQSの中身は初期ではこのようになっています。BehaviorTreeのエディタとかなり似ていますね。

f:id:wvigler:20191128141656p:plain

 

◎Generator

見ての通りRootからノードを繋ぐ形式なわけですが、繋げるのはGeneratorsというカテゴリのノードのみとなっています。

f:id:wvigler:20191128180518p:plain

これらはItem(位置情報またはアクタ情報)を生成する機能です。試しにですが、個人的には最も分かりやすいPoints:GridをViewport上で視覚化してみましょう。

f:id:wvigler:20191128182243p:plain

追加したところです。Detailsは今はそのままで大丈夫です。これを先程Viewport上に配置したEQSTestingPawnのDetails>EQS>QueryTempleteに追加します。

f:id:wvigler:20191128183039p:plain

さてViewportを見てみましょう。

f:id:wvigler:20191128183207p:plain

EQSTestingPawnを中心に沢山の水色Sphereが生成されています。これがItemsが視覚化されたものです。基本的にPoints:~という名前のGeneratorは位置情報を生成します。それぞれの違いは生成の仕方です。紹介していきます。

 

・Points:Grid

f:id:wvigler:20191128182243p:plain

中心からGridHalfSize*2の大きさの辺を持つ正方形内に、SpaceBetween間隔でグリッド状にItemsを生成します。

 

・Points:Circle

f:id:wvigler:20191128190846p:plain

f:id:wvigler:20191128191602p:plain

放射状にItemsを生成します。障害物に当たるとそこで止まります。生成間隔はSpaceBetweenで間隔指定かNumberOfPointsで数指定かPointOnCircleSpacingMethodで選択できます。またArcAngleとArcDirectionの組み合わせで敵などに向かって扇状にItemを飛ばすこともできます。(活用方法はちょっと思いつきませんが…)

 

Points:Cone

f:id:wvigler:20191128191831p:plain

f:id:wvigler:20191128191946p:plain

X軸方向に扇状にItemsを生成します。AlignedPointsDistanceで各々の軸(扇で言う所の"骨")に生成するItemsの間隔、ConeDegreeで角度指定、AngleStepで"骨"そのものの数の指定をします。

 

Points:Donut

f:id:wvigler:20191128193200p:plain

f:id:wvigler:20191128193319p:plain

ドーナツ状にItemsを生成します。これはパラメーターは比較的分かりやすいですね。UseSpiralPatternはチェックを入れると

f:id:wvigler:20191128193558p:plain

こうなります。

 

・Points:PathingGrid

f:id:wvigler:20191128195553p:plain

f:id:wvigler:20191128195649p:plain
基本的にはPoints:Gridと変わらないのですが、こちらはNavMeshに沿ってグリッド状に生成します。Points:Gridだと段差のある所などに生成する時、生成されなかったり歪んだ形になってしまったりするのですが、こちらでは比較的綺麗に配置されていることが分かります。ただNavModifierVolumeを使用しても生成をコントロールすることはできないようです。

 

・CurrentLocation

f:id:wvigler:20191128201638p:plain

これはPoints:~という名前は付いていませんが位置情報を扱うGeneratorです。生成の中心位置を返します。それだけです。

 

アクタ情報を扱うGeneratorは現状一つだけです。

・ActorsOfClass

f:id:wvigler:20191128202404p:plain

f:id:wvigler:20191128202550p:plain

Radius内のActorの情報を取得します。取得するActorの種類はSearchedActorClassで指定できます。

f:id:wvigler:20191128202809p:plain

 

・Composite

f:id:wvigler:20191128230308p:plain

2つ以上のGeneratorを組み合わせる時に使うノードです。Generatorsパラメーターで配列を追加して使用します。

 

◎Test

現状ではItemsが生成されましたが、これだけでは何の意味もありません。今度はこのItemsに"重み"を付けていきます。それにはTestを使用します。これはBehaviorTreeにおけるDecoratorまたはServiceのように、Generatorにくっつけるような形で機能するノードです。もちろん複数個くっつけることもできます。

f:id:wvigler:20191129052552p:plain

いくつか種類が並んでいますが全てについて書いていくと長くなるのでやめにして、ドキュメントに投げます。

EQS ノードのリファレンス:テスト | Unreal Engine ドキュメント

個々のTestについて詳しくは上で見ていただくとして、共通パラメーターで重要なものがいくつかあります。まずはTestPurposeというパラメーターです。

f:id:wvigler:20191129080703p:plain

これにはFilterとScore、あるいはその両方を選択するようになっています。

Scoreは言わば"重み付け"です。例えば0~1の間で条件に最も合うItemを1とし、最も合わないItemを0とします。それ以外のものはその間の値で推移します。

Filterは"分類"です。こちらはもっと単純に条件に合うItemは1とし、合わないItemは0とします。

もう一つはScoreカテゴリにあるパラメーター郡です。これはTestの種類やTestPurposeパラメーターによって変更できるものが変わってくるのですが、下の画像はGeneratorはPoints:Cone、Testの種類はDistance、TestPurposeはScoreOnlyで設定した時において表示されるパラメーターです。

f:id:wvigler:20191129230059p:plain

ScoringEquationは補完の種類です。今はLinearなので発生源からの距離に対しての線形補完になっています。

ScoringFactorは複数のTest間で重みの調整を行う時に弄ることになります。

では具体的にViewport上でこれはどう見えるのでしょうか?それがこちらです。

f:id:wvigler:20191129230434p:plain

見ての通り発生源からの距離に応じて0~1の数値が付与されているのが分かります。最も数値が高いのが扇状の外縁部なので、この場合EQSを呼び出すと外縁のItemsがBlackboardに書き込まれます。

Points:PathingGrid(Generator)とTrace(Test)とFilterOnly(TestPurpose)の組み合わせではこうなります。

f:id:wvigler:20191129231738p:plain

赤くなっている部分が"使用可能な"Itemsです。青いItemsはこれ以降強制的に0になり無視されます。

EQSTestingPawnからLineTraceがヒットする部分は青く、ヒットしない部分は赤くなっているのが分かります。これを反転させるにはBoolMatchパラメーターのチェックを外します。

f:id:wvigler:20191129234340p:plain

f:id:wvigler:20191129231420p:plain

複数のTestを組み合わせると、例えば「プレイヤーから逃げる敵」「プレイヤーから隠れる敵」などを実装できます。もっと工夫すると「プレイヤーが見えている時にプレイヤーから逃げる敵」や「自分から見えている範囲内でプレイヤーから隠れる敵」なども作ることができます。

 

◎EnvironmentQueryContext

ここまで見てきた中で例えばPoints:GridのGenerateAroundというパラメーターに注目してみましょう。これはその名の通り生成の中心点を決めるパラメーターですが、なんだか見慣れないリストが出てきます。

f:id:wvigler:20191128203401p:plain

これはEQS内でのこれらがEnvironmentQueryContext(以下EQC)というシステムによって返された位置あるいはActorの情報で指定されることになっているからです。このパラメーターではそのEQCの種類を指定しています。ここでは2つプリセットとしてEQSが登録されています。EnvQueryContext_QuerierはこのEQSを呼び出した本体の情報を返します。今まで何の気無しにItemsを生成していましたが、常にEQSTestingPawnを中心としてそれらが生成されていたのは、生成位置を決めるパラメーターにこのEQCが初期設定されていたからです。EnvQueryContext_Itemはそのまま各々のItemの情報を返します。しかしこれ以外にもEQCは自作拡張が容易にできます。ではこのEQCをどのように扱うのかを見ていきましょう。

まずはEnvQueryContext_BlueprintBaseというObjectを作成します。f:id:wvigler:20191128230624p:plain

f:id:wvigler:20191128231536p:plain

作成した時点で既にEQCのリストには加えられています。早速中身を見てみましょう。

EventGraphには何もありません。このObjectはOverrideすることで活用します。OverrideできるFunctionは以下の通りです。

f:id:wvigler:20191129022938p:plain

 上からActor型配列、Vector型配列、Actor型単体変数、Vector型単体変数をそれぞれ返すためのFunctionです。いずれもInputノードとReturnノードを内包しており、これを繋げることでEQCを表現します。

例えば、ゲームに存在する全てのActorからPoints:ConeでItemsを生成させたい時はProvideActorSetをこのようにOverrideし…

f:id:wvigler:20191129023906p:plain

Points:ConeのCenterActorパラメーターをこのEQCに変更すればそのように動きます(重いのでおすすめはしません)。

 

◎BehaviorTreeからEQSを呼び出す

 さていよいよBehaviorTreeからのEQSを呼び出しを実装します。これにはBehaviorTreeのRunEQSQueryというTaskを使用します。

f:id:wvigler:20191130020528p:plain

EQSカテゴリのQueryTemplateパラメーターに作成したEQSを設定します。次にRunModeの設定ですが…まあ書いてある通りなので迷うことはないでしょう。

f:id:wvigler:20191130020943p:plain

最後にBlackboardのKeyを設定します。これでこのノードが走ったタイミングでEQSが呼び出され、Blackboardの値が書き換わります。

f:id:wvigler:20191130021300p:plain

無事EQSが動作します。以上です。お疲れさまでした。

 

EQSはEQSTestingPawnのおかげで視覚化しやすく、触った時点では簡単な印象がありましたが、文章にすると結構かかりますね。まあ一つ一つの要素の関係性は分かりやすいのでそんなに難しさは感じないのではないかと思います。

あと書いている内に気付いてしまったんですがGeneratorも自作で作れそうですね…これはもうちょっと暇な時に調べようかな?(^_^;)

 

ご指摘、ご質問があれば是非お願いいたします。