⛲️

【UE5】IDetailCustomizationとSlateを使ってC++で作ったDataAssetにボタンを追加する

2024/10/31に公開

概要

【UE4】手軽に独自のデータアセットを作成する 【★★】

こちらの記事でBPで作成したPrimaryDataAssetには自作した関数にCallInEditorにチェックを入れるとボタンを追加されるのを知りました。

ただ、C++で作成したPrimaryDataAssetはこれをBP側で継承したら同じことが出来ると思いますが、元々のDataAssetでできないのか?そもそもCallInEditorはどうやってボタンを追加するように実装してるのか?が気になり、CallInEditorでプログラムを調べた所、EngineのソースのObjectDetails.cppにたどり着きました。

中身を見るとSlateが使われることがわかり、自分でSlateを書けばCallInEditorのように出来ると思い、今回試した所無事できたのでそのことについて書こうと思います。

今回はCallInEditorと同じように特定のDataAssetにボタンを追加するように実装しながら説明していきます。


DataAssetに対してSlateで書いたボタンが追加されてる図

環境

UE5.4.4

やり方

手順

  1. (なければ)Editorモジュールを作成する
  2. EditorモジュールのPrivateDependencyModuleNamesUnrealEdを追加する
  3. DataAssetを用意する
  4. IDetailCustomizationを継承したクラスを用意する
  5. Startupモジュールに作成したクラスの詳細レイアウトを自作レイアウトに自作のレイアウトを割り当てる

EditorモジュールのPrivateDependencyModuleNamesUnrealEdを追加する

今回の機能を使うにはUnrealEdモジュールが必要なので追加します。

[プロジェクト名orプラグイン名].Build.cs
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の詳細ビュー

今回はボタンを追加して、ボタンが押されたらログ表示するように作成しました。

ExampleDataAssetDetailCustomization.h
#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();
};
ExampleDataAssetDetailCustomization.cpp
#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関数で呼び出します。

[プロジェクト名orプラグイン名].h
#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);
};

[プロジェクト名orプラグイン名].cpp
#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)

参考

Slate を使用時に知っておきたいこと(テスト実装まで)

[UE4 エディタ拡張] 詳細パネルで UStruct のプロパティカスタマイズ

Discussion