UPROPERTY member vars reset to NULL by ObjectInitializer

In fact, the solution has been around for a long time, and the engine developers are using it.
How this is implemented can be viewed in USplineComponent:

If we are talking about components, then in order to prevent resetting property values to predefined ones, we need to do 3 steps:

  1. We need to define the FMyComponentInstanceData structure, which will store the values of properties that need to be restored when the component is recreated:
USTRUCT()
struct FMyComponentInstanceData : public FSceneComponentInstanceData {
    GENERATED_BODY()

public:
    // As an example, let's take a reference to instance of another component.
    // We will store 2 property values: current property value, and property value before user
    // construction script:

    /** Current property value. */
    UPROPERTY()
    TObjectPtr<UOtherComponent> ReferenceToOtherComponent;

    /** Before user construction script property value. */
    UPROPERTY()
    TObjectPtr<UOtherComponent> ReferenceToOtherComponentPreUCS;

public:
    FMyComponentInstanceData() {}
    explicit FMyComponentInstanceData(const UMyComponent* SourceComponent)
        : FSceneComponentInstanceData(SourceComponent) {}
    virtual ~FMyComponentInstanceData() = default;

    virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override {
        Super::ApplyToComponent(Component, CacheApplyPhase);
        CastChecked<UMyComponent>(Component)->ApplyComponentInstanceData(
                this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript));
    }
};
  1. Define the method in our component that will create and fill this structure:
virtual TStructOnScope<FActorComponentInstanceData> GetComponentInstanceData() const override
{
	TStructOnScope<FActorComponentInstanceData> InstanceData = MakeStructOnScope<FActorComponentInstanceData, FMyComponentInstanceData>(this);
	FMyComponentInstanceData* MyComponentInstanceData = InstanceData.Cast<FMyComponentInstanceData>();
	
    //	Store the property value
    MyComponentInstanceData->ReferenceToOtherComponent = ReferenceToOtherComponent;

	return InstanceData;
}
  1. Define the method in our component that will restore the properties when component is recreated:
void ApplyComponentInstanceData(struct FMyComponentInstanceData* ComponentInstanceData, const bool bPostUCS)
{
    if (bPostUCS)
    {
        // Here we can handle changes in user construction script if needed.
        // If the property was changed in the user construction script, then there 
        // is no need to restore the property value:
        if(!IsReferenceToOtherComponentChangedInUCS())
        {
            // Restoring property value
            ComponentInstanceData->ReferenceToOtherComponent= ComponentInstanceData->ReferenceToOtherComponentPreUCS;
        }
    }
    else
    {
        ComponentInstanceData->ReferenceToOtherComponentPreUCS = ReferenceToOtherComponent;
    }
    
    ReferenceToOtherComponent = ComponentInstanceData->ReferenceToOtherComponent;
    
    SomeComponentUpdatesRelatedToPropertyChange();
}

Note:

  • In this example, it is assumed that UMyComponent is a descendant from the USceneComponent class. Otherwise, creating and filling of the FMyComponentInstanceData structure may be somewhat more difficult to take into account the algorithm for restoring the properties of the parent component class.

  • The UMyComponent class has the TObjectPtr<UOtherComponent> ReferenceToOtherComponent property, which refers to an instance of another component.

  • The UMyComponent::IsReferenceToOtherComponentChangedInUCS() method code is not given because it depends on the meaning of the property being restored and the logic of the component (let me remind you once again that you can spy on the example of the implementation in the USplineComponent class in the source code of the engine).

I’ve been pulling my hair out with this on 5.1. Well, that’s not all true. I’m bald. But still.

Here’s one solution that worked for me.

I renamed the property (create a define or something to avoid modifying too much code) and make sure you use a name you will never use. Then compile and open all the instances that used the component. Maybe you can just open the asset itself, but I just wanted to be sure. Change anything in the component and change it back so that you can resave it. I’m thinking it will now remove the old property from being serialized.

Close everything down and rename it back. Rebuild and relaunch.

It now works for me.

I think the temporary name is now serialized which is why you should have used something that you will never used. You can resave just to be sure.

edit: Never mind. It worked for a while and then it reverted to being null at some point.
edit2: Ah, it’s that there seems to be different serialization for debug and release.

1 Like

In my case, I was using the wrong way of constructing a UObject in the constructor. I was using NewObject but I needed to be using CreateDefaultSubobject. This post on SO set me straight.

1 Like

It works for me.
Epic, work too!

Confirming - there still this problem. The code which was working for a long time suddenly got broken and my object becomes creating with fields reinitialized by default object.

The fix proposed in this thread does solve my problem. In my case:

  1. Just reparent your BP class to the basic UE (AActor in my case)

  2. Compile / save. Your BP class may become broken - it’s OK.

  3. Restart editor

  4. Reparent you BP class to the correct basic class

  5. Compile / save. Say Halleluya!

2 Likes

Thank you. This worked for me.