やあ
データテーブルから大量のデータアセットを作ろうと思ったときに、面倒だったから自動化したいなーって思って調べてみたよ。
環境
UE4.26.0
準備
C++の処理をエディタから呼び出すためにEditorUtilityWidget
を使うよ。
docs.unrealengine.com
まず、Hoge.Build.cs
にモジュールを追加。
using UnrealBuildTool;
public class Hoge : ModuleRules
{
public Hoge(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "InputCore",
"AssetTools",
});
PrivateDependencyModuleNames.AddRange(new string[] {
"Slate", "SlateCore", "GraphEditor", "UnrealEd", "BlueprintGraph",
"EditorScriptingUtilities", "UMG",
});
}
}
追加したら、Hoge.uproject
を右クリックして、Generate Visual Studio project files
する。
やる
今回はボタンを押すと、DataAsset
クラスが作られるようにするよ。
新しいDataAssetFactoryクラス
まず最初に、DataAsset
クラスをコンテンツブラウザから作ろうとすると、
こんなのがでていちいち選択するのはめんどうなので、新しくDataAssetFactory
クラスを継承したC++クラスMyDataAssetFactory
を作ります。
MyDataAssetFactory.h
に追加
#pragma once
#include "CoreMinimal.h"
#include "Factories/DataAssetFactory.h"
#include "MyDataAssetFactory.generated.h"
UCLASS(abstract)
class HOGE_API UMyAssetFactory : public UDataAssetFactory
{
GENERATED_BODY()
virtual bool ConfigureProperties() override;
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
};
・UCLASS(abstract)を外すと、コンテンツブラウザを右クリックしてデータアセットを作ろうとしたときに、データアセットが二つ表示されるます。
MyDataAssetFactory.cpp
に追加
#include "MyDataAssetFactory.h"
#if WITH_EDITOR
#include "Misc/MessageDialog.h"
#endif
#define LOCTEXT_NAMESPACE "My EditorFactories"
bool UMyDataAssetFactory::ConfigureProperties()
{
if (DataAssetClass == nullptr)
{
const FText TitleText = LOCTEXT("Title", "WarningMessage");
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Message", "DataAssetClass is null"), &TitleText);
return false;
}
return true;
}
UObject* UMyDataAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
if (DataAssetClass != nullptr)
{
return NewObject<UDataAsset>(InParent, DataAssetClass, Name, Flags | RF_Transactional);
}
else
{
check(Class->IsChildOf(UDataAsset::StaticClass()));
return NewObject<UDataAsset>(InParent, Class, Name, Flags);
}
}
#undef LOCTEXT_NAMESPACE
親クラスのDataAssetFactoryでは、DataAssetClassをnullptrにして、人に選択させようとするので、それを全部消して、外部からDataAssetClassを設定するようにするよ。
エディタを開いたら、EditorUtilityWidget
を継承したC++クラスTestEditorUtilityWidget
を追加。
TestEditorUtilityWidget.h
に追加
#pragma once
#include "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "TestEditorUtilityWidget.generated.h"
class UButton;
class UDataAsset;
UCLASS(BlueprintType)
class HOGE_API TestEditorUtilityWidget : public UEditorUtilityWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget))
UButton* StartButton;
UFUNCTION()
void ClickedStart();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UDataAsset> AssetClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FName AssetName;
};
・TSubclassOf<UDataAsset>
には作りたいアセットのクラスを設定。
ここで設定したものをさっき作った、MyDataAssetFactory
のDataAssetClass
に設定してやることで、そのクラスのアセットを作る。
・StartButton
をmeta = (BindWidget)
することで、ウィジェットブループリントに配置した、StartButton
という名前のButton
ウィジェットにアクセスできるようになるよ。
便利だからぜひ覚えてね。
TestEditorUtilityWidget.cpp
に追加
#include "TestEditorUtilityWidget.h"
#include "Components/Button.h"
#include "MyDataAssetFactory.h"
#include "EditorAssetLibrary.h"
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#include "Modules/ModuleManager.h"
void UTestEditorUtilityWidget::NativeConstruct()
{
if (StartButton != nullptr)
{
if (!StartButton->OnClicked.IsBound())
{
StartButton->OnClicked.AddDynamic(this, &UTestEditorUtilityWidget::ClickedStart);
}
}
Super::NativeConstruct();
}
void UTestEditorUtilityWidget::NativeDestruct()
{
if (StartButton != nullptr)
{
if (!StartButton->OnClicked.IsBound())
{
StartButton->OnClicked.RemoveDynamic(this, &UTestEditorUtilityWidget::ClickedStart);
}
}
Super::NativeDestruct();
}
#define LOCTEXT_NAMESPACE "pto test"
void UTestEditorUtilityWidget::ClickedStart()
{
if (AssetClass == nullptr)
{
const FText TitleText = LOCTEXT("Title", "WarningMessage");
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Message", "AssetClass is null"), &TitleText);
return;
}
IAssetTools& AssetTool = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
UMyDataAssetFactory* Factory = NewObject<MyDataAssetFactory>();
Factory->DataAssetClass = DataAssetClass;
FString DirPath = "/Game/test/";
if (!UEditorAssetLibrary::DoesDirectoryExist(DirPath))
{
UEditorAssetLibrary::MakeDirectory(DirPath);
}
UObject* NewAsset = AssetTool.CreateAssetWithDialog(
AssetName.ToString(), DirPath, AssetClass, Factory
);
}
#undef LOCTEXT_NAMESPACE
・作りたいアセットによって、使うFactory
クラスが違うので注意。
Engine\Source\Editor\UnrealEd\Private\Factories\EditorFactories.cpp
で色々見れるよ。
これで、おーけー。
コンパイルしてね。
コンパイルが終わったら、EditorUtilityWidgetBlueprint
を作る。
EBP_CreateAssetTest
と呼ぶよ。
EBP_CreateAssetTest
を開いて、クラス設定を編集を押して、親クラスをさっき作った、TestEditorUtilityWidget
にする。
すると、StartButton
がないぞって怒られるので、StartButton
を追加。
ウィジェットからアセットのクラス、アセットの名前を設定するためにちょっと改造。
SinglePropertyView
を二つ追加して。
それぞれにプロパティの名前、AssetClass
, AssetName
を設定。
コンストラクタでSinglePropertyView
が参照するオブジェクトを設定する。
これでおーけー。
EBP_CreateAssetTest
を右クリックして、実行。
するとこんな画面が出るので、
クラスと名前を設定してボタンをクリック。
できました。やったー!・。・!
これで終わりです。
おまけ
BP版
かきかけ
BPから作る場合も後で追記するよ。(完全にBPだけではなくて、ちょっとだけBlueprintFunctionLibraryを継承したクラスを使ってノードを追加するよ。)