4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【UE4】独自アセット実装マニュアル(前編)

Last updated at Posted at 2021-12-23

#はじめに
本記事は、筆者がUE4を用いて独自のアセット及びそのエディタを組んでいた際のメモを、マニュアルとしてまとめ直したものです。
その際、最も参考になったのは @ayumu_nagamatsu さんと @negimochi さんの記事でした。

しかし UE4 におけるアセットやエディタ周りに関しては Unity ほど簡単ではなく、文献も少なく、自身で機能を実装するまではかなり大変でした。
どこかで手順を取りまとめておいたほうがよさそうであると考え、本記事を執筆した次第です。
内容としては前述のお二方の内容をベースにミックスして拡張し、詳細手順を記載したものとなります。

記事は前編と後編に別れています。
前編である本記事では、独自アセットの作成手順を記載します。
後編ではそのアセットの持つプロパティをカスタマイズし、独自のエディタを作成する手順を記載します。
基本的にはデフォルトのエディタが自動で組まれるため、本記事である前編だけで一応完結します。

本記事はサンプルを作成していくマニュアルという体であるため、「○○を参考にしてください」と書いてリンク先を貼る、といったことは極力しないようにしています。
代わりに巻末に参考にさせていただいたページをリンク集としてまとめていますので、興味が湧いたらぜひ閲覧ください。
本記事を通してすべての項目を実装しきると手元にサンプルコードが出来上がる状態を想定していますので、ソースコードを Github などで公開もしていません。ご了承ください。

要素が複合的であることから、できる限り情報を集約することを試みています。
そのため、説明が非常に長く下手な部分があると思います。
筆者自身もまだ UE4 をさわり始めて1ヶ月半程度のビギナーですので、間違いなどがあればお気軽にご指摘ください。

##バージョン表記
UE4 のバージョンは 4.27.2 を使用しています。
Visual Studio は VS2019 を使用しています。
それぞれバージョンによって手順が変わることがあります。

また、UE4 の言語設定は英語です。
日本語版を使用されている方は適宜読み替えていただければと思います。

##対象読者

  • UE4 を用いて、自身で定義したアセットを作って制御したい方。
  • 制御だけでなく、エディタのカスタマイズも行いたい方。
  • 文章を読むより手を動かしたほうが理解できる方。(筆者もこのタイプです)

##前提知識・準備
UE4 及び UE4 に Visual Studio を連動させて動かせる状態になっていること。
また、C++、及び UE4 と UE4 のプラグイン、モジュールについてある程度の知識を必要とします。
デリゲートについては特に説明はしませんのでご了承ください。

掲載しているマニュアルは Blank プロジェクト上で行うのを推奨します。
(サンプルプログラムを作りあげるため、他のプロジェクト上だと邪魔になると思います)
Visual Studio からビルドし、DebugGame Editor または Development Editor で Unreal Editor を実行できるようにしてください。

Unreal Editor 上からソースコードの追加を行う場合、Visual Studio から実行した Unreal Editor 上で追加しないようご注意ください。

モジュールについては、以下が把握・理解できていれば大丈夫です。

  • 1モジュール == 1DLL ファイルとなる
  • 通常ゲームでも使用可能だが、主にプラグイン作成・設計時に考慮すべき内容となる
  • 指定した Typeに 応じて読み込まれ、実行されるプログラムの塊である
  • Type とは、「Runtime」や「Editor」などの読み込み指定項目を指す
  • Type は .uproject または .uplugin ファイル内でモジュール情報として記載される

##独自アセットについて
「独自アセット」という名称は筆者が適当に誂えた単語です。
一般的には「Asset Type」と呼称するようですが、どうにも分かりづらかったためです。
以下の要件を満たすものとします。

  • UObject クラスを継承したアセットクラスである
  • 必要なパラメータを構造体(USTRUCT)としてまとめている
  • 上記パラメータの構造体を、アセットクラスはメンバ変数として1つ以上持っている
  • アセットクラスは C++ 及び Blueprint から参照できる
  • 構造体は C++ 及び Blueprint から参照できる
  • コンテンツブラウザから右クリックなどでアセットクラスをアセットとして追加できる
  • 構造体のパラメータは独自のエディタで編集できる(後半で作成します)

#独自アセットの作成手順

##1.モジュール作成プラグインの導入
簡単にモジュールを作成できるプラグインがありますので、こちらを導入します。

導入していない場合を想定し、導入手順を掲載しておきます。

Epic Games Launcher を開き、「C++ Module Generator」を検索します。
見つけたら「エンジンにインストール」をクリックしてください。
screenshot.1.png
Unreal Editor を起動します。
メニューバーの [Edit] から [Plugins] を選択します。
screenshot.1.png
Plugins ウィンドウが開いたら、右上の検索ボックスに「C++」と入力すると、先ほどインストールしたプラグインが出てきます。
「Enabled」にチェックが入っていなければチェックを入れ、Unreal Editor を再起動してください。
screenshot.2.png
「Enabled」にチェックが入っていて、再起動を要請されなければ有効になっています。

##2.自作プラグインの作成
本記事のソースコードは、基本的にゲーム上ではなくプラグイン上に作成していきます。
まず Unreal Editor のメニューバーの [Edit] から [Plugins] を選択します。
screenshot.1.png
Plugins ウィンドウが開いたら、右下の「New Plugin」をクリックして自作プラグインの設定をします。
screenshot.39.png

screenshot.3.png
今回作成するプラグイン名はマニュアル上では「MyPlugin」とします。
テンプレートは「Blank」を選択しています。
Author などの設定は適宜行ってください。
左下の「Show Content Directory」にチェックを入れておくと、Contents Browser のディレクトリツリーに表示することができます。
設定を終えたら、右下の「Create Plugin」をクリックします。
プラグインはBlank設定で作成すると、デフォルトで「Runtime」設定のモジュールが生成されます。

##3.Editorモジュールの作成
プラグイン作成後、今度は「Editor」設定のモジュールを作成します。
メニューバーの [Window] から [Module Generator] を選択します。
screenshot.8.png
Create New Module ウィンドウ内の項目を設定していきます。
screenshot.4.png
わかりやすいように「MyPluginEditor」モジュールとしました。
Module Template を「Custom」にすると、Type を選択できます。
Type は「Editor」に変更します。
Loading Phase は「Default」でよいでしょう。
Add to Plugin にチェックを入れ、先ほど作成した「MyPlugin」を追加先に指定します。
最下部の「Generate New Module」ボタンを押します。
screenshot.5.png
「追加に成功したので再起動するかどうか」を聞かれます。
「Yes」を押してもいいのですが、再起動するのは Unreal Editor だけなので Visual Studio 上ではまだ反映されていません。
なので「No」を押してこのダイアログを閉じ、一旦 Unreal Editor そのものを終了させます。
(「Yes」を押してしまった方は Unreal Editor の再起動を待ってから再度終了させてください)
screenshot.6.png
エクスプローラ上で .uproject ファイルの右クリックメニューを開き、「Generate Visual Studio project files」を選択します。
これで Unreal Editor からも、VisualStudio からも MyPluginEditor モジュールが認識されるようになります。

##4.アセットクラスの作成
ソースコードを追加していきます。
UObject クラスを継承したクラスを作成しますので、Unreal Editor 上から C++ を追加します。
まずは Contents Browser の左上を押してディレクトリツリーを表示させます。
screenshot.7.png
  ↓
screenshot.6.png
「C++ Classes」フォルダを選択し、ここで右クリックメニューを開きます。
screenshot.7.png
「New C++ Class...」を選択します。
プラグインではなくプロジェクトで選択していますが、後述のダイアログから追加先を選択できます。
そのため、Unreal Editor からソースコードを追加する際は基本的に右クリックメニューから「New C++ Class...」が選べれば場所はどこでも大丈夫です。
screenshot.10.png
Add C++ Class ウィンドウが開きます。
右上の「Show All Classes」にチェックを入れ、一番上にある「Object」を選択します。
この「Object」が「UObject」です。
「Next」をクリックし次の設定を行います。
screenshot.9.png
名称はわかりやすいように「MyAsset」クラスとしました。
追加先を「MyPlugin (Runtime)」にし、Public 設定にします。
「Create Class」を押します。
screenshot.10.png
この際、自動生成ヘッダでコンフリクトが起こる場合があります。
「出力ログの詳細を確認しますか」と聞かれますが、「Yes」でも「No」でもどちらでもいいのでダイアログを閉じて Unreal Editor 上から再コンパイルしてください。
また、Visual Studio 上でもビルドを走らせて通れば、Unreal Editor 上の Compile も成功するようになります。
追加された MyAsset のヘッダファイルは、概ね以下のようになっているはずです。

MyAsset.h
#pragma once

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

UCLASS()
class MYPLUGIN_API UMyAsset : public UObject
{
	GENERATED_BODY()
	
};

Unreal Engine のコーディング規約に則って、コード上ではUMyAssetという名称になります。
.cpp ファイル側はヘッダインクルードしているだけなので何もありません。

ひとまずこの状態でアセットクラスはおいておきます。

##5.パラメータ構造体の作成
パラメータを取りまとめた構造体を作成し、アセットクラスのメンバ変数にします。
整数や文字列などはこの構造体に記述し、アセットクラス自体には記述しません。

こちらもアセットクラス同様、Unreal Editor 上から追加します。
アセットクラス追加時と同様に「New C++ Class...」を選択し、Add C++ Class ウィンドウを開きます。
screenshot.14.png
継承設定は「None」にし、「Next」をクリックします。
※「Create Class」はまだクリックしないでください。
screenshot.11.png
アセットクラス同様、追加先を「MyPlugin(Runtime)」、公開は Public にします。
名称は「Parameter」としました。
「Create Class」を押して作成します。
追加されたヘッダファイルは、概ね以下の状態になっているはずです。

Parameter.h_追加直後
#pragma once

#include "CoreMinimal.h"

class MYPLUGIN_API Parameter
{
public:
	Parameter();
	~Parameter();
};

これを次のように全面的に書き換えます。

Paramter.h_全面書き換え後
#pragma once

#include "CoreMinimal.h"
#include "Parameter.generated.h"

USTRUCT( BlueprintType )
struct FParameter {
GENERATED_USTRUCT_BODY()
public:

	FParameter() {}

	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Parameter" )
	int32 Value;

	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Parameter" )
	FText Text;

	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Parameter" )
	FColor Color;

	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Parameter" )
	FIntPoint Point;

	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "Parameter" )
	TMap<int32, FIntPoint> Array;
};

Parameter.cpp 側のコンストラクタとデストラクタは、すべて削除してください。
変更の要点としては以下になります。

  • .generated.h インクルードの追加
  • UCLASS から USTRUCT に変更
  • かつ、Blueprint で参照可能に
  • コンストラクタ本体をヘッダ側に移行
  • パラメータとなるメンバ変数を追加、UPROPERTY でプロパティ化
  • 編集可能設定(EditAnywhere)、Blueprint からの設定とカテゴリを記述

パラメータとしているメンバ変数は、主に後編で使用します。
BlueprintReadWrite とすると Blueprint 上から値が変更できます。
BlueprintReadOnly とすると Blueprint 上からは値の変更はできなくなります。

また、generated の記述を追加しているためこの状態でコンパイルを走らせるとやはりコンフリクトを起こします。
Unreal Editor を終了させてから Visual Studio でビルドするか、Unreal Editor で2回コンパイルを行ってください。

ビルドが通ったら、アセットクラスにパラメータの構造体をメンバ変数として追加します。

MyAsset.h
#pragma once

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

#include "Parameter.h" // 追加

#include "MyAsset.generated.h"

/**
 * 
 */
UCLASS( BlueprintType )
class MYPLUGIN_API UMyAsset : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = "MyAsset" )
	FParameter Parameter;
};

MyAsset.generated.h 以下に Parameter.h のインクルードを記述すると、ビルドに失敗します。
追加のインクルードは必ず .generated.h よりも上に記述する必要があるので、注意してください。

UCLASS マクロにも BlueprintType を追加記述しています。
これで UMyAsset クラスを用いたアセットは Blueprint 上からも参照できるようになります。
また、メンバを public 設定にし、BlueprintReadWrite 属性を追加しています。
これで Paramter 構造体も Blueprint から参照できます。

ビルドが通れば、アセットクラスそのものは完成です。

##6.アセットファクトリクラスの作成
アセットクラスである UMyAsset を生成するファクトリクラスを作成します。
UFactory クラスを継承して作成します。
アセットクラス作成時同様、「New C++ Class」から Add C++ Class ウィンドウを開いてください。
screenshot.12.png
Show All Classes にチェックマークを入れ、検索窓に UFactory と入力します。
「Factory」とだけ書かれたものが継承元になります。
screenshot.13.png
名称は MyAssetFactoryとしました。
前回までと違うところは、追加先のモジュールが「MyPluginEditor(Editor)」であるところです。
アセットそのものの生成は Unreal Editor 上でしか行わないため、Runtime に追加するべきではありません。

追加時にやはりコンフリクトを起こします。
以降のソースコード追加からはコンフリクトについては特に言及しませんので、前セクションと同様に対処をしてください。

続いてソースコードを改変していきます。

MyAssetFactory.h
#pragma once

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

UCLASS()
class MYPLUGINEDITOR_API UMyAssetFactory : public UFactory
{
	GENERATED_BODY()
public:
	UMyAssetFactory();

	virtual UObject * FactoryCreateNew( UClass * InClass, UObject * InParent,
										FName InName, EObjectFlags Flags,
										UObject * Context, FFeedbackContext * Warn ) override;
};
MyAssetFactory.cpp
#include "MyAssetFactory.h"
#include "MyAsset.h"

UMyAssetFactory::UMyAssetFactory()
	: Super()
{
	bCreateNew = true;
	SupportedClass = UMyAsset::StaticClass();
}

UObject * UMyAssetFactory::FactoryCreateNew( UClass * InClass, UObject * InParent,
											 FName InName, EObjectFlags Flags,
											 UObject * Context, FFeedbackContext * Warn )
{
	return CastChecked<UMyAsset>( StaticConstructObject_Internal( InClass, InParent, InName, Flags ) );
}

主にコンストラクタと FactoryCreateNew 関数を追加し、処理を記述しています。
CreateNew メンバは true にすると新規作成が可能になります。

しかし、この状態でビルドすると「MyAsset.h のインクルードに失敗」というエラーが出てビルドが通らないはずです。
これを解消していきます。
VisualStudio のソリューションエクスプローラから MyPluginEditor.build.cs を開きます。
screenshot.14.png

MyPluginEditor.build.cs_改変前
using UnrealBuildTool;
 
public class MyPluginEditor : ModuleRules
{
	public MyPluginEditor(ReadOnlyTargetRules Target) : base(Target)
	{
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "UnrealEd"});
 
		PublicIncludePaths.AddRange(new string[] {"MyPluginEditor/Public"});
		PrivateIncludePaths.AddRange(new string[] {"MyPluginEditor/Private"});
	}
}

PublicDependencyModuleNames に「MyPlugin」、PublicIncludePathsMyPlugin/Public をそれぞれ追記します。

MyPluginEditor.build.cs_改変後
using UnrealBuildTool;
 
public class MyPluginEditor : ModuleRules
{
	public MyPluginEditor(ReadOnlyTargetRules Target) : base(Target)
	{
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "UnrealEd", "MyPlugin"});
 
		PublicIncludePaths.AddRange(new string[] {"MyPluginEditor/Public", "MyPlugin/Public" } );
		PrivateIncludePaths.AddRange(new string[] {"MyPluginEditor/Private"});
	}
}

これはインクルードパスと DLL 参照パスを記述しています。
つまり、MyPluginEditor モジュールが MyPlugin モジュールに依存していることを示すものです。

上記を記述すればビルドが通るようになります。

##7.アセットアクションクラスの作成
アセットクラスである UMyAsset の挙動を定義するクラスを作成します。
今回は FAssetTypeActions_Base クラスを継承して作成しますが、これは Unreal Editor 上からは選択できません。
そのため、パラメータ構造体と同様に継承なしの設定で追加します。
screenshot.15.png
名称は MyAssetActions としました。
ファクトリクラスと同様に、追加先のモジュールを「MyPluginEditor(Editor)」にしています。
ソースコードを追加したら、中身を面的に書き換えていきます。

MyAssetActions.h
#pragma once

#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"

class FMyAssetActions : public FAssetTypeActions_Base {
public:
	FMyAssetActions();
	FMyAssetActions( EAssetTypeCategories::Type AssetCategoryBit );

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

	virtual void OpenAssetEditor( const TArray<UObject *> & InObjects,
								  TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>() ) override;

private:
	FText						Name;
	EAssetTypeCategories::Type	AssetCategoryBit;
};
MyAssetActions.cpp
#include "MyAssetActions.h"
#include "MyAsset.h"

#include <Toolkits/SimpleAssetEditor.h>

FMyAssetActions::FMyAssetActions()
	: Name( FText::FromString( TEXT( "My Asset" ) ) )
	, AssetCategoryBit( EAssetTypeCategories::Misc )
{
}

FMyAssetActions::FMyAssetActions( EAssetTypeCategories::Type AssetCategoryBit )
	: Name( FText::FromString( TEXT( "My Asset" ) ) )
	, AssetCategoryBit( AssetCategoryBit )
{
}

FText FMyAssetActions::GetName() const
{
	return Name;
}

UClass * FMyAssetActions::GetSupportedClass() const
{
	return UMyAsset::StaticClass();
}

FColor FMyAssetActions::GetTypeColor() const
{
	return FColor::Purple;
}

uint32 FMyAssetActions::GetCategories()
{
	return AssetCategoryBit;
}

void FMyAssetActions::OpenAssetEditor( const TArray<UObject *> & InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor )
{
	for( int i = 0; i < InObjects.Num(); ++i ) {
		FSimpleAssetEditor::CreateEditor( EToolkitMode::Standalone, EditWithinLevelEditor, InObjects[ i ] );
	}
};

アセットのカテゴリビットは Contents Browser を右クリックした際に出てくる追加可能なアセット一覧のカテゴリを識別するものです。
デフォルトでは「Misc」(Miscellaneous)にしていますが、独自のビットを定義することで新たなカテゴリを作り出すことが可能です。

Name はカテゴリ上で表示される名称、Color はアセットのアイコンのバックグラウンドカラーになります。
サンプルでは Purple にしていますが、好きな色に設定可能です。
アイコン画像も指定できますが、画像の読み込みなどは別分野になってくるためここでは記載しません。

OpenAssetEditor 関数にて、ダブルクリック時に開くエディタを指定できます。
しかし、プロパティそのものをカスタマイズする場合は FSimpleAssetEditor を用いるだけで十分です(FSimpleAssetEditor にもカスタマイズ内容がちゃんと反映されるため)。

##8.アセットアクションクラスの登録
作成した FMyAssetActions クラスを登録する処理を記述します。
MyPluginEditor モジュール内にある、MyPluginEditor.cpp を開きます。
以下のように改変します。

MyPluginEditor.cpp
#include "MyPluginEditor.h"
#include "MyAssetActions.h"
#include "AssetToolsModule.h"

DEFINE_LOG_CATEGORY(MyPluginEditor);

#define LOCTEXT_NAMESPACE "FMyPluginEditor"

void FMyPluginEditor::StartupModule()
{
	auto & moduleMgr = FModuleManager::Get();

	if( moduleMgr.IsModuleLoaded( "AssetTools" ) ) {

		auto & assetTools = moduleMgr.LoadModuleChecked<FAssetToolsModule>( "AssetTools" ).Get();

		auto assetCategoryBit = assetTools.RegisterAdvancedAssetCategory( FName( TEXT( "MyPlugin" ) ), LOCTEXT( "NewAssetCategory", "My Plugin" ) );

		auto actions = MakeShareable( new FMyAssetActions( assetCategoryBit ) );

		assetTools.RegisterAssetTypeActions( actions );
	}
}

void FMyPluginEditor::ShutdownModule()
{
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FMyPluginEditor, MyPluginEditor)

モジュールマネージャから AssetTools モジュールを引き出し、FMyAssetActions クラスを登録しています。
その際、独自のカテゴリビットを生成して追加しています。
カテゴリ名は任意に変更して構いません。

旧バージョンの UE4 では、登録情報を取っておいて ShutdownModule 時に破棄する必要があったようですが、現バージョンでそれをやろうとするとクラッシュします。
(正確には ShutdownModule 関数が呼ばれるタイミングですでに AssetTools モジュールが破棄されており、ModuleManager から取得することができません。よって、手動で破棄することができません。)

これでビルドが通れば、Contents Browser 上から UMyAsset クラスを追加することができるようになります。
また、Blueprint 上からアセットを読み込んだり、パラメータを展開することもできます。

##9.アセットブラウザからの挙動確認
確認してみましょう。
ビルド後実行し、Contents Browser 上で右クリックします。
screenshot.16.png
一覧から自分で定義したアセットが選択できるようになっています。
カテゴリも独自で追加した「My Plugin」に属した「My Asset」となっています。
screenshot.17.png
作成してみました。
ダブルクリックするとエディタが起動します。
screenshot.26.png
FParameter 構造体のメンバが編集できるようになっています。
デフォルトではゼロクリアされた状態が初期値になりますが、FParameter 構造体のコンストラクタでメンバ変数の初期値を設定することができます。

左上の保存ボタンも効きます。
編集後に保存してエディタを閉じたあと、再度開けば保存した状態から編集を再開することができます。

##10.Blueprint からの挙動確認
適当に Actor を作り、MyAsset アセットをロードできるか見てみます。
Blueprint を開き、Load Asset Blocking ノードを置いてみます。
screenshot.20.png
作成した MyAsset はロードできるようになっています。
Return Value は UObject なので、Cast to MyAsset ノードをつなぎます。
FParamter 構造体のメンバ変数を取り出すには以下のような構成にすると取り出すことができます。
screenshot.27.png
また、Blueprint 上で FParameter 構造体の変数を定義すると、持っているメンバ変数も編集できます。
もちろん MyAsset クラスの変数を定義して設定することも可能です。
これはまだデフォルトのエディタ(アセットをダブルクリックした際に起動するエディタ)と同内容になっています。

#前半戦終了
だいぶ長い記事になりました。
FSimpleAssetEditor を利用しているためこの状態でも十分使えますが、Slate を用いることでエディタはより自由に、思うままにカスタマイズすることが可能になります。
後編では本記事で作成した FParameter 構造体を主軸に据えた説明となります。

以上、読んでいただきありがとうございました。

後編へ続きます。

#参考リンク集
https://qiita.com/ayumu_nagamatsu/items/c384e1d7f7ec671388ed

4
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?