Writing a Custom Asset Editor for Unreal Engine 4 - Part 2


In the previous part of this guide, we went over how to set up the logic for your own custom asset editor but we have not yet told the editor to spawn our asset editor. In this part, we will continue from where we last left of and we will be proving the editor with some details on how to treat our custom asset and when to spawn our custom asset editor!

What will you learn

In this part, we will discuss how to use AssetTypeActions to modify our custom asset and spawn our custom asset editor.

Please note that this guide is part of a more comprehensive tutorial and it is essential that you either followed the previous parts or have already created something similar. Please check the prerequisites to make sure you have everything you need before starting to read this guide.

Prerequisites

Setting up the Asset Type Actions

AssetTypeActions_MyCustomAsset.h (Header file)
#pragma once

#include "CoreMinimal.h"
#include "Toolkits/IToolkitHost.h"
#include "AssetTypeActions_Base.h"

/*
 *
 */
class FAssetTypeActions_MyCustomAsset : public FAssetTypeActions_Base
{
public:
	// IAssetTypeActions 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;
	// End of IAssetTypeActions interface
};

We start by creating a AssetTypeActions class for our asset which we will conveniently name AssetTypeActions_MyCustomAsset. We start by overriding some of the base functions, notice that we are overriding the OpenAssetEditor function, this function will be responsible for spawning a custom editor for this asset. The other functions give us the option to customize our asset even further.

The AssetTypeActions class is not limited to our own Custom Asset, if you take a look at the function named GetSupportedClass you notice that you can specify to which UClass we add actions to. For more information I encourage you to take a look at the documentation.

AssetTypeActions_MyCustomAsset.cpp (Implementation file)

#include "AssetTypeActions_MyCustomAsset.h"
#include "MyCustomAsset.h"
#include "CustomAssetEditorModule.h"

#define LOCTEXT_NAMESPACE "AssetTypeActions_MyCustomAsset"

FText FAssetTypeActions_MyCustomAsset::GetName() const
{
	return NSLOCTEXT("AssetTypeActions_MyCustomAsset", "AssetTypeActions_MyCustomAsset", "MyCustomAsset");
}

FColor FAssetTypeActions_MyCustomAsset::GetTypeColor() const
{
	return FColor::Magenta;
}

UClass* FAssetTypeActions_MyCustomAsset::GetSupportedClass() const
{
	return UMyCustomAsset::StaticClass();
}

uint32 FAssetTypeActions_MyCustomAsset::GetCategories()
{
	return EAssetTypeCategories::Misc;
}

#undef LOCTEXT_NAMESPACE

We define a localized namespace in our implementation file and start implementing the functions that we have overridden earlier. We start by implementing a localized name for our asset as well as assign a color to the thumbnail displayed in the editor.

We add our Custom Asset as the supported class to make sure these AssetTypeActions are supporting our asset. Finally, we also add this asset to the miscellaneous category.

AssetTypeActions_MyCustomAsset.cpp (Implementation file)

void FAssetTypeActions_MyCustomAsset::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor /*= TSharedPtr<IToolkitHost>()*/)
{
	EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;

	for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
	{
		auto MyCustomAsset = Cast<UMyCustomAsset>(*ObjIt);
		if (MyCustomAsset != NULL)
		{
			ICustomAssetEditorModule* CustomAssetEditorModule = &FModuleManager::LoadModuleChecked<ICustomAssetEditorModule>("CustomAssetEditor");
			CustomAssetEditorModule->CreateCustomAssetEditor(Mode, EditWithinLevelEditor, MyCustomAsset);
		}
	}
}

With everything set-up, we can now start to implement the OpenAssetEditor function. We will start by caching the toolkit mode and iterating over the TArray<UObject*> InObjects. If we are able to cast the object to MyCustomAsset, we will load our Custom Asset Editor Module from the module manager and we will create our Custom Asset Editor.

Unfortunately, if you recompile the code and open the asset editor, you will notice it will not spawn our custom editor nor has the asset thumbnail been changed. This is because we have not yet registered this AssetTypeAction to the editor. In the next step, we will be adding the AssetTypeAction to the module and spawn our custom editor.

Register the Asset Action in our Module Source

CustomAssetEditorModule.h (Implementation file)
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#include "AssetTypeActions_MyCustomAsset.h"
We start by adding the following includes to our module its implementation file. These includes allow us to use the AssetToolsModule which we will need to register the AssetTypeAction created earlier.

CustomAssetEditorModule.h (Implementation file)
void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action)
{
    AssetTools.RegisterAssetTypeActions(Action);
    CreatedAssetTypeActions.Add(Action);
}

TArray<TSharedPtr<IAssetTypeActions>> CreatedAssetTypeActions;
We create a new function called RegisterAssetTypeAction which will be responsible for registering a user-defined action to the asset tools module. Also note that we have created an array of shared pointers to the IAssetTypeActionsinterface, in this array will cache our registered asset type actions so we can easily clean them up at a later time.

CustomAssetEditorModule.h (Implementation file)
virtual void StartupModule() override
{
    MenuExtensibilityManager = MakeShareable(new FExtensibilityManager);
    ToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager);

    IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
    RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_MyCustomAsset()));
}

We start by modifying our StartupModule function where we will load the AssetTools module from the module manager and register our asset type action. Notice that we pass our newly created AssetTypeAction as a shared reference into the function by converting the pointer to a reference using the MakeSharable function.

We have now registered our custom asset type action and if you recompile, you should be able to see everything working in the editor. However, before we continue and test our asset, we should make sure to clean up our created asset type actions when shutting down the module.

CustomAssetEditorModule.h (Implementation file)

virtual void ShutdownModule() override
{
    MenuExtensibilityManager.Reset();
    ToolBarExtensibilityManager.Reset();

    if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
    {
        // Unregister our custom created assets from the AssetTools
        IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
        for (int32 i = 0; i < CreatedAssetTypeActions.Num(); ++i)
        {
            AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[i].ToSharedRef());
        }
    }

    CreatedAssetTypeActions.Empty();
}
Before we start unregistering our asset type action form the asset tools module, we need to make sure that the module is loaded. We simply check if the name exists within the module manager and if it does, continue by iterating over our array of CreatedAssetTypeActions. We unregister the asset actions and empty the array.

CustomAssetEditorModule.h (Implementation file)
#include "CustomAssetEditorModule.h"
#include "MyCustomAsset.h"
#include "Modules/ModuleManager.h"
#include "IToolkit.h"
#include "CustomAssetEditor.h"
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#include "AssetTypeActions_MyCustomAsset.h"

const FName CustomAssetEditorAppIdentifier = FName(TEXT("StaticMeshEditorApp"));

#define LOCTEXT_NAMESPACE "FCustomAssetEditorModule"

/**
 * StaticMesh editor module
 */
class FCustomAssetEditorModule : public ICustomAssetEditorModule
{
public:
	/** Constructor */
	FCustomAssetEditorModule() { }

	/**
	* Called right after the module DLL has been loaded and the module object has been created
	*/
	virtual void StartupModule() override
	{
		MenuExtensibilityManager = MakeShareable(new FExtensibilityManager);
		ToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager);

		IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
		RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_MyCustomAsset()));
	}

	/**
	* Called before the module is unloaded, right before the module object is destroyed.
	*/
	virtual void ShutdownModule() override
	{
		MenuExtensibilityManager.Reset();
		ToolBarExtensibilityManager.Reset();

		if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
		{
			// Unregister our custom created assets from the AssetTools
			IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
			for (int32 i = 0; i < CreatedAssetTypeActions.Num(); ++i)
			{
				AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[i].ToSharedRef());
			}
		}

		CreatedAssetTypeActions.Empty();
	}

	/**
	* Creates a new custom asset editor for a MyCustomAsset
	*/
	virtual TSharedRef<ICustomAssetEditor> CreateCustomAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UMyCustomAsset* CustomAsset) override
	{
		TSharedRef<FCustomAssetEditor> NewCustomAssetEditor(new FCustomAssetEditor());
		NewCustomAssetEditor->InitCustomAssetEditorEditor(Mode, InitToolkitHost, CustomAsset);
		return NewCustomAssetEditor;
	}

	void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action)
	{
		AssetTools.RegisterAssetTypeActions(Action);
		CreatedAssetTypeActions.Add(Action);
	}

	/** Gets the extensibility managers for outside entities to extend static mesh editor's menus and toolbars */
	virtual TSharedPtr<FExtensibilityManager> GetMenuExtensibilityManager() override { return MenuExtensibilityManager; }
	virtual TSharedPtr<FExtensibilityManager> GetToolBarExtensibilityManager() override { return ToolBarExtensibilityManager; }

private:
	TSharedPtr<FExtensibilityManager> MenuExtensibilityManager;
	TSharedPtr<FExtensibilityManager> ToolBarExtensibilityManager;

	TArray<TSharedPtr<IAssetTypeActions>> CreatedAssetTypeActions;
};

#undef LOCTEXT_NAMESPACE

IMPLEMENT_GAME_MODULE(FCustomAssetEditorModule, CustomAssetEditor);

Finally, your FCustomAssetEditorModule should now look something like the above. With everything set up we can now start testing our Asset Type Action and Custom Asset Editor within our Unreal Engine 4 project. You can now recompile the code and start the editor.

Testing the Custom Asset Editor in Unreal


If we open up the editor and right-click the content browser, we can navigate to the miscellaneous category. Here you will find our custom asset with a magenta background. We create the asset and double-click it to open our custom asset editor.

Note

Please note that this editor does not look any different from the default details editor spawned when creating a custom asset. You do have a solid foundation to start extending this editor upon with your own functionality.

Conclusion

In the previous part, we have set up all the logic of our custom editor but we were not yet able to spawn it when double-clicking our asset. We created our own Asset Type Action for our custom asset and modified it to spawn our custom asset editor. We made some additional changes to our editor module implementation to register our newly created AssetTypeAction to the Asset Tools module. This allows the editor to initialize the Custom Asset Type Actions and spawn our custom editor.

Getting started with extending the editor is not the most exciting task available but it can prove extremely powerful when you want to create custom workflows and pipelines for your assets before your team enters production. If you have any questions or comments on this guide, feel free to email me at cairan.steverink@gmail.com and I will be happy to answer your questions.

Source Code

The complete source code can be found on the Github page below:
https://github.com/caiRanN/Custom-Asset-Editor-Source

Further steps

As you probably noticed, we recreated the default "simple editor" provided within the Unreal Editor. We now have a solid foundation to start extending the editor upon. You can now start improving our custom editor by building your own slate style and expose it as a tab within our editor. In a upcoming guide, I will go through the process of extending this editor with new tabs and custom dialog windows.

Further Reading and References


Share this Post: