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
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
Thank you! You’re right - advanced asset categories should probably have made an appearance. Thank you for providing an example!
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
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.
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?
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:
- Customizing Details & Property Type panel - tutorial | Unreal Engine Community Wiki
- Details Panel Customization in Unreal Engine | Unreal Engine 5.1 Documentation
- Customizing Detail Panels | Unreal Engine Community Wiki
-
Engine\Source\Editor\DetailCustomizations\Private\DetailCustomizations.cpp
in the engine source code
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.