UE4のAdvancedSearchを活用しよう

#はじめに
UE4には[AdvancedSearch（詳細検索）](https://docs.unrealengine.com/ja/Basics/ContentBrowser/AdvancedSearchSyntax/index.html)というものがあります。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/efe81f36-dde8-f9f1-1302-61290dc2508c.png)
コンテンツブラウザでアセット名ではなくそのアセットが持つメタデータで検索をかけることができる機能です。
メタデータというのはコンテンツブラウザでアセットにオンカーソルしたときに見られる情報のことです。
![1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/3654b84a-0626-4e9c-5dda-d21cf32d09f0.png)
[こちらのドキュメント](https://docs.unrealengine.com/ja/Basics/ContentBrowser/AdvancedSearchSyntax/index.html)にも書いてある通り、デフォルトで様々なメタデータがありANDやORなどを使うことでより自由度の高い検索を行うことができます。
エディタ上での検索方法などは[こちらのドキュメント](https://docs.unrealengine.com/ja/Basics/ContentBrowser/AdvancedSearchSyntax/index.html)で詳しく解説されているため、この記事では独自のメタデータを追加する方法をご紹介します。

#つくってみる
今回は`UDataAsset`を継承した独自のデータアセットでメタデータを追加してみます。
`UObject`を継承しているクラスであれば同様の方法で追加できます。

[TestDataAsset.h
](https://github.com/Naotsun19B/AdvancedSearchTest/blob/0c66b2832435c10497efc774eddbc23a10c08c1e/Source/AdvancedSearchTest/TestDataAsset.h)

``` TestDataAsset.h
UCLASS()
class ADVANCEDSEARCHTEST_API UTestDataAsset : public UDataAsset
{
	GENERATED_BODY()

public:
	// UObject interface.
	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
	// End of UObject interface.
	
protected:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FName CharacterName;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	int32 TeamId;
};
```
一先ず、ゲームに登場するキャラクターのデータアセットだと想定してキャラクター名前とチームのIDを定義してみます。
次に、`UObject::GetAssetRegistryTags`をオーバーライドします。この関数の`OutTags`に追加されたデータがメタデータになります。

[TestDataAsset.cpp](https://github.com/Naotsun19B/AdvancedSearchTest/blob/master/Source/AdvancedSearchTest/TestDataAsset.cpp)

``` TestDataAsset.cpp
void UTestDataAsset::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
	Super::GetAssetRegistryTags(OutTags);

	// キャラの名前を追加.
	OutTags.Add(FAssetRegistryTag(
		GET_MEMBER_NAME_CHECKED(UTestDataAsset, CharacterName),
		CharacterName.ToString(),
		FAssetRegistryTag::ETagType::TT_Alphabetical,
		FAssetRegistryTag::ETagDisplay::TD_None
	));
	
	// チームIDを追加.
	OutTags.Add(FAssetRegistryTag(
		GET_MEMBER_NAME_CHECKED(UTestDataAsset, TeamId),
		FString::FromInt(TeamId),
		FAssetRegistryTag::ETagType::TT_Numerical,
		FAssetRegistryTag::ETagDisplay::TD_None
	));
}
```

`OutTags`に追加したいデータを追加していうわけですが、この変数の型は`FAssetRegistryTag`の配列で、`FAssetRegistryTag`のコンストラクタはこのようになっており、

``` Object.h
	FAssetRegistryTag(FName InName, const FString& InValue, ETagType InType, uint32 InDisplayFlags = TD_None)
			: Name(InName), Value(InValue), Type(InType), DisplayFlags(InDisplayFlags) {}
```

それぞれの説明は以下の通りです。

|パラメータ|詳細||
|---|---|---|
|InName|メタデータの名前を指定します。オンカーソルした時に表示されるUIで:の左側に表示されます|
|InValue|メタデータの値を指定します。オンカーソルした時に表示されるUIで:の右側に表示されます。||
|InType|メタデータの種類を定義します。||
||ETagType::TT_Hidden|これを指定するとオンカーソルした時のUIで表示されません。|
||ETagType::TT_Alphabetical|これを指定するとアルファベット順でソートされます。|
||ETagType::TT_Numerical|データが数値の場合これを指定します。|
||ETagType::TT_Dimensional|xで区切る次元表示の場合これを指定します。(2x2のような感じ)|
||ETagType::TT_Chronological|`FDateTime::ToString`から作成した文字列の場合にこれを指定します。|
|InDisplayFlags|オンカーソルしたUI上で特殊な表示方法を使いたい場合に指定します。||
||ETagDisplay::TD_None|特殊な表示方法を使わない場合はこれを指定します。(基本これ)|
||ETagDisplay::TD_Date|`TagType`が`ETagType::TT_Chronological`の時に日にちを表示したい場合はこれを指定します。(30/4/21のような感じ)|
||ETagDisplay::TD_Time|`TagType`が`ETagType::TT_Chronological`の時に時間を表示したい場合はこれを指定します。(3:58 AMのような感じでもしかしたら[MDT 米国山岳部標準時(夏時間) UTC-0600 ](https://www.jisakeisan.com/?y=2021&m=4&d=30&hh=19&mm=0&t1=jst&t2=mdt)固定で表示されるかも？)|
||ETagDisplay::TD_InvariantTz|`TagType`が`ETagType::TT_Chronological`の時にタイムスタンプを不変のタイムゾーンを使用して表示する場合はこれを指定します。(使用してみたところそもそも表示されませんでした...)|
||ETagDisplay::TD_Memory|`TagType`が`ETagType::TT_Numerical`で`FText::AsMemory`から取得した場合に使用可能です。バイト数を表示するものだと思われます。|

#おまけ
上記の方法で`UObject`を継承したアセットに独自のメタデータを追加できることがわかりました。
しかしBlueprintアセットの場合、コンテンツブラウザ上にあるのはそのクラスのオブジェクトではなく`UBlueprint`型のブループリントアセットのオブジェクトなため上記の方法が使えないのではないかと思い少し調べてみました。

``` UBlueprint.cpp
void UBlueprint::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
	// We use Generated instead of Skeleton because the CDO data is more accurate on Generated
	if (GeneratedClass)
	{
		if (UObject* CDO = GeneratedClass->GetDefaultObject())
		{
			CDO->GetAssetRegistryTags(OutTags);
		}
	}
```
`UBlueprint::GetAssetRegistryTags`では設定されているクラスのデフォルトオブジェクトの`GetAssetRegistryTags`を呼んでいるため、`UObject`を継承したものと同様の方法で独自のメタデータを追加できると思い試してみたところ、オンカーソルしたUI上では確認できませんでしたが検索では指定できたため追加はできるようでした。(どこかでデフォルトオブジェクトのメタデータを明示的に非表示にしているのかもしれない...)

[TestObject.h](https://github.com/Naotsun19B/AdvancedSearchTest/blob/master/Source/AdvancedSearchTest/TestObject.h)

``` TestObject.h
UCLASS(Blueprintable)
class ADVANCEDSEARCHTEST_API UTestObject : public UObject
{
	GENERATED_BODY()

public:
	// UObject interface.
	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
	// End of UObject interface.
	
private:
	UPROPERTY(EditDefaultsOnly)
	TMap<FName, FString> UserDefinedTags;

	UPROPERTY(EditDefaultsOnly)
	EObjectCategory ObjectCategory;
};
```
[TestObject.cpp](https://github.com/Naotsun19B/AdvancedSearchTest/blob/master/Source/AdvancedSearchTest/TestObject.cpp)

``` TestObject.cpp
void UTestObject::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
	Super::GetAssetRegistryTags(OutTags);

	FString ObjectCategoryString = FString(FName(NAME_None).ToString());
	{
		if (UEnum* EnumPtr = StaticEnum<EObjectCategory>())
		{
			ObjectCategoryString = EnumPtr->GetValueAsString(ObjectCategory);
		}	
	}
	
	// オブジェクトのカテゴリを追加.
	OutTags.Add(FAssetRegistryTag(
		GET_MEMBER_NAME_CHECKED(UTestObject, ObjectCategory),
		ObjectCategoryString,
		FAssetRegistryTag::ETagType::TT_Alphabetical,
		FAssetRegistryTag::ETagDisplay::TD_None
	));
	
	for (const auto& UserDefinedTag : UserDefinedTags)
	{
		const FAssetRegistryTag::ETagType TagType =
			UserDefinedTag.Value.IsNumeric() ?
			FAssetRegistryTag::ETagType::TT_Numerical :
			FAssetRegistryTag::ETagType::TT_Alphabetical;
		
		
		OutTags.Add(FAssetRegistryTag(
			UserDefinedTag.Key,
			UserDefinedTag.Value,
			TagType,
			FAssetRegistryTag::ETagDisplay::TD_None
		));
	}
}
```
![3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/008d6653-e64e-1d89-dfda-076db1762fc3.png)
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/81047001-df46-1798-4e1a-f99fd4b6ae62.png)
また、`TMap`を使ってメタデータの名前と値を自由に設定することもできるようです。

#おわりに
商用タイトルの開発などでは、キャラクター名ではなく開発IDで管理されデータアセットなどの名前もファイルパスを短くする目的や解析対策として開発IDになっていることが多いと思います。
キャラクターの名前などを独自のメタデータとして追加することでAdvancedSearchを使ってキャラクターの名前などで検索できるため、アセットの検索をする効率がかなりアップするかと思います。

この記事で紹介したプロジェクトは以下でダウンロードできます。
https://github.com/Naotsun19B/AdvancedSearchTest