I’ve created a customization to add a button to all static mesh actors
Here’s the code:
MyActorCustomization.h
#pragma once
#include "IDetailCustomization.h"
#include "IPropertyChangeListener.h"
DECLARE_LOG_CATEGORY_EXTERN(LogMyActorCustomization, Log, All);
class MYGAMEEDITOR_API FMyActorCustomization : public IDetailCustomization {
public:
// IDetailCustomization interface
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
// End of IDetailCustomization interface
static TSharedRef<IDetailCustomization> MakeInstance();
static FReply ExecuteCommand(IDetailLayoutBuilder* DetailBuilder);
};
MyActorCustomization.cpp
#include "MyGameEditorPrivatePCH.h"
#include "MyActorCustomization.h"
#include "PropertyEditing.h"
#include "IDetailsView.h"
#define LOCTEXT_NAMESPACE "DungeonArchitectEditorModule"
DEFINE_LOG_CATEGORY(LogMyActorCustomization)
template<typename T>
T* GetBuilderObject(IDetailLayoutBuilder* DetailBuilder) {
TArray<TWeakObjectPtr<UObject>> OutObjects;
DetailBuilder->GetObjectsBeingCustomized(OutObjects);
T* Obj = nullptr;
if (OutObjects.Num() > 0) {
Obj = Cast<T>(OutObjects[0].Get());
}
return Obj;
}
//////////////// FMyActorCustomization ////////////////////
void FMyActorCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("My Category");
FText Caption = FText::FromString("My Custom Button");
Category.AddCustomRow(Caption)
.ValueContent()
SNew(SButton)
.Text(Caption)
.OnClicked(FOnClicked::CreateStatic(&FMyActorCustomization::ExecuteCommand, &DetailBuilder))
];
}
TSharedRef<IDetailCustomization> FMyActorCustomization::MakeInstance()
{
return MakeShareable(new FMyActorCustomization);
}
FReply FMyActorCustomization::ExecuteCommand(IDetailLayoutBuilder* DetailBuilder)
{
AStaticMeshActor* StaticMesh = GetBuilderObject<AStaticMeshActor>(DetailBuilder);
if (StaticMesh) {
UE_LOG(LogMyActorCustomization, Log, TEXT("Running custom code"));
// TODO: Add your button logic here
}
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE
MyGameEditor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Engine.h"
#include "ModuleManager.h"
#include "UnrealEd.h"
DECLARE_LOG_CATEGORY_EXTERN(MyGameEditor, All, All)
class FMyGameEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
MyGameEditor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameEditorPrivatePCH.h"
#include "MyGameEditor.h"
#include "ModuleManager.h"
#include "MyActorCustomization.h"
#include "PropertyEditorModule.h"
IMPLEMENT_GAME_MODULE(FMyGameEditorModule, MyGameEditor);
DEFINE_LOG_CATEGORY(MyGameEditor)
#define LOCTEXT_NAMESPACE "MyGameEditor"
void FMyGameEditorModule::StartupModule()
{
UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Started"));
// Register the details customization
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
// Leave out the suffix "A" when specifying the name of your actor in the first param
PropertyEditorModule.RegisterCustomClassLayout("StaticMeshActor", FOnGetDetailCustomizationInstance::CreateStatic(&FMyActorCustomization::MakeInstance));
}
void FMyGameEditorModule::ShutdownModule()
{
UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Ended"));
}
#undef LOCTEXT_NAMESPACE
MyGameEditorPrivatePCH.h
#include "CoreUObject.h"
// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.
#include "Engine.h"
#include "UnrealEd.h"
MyGameEditor.Build.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class MyGameEditor : ModuleRules
{
public MyGameEditor(TargetInfo Target)
{
PublicIncludePaths.AddRange(
new string]{
"MyGameEditor/Public"
});
PrivateIncludePaths.AddRange(
new string] {
"MyGameEditor/Private"
});
PublicDependencyModuleNames.AddRange(
new string]
{
"Core",
"CoreUObject",
"Engine",
"EditorStyle",
"UnrealEd",
"PropertyEditor",
"MyGame"
}
);
// Uncomment if you are using Slate UI
PrivateDependencyModuleNames.AddRange(new string] { "Slate", "SlateCore" });
}
}
MyGame.Target.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class MyGameTarget : TargetRules
{
public MyGameTarget(TargetInfo Target)
{
Type = TargetType.Game;
}
//
// TargetRules interface.
//
public override void SetupBinaries(
TargetInfo Target,
ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
ref List<string> OutExtraModuleNames
)
{
OutExtraModuleNames.AddRange( new string] { "MyGame" } );
if (UEBuildConfiguration.bBuildEditor)
{
OutExtraModuleNames.Add("MyGameEditor");
}
}
}
MyGameEditor.Target.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class MyGameEditorTarget : TargetRules
{
public MyGameEditorTarget(TargetInfo Target)
{
Type = TargetType.Editor;
}
//
// TargetRules interface.
//
public override void SetupBinaries(
TargetInfo Target,
ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
ref List<string> OutExtraModuleNames
)
{
OutExtraModuleNames.AddRange( new string] { "MyGameEditor" } );
}
}
MyGame.uproject
{
"FileVersion": 3,
"EngineAssociation": "4.12",
"Category": "",
"Description": "",
"Modules":
{
"Name": "MyGame",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "MyGameEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
],
"Plugins":
{
"Name": "Substance",
"Enabled": true,
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/offers/2f6439c2f9584f49809d9b13b16c2ba4"
}
]
}