UE4 EQSをC++から直接実行する

# 概要
UnrealEngine4 の Environment Query System(EQS)をC++から直接実行するためのメモです。
Behavior Tree は使用しません。


## 参考
以下の記事を参考にいたしました、ありがとうございます。

[公式：Environment Query System ユーザー ガイド](https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/ArtificialIntelligence/EQS/EQSUserGuide/)
[UE4 Document@com04 EQS](http://com04.sakura.ne.jp/unreal/wiki/index.php?EQS#v4c29ce7)
[AIで色々悩んでみた：EQS編](https://wvigler.hatenablog.com/entry/2019/11/30/022039)

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.26.2

# 関連ソース
Engine\Source\Runtime\AIModule\Classes\EnvironmentQuery\EnvQueryManager.h
Engine\Source\Runtime\AIModule\Classes\EnvironmentQuery\EnvQueryTypes.h
Engine\Source\Runtime\AIModule\Classes\EnvironmentQuery\EnvQuery.h


# 準備
現時点で公式マニュアルでは「実験的」となっていますが、エンジンプラグインとしてデフォルトで使用できるようです。

## 環境クエリを作る
コンテンツブラウザから右クリック -> AI -> 環境クエリ で作成します。

ジェネレータとテストを適当に。
![envquery00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f11afb82-d817-b00c-f344-1e30c1420ea3.png)

TestingPawn 経由でエディタで見るとこんな感じです。
![envquery01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c2ed8565-f8d5-fe7b-6a5f-22cefa77eb1f.png)


## モジュールの設定
C++で扱うためにモジュールの追加設定をします。
`GameplayTasks`と`AIModule`の追加が必要です。

```cs:[Project].Build.cs
	PublicDependencyModuleNames.AddRange(
		new string[] {
			(省略)
			"GameplayTasks",	// ←追加
			"AIModule",			// ←追加
	});
```


# 関連クラス/データ型
## UEnvQuery
環境クエリのデータ型。`UDataAsset` から継承されています。

## FEnvQueryRequest
環境クエリを実行するためのラッパー。
これを経由して実行をします。ジェネレータへ渡すパラメータもこれを経由します。

## FEnvQueryResult
結果を受け取るためのデータ型。ジェネレートしたアクター（アイテム）の情報がソートされて保持される模様。


# C++からのEQS呼び出し
## アクターコード
該当アクターに環境クエリデータを持たせます。アクターに参照を保持してUEエディターで設定をします。
次に実行用メソッドと結果受け取り用のデリゲートオブジェクトを用意します。


```cpp:MyCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "EnvironmentQuery/EnvQuery.h"

class UEnvQuery;

UCLASS(config=Game)
class AMyCharacter : public ACharacter
{
	GENERATED_BODY()

	// ...省略...

public:
	// 環境クエリ
	UPROPERTY(EditAnywhere, Category = "AI", meta = (ToolTip="環境クエリ"))
		UEnvQuery*	MyEnvQuery = nullptr;

	// クエリ実行
	UFUNCTION(BlueprintCallable, meta = (ToolTip="クエリ実行メソッド"))
	void ExecuteMyEnvQuery();

	// 結果受け取り
	void MyEnvQueryResult(TSharedPtr<FEnvQueryResult> _Result);

};
```

```cpp:cpp:MyCharacter.cpp
#include "MyCharacter.h"
#include "EnvironmentQuery/EnvQueryManager.h"


// クエリ実行
void AMyCharacter::ExecuteMyEnvQuery()
{
	FEnvQueryRequest _MyQueryRequest = FEnvQueryRequest(MyEnvQuery, this);

	// パラメータセット（ジェネレーターの該当パラメータをQueryParamsでバインド）
	_MyQueryRequest.SetFloatParam(FName(TEXT("SimpleGrid.GridSize")), 500.0f);
	_MyQueryRequest.SetFloatParam(FName(TEXT("SimpleGrid.SpaceBetween")), 200.0f);

	_MyQueryRequest.Execute(EEnvQueryRunMode::SingleResult, this, &AMyCharacter::MyEnvQueryResult);
}

// 結果受け取り
void AMyCharacter::MyEnvQueryResult(TSharedPtr<FEnvQueryResult> _Result)
{
	if (_Result->IsSuccsessful()) {
		// リザルトの中身を表示
		const int32	_ItemNum = _Result->Items.Num();
		for(int32 _Lp=0;_Lp<_ItemNum;_Lp++){
			FVector _Loc = _Result->GetItemAsLocation(_Lp);
			float	_Score = _Result->GetItemScore(_Lp);
			
			UE_LOG(LogTemp, Log, TEXT("%d, %.2f, (%.2f, %.2f)"), _Lp, _Score, _Loc.X, _Loc.Y );
		}
	}
}

```

クエリ実行時のパラメータは BehaviorTree で使用可能なものと同様に `SingleResult` `RandomBest5Pct` `RandomBest25Pct` `AllMatching` とありますが、それとは関係なく、`FEnvQueryResult`で受け取った結果はスコア順でソートされているようです。


## UEエディタ設定
アクターのパラメータに作成した環境クエリを設定し、実行用メソッド `ExecuteMyEnvQuery` の呼び出しコードを用意します。（適当にキーボードから呼び出し）

![example00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2e05b03f-d735-b241-9dff-89cb85b8765c.png)


## 実行結果
`FEnvQueryResult` で受け取ったデータが表示されます。
実行時に `EEnvQueryRunMode::SingleResult` で結果を1つで指定していますが、中身はソートされてすべて持っているようです。

```text:アウトプットログ
LogTemp: 0, 1.00, (-751.00, -299.00)
LogTemp: 1, 1.00, (-751.00, 101.00)
LogTemp: 2, 1.00, (-551.00, -499.00)
LogTemp: 3, 1.00, (-551.00, 301.00)
LogTemp: 4, 1.00, (-151.00, -499.00)
...省略
LogTemp: 32, 0.00, (249.00, -99.00)
LogTemp: 33, 0.00, (249.00, 101.00)
LogTemp: 34, 0.00, (249.00, 301.00)
LogTemp: 35, 0.00, (249.00, 501.00)

```


## パラメータの受け渡し
クエリ実行時に以下のようにパラメータを渡しています。

```cpp:パラメータの設定
_MyQueryRequest.SetFloatParam(FName(TEXT("SimpleGrid.GridSize")), 500.0f);
_MyQueryRequest.SetFloatParam(FName(TEXT("SimpleGrid.SpaceBetween")), 200.0f);
```

受け取るためには実行するEQSのジェネレータの `DataBinding`を`QueryParams`に変更し、パラメータ名を正しく設定する必要があります。
![example01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/7ef9156d-9766-c31e-adfc-1fcf0344ce81.png)



# まとめ
公式ページの「ネイティブ コードで EQS を使用する」を元に試してみた結果です。
EQSはAIとセットになっていますが、実際はAIに限らずに使うことができるため利用範囲が広がるのではないかと思います。