Customizing an asset is a very commonly used operation in Unreal Engine, for example, we use UDataAssets for those simple, data-only assets.

But somethings, what we need is not simply data-only asset, we need more complex, editable asset, a classical example is the behaviour tree, which is an asset which a graph editor.

For some reasons, we need to customizing assets and providing editor to them, for example, the dialogue system in the Witcher : Wild Hunt is based on an asset with a graph editor and a timeline editor.

1. What is an asset

to be short, an asset in Unreal Engine must be a UObject, all contents in Content Browser are all assets, even if it is only an image shown in the Content Brwoser, it is an encapsuled UObject.

animation sequencer for example, is an asset:

An assst perhaps has no editor, actually, every UObject you created has an oppotunity to be an asset. However, define asset like this is useless for us. Here, we define assets as those can be shown in Content Browser and editted by an editor, no matter an original editor or customizing editor. It is not an academical conception, it is just for convinience.

In this post, we declare a simple UObject:

UCLASS(Blueprintable, BlueprintType)
class ADVANCEDALS_API UOurAssetsObject : public UObject
{
	GENERATED_BODY()

public:

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Our Assets")
	FName AssetsName;
};

This UObject only contains a FName member, you can use UDataAssets to edit it for now, but it very be different soon. There is no essential difference between Assets And UObjects, what makes it special is usually the editor, not the class itself.

2. Assets Action

Now that all the assets are UObject, why some can be created in the menu shown after you clicked right button of the mouse in the Content Browser, while your costom assets cannot? Actually, Unreal uses a speical class called AssetTypeActionDefinition to do that, which will be introduced in the following.

Before we start this, create a editor module first, otherwise you need to isolate your editor code with #WITH_EDITOR, which is complicated. And remember to import AssetTool module.

using UnrealBuildTool;

public class AdvancedAlsEditor : ModuleRules
{
    public AdvancedAlsEditor(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(
            new string[]
            {
                "Core", 
                "UnrealEd" // 注意这个UnrealEd,妈的
            }
        );

        PrivateDependencyModuleNames.AddRange(
            new string[]
            {
                "CoreUObject",
                "Engine",
                "Slate",
                "SlateCore", 
                "AdvancedALS", // 我的GAMEPLAY_PRIMARY_MODULE
                "AssetTools"
            }
        );
    }
}

Then inherits from FAssetTypeAction_Base and implement the class declaration, remember to place it in the editor module:

class ADVANCEDALSEDITOR_API FAssetTypeActions_OurAssets : public FAssetTypeActions_Base
{
public:
	FAssetTypeActions_OurAssets(EAssetTypeCategories::Type Type);
	// 返回资产的名字
	virtual FText GetName() const override;
	// 资产在Content Browser里的浏览颜色
	virtual FColor GetTypeColor() const override;
	// 该Action对应的类,在这里,就是我们的OurAssetObject
	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 MyAssetCategory;
};

I declare a private member here, which type is EAssetTypeCategories::Type, it eventually decide where the create button is in the Content Browser. It will be assigned by a parameter passed by contruct function.

This class has no property specifier, and no meta property specifier either, so, there is no magic to help you to manage it. You have to register it by yourself. We can register it while start up the module, and passing the categories at the same time:

#include "AdvancedAlsEditor.h"
#include "AssetToolsModule.h"
#include "AssetTypeActions_OurAssets.h"

#define LOCTEXT_NAMESPACE "FAdvancedAlsEditorModule"

void FAdvancedAlsEditorModule::StartupModule()
{
	RegisterAssetsAction();
}

void FAdvancedAlsEditorModule::ShutdownModule()
{
    
}

void FAdvancedAlsEditorModule::RegisterAssetsAction() const
{
	IAssetTools& AssetToolModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
	const auto Category = AssetToolModule.RegisterAdvancedAssetCategory(FName(TEXT("自定义资产")), NSLOCTEXT("Editor", "Our Defined Asset", "我们的自定义资产"));
	const TSharedPtr<FAssetTypeActions_OurAssets> AssetsTypeAction = MakeShareable(new FAssetTypeActions_OurAssets(Category));
	AssetToolModule.RegisterAssetTypeActions(AssetsTypeAction.ToSharedRef());
}

#undef LOCTEXT_NAMESPACE
    
IMPLEMENT_MODULE(FAdvancedAlsEditorModule, AdvancedAlsEditor)

Which implements is :

#include "AssetTypeActions_OurAssets.h"
#include "Tutorial/OurAssetsObject.h"

FAssetTypeActions_OurAssets::FAssetTypeActions_OurAssets(EAssetTypeCategories::Type Type)
{
	MyAssetCategory = Type;
}

FText FAssetTypeActions_OurAssets::GetName() const
{
	return NSLOCTEXT("Editor", "Out Assets Name", "我们的资产");
}

FColor FAssetTypeActions_OurAssets::GetTypeColor() const
{
	return FColor::Green;
}

UClass* FAssetTypeActions_OurAssets::GetSupportedClass() const
{
	return UOurAssetsObject::StaticClass();
}

void FAssetTypeActions_OurAssets::OpenAssetEditor(const TArray<UObject*>& InObjects,
	TSharedPtr<IToolkitHost> EditWithinLevelEditor)
{
	FAssetTypeActions_Base::OpenAssetEditor(InObjects, EditWithinLevelEditor);
}

uint32 FAssetTypeActions_OurAssets::GetCategories()
{
	return Type;
}

We haven’t finish yet, we also need a factory class, a factory will be collected and registered by Unreal Engine automatically and doestn’t need to be registered by you.

Factory must be replaced in the editor module, here is an example:

#pragma once

#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "OurAssetFactory.generated.h"

/**
 * 
 */
UCLASS()
class ADVANCEDALSEDITOR_API UOurAssetFactory : public UFactory
{
	GENERATED_BODY()

public:

	UOurAssetFactory();

	virtual bool CanCreateNew() const override;
	virtual bool ShouldShowInNewMenu() const override;
	virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
};

Its implementation is:

#include "OurAssetFactory.h"
#include "Tutorial/OurAssetsObject.h"

UOurAssetFactory::UOurAssetFactory()
{
	SupportedClass = UOurAssetsObject::StaticClass();
	bCreateNew = true;
}

bool UOurAssetFactory::CanCreateNew() const
{
	return true;
}

bool UOurAssetFactory::ShouldShowInNewMenu() const
{
	return true;
}

UObject* UOurAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags,
	UObject* Context, FFeedbackContext* Warn)
{
	return NewObject<UOurAssetsObject>(InParent, InClass, InName, Flags);
}

Restart the engine editor, you shall find:

We call OpenAssetEditor for generating an editor for our asset, and it eventually calls OpenAssetEditor of its super class, in the following posts, we will learn about how to create our custom editors.

 

 

By JiahaoLi

Hypergryph - Game Programmer 2023 - Now Shandong University - Bachelor 2019-2023

2 thoughts on “Using Slate for UI development and editor(7): Custom Assets”
  1. I’m really enjoying the design and layout of your website.

    It’s a very easy on the eyes which makes it much more pleasant for me to come here and visit more often. Did you
    hire out a designer to create your theme? Fantastic work!

Leave a Reply

Your email address will not be published. Required fields are marked *