Skip to content

Instantly share code, notes, and snippets.

@kirby561
Created May 13, 2024 23:25
Show Gist options
  • Save kirby561/d10de39f457f15ab24cffbc1787c183f to your computer and use it in GitHub Desktop.
Save kirby561/d10de39f457f15ab24cffbc1787c183f to your computer and use it in GitHub Desktop.
Custom Asset Editors in Unreal Engine 5 - This is the source code from the Custom Asset Editor Youtube video.
#pragma once
#include "CoreMinimal.h"
#include "CustomAsset.generated.h"
UCLASS(BlueprintType)
class CUSTOMASSETEDITORRUNTIME_API UCustomAsset : public UObject {
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
FString SomeData = TEXT("DefaultData");
UPROPERTY(EditAnywhere)
int32 SomeNumber = 0;
UPROPERTY(EditAnywhere)
bool SomeBool = false;
};
#include "CustomAssetAction.h"
#include "CustomAsset.h"
#include "CustomAssetEditorApp.h"
CustomAssetAction::CustomAssetAction(EAssetTypeCategories::Type category) {
_assetCategory = category;
}
FText CustomAssetAction::GetName() const {
return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_MyCustomAsset", "My Custom Asset");
}
FColor CustomAssetAction::GetTypeColor() const {
return FColor::Cyan;
}
UClass* CustomAssetAction::GetSupportedClass() const {
return UCustomAsset::StaticClass();
}
void CustomAssetAction::OpenAssetEditor(const TArray<UObject*>& inObjects, TSharedPtr<class IToolkitHost> editWithinLevelEditor) {
EToolkitMode::Type mode = editWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (UObject* object : inObjects) {
UCustomAsset* customAsset = Cast<UCustomAsset>(object);
if (customAsset != nullptr) {
TSharedRef<CustomAssetEditorApp> editor(new CustomAssetEditorApp());
editor->InitEditor(mode, editWithinLevelEditor, customAsset);
}
}
}
uint32 CustomAssetAction::GetCategories() {
return _assetCategory;
}
#pragma once
#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"
class CustomAssetAction : public FAssetTypeActions_Base {
public:
CustomAssetAction(EAssetTypeCategories::Type category);
public: // FAssetTypeActions_Base interface
virtual FText GetName() const override;
virtual FColor GetTypeColor() const override;
virtual UClass* GetSupportedClass() const override;
virtual void OpenAssetEditor(const TArray<UObject*>& inObjects, TSharedPtr<class IToolkitHost> editWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
virtual uint32 GetCategories() override;
private:
EAssetTypeCategories::Type _assetCategory;
};
#include "CustomAssetAppMode.h"
#include "CustomAssetEditorApp.h"
#include "CustomAssetPrimaryTabFactory.h"
CustomAssetAppMode::CustomAssetAppMode(TSharedPtr<CustomAssetEditorApp> app) : FApplicationMode(TEXT("CustomAssetAppMode")) {
_app = app;
_tabs.RegisterFactory(MakeShareable(new CustomAssetPrimaryTabFactory(app)));
TabLayout = FTabManager::NewLayout("CustomAssetAppMode_Layout_v1")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->AddTab(FName(TEXT("CustomAssetPrimaryTab")), ETabState::OpenedTab)
)
);
}
void CustomAssetAppMode::RegisterTabFactories(TSharedPtr<class FTabManager> InTabManager) {
TSharedPtr<CustomAssetEditorApp> app = _app.Pin();
app->PushTabFactories(_tabs);
FApplicationMode::RegisterTabFactories(InTabManager);
}
void CustomAssetAppMode::PreDeactivateMode() {
FApplicationMode::PreDeactivateMode();
}
void CustomAssetAppMode::PostActivateMode() {
FApplicationMode::PostActivateMode();
}
#pragma once
#include "CoreMinimal.h"
#include "WorkflowOrientedApp/ApplicationMode.h"
#include "WorkflowOrientedApp/WorkflowTabManager.h"
/** Application mode for main behavior tree editing mode */
class CustomAssetAppMode : public FApplicationMode
{
public:
CustomAssetAppMode(TSharedPtr<class CustomAssetEditorApp> app);
virtual void RegisterTabFactories(TSharedPtr<class FTabManager> InTabManager) override;
virtual void PreDeactivateMode() override;
virtual void PostActivateMode() override;
protected:
TWeakPtr<class CustomAssetEditorApp> _app;
FWorkflowAllowedTabSet _tabs;
};
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class CustomAssetEditor : ModuleRules
{
public CustomAssetEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"CustomAssetEditorRuntime",
"AssetTools",
"UnrealEd",
"PropertyEditor"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CustomAssetEditor.h"
#include "CustomAssetAction.h"
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#define LOCTEXT_NAMESPACE "FCustomAssetEditorModule"
void FCustomAssetEditorModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
IAssetTools& assetToolsModule = IAssetTools::Get();
EAssetTypeCategories::Type assetType = assetToolsModule.RegisterAdvancedAssetCategory(FName(TEXT("CustomAssets")), LOCTEXT("CustomAssets", "Custom Assets"));
TSharedPtr<CustomAssetAction> customAssetAction = MakeShareable(new CustomAssetAction(assetType));
assetToolsModule.RegisterAssetTypeActions(customAssetAction.ToSharedRef());
}
void FCustomAssetEditorModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FCustomAssetEditorModule, CustomAssetEditor)
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FCustomAssetEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
#include "CustomAssetEditorApp.h"
#include "CustomAssetAppMode.h"
#include "CustomAsset.h"
void CustomAssetEditorApp::RegisterTabSpawners(const TSharedRef<class FTabManager>& tabManager) {
FWorkflowCentricApplication::RegisterTabSpawners(tabManager);
}
void CustomAssetEditorApp::InitEditor(const EToolkitMode::Type mode, const TSharedPtr<class IToolkitHost>& initToolkitHost, UObject* inObject) {
TArray<UObject*> objectsToEdit;
objectsToEdit.Add(inObject);
_workingAsset = Cast<UCustomAsset>(inObject);
InitAssetEditor(
mode,
initToolkitHost,
TEXT("CustomAssetEditor"),
FTabManager::FLayout::NullLayout,
true, // createDefaultStandaloneMenu
true, // createDefaultToolbar
objectsToEdit);
// Add our modes (just one for this example)
AddApplicationMode(TEXT("CustomAssetAppMode"), MakeShareable(new CustomAssetAppMode(SharedThis(this))));
// Set the mode
SetCurrentMode(TEXT("CustomAssetAppMode"));
}
#pragma once
#include "CoreMinimal.h"
#include "WorkflowOrientedApp/WorkflowCentricApplication.h"
class CustomAssetEditorApp : public FWorkflowCentricApplication, public FEditorUndoClient, public FNotifyHook {
public: // FWorkflowCentricApplication interface
virtual void RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
void InitEditor(const EToolkitMode::Type mode, const TSharedPtr<class IToolkitHost>& initToolkitHost, UObject* inObject);
class UCustomAsset* GetWorkingAsset() { return _workingAsset; }
public: // FAssetEditorToolkit interface
virtual FName GetToolkitFName() const override { return FName(TEXT("CustomAssetEditorApp")); }
virtual FText GetBaseToolkitName() const override { return FText::FromString(TEXT("CustomAssetEditorApp")); }
virtual FString GetWorldCentricTabPrefix() const override { return TEXT("CustomAssetEditorApp"); }
virtual FLinearColor GetWorldCentricTabColorScale() const override { return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f); }
virtual FString GetDocumentationLink() const override { return TEXT("https://github.com/kirby561"); }
virtual void OnToolkitHostingStarted(const TSharedRef<class IToolkit>& Toolkit) override { }
virtual void OnToolkitHostingFinished(const TSharedRef<class IToolkit>& Toolkit) override { }
private:
UPROPERTY()
class UCustomAsset* _workingAsset = nullptr;
};
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class CustomAssetEditorRuntime : ModuleRules
{
public CustomAssetEditorRuntime(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CustomAssetEditorRuntime.h"
#define LOCTEXT_NAMESPACE "FCustomAssetEditorRuntimeModule"
void FCustomAssetEditorRuntimeModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FCustomAssetEditorRuntimeModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FCustomAssetEditorRuntimeModule, CustomAssetEditor)
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FCustomAssetEditorRuntimeModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
#include "CustomAssetFactory.h"
#include "CustomAsset.h"
UCustomAssetFactory::UCustomAssetFactory(const FObjectInitializer& objectInitializer) : Super(objectInitializer) {
SupportedClass = UCustomAsset::StaticClass();
}
UObject* UCustomAssetFactory::FactoryCreateNew(UClass* uclass, UObject* inParent, FName name, EObjectFlags flags, UObject* context, FFeedbackContext* warn) {
UCustomAsset* asset = NewObject<UCustomAsset>(inParent, name, flags);
return asset;
}
bool UCustomAssetFactory::CanCreateNew() const {
return true;
}
#pragma once
#include "CoreMinimal.h"
#include "CustomAssetFactory.generated.h"
UCLASS()
class UCustomAssetFactory : public UFactory {
GENERATED_BODY()
public:
UCustomAssetFactory(const FObjectInitializer& objectInitializer);
public: // UFactory interface
virtual UObject* FactoryCreateNew(UClass* uclass, UObject* inParent, FName name, EObjectFlags flags, UObject* context, FFeedbackContext* warn) override;
virtual bool CanCreateNew() const override;
};
#include "CustomAssetPrimaryTabFactory.h"
#include "CustomAssetEditorApp.h"
#include "CustomAsset.h"
#include "IDetailsView.h"
#include "PropertyEditorModule.h"
CustomAssetPrimaryTabFactory::CustomAssetPrimaryTabFactory(TSharedPtr<CustomAssetEditorApp> app) : FWorkflowTabFactory(FName("CustomAssetPrimaryTab"), app) {
_app = app;
TabLabel = FText::FromString(TEXT("Primary"));
ViewMenuDescription = FText::FromString(TEXT("Displays a primary view for whatever you want to do."));
ViewMenuTooltip = FText::FromString(TEXT("Show the primary view."));
}
TSharedRef<SWidget> CustomAssetPrimaryTabFactory::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const {
TSharedPtr<CustomAssetEditorApp> app = _app.Pin();
FPropertyEditorModule& propertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
FDetailsViewArgs detailsViewArgs;
{
detailsViewArgs.bAllowSearch = false;
detailsViewArgs.bHideSelectionTip = true;
detailsViewArgs.bLockable = false;
detailsViewArgs.bSearchInitialKeyFocus = true;
detailsViewArgs.bUpdatesFromSelection = false;
detailsViewArgs.NotifyHook = nullptr;
detailsViewArgs.bShowOptions = true;
detailsViewArgs.bShowModifiedPropertiesOption = false;
detailsViewArgs.bShowScrollBar = false;
}
TSharedPtr<IDetailsView> detailsView = propertyEditorModule.CreateDetailView(detailsViewArgs);
detailsView->SetObject(app->GetWorkingAsset());
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.HAlign(HAlign_Fill)
[
detailsView.ToSharedRef()
];
}
FText CustomAssetPrimaryTabFactory::GetTabToolTipText(const FWorkflowTabSpawnInfo& Info) const {
return FText::FromString(TEXT("A primary view for doing primary things."));
}
#pragma once
#include "CoreMinimal.h"
#include "WorkflowOrientedApp/WorkflowTabFactory.h"
class CustomAssetPrimaryTabFactory : public FWorkflowTabFactory {
public:
CustomAssetPrimaryTabFactory(TSharedPtr<class CustomAssetEditorApp> app);
virtual TSharedRef<SWidget> CreateTabBody(const FWorkflowTabSpawnInfo& Info) const override;
virtual FText GetTabToolTipText(const FWorkflowTabSpawnInfo& Info) const override;
protected:
TWeakPtr<class CustomAssetEditorApp> _app;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment