Download

[Tutorial + GitHub] Value preserving instanced properties

Hi all,

Today I wanted to share something we did for the project I am currently working on…

Instanced properties are not-so-well-known yet very powerful feature of the Unreal Engine. They allow you to instantiate objects right from the editor, build complex nested structures in-place and use polymorphism as they allow you to instantiate derived classes for a base class property.

Here’s an example of a class that has an instanced property:


UCLASS(abstract, editinlinenew) class UInstancedBase : public UObject
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="Base")
    FString CommonProperty;
};

UCLASS() class UInstancedTypeA : public UInstancedBase
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="TypeA")
    int32 IntProperty;
};

UCLASS() class UInstancedTypeB : public UInstancedBase
{
    UPROPERTY(editanywhere, category="TypeB")
    float FloatProperty;
};

UCLASS() class ATestActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="Test", instanced)
    UInstancedBase* TestProperty;
};

If you place the Test Actor on a level in the Editor you will see the Test Property with a dropdown list that would allow you to pick either “Instanced Type A” or “Instanced Type B” and edit their properties in-place.

While this can be extremely useful there’s one serious problem.

Imagine that you’ve been using the “Instanced Type A” for some time and then decided to switch to using “Instanced Type B” for any reason. If you go to your actor, open the class picker again and pick “Instanced Type B” you will change the class but you will lose all values you had entered previously including the value of the Common Property that is shared between two classes!

This may seem silly with this trivial example but imagine if UInstancedBase had a dozen of properties which would all be lost.

The solution for this problem is modifying the Property Editor so that when picking a new class it would copy all common properties (found in the common ancestor shared by both classes) from the previous instance to the newly instantiated object.

The simplest implementation would just take the old object and copy common values to the new object. However this would change the behavior for all classes including the engine-defined Actors and Objects. This may be dangerous because some objects expect to get notified whenever their values are modified by the editor - they achieve that by overriding PostEditChangeProperty() function. Now if we just blindly copy old values to the new object it would not get PostEditChangeProperty() called and may end up in an incorrect state!

We avoided that by adding a new class directly to the Engine:


UCLASS(abstract, editinlinenew, minimalapi) class UEntity : public UObject
{
    GENERATED_BODY()
};

This class would serve as a base for all instanced objects specific for our project. When switching classes the Property Editor would check whether the Common Class for the old class and the new class is a UEntity and only then it would copy properties.

The previous example can now be fixed like this:


#include "Engine/Entity.h"

UCLASS(abstract) class UInstancedBase : public UEntity
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="Base")
    FString CommonProperty;
};

UCLASS() class UInstancedTypeA : public UInstancedBase
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="TypeA")
    int32 IntProperty;
};

UCLASS() class UInstancedTypeB : public UInstancedBase
{
    UPROPERTY(editanywhere, category="TypeB")
    float FloatProperty;
};

UCLASS() class ATestActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(editanywhere, category="Test", instanced)
    UInstancedBase* TestProperty;
};

If you go to the editor now, instantiate “Instanced Type A” then modify Common Property and then switch to “Instanced Type B” you will see that Common Property has preserved its value.

Hope this is useful!

~Robert

GitHub: https://github.com/nbjk667/UnrealEngine/compare/master...nbjk667:instanced