Unexpected behaviour with UObjects created via DuplicateObject

Hey all!

I have the concept of a frame in my Visual Novel engine. Each frame holds the state of the novel at that time. So if I change say, the narration text, I duplicate the existing frame and then change the narration text property on the duplicated frame.

I’m making use of DuplicateObject() to achieve this and for the most part it works fine. I can assign different text to use as “narration” text on different frames, but when I set the scenePos enum on my 4th frame, it also sets it for every other frame previous to it. Can someone explain what is happening here? Have I made a bad assumption about UObjects being created by DuplicateObject() being completely independent from each other?

Let me know if any other source or information is required.

VNFrame.h



#pragma once

#include "Object.h"
#include "VNCharacter.h"
#include "VNFrame.generated.h"

UCLASS( BlueprintType )
class PROJECT_DREAMCATCHER_API UVNFrame : public UObject
{
	GENERATED_BODY()

public:
	UVNFrame();

	// Get a reference to a specific character by alias
	UVNCharacter* getCharacterByAlias( FString alias );

	// Text to show on screen (Dialogue/narration/etc)
	UPROPERTY( BlueprintReadOnly )
		FText displayText;
	// Person speaking the text on screen
	UPROPERTY( BlueprintReadOnly )
		FText characterSpeakingName;
	// Background image
	UPROPERTY( BlueprintReadOnly )
		UTexture2D* background;
	// Music track to play in the background
	UPROPERTY( BlueprintReadOnly )
		USoundCue* music;

	// Array of every character defined in the characters file.
	// It's possible that only a subset will be used in a scene

	UPROPERTY( BlueprintReadOnly )
		TArray< UVNCharacter* > characters;
};


VNCharacter.h



#pragma once

#include "Object.h"
#include "VNCharacter.generated.h"


// Enum for the characters position in the scene
// Left, Mid & Right are set in Blueprint with offsets handling
// multiple characters
UENUM( BlueprintType )
enum class EScenePosition : uint8
{
	SCENE_LEFT UMETA( DisplayName = "Left" ),
	SCENE_MID UMETA( DisplayName = "Mid" ),
	SCENE_RIGHT UMETA( DisplayName = "Right" ),
	NOT_IN_SCENE UMETA( DisplayName = "Not in scene" )
};

/**
 * 
 */
UCLASS( BlueprintType )
class PROJECT_DREAMCATCHER_API UVNCharacter : public UObject
{
	GENERATED_BODY()

public:
	// Default constructor
	UVNCharacter();
	// Character alias. The internal code will look for this character by alias
	UPROPERTY( BlueprintReadOnly )
		FString alias;
	// What is the current emotion displayed on the screen
	UPROPERTY( BlueprintReadOnly )
		UTexture2D* currentEmotion;
	// What is this characters position in the scene?
	UPROPERTY( BlueprintReadOnly )
		EScenePosition scenePos;
	// Add an emotion to the characters available emotions
	void addEmotion( FString& emotionName, FString& pathToEmotion2DTex );
	// Set the current emotion of the character from one of the available emotions
	void setEmotion( FString& emotionName );
private:
	// All available emotions for this character
	TMap< FString, UTexture2D*> emotionMap;
};


If I read your post correctly what you’re experiencing is the difference between a deep and shallow copy.

It looks like DuplicateObject() will only create a shallow copy. That means when you create a copy of an UVNFrame instance the ‘characters’ array on both the original and the duplicate will contain the same things (pointer to UVNCharacter). You can see this in the Watch window in your screenshot both ‘characters’ arrays in pages[0] and pages[3] contain the same pointers/characters and changing one will make it look like you’ve changed the other while they really are just one and the same because they were NOT duplicated.

Pseudo code


//create original UVNFrame
UVNFrame* Original = NewObject<UVNFrame>();
//manipulate the original as desired
Original->... = ...;
SomeCharacter->scenePos = EScenePosition::NOT_IN_SCENE;
Original->characters.Add(SomeCharacter); //lets say the index of SomeCharacter will be 0


//duplicate
UVNFrame* Duplicate = DuplicateObject<UVNFrame>(Original);
//at this point Duplicate->characters[0] will also have scenePos == EScenePosition::NOT_IN_SCENE because it is the same object (same pointer/address)

//this will also change Original->characters[0]->scenePos because it refers to the same object
Duplicate->characters[0]->scenePos = EScenePosition::SCENE_MID;


bool bTest = Original->characters[0] == Duplicate->characters[0]; // will always be true after duplicating


The required steps to get the desired result depends on how you want your system to behave…

Thanks, I suspected I may have been hitting a shallow copy which is unfortunate.

The way I want the system to behave is when I duplicate a VNFrame I also get a duplicate of the characters array (So new VNCharacter objects).

My initial thought is to use DuplicateObject() for the frame and then use DuplicateObject() on each of the characters. I’ll see how that goes