【UE5】IDetailCustomizationとSlateを使ってC++で作ったDataAssetにボタンを追加する
概要
こちらの記事でBPで作成したPrimaryDataAsset
には自作した関数にCallInEditorにチェックを入れるとボタンを追加されるのを知りました。
ただ、C++で作成したPrimaryDataAsset
はこれをBP側で継承したら同じことが出来ると思いますが、元々のDataAssetでできないのか?そもそもCallInEditorはどうやってボタンを追加するように実装してるのか?が気になり、CallInEditorでプログラムを調べた所、EngineのソースのObjectDetails.cpp
にたどり着きました。
中身を見るとSlateが使われることがわかり、自分でSlateを書けばCallInEditorのように出来ると思い、今回試した所無事できたのでそのことについて書こうと思います。
今回はCallInEditorと同じように特定のDataAssetにボタンを追加するように実装しながら説明していきます。
DataAssetに対してSlateで書いたボタンが追加されてる図
環境
UE5.4.4
やり方
手順
- (なければ)Editorモジュールを作成する
- Editorモジュールの
PrivateDependencyModuleNames
にUnrealEd
を追加する - DataAssetを用意する
- IDetailCustomizationを継承したクラスを用意する
- Startupモジュールに作成したクラスの詳細レイアウトを自作レイアウトに自作のレイアウトを割り当てる
PrivateDependencyModuleNames
にUnrealEd
を追加する
Editorモジュールの今回の機能を使うにはUnrealEdモジュールが必要なので追加します。
PrivateDependencyModuleNames.AddRange(
new string[]
{
... //省略
"UnrealEd", //追加
}
);
C++で作成したPrimaryDataAssetを用意する
ボタンを追加したいDataAssetを用意します。
今回はこういう内容のDataAssetでテストをします。
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ExampleDataAsset.generated.h"
/**
*
*/
UCLASS()
class UExampleDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere,Blueprintable)
FString ExampleText;
};
IDetailCustomizationを継承したクラスを用意する
今回は詳細ビューを拡張するので、IDetailCustomizationを実装したクラスを用意します
例としてActorの詳細ビュー
今回はボタンを追加して、ボタンが押されたらログ表示するように作成しました。
#pragma once
#include "IDetailCustomization.h"
class FExampleDataAssetDetailCustomization : public IDetailCustomization
{
public:
FExampleDataAssetDetailCustomization();
//レイアウトの拡張処理の本体
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
//モジュール登録時に必要
static TSharedRef<IDetailCustomization> MakeInstance()
{
return MakeShareable(new FExampleDataAssetDetailCustomization);
}
private:
FReply OnExecuteClicked();
};
#include "ExampleDataAssetDetailCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Widgets/Layout/SWrapBox.h"
FExampleDataAssetDetailCustomization::FExampleDataAssetDetailCustomization()
{
}
void FExampleDataAssetDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
//描画レイアウトの作成
TSharedPtr<SWrapBox> WrapBox = SNew(SWrapBox);
const FText Execute = FText::FromString("Execute");
WrapBox->AddSlot().Padding(0.0f,0.0f,5.0f,3.0f)
[
SNew(SButton)
.Text(Execute)
.OnClicked(this,&FExampleDataAssetDetailCustomization::OnExecuteClicked)
];
const FText Empty = FText::GetEmpty();
//新規でボタンを追加するためのカテゴリを編集する(指定したカテゴリがなければ新規追加になり、クラス名を入れると別カテゴリにならず同じカテゴリに追加される)
IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory("Example");
CategoryBuilder.AddCustomRow(Empty) //SearchTextは必要ないので空
.RowTag("Empty") //TODO これだけなんなのかわからなかった…。
[
WrapBox.ToSharedRef() //描画インスタンスを渡して表示してもらう
];
}
//FReplyを戻り値にしないとOnClickedに登録できないのでそうする
FReply FExampleDataAssetDetailCustomization::OnExecuteClicked()
{
UE_LOG(LogTemp,Type::Log,TEXT("Called Slate Button"));
return FReply::Handled();
}
ボタンの追加部分は以下になります。
これはEngine\Source\Editor\DetailCustomizations\Private\ObjectDetails.cpp
から拝借してきた形になりますので、これ以外の形にしたい場合は適宜変えていただければ幸いです。
WrapBox->AddSlot().Padding(0.0f,0.0f,5.0f,3.0f)
[
SNew(SButton)
.Text(Execute)
.OnClicked(this,&FExampleDataAssetDetailCustomization::OnExecuteClicked)
];
カテゴリに追加する部分はこちらになります。今回はExampleという別カテゴリ内にボタンを表示してもらうようにしてます。
IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory("Example");
余談になりますが、Exampleの部分をクラス名(今回の場合はExampleDataAsset
)に変えると別カテゴリにならず同じカテゴリ内にボタンが表示されます
クラス名に変えた図
Startupモジュールに作成したクラスの詳細レイアウトを自作レイアウトに自作のレイアウトを割り当てる
先ほどのクラスを今回使うDataAssetのレイアウトとして登録します。
追加する場所は[プロジェクト名orプラグイン名].h
と[プロジェクト名orプラグイン名].cpp
の2つになります。
以下の処理が対象のクラスに自作したレイアウトを登録する部分になります。
static FName PropertyEditor("PropertyEditor");
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(PropertyEditor);
PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate);
これをStartupModule
関数で呼び出します。
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FExampleEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate );
void UnregisterCustomClassLayout(FName ClassName);
};
#include "ExampleEditor.h"
#include "ExampleEditor/ExampleDataAssetDetailCustomization.h"
#define LOCTEXT_NAMESPACE "FExampleEditorModule"
void FExampleEditorModule::StartupModule()
{
RegisterCustomClassLayout("ExampleDataAsset", FOnGetDetailCustomizationInstance::CreateStatic(&FExampleDataAssetDetailCustomization::MakeInstance));
}
void FExampleEditorModule::ShutdownModule()
{
UnregisterCustomClassLayout("ExampleDataAsset");
}
void FExampleEditorModule::RegisterCustomClassLayout(FName ClassName,FOnGetDetailCustomizationInstance DetailLayoutDelegate)
{
static FName PropertyEditor("PropertyEditor");
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(PropertyEditor);
PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate);
}
void FExampleEditorModule::UnregisterCustomClassLayout(FName ClassName)
{
static FName PropertyEditor("PropertyEditor");
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(PropertyEditor);
PropertyModule.UnregisterCustomClassLayout(ClassName);
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FExampleEditorModule, ExampleEditor)
Discussion