Adding New Asset Types

Article written by Alex K.

It’s not uncommon for you to have some custom data for your project that you need to edit, store, and associate with different objects. Rather than represent this data as an existing asset type such as a data table, you can set up your own custom asset type and fully control how it’s created, represented, and used within your project. Editor factories let you set up your own custom import behavior to read in data from other applications, and give you the freedom to add a workflow for creating and even exporting new assets within Unreal.

Defining the class

The first step to creating your own data asset will be to define the actual class that stores the data. You can add any UProperties you need to associate different information with your data asset, keeping in mind that each time a new asset of your type is created or imported, it’ll have its own set of these properties.

UCLASS()
class MYMODULE_API UMyCustomData : public UObject
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    float myFloat;

    // Add properties here
   
};

Creating the factories

Next, you’ll want to create factories to support importing your asset type, as well as creating new assets within the editor. You can implement one or both, depending on what makes sense for your asset type. Asset factories will be automatically discovered, and you can specify an ImportPriority if you want to be sure that your factory will have a chance to process a given file extension before some other factory does. First, you’ll want to set up the constructor and specify some options for how the factory should be treated. You can look at the header for UFactory for more details on what these different switches control.

UMyCustomDataFactory::UMyCustomDataFactory(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    SupportedClass = UMyCustomData::StaticClass();
    Formats.Add(TEXT("myasset;My custom asset extension"));

    bCreateNew = true;
    bText = false;
    bEditorImport = true;
    bEditAfterNew = false;
}

If you want to import assets from some external file, you’ll want to implement the UFactory functions FactoryCanImport and FactoryCreateBinary. FactoryCanImport will be used by the editor to see if your factory is eligible for importing the file. It may be sufficient to check the file extension to determine if the file being imported should be handled by your factory, or in some cases (such as with FBX files) you may actually need to inspect the contents of the file to route it to the right importer.

bool UMyCustomDataFactory::FactoryCanImport(const FString& Filename)
{
    const FString Extension = FPaths::GetExtension(Filename);

    if( Extension == TEXT("mydata") )
    {
        return true;
    }
    return false;
}

In FactoryCreateBinary, you’ll create the actual asset in the content browser, given a buffer containing the file contents so you can parse out whatever data you’re interested in.

UObject* UMyCustomDataFactory::FactoryCreateBinary(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
{
    UMyCustomData* CustomData = NewObject<UMyCustomData>(InParent, InClass, InName, Flags);

    //Do whatever initialization you need to do here

    return CustomData;
}

It may also be useful to implement ConfigureProperties, which is where you can create a popup dialog window to designate some import settings before the asset is imported.

If your asset factory should support creation of new assets within the editor, two additional functions should be implemented. ShouldShowInNewMenu will determine if the type should appear in the editor’s “Add New” menu, and can just return true or false. FactoryCreateNew is similar to FactoryCreateBinary above, except that you won’t have a loaded file and instead will be creating a fresh asset.

Customizing the asset type

You can now import your asset or create a new asset in the editor, but you’ll likely want to apply some more customization to sort it into the proper category and affect how it’s displayed in the content browser. To do this, you’ll write an AssetTypeActions class, which you can register early on in your module’s StartupModule function.

IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
AssetTools.RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_MyCustomData));

For the implementation, you’ll extend FAssetTypeActions_Base and implement whatever functions you need to apply the desired customization.

class FAssetTypeActions_MyCustomData : public FAssetTypeActions_Base
{
public:
    // IAssetTypeActions Implementation
    virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "FAssetTypeActions_MyCustomData", "MyCustomData"); }
    virtual FColor GetTypeColor() const override { return FColor(0, 255, 255); }
    virtual UClass* GetSupportedClass() const override { return UMyCustomData::StaticClass(); }
    virtual uint32 GetCategories() override { return EAssetTypeCategories::Gameplay; }
    virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
    virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return true; }
    virtual void GetActions(const TArray<UObject*>& InObjects, FMenuBuilder& MenuBuilder) override;
};

Many of these are straightforward, while a few let you dive deeper into customizing how your asset interacts with the rest of the engine. For example, HasActions/GetActions will give you a chance to add buttons to the asset’s context menu that you might want to use to execute custom actions on that asset. OpenAssetEditor will let you wire your asset into an entire custom asset editor if you create one.

What’s next?

While creating a custom asset editor is beyond the scope of this article, you can take a look at FAssetTypeActions_DataTable to see how we create a custom data table editor in OpenAssetEditor and use it to modify the data asset without having to update the data externally and reimport the asset. If you want to support further interoperability between the editor and the applications where you’re authoring your data, you can also write your own UExporter class that defines how the editor will take an asset from the content browser and generate a file on disk that you can import elsewhere. There’s quite a bit of flexibility in how you import and manage data in the editor, and plenty of UFactory implementations included in the engine that you can look to for examples of how to turn your data into something readable and editable within Unreal.

4 Likes

Thank you, this helps! Could you give me a hint how to display the user a message if he drags in a file which is already impotred to ask him if he wants to overwrite the file?

You could start by checking this docs page out and seeing if there’s a hint for you in there. Beyond that, I would ask over in the Editor Scripting forum.

1 Like