Community Tutorial: Creating a Custom Asset Type with its own Editor in C++

In this tutorial, you will learn how to create a code plugin that adds a custom asset type (complete with its own editor) to the engine. As an example, I will walk you through the creation of a normal distribution asset type with mean and standard deviation properties that can be set in a graphical editor.

https://dev.epicgames.com/community/learning/tutorials/vyKB/unreal-engine-creating-a-custom-asset-type-with-its-own-editor-in-c

8 Likes

Great addition!

There is only one thing I thought people might miss, and that is how to declare custom categories.

Here is a simple way to do so:

void FActorInventoryPluginEditor::StartupModule()
{
	UE_LOG(ActorInventoryPluginEditor, Warning, TEXT("ActorInventoryPluginEditor module has been loaded"));
	
	FAssetToolsModule::GetModule().Get().RegisterAdvancedAssetCategory(FName("Inventory"), FText::FromString("Inventory"));

	InventoryCategoryAssetActions = MakeShared<FInventoryCategoryAssetActions>();
	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(InventoryCategoryAssetActions.ToSharedRef());
}

This is the Editor Module Startup, where this line of code is provided:

FAssetToolsModule::GetModule().Get().RegisterAdvancedAssetCategory(FName("Inventory"), FText::FromString("Inventory"));

This registers new Advanced Asset Category, which has two parameters:

  • Key: A specific and unique Name of the Category, could be anything
  • Name: A Text name which will be displayed in the Editor

In the AssetActions class the Category can be loaded easily, using the specified Key value:

if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
	{
		return FAssetToolsModule::GetModule().Get().FindAdvancedAssetCategory(FName("Inventory"));
	}

Have fun!

Dominik from Mountea Framework

5 Likes

Thank you! You’re right - advanced asset categories should probably have made an appearance. Thank you for providing an example!

1 Like

Wait, there is more!

Your great tutorial is covering how to create just the Objects, but people might be interested in creating Blueprints too!

Let’s imagine the situation. You have a very specific component or Object (in my case, it is an Inventory Item Object), but you want to show the Blueprint Editor and Event Graph.

Well, luckily for those people, there is a way! It is a little bit more complicated, however, worth the shot.

So let’s take a look, shall we?

Things we need:

  • AssetActions

  • AssetFactory

  • Helper Utilities

In AssetActions header file (there is no need for .cpp file):


class FInventoryItemAssetActions : public FAssetTypeActions_Base

{

public:

virtual UClass* GetSupportedClass() const override {return UInventoryItem::StaticClass(); };

virtual FText GetName() const override { return FText::FromString(TEXT("Inventory Item Object")); };

virtual FColor GetTypeColor() const override { return FColor::Yellow; };

virtual uint32 GetCategories() override

{

if (FModuleManager::Get().IsModuleLoaded("AssetTools"))

{

return FAssetToolsModule::GetModule().Get().FindAdvancedAssetCategory(FName("Inventory"));

}

return EAssetTypeCategories::Misc;

};

};

Nothing weird is happening here, only getting Supported Class, Name, and Colour to show in the Menu and the Category Itself (must be registered in the Module Startup!).

Let’s take a look at the Asset Factory:

Header:


UCLASS()

class ACTORINVENTORYPLUGINEDITOR_API UInventoryItemAssetFactory : public UFactory

{

GENERATED_BODY()

public:

UInventoryItemAssetFactory(const FObjectInitializer& ObjectInitializer);

virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;

virtual bool ConfigureProperties() override;

private:

// Holds the template of the class we are building

UPROPERTY()

TSubclassOf<UInventoryItem> ParentClass;

};

Constructor, Create New function, Configure Properties (important!) and private property to hold the Parent class.

Here is the implementation:


#include "InventoryItemAssetFactory.h"

#include "Utilities/ActorInventoryEditorUtilities.h"

#include "Definitions/InventoryItem.h"

#include "Kismet2/KismetEditorUtilities.h"

#define LOCTEXT_NAMESPACE "ActorInventory"

UInventoryItemAssetFactory::UInventoryItemAssetFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)

{

bCreateNew = true;

// true if the associated editor should be opened after creating a new object.

bEditAfterNew = false;

SupportedClass = UInventoryItem::StaticClass();

// Default class

ParentClass = SupportedClass;

}

bool UInventoryItemAssetFactory::ConfigureProperties()

{

static const FText TitleText = FText::FromString(TEXT("Pick Parent Item Class for new Inventory Item Object"));

ParentClass = nullptr;

UClass* ChosenClass = nullptr;

const bool bPressedOk = FActorInventoryEditorUtilities::PickChildrenOfClass(TitleText, ChosenClass, SupportedClass);

if (bPressedOk)

{

ParentClass = ChosenClass;

}

return bPressedOk;

}

UObject* UInventoryItemAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)

{

// Something is not right!

if (ParentClass == nullptr || !FKismetEditorUtilities::CanCreateBlueprintOfClass(ParentClass))

{

FFormatNamedArguments Args;

Args.Add(TEXT("ClassName"), ParentClass ? FText::FromString(ParentClass->GetName()) : NSLOCTEXT("UnrealEd", "Null", "(null)"));

FMessageDialog::Open(EAppMsgType::Ok, FText::Format(NSLOCTEXT("UnrealEd", "CannotCreateBlueprintFromClass", "Cannot create a blueprint based on the class '{0}'."), Args));

return nullptr;

}

// Create a new Blueprint

return FKismetEditorUtilities::CreateBlueprint(

ParentClass,

InParent,

Name,

BPTYPE_Normal,

UBlueprint::StaticClass(),

UBlueprintGeneratedClass::StaticClass(),

NAME_None

);

}

#undef LOCTEXT_NAMESPACE

What is in included:

  • Header file of this Factory Class

  • Helper Utility library (will cover later on)

  • Class to be created (Inventory Item in this case)

  • Kismet Editor Util for the magical stuff!

Constructor

Nothing fancy here to see, just allowing the asset to be created, edited and assign the class.

Configure Properties

Here is the magical part, so let’s break it down a little.

  • Title Text is what will be displayed in the Class selector

  • Chosen Class is by default nothing, so nullptr, will be used n the Utilities

  • bPressedOk defined whether we have tried to press OK or not, which is only allowed if a class is selected

And we are calling the EditorUtilities::PickChildrenOfClass(TitleText, ChosenClass, SupportedClass) here, which will be covered soon.

Factory Crate New

The final step here, the juicy one! Here we are asking whether we can create the blueprint, either by having the Parent class or by that class not being allowed to be created (like abstract ones).

If we cannot create the class, we create an error message to be displayed and return nullptr.

If we can create the class, we simply call the function FKismetEditorUtilities::CreateBlueprint which creates the Blueprint class for us. Done!

Utilities

Header


#include "CoreMinimal.h"

/**

*

*/

class FActorInventoryEditorUtilities

{

public:

// Helper function which defined what classes are available to class selector

static bool PickChildrenOfClass(const FText& TitleText, UClass*& OutChosenClass, UClass* Class);

};

Static function `PickChildrenOfClass` which returns boolean. This is mostly data for `Slate` setup, waiting for OK to be pressed.

Implementation:


#include "ActorInventoryEditorUtilities.h"

#include "K2Node_Event.h"

#include "Factories/ActorInventoryClassViewerFilter.h"

#include "Kismet2/BlueprintEditorUtils.h"

#include "Kismet2/KismetEditorUtilities.h"

#include "Kismet2/SClassPickerDialog.h"

bool FActorInventoryEditorUtilities::PickChildrenOfClass(const FText& TitleText, UClass*& OutChosenClass, UClass* Class)

{

// Create filter

TSharedPtr<FActorInventoryClassViewerFilter> Filter = MakeShareable(new FActorInventoryClassViewerFilter);

Filter->AllowedChildrenOfClasses.Add(Class);

// Fill in options

FClassViewerInitializationOptions Options;

Options.Mode = EClassViewerMode::ClassPicker;

Options.ClassFilter = Filter;

Options.bShowUnloadedBlueprints = true;

Options.bExpandRootNodes = true;

Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::Dynamic;

return SClassPickerDialog::PickClass(TitleText, Options, OutChosenClass, Class);

}

As you can see here, we are creating a new Filter, which defines what class to display (all child classes from that class will be displayed, filter parameters can be changed by updating the Struct FClassViewerInitializationOptions Options;).

And that shall be it all! This way you can create the Blueprint class, not just an Object! This Blueprint class can be Actor, ActorComponent or anything that inherits from UObject. And you can create Blueprint nodes for those assets!

If you are interested in my implementation, here is the repository link: ActorInventoryPlugin/Source/ActorInventoryPluginEditor at main · Mountea-Framework/ActorInventoryPlugin · GitHub

This is the Editor Module, so there shall be everything covered! If you have any questions, or might be interested in helping out with that Open-Source project, feel free to ask me on GitHub!

Cheers,

Dominik

1 Like

Thank you for this additional info! Though I’m not quite sure I see the benefit of using a custom asset type to create a blueprint vs. simply using Unreal’s standard new blueprint functionality in the editor and choosing your blueprintable class (e.g. UInventoryItem) as a base?

The only advantage is simplicity. You don’t have to pick manually anything, just right click and selected.
In some cases it is more convenient, let’s say with DataAssets. You have to select specific parent class when creating the Data Asset. This way, you always create the one you wanted.

3 Likes

I have loved this tutorial, it’s the little things in game dev (seeing your own asset in the context menu) that just make it all worth it sometimes.

I am having one issue that I’m wondering if anyone in here might know the solution to. I have an FText in my custom data class that I want to have my asset editor display and edit as a Multiline Editable Textbox, rather than the standard text box. Any Ideas how I would go about this?

1 Like

If you add meta=(MultiLine=true) argument to the UPROPERTY macro on the field on your asset type (as long as it also has EditAnywhere argument as well), it should render as a multiline editable textbox.

Source: All UPROPERTY Specifiers · ben🌱ui

In general, you can customize the controls used in the asset editor (and any details panes in general) for your custom classes/structs using the Details Customization functionality in the engine. See:

3 Likes

should i create a class that inherent from class viewer filter to be able to make my class filter for the shared pointer here ?

I am currently working on a plugin and I am wondering if it is possible to create “nested categories”. What I mean by that is instead of having a single category like for example “Inventory”, I would like to have another category in that category, for example: “Inventory → Items”. Do you know (or anyone else) how I would achieve something like this? I have played around with Asset Tools for a bit but was not able to find a solution so far.