[UE4] AssetTypeをつくる

[コンテンツアセットタイプ](https://docs.unrealengine.com/4.26/ja/WorkingWithContent/Types/)とはテクスチャやスタティックメッシュなど、エディタ上で作成できるオブジェクトで、必ずしもレベルに配置せずともよく、エディター内で静的なインスタンスをつくることができるというものです。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/2d2f6de7-6699-7c50-8100-690b36f4b395.gif)


ここでは、独自のコンテンツアセットタイプを作成する最小構成のみの手順だけまとめます。Pluginとして実装する前提ですが、プロジェクト内でも同様のはずです。
本記事は、Gerke Max Preussner氏の [Adding New Asset Types to UE4](https://gmpreussner.com/reference/adding-new-asset-types-to-ue4) を参考にしています。実際のコードも氏の[Githubレポ](https://github.com/ue4plugins/TextAsset)を参考にするとより実用の理解が進むはずです。

https://gmpreussner.com/reference/adding-new-asset-types-to-ue4

https://github.com/ue4plugins/TextAsset


執筆時のバージョンは `4.27.0` です。

# 1. RuntimeモジュールとEditorモジュールを用意する

Runtimeモジュールに加えてEditorモジュールも用意します。モジュールを分けなくても動作しますが、慣例に従いEditorモジュールを作成したほうが良いでしょう。配布時の容量や動作の効率性など恩恵が多いはずです。

最初に、Plugins > New Plugin から空のプラグイン（ここでは便宜上 "TestPlugin" と命名）を作成します。

![newplugin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/38cbc247-7d93-53a6-3311-61453b2e0c07.png)


エクスプローラー上でプラグインモジュール（ Plugins / TestPlugin / Source 配下のディレクトリ）をコピペしつつ、設定ファイルやビルドスクリプトを書き換えます。

```
/TestPlugin
  ...
  /Source
    /TestPlugin
      /Private
        TestPlugin.cpp
      /Public
        TestPlugin.cpp
      TestPlugin.Build.cs
    /TestPluginEditor         <---- TestPluginをコピペしてディレクトリ名とファイル名をリネーム
      /Private
        TestPluginEditor.cpp
      /Public
        TestPluginEditor.h
      TestPlugin.Build.cs
  TestPlugin.uplugin
```

`.uplugin`ファイルのモジュールにEditorモジュールを追加します。

```json:TestPlugin.uplugin
{
	...
	"Modules" :
	[
		{
			"Name" : "TestPlugin",
			"Type" : "Runtime",
			"LoadingPhase" : "Default"
		},
		{
			"Name" : "TestPluginEditor",
			"Type" : "Editor",
			"LoadingPhase" : "Default"
		}
	]
}
```

```C#:Source/TestPlugin/TestPlugin.Build.cs
namespace UnrealBuildTool.Rules
{
	public class TestPlugin : ModuleRules
	{
		public TestPlugin(ReadOnlyTargetRules Target) : base(Target)
		{
			PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

			PublicDependencyModuleNames.AddRange(
				new string[] {
					"Core",
					"CoreUObject",
				});
		}
	}
}
```

Editorモジュール用のビルドスクリプトのネームスペースやクラス名、依存先を書き換えます。`TestPlugin`を`TestPluginEditor`と書き換えます。

```C#:Source/TestPluginEditor/TestPluginEditor.Build.cs
using UnrealBuildTool;

public class TestPluginEditor : ModuleRules
{
	public TestPluginEditor(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				"CoreUObject",
				"TestPlugin", // アセットを定義するRuntimeモジュールを依存先に追加
				"UnrealEd", // Editor用のモジュールを依存先に追加
			});
	}
}
```
デフォルトで配置されているモジュール自体の定義クラスもリネームしておきます。`TestPlugin`を`TestPluginEditor`と書き換えます。

```C++:Source/TestPluginEditor/Public/TestPluginEditor.h
#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FTestPluginEditorModule : public IModuleInterface
{
public:

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
};
```
```C++:Source/TestPluginEditor/Private/TestPluginEditor.cpp
#include "TestPluginEditor.h"
#define LOCTEXT_NAMESPACE "FMTestluginEditorModule"

void FTestPluginEditorModule::StartupModule()
{}

void FTestPluginEditorModule::ShutdownModule()
{}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FTestPluginEditorModule, TestPluginEditor)
```

# 2. UObject派生のクラスを定義する

AssetTypeとなる `UObject` 派生クラスを、Runtimeモジュール配下に、`Public`として定義します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/e221fff4-f210-348d-4a03-fcb76fd33036.png)

パラメータは任意です。確認用に複数作って置くと良いでしょう。

```C++:Source/TestPlugin/Public/UserDefinedObject.h
#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "UserDefinedAsset.generated.h"

UCLASS(BlueprintType)
class TESTPLUGIN_API UUserDefinedAsset : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere)
	int32 IntParam;

	UPROPERTY(EditAnywhere)
	FText TextParam;
};
```

この時点でビルドしてみると、C++クラスはつくられていることが確認できますが、AssetTypeとしてエディター内インスタンスを作成することはできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/a7afebf6-2665-693b-041b-68e09f76dac6.png)


# 3. AssetFactoryクラスを実装する

`UFactory`を継承するオブジェクトをEditorモジュール配下に`Private`で実装します。
ファクトリとは、インスタンス生成時の処理をテンプレートをもとにして切り分けることで、柔軟なクラス定義を可能にするというデザインパターンに則った概念です。つまり、当該アセットのインスタンス生成時の振る舞いをお作法に則って定義するということです。

```C++:Source/TestPluginEditor/Private/UserDefinedAssetFactory.h
#pragma once

#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "UserDefinedAssetFactory.generated.h"

UCLASS()
class UUserDefinedAssetFactory : public UFactory
{
	GENERATED_UCLASS_BODY()

public:
	virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
	virtual bool ShouldShowInNewMenu() const override;
};

```

ファクトリの実装内容としては、

* コンストラクター内でメンバー変数である`SupportedClass`に、先程作ったUObject派生クラスの`StaticClass`を代入します。
* `FactoryCreateNew`関数をオーバーライドして、先程作ったUObject派生クラスのインスタンスを返すようにします。
* `ShouldShowInMenu`をオーバーライドして、`True`を返すようにすることで、アセットメニューの表示を許可します。

```C++:Source/TestPluginEditor/Private/UserDefinedAssetFactory.cpp
#include "UserDefinedAssetFactory.h"
#include "UserDefinedAsset.h"

UUserDefinedAssetFactory::UUserDefinedAssetFactory(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SupportedClass = UUserDefinedAsset::StaticClass();
	bCreateNew = true;
	bEditAfterNew = true;
}

UObject* UUserDefinedAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	return NewObject<UUserDefinedAsset>(InParent, InClass, InName, Flags);
}

bool UUserDefinedAssetFactory::ShouldShowInNewMenu() const
{
    return true;
}
```

これでもまだアセットタイプの実装は完了していません。

# 4. AssetTypeActionsをつくり、登録する

`FAssetTypeActions_Base`を継承するクラスをオブジェクトを作成します。エディター内でインスタンス生成される際のエディターの挙動をより具体的に定義することができます。
エディター内インスタンスのアイコンや色、アセットタイプのカテゴリーなどを指定します。

`OpenAssetEditor`や`GetActions`といった関数をコメントアウトしていますが、これらをオーバーライドすると、アセットエディターのインターフェースや、ファイルをドラッグするインスタンス生成など高度な定義ができますが、非常に長くなりそうなので今回はそれらを扱いません。代わりに、デフォルトの（スーパークラス）の関数をそのまま使います。


```C++:Source/TestPluginEditor/Private/TestPluginActions.h
#pragma once

#include "AssetTypeActions_Base.h"

class FTestPluginActions : public FAssetTypeActions_Base
{
public:

	virtual FText GetName() const override;
	virtual FColor GetTypeColor() const override;
	virtual UClass* GetSupportedClass() const override;
	virtual uint32 GetCategories() override;

	//virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
	//virtual void GetActions(const TArray<UObject*>&InObjects, FMenuBuilder & MenuBuilder) override;
	//virtual bool HasActions(const TArray<UObject*>& InObjects) const override;

private:
};
```

ここでは、

* AssetActionの名称と、適用対象のクラス（`UUserDefinedAsset`）を設定
* アセットタイプのアイコンの色を白
* カテゴリーは`Media`配下

としています。

```C++:Source/TestPluginEditor/Private/TestPluginActions.cpp
#include "TestPluginActions.h"
#include "UserDefinedAsset.h"

FText FTestPluginActions::GetName() const
{
	return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_TestPlugin", "Test Plugin");
}

FColor FTestPluginActions::GetTypeColor() const
{
	return FColor::White;
}

UClass* FTestPluginActions::GetSupportedClass() const
{
	return UUserDefinedAsset::StaticClass();
}

uint32 FTestPluginActions::GetCategories()
{
	return EAssetTypeCategories::Media;
}
```

最後にAssetTypeActionsを登録します。`UFactory`など`UObject`派生クラスのエンジンへの登録は暗黙的におこなれますが、それ以外は、明示的にコードを書かないといけなかったりする罠があったりします。`AssetTypeActions`も`AssetTools`に登録してあげる必要があるので、モジュールの定義クラスにコードを追加します。


```C++:Source/TestPluginEditor/Public/TestPluginEditor.h
#include "TestPluginEditor.h"
#include "Modules/ModuleInterface.h"
#include "TestPluginActions.h"

#define LOCTEXT_NAMESPACE "FTestPluginEditorModule"

void FTestPluginEditorModule::StartupModule()
{
	IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
	AssetTools.RegisterAssetTypeActions(MakeShareable(new FTestPluginActions()));
}

void FTestPluginEditorModule::ShutdownModule()
{}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FTestPluginEditorModule, TestPluginEditor)
```

以上で、最小構成でのアセットタイプの作成が完了しました。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76966/2d2f6de7-6699-7c50-8100-690b36f4b395.gif)

`AssetTypeActions`の`OpenAssetEditor`をオーバーライドをしていないので、デフォルト表示のアセットエディタが開かれます。アセットエディタのカスタマイズ自体はまた別に扱いたいと思います。