UE4 C++からアセットを作る
やあ
データテーブルから大量のデータアセットを作ろうと思ったときに、面倒だったから自動化したいなーって思って調べてみたよ。
環境
UE4.26.0
準備
C++の処理をエディタから呼び出すためにEditorUtilityWidget
を使うよ。
まず、Hoge.Build.cs
にモジュールを追加。
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 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
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Factories/DataAssetFactory.h" #include "MyDataAssetFactory.generated.h" /* エディタに表示したくなかったのでabstractを使ったんだけど、正しいのかな? */ UCLASS(abstract) class HOGE_API UMyAssetFactory : public UDataAssetFactory { GENERATED_BODY() // UFactory interface virtual bool ConfigureProperties() override; virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; // End of UFactory interface };
・UCLASS(abstract)を外すと、コンテンツブラウザを右クリックしてデータアセットを作ろうとしたときに、データアセットが二つ表示されるます。
MyDataAssetFactory.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #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 { // if we have no data asset class, use the passed-in class instead check(Class->IsChildOf(UDataAsset::StaticClass())); return NewObject<UDataAsset>(InParent, Class, Name, Flags); } } #undef LOCTEXT_NAMESPACE
親クラスのDataAssetFactoryでは、DataAssetClassをnullptrにして、人に選択させようとするので、それを全部消して、外部からDataAssetClassを設定するようにするよ。
EditorUtilityWidgetクラスを作る
エディタを開いたら、EditorUtilityWidget
を継承したC++クラスTestEditorUtilityWidget
を追加。
TestEditorUtilityWidget.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #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
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #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(); /* さっき作ったMyDataAssetFactoryを作る。 */ 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を継承したクラスを使ってノードを追加するよ。)