Instanced UObject on component not duplicated with component

Instanced UObjects should be instanced with the parent object, but this doesn’t happen if the object is in a component which is duplicated in the blueprint editor.

UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class UTestEditInlineObject : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, Category=Test)
	float TestProperty = 0.5f;

	UPROPERTY(EditAnywhere, Category=Test)
	bool bTestProperty2 = false;
};

UCLASS(BlueprintType, meta = (BlueprintSpawnableComponent))
class UTestComponent : public USceneComponent
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, Instanced, Category=Test)
	UTestEditInlineObject* EditInlineObject;
};
  1. Create an actor blueprint and add a “Test” component.
  2. Add a “TestEditInlineObject” as the “Test” component’s “EditInlineObject” in its details panel.
  3. Select the “Test” component and duplicate it (Ctrl+w)
  4. The new component (“Test1”) now has an “EditInlineObject” property pointing to the original component’s instance. Changing properties on either component affects the object on the other component. This link can be broken by clearing the object on the second component and recreating it, at which point a normal instance will be created.

If this goes undetected and you create instances of the actor in the world, the duplicated components will still be pointing to the object instance from the first component in the blueprint, so editing properties on the duplicated component’s object instance will actually change the blueprint’s object’s properties (and affect all other instances).

Obviously I think this is an engine bug, but I’d really like a workaround for 4.26 if possible. Can I detect the duplication and fix up the objects manually?

Workaround I came up with is to detect this case and manually duplicate the object in Serialize():

void UTestComponent::Serialize(FArchive& Ar)
{
	Super::Serialize(Ar);

	if (Ar.IsLoading())
	{
		// Check the subobject
		if (EditInlineObject && EditInlineObject->GetOuter() != this)
		{
			UE_LOG(LogTemp, Log, TEXT("Serialize(): (post-load) %s got inline object %s with a different outer: %s. Fixing up"), *GetName(), *EditInlineObject->GetName(), *GetNameSafe(EditInlineObject->GetOuter()));

			const FName DesiredName = EditInlineObject->GetFName();
			UTestEditInlineObject* NewEditInlineObject = DuplicateObject(EditInlineObject, this, DesiredName);
			EditInlineObject = NewEditInlineObject;
		}
	}
}

Serialize() gets two loading calls for the duplication, one for the GEN_VARIABLE and another for the actual (?) component. This fixes it up for the GEN_VARIABLE, which apparently also fixes it up for the main component.

Have you tried adding the DuplicateTransient specifier to the EditInlineObject property?

2 Likes