Apply Instance Changes to Blueprint with UObject references

Hello there,

I have an Actor that has an array of UObjects.

UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, meta=(EditInline), Instanced, Category=Events)
TArray<USplineEvent*> SplineEvents;

I can populate these in the actor instance in the viewport, but when I apply instance changes to blueprint to create a prefab the array gets reset and the UObject instances aren’t stored. Is there any way to achieve this?

I think you can just right click copy the array and then paste it in the BP defaults array

It looks like you cannot copy object instances like that.

I am digging around in the ApplyInstanceChangesToBlueprint function, and one detail caught my eye

const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties);

Both of these options were excluding my object instances. But even after making a copy of the function and removing those flags just to try things out, I still end up with an array that has a bunch of null objects

You can for sure copy instanced objects, I have done it in the past on multiple occasions
I actually just tried as well, with an Instanced TArray, and I could copy and paste objects normally

(Even between blueprints I can copy just fine)

You might have to change your property to be EditAnywhere, perhaps that will solve the apply instance changes to blueprint issue as well

Then I don’t understand what I’m doing wrong. I tried to reproduce a simple case to get the point across on what I want to do.

No matter what, the Blueprint itself cannot hold an instance, and I guess that makes sense to some degree, since instances are memory data, and I guess Unreal does not handle those by default through reflection? This is just an assumption on my end thus far.

So unless there’s a way to have this work out of the box (or with some work arounds) I’ll have to create my own reflection/construction methods to store the data in Blueprints.

I’m confused, you have an instanced array, and you are testing with an object instance variable instead. If you mark a uobject property instanced in C++, then you will be able to create an object like I showed you in the video, both in the default object (blueprint), and in instances. The variable will be a drop down where you will be able to select a base or template object that fits the type of the property, and it will automatically duplicate the selected object, and save it to the property. For that to work though, your object class needs to have the EditInLineNew metadata:
image

Then you also won’t need to create the object in the construction script

Yeah, i’ve just been testing different cases. Looks like this is not supported through pure BP. I kept playing around and with

Snippet
UPROPERTY(Instanced, EditAnywhere, BlueprintReadOnly) TArray<UTestClass*> UObjectArray;

and

Snippet
UCLASS(DefaultToInstanced, EditInlineNew, Blueprintable, BlueprintType) class UTestClass : public UObject

It does work as you explain. However: UObject instances are completely and entirely ignored by “Apply Changes to Blueprint” telling me
image

After digging into it somewhat, it turns out that the process in CopyActorProperties is deliberately skipped for instanced variables.

Interesting also:
If I create a USTRUCT(), obviously all properties can be modified and propagated to the BP with Apply Changes. But as soon as an instance of a UObject is added to the struct, none of the values are considered for copying. Weird that. But since that’s not my use-case I didn’t investigate further.

My next Attempt was to create a new Blueprint asset (based on the UObjects class) from the instanced object to generate a sort of prefab, but turns out I’m running into the same problem here. I’ll have to manually go into the Blueprint Asset and change the properties using reflection. I’m currently looking into how to do that. There doesn’t seem to be a good comprehensive guide or document on how to work with FProperties, and the forums are full of threads with loose snippets… Difficult topic to get a grasp on x)

Since you are able to copy and paste those instanced objects though, why don’t you manually do that to transfer it to the BP default? Sure, it’s not optimal, but it can be done still, and it’s not too much of an effort to copy and paste, and I think it will solve your problem, unless I misunderstood what you want to do. You should be able to copy the array of uobjects from the array of the instance, and then paste them in the BP default array, which should then be propagated to all instances after that

I guess that could be done, but who wants to do manual labor when you can have a button do it? :slight_smile:

This is an operation with the potential to be used frequently in the project I’m working on, and i’d rather make the workflow as smooth as possible, and additionally reduce the amount of user error.

I found something that works. No idea whether it’s good practice, but it seems functional for now. Was not easy to find a way to generically read properties from one object and write to another without setting up a case for each FProperty Type.

void UUtilityFunctionLibrary::WriteValuesToBlueprint(UBlueprint* Blueprint, UTestClass* Source)
{
	UClass* SourceClass = Source->GetClass();
	auto CDO = Blueprint->GeneratedClass->GetDefaultObject();
	UClass* TargetClass = CDO->GetClass();
	
	if(SourceClass && TargetClass && (SourceClass == TargetClass))
	{
		for (TFieldIterator<FProperty> it(TargetClass); it; ++it)
		{
			FProperty* Property = *it;
			FProperty* SourceProperty = Source->GetClass()->FindPropertyByName(Property->GetFName());
			
			if(Property && SourceProperty)
			{
				auto PropertyValue = Property->ContainerPtrToValuePtr<void>(CDO);
				auto SourcePropertyValue = SourceProperty->ContainerPtrToValuePtr<void>(Source);
				Property->CopySingleValue(PropertyValue, SourcePropertyValue);
			}
		}
	}
	
	Blueprint->MarkPackageDirty();
	FKismetEditorUtilities::CompileBlueprint(Blueprint);
}

Glad I came across this bit, might come in handy in other Projects as well.

UCLASS(DefaultToInstanced, EditInlineNew, Blueprintable, BlueprintType)
class UTestClass : public UObject
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere)
	float FloatValue;

};

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

	UPROPERTY(Instanced, EditAnywhere, BlueprintReadOnly)
	TArray<UTestClass*> UObjectArray;
};
1 Like