Variable in child blueprint does not derive from parent blueprint after restarting the editor

I know that this topic has been brought up in the past, and I’ve been following the recommendations outlined in the Investigating Blueprint Data Loss Issues guide. However, even after making the suggested engine changes and setting data change breakpoints to observe what’s been going on, I have not been able to figure out why a child blueprint does not derive from its parent blueprint after restarting the editor.

Let me explain my particular case. I have a blueprint actor with a component. That component contains a UProperty that is a TArray of a UStruct (let’s call the array Attacks). That UStruct contains a UProperty of UStruct, and that UStruct contains a UProperty of a TObjectPtr to a class that derives from UDataAsset (let’s call this property ImpactConfig).

I have a base blueprint actor (let’s call it Melee_T0_DA) and a child blueprint actor (let’s call it Melee_T1_DA) that derives from Melee_T0_DA. I make a change to the first entry of T0_DA’s Attack array, changing the ImpactConfig variable from Melee_Impact_DA to FireMelee_Impact_DA. Making this change on the base class means that in Melee_T1_DA, the first entry of its Attacks array should also update its ImpactConfig variable to match its parent’s (FireMelee_ImpactConfig_DA) instead of the default (Melee_ImpactConfig_DA). Yet, when I restart the editor, the ImpactConfig variable of the derived class is the default value instead of its parent’s value.

I have set breakpoints in the PostConstructInit function, and after opening Melee_T1_DA in the Content Browser, I verified that Melee_T1_DA does initially have its ImpactConfig variable set to FireMelee_ImpactConfig_DA after InitProperties. This at least confirms that the base CDO is copying its values to the derived CDO. However, after setting a data change breakpoint on ImpactConfig, it gets triggered twice. The first is when the memory is cleared from an EmptyAndAddValue call from FArrayProperty’s SerializeItem function, and the second is after a Inner->SerializeItem call in that same FArrayProperty::SerializeItem call (screengrab of callstack included).

Here’s exactly where the value gets stomped in FObjectProperty::SerializeItem.

FObjectHandle OriginalHandle = ObjectPtr->GetHandle();

Slot << *ObjectPtr;

I believe this is where the problem begins--in UStruct::SerializeVersionedTaggedProperties

FStructuredArchive::FSlot ValueSlot = PropertyRecord.EnterField(TEXT(“Value”));

uint8* DestAddress = Property->ContainerPtrToValuePtr<uint8>(Data, Tag.ArrayIndex);

uint8* DefaultsFromParent = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Tag.ArrayIndex);

// This property is ok.

Tag.SerializeTaggedProperty(ValueSlot, Property, DestAddress, DefaultsFromParent);

When I cast Data to UObject* prior to the Tag.SerializeTaggedProperty call, it has the correct value (FireMelee_Impact_DA). After the call, it gets replaced with the default value (Melee_Impact_DA). It appears that ValueSlot has the older default value, and that gets used to overwrite what was originally stored in DestAddress/Data.

There are no subsequent calls that tries to fix this. After this, the Melee_T1_DA blueprint is open in the Blueprint Editor and its ImpactConfig variable is set to Melee_Impact_DA instead of FireMelee_Impact_DA.

I did note in the guide how blueprint actors that contain circular dependencies can cause issues, so I performed two checks. I confirmed in the Reference Viewer that the reference direction only goes one way (from Melee_T1_DA to Melee_T0_DA). Furthermore, I set breakpoints on the constructor for when those specific blueprint actors were created, and verified that there was only one call to LoadPackageInternal on the callstack for both actors. I’m certain that the blueprint actors don’t have circular dependencies, but if there are other ways to check, I’m more than willing to try them out.

Do you have any suggestions on what else I can do to help me get to the bottom of this?

[Attachment Removed]

Hey Jonathan, I’m sorry it took this long to respond. I wrote that guide, so I’m happy to hear you’ve ruled out quite a few causes already. Circular dependencies doesn’t sound like the issue here.

Reading the description of your issue, I wonder if you are “simply” encountering the array limitations described in UE-96195. I suspect the child blueprint Melee_T1_DA has been resaved with the old value Melee_Impact_DA. That’s why on reload, it first adopts the parent’s value FireMelee_Impact_DA because inheritance is always applied. Then Melee_T1_DA is applied because that’s what the asset has been saved with, as an override.

If you can reproduce this problem at will, here is something you could that would be useful to know:

  • Open the base blueprint (“Melee_T0_DA”)
  • Open the child blueprint (“Melee_T1_DA”) to guarantee that it’s already loaded
  • Change FireMelee_Impact_DA on Melee_T0_DA to anything else
  • See if the derived BP Melee_T1_DA has been updated. There are 3 outcomes I can think of and it will be interesting to hear which happens:
    • If Melee_T1_DA isn’t updated
      • It’s either a bug in editor propagation code. So when you save the child BP without realizing it still has an old value, that old value is now saved as an override.
      • Or the child already is considered as overriding the property. Note that if the child has any modification in any place in the component’s array, then it won’t inherit any parent value anymore. Array inheritance is all or nothing.
    • If Melee_T1_DA is updated…
      • I’m curious whether saving or not saving Melee_T1_DA breaks inheritance on editor restart. Can you tell me which is the case?

Can you try that out and see which scenario is happening?

[Attachment Removed]

So Melee_T1_DA is updated immediately if it’s loaded, but if not saved, it won’t inherit the latest value on editor restart. That’s good to know, to categorize the type of problem.

Within your array of all “Attacks”, has “Melee_T1_DA” made any change at all in any of the entries? If not, I would expect the entire array to inherit from “Melee_T0_DA” on editor restart. However, if “Melee_T1_DA” has modified anything in the array in any way, that means the entire array will be serialized in “Melee_T1_DA” and those values are what will be reloaded.

Our offices will be closed for the coming two weeks, so I’ll follow up with you in the new year. Happy holidays!

[Attachment Removed]

Hello and happy new year!

Thanks for that latest information. So, since Melee_T1_DA has already made a modification to the array, it won’t inherit values anymore from the parent on reload. It is very unlikely that we’ll change the array inheritance behavior in UE5 even though “per array item inheritance” comes up as a feature request quite a lot.

I realize this behavior is extra confusing since the values do propagate from Melee_T0_DA to Melee_T1_DA while the editor is open. It would have been better if the propagation and reload behaviors were the same. I’ll ask internally whether we can fix that unexpected propagation behavior, based on the rule that arrays as a whole are either still inheriting or aren’t.

On your end, I think it’s best to educate the team about this quirk. If you want per-array-element inheritance, the GAS UGameplayEffect class implements that for UGameplayEffectComponent in a custom way. There is quite some engineering overhead, but it’s a good example of how you can achieve flexibly inheriting arrays.

[Attachment Removed]

Happy to think along and share that info. I’ll close this case, but if you have follow-up questions feel free to respond here.

[Attachment Removed]

Hi, Zhi! Thank you for taking the time to respond to my question. I appreciate it.

I followed your instructions, and I agree with you that this issue is most likely caused by the array limitations described in UE-96195.

To recap, Melee_T0_DA had its Impact Config variable (which is inside the first element of an Attacks array) changed from Melee_Impact_DA to FireMelee_Impact_DA. Melee_T0_DA and Melee_T1_DA were both open when this change was made, and Melee_T1_DA’s Impact Config variable was updated to match its parent. I saved and checked out Melee_T0_DA (but not Melee_T1_DA), then restarted the editor. Upon opening those assets again after the restart, Melee_T1_DA’s Impact Config no longer matched its parent’s--it reverted back to the old Melee_Impact_DA value.

Following your instructions, I changed Melee_T0_DA’s Impact Config variable to CorrosiveMelee_Impact_DA while both assets were open. Melee_T1_DA’s Impact Config did not change, remaining on Melee_Impact_DA. This occurred even after I blueprint compiled and saved Melee_T0_DA.

I think Melee_T1_DA’s Impact Config variable (Melee_Impact_DA) is considered an override, which explains why the array inheritance doesn’t happen. This is further confirmed when I reset Melee_T1_DA’s Impact Config variable to its default value--it changes it to CorrosiveMelee_Impact_DA, which matches that of its parent’s variable (it also asks that I check out Melee_T1_DA)!

[Attachment Removed]

You would be right again. In Melee_T0_DA and Melee_T1_DA, Impact Config is part of a larger struct called Hit Config (which is in the Attacks array), and that struct also has an array of Impact Effects, which has two elements that are different from its parent (they have T1 values instead of T0 values). Because of those overrides, the entire array element is serialized and is what is being reloaded.

[Attachment Removed]

Thank you for your response and help. I will definitely educate the team that this is by design, and that they’ll have to be careful how they design their data structures.

[Attachment Removed]