Persistent class / array between Editor sessions?

I ran into a very confusing problem.

I have a character …


class ... ABaseCharacter : public ACharacter

… which has an inventory …



UPROPERTY( Replicated, VisibleAnywhere, BlueprintReadWrite, Category = "Inventory" )
UInventory* Inventory;


That UInventory is just inherited from UObject:


UCLASS(Transient)
class ... UInventory : public UObject


It contains a bunch of functions to Add, Remove and Get stuff and an Array



UPROPERTY()
TArray<class ABaseMeshActor*> Inventory;


In my PlayerController’s “BeginPlay” I populate the Inventory with some items:



ABaseMeshActor* Item;

Item = Cast<ABaseMeshActor>( GameMode->SpawnItemByName( Character, TEXT( "WeaponSword1" ) ) );
if ( Item )
	Character->Inventory->Add( Item );

// ...

UE_LOG( LogTemp, Warning, TEXT( "Default Inventory spawned. %d" ), Character->Inventory->Num() );


The first time I start the editor and run the game, I see an output in the log window like that:



LogTemp:Warning: Default Inventory spawned. 4
LogTemp:Warning: Item "WeaponSword1" added to Quick Slot 1
LogTemp:Warning: Item "Shield1" linked to Quick Slot 1
LogTemp:Warning: Item "Bow1" added to Quick Slot 2


And everything is fine.

BUT … when I closed the game with ESC and ran it again, I got an access violation and the editor crashes. Rinse and repeat. First time it works. Second time it crashes, always. After a bit of wondering, adding some pointer checks here and there and outputting number of items in “Inventory” with the “Default inventory spawned” message, I saw that the array size actually increases with each session?!?

So every time I hit ESC and “Play” again, the Inventory size increases by 4 (the number of items I add) …

Fresh editor, Play …



LogTemp:Warning: Default Inventory spawned. 4
LogTemp:Warning: Item "WeaponSword1" added to Quick Slot 1
LogTemp:Warning: Item "Shield1" linked to Quick Slot 1
LogTemp:Warning: Item "Bow1" added to Quick Slot 2


… ESC … Play …



LogTemp:Warning: Default Inventory spawned. 8
LogTemp:Warning: ERROR: Inventory[0] is nullptr


… ESC … Play …



LogTemp:Warning: Default Inventory spawned. 12
LogTemp:Warning: ERROR: Inventory[0] is nullptr


I never told him to store anything?! Even tho it doesn’t make any sense (because you expect stuff to be 0, NULL, empty on program start …) I already tried “Inventory.Empty()” in the “UInventory” constructor, no change.

What is going on? Why is UE doing this? :confused:

Oh … Could this be because I use a Dedicated Server setup? And the Dedicated Server is still running and everytime I “reconnect” when hitting “Play” again he replicates the “old” inventory?

No, I think this is because you haven’t marked your property as instanced. I’m guessing you create your inventory object within your character’s constructor?
Try


UPROPERTY( Instanced, Replicated, VisibleAnywhere, BlueprintReadWrite, Category = "Inventory" )
UInventory* Inventory;

Without the Instanced specifier, the inventory object will be shared between each instance of the character class - they will just get a pointer to the existing in-memory inventory.
Normally stuff like this would be done using a component, and all components are Instanced by default. If you want to use a custom UObject, you need to mark it explicitly as above.

Thanks for your response! :slight_smile:

You were right, I didn’t use the “Instanced” specifier. But I just changed it, rebuilt and restarted the editor and tested again, the problem is still the same. And yes, I instance the UInventory in the character’s constructor.

How would you do it correctly? I’m pretty new to UE4, spent like 1 month with it. Started with Blueprints, but I don’t like that. Unfortunately the power and amount of tools and possibilities you have available is pretty stunning and confusing, because you don’t know what to use correctly and when to use it.

Most of the tutorials I saw are in Blueprints, not meant for networking or kind of hard to follow. Either the narrator spends most of the time explaining and showing UMG (which I don’t want to care about atm), like this one - YouTube or requires 10+ parts, where every 2nd part is fixing the mistakes he made from the part before, like this one - YouTube

Or Epic’s “Survival Game” example simply uses a replicated “TArray<ASWeapon*>”.

I assumed using an instance of UObject is good, because it just needs to hold an array and provide some functionality for easier handling. But I have the feeling it actually made things worse … :rolleyes:

Surprised it’s still behaving that way after adding Instanced. Perhaps try removing the Transient specifier from your inventory class? I’ve never used that in combination with a default subobject before, though it’s not clear to me why it wouldn’t work.

Regardless, I’d suggest deriving your inventory from UActorComponent unless you have a specific reason not to. Components just provide some extra functionality, and get special treatment including more specific editor integration. Any modular functionality that you will be adding only to actors should most often be implemented as a component.

If you still have the issue, you’d have to post the code that sets up the inventory.

Thats really weird. I tried deriving the Inventory from UActorComponent and it still behaves the same. I am also able to reproduce that behaviour in a blank new Third Person C++ template project.

UE 4.10.2, New C++ Project -> Third Person Template … New C++ class -> UActorComponent

BaseInventoryComponent.h



#pragma once

#include "Components/ActorComponent.h"
#include "BaseInventoryComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYPROJECT7_API UBaseInventoryComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	UPROPERTY()
		TArray<AActor*> Items;

	UFUNCTION()
		void Add( AActor *Item );

	// ... Default stuff
	
};


BaseInventoryComponent.cpp



// Fill out your copyright notice in the Description page of Project Settings.

#include "MyProject7.h"
#include "BaseInventoryComponent.h"


// ... Unchanged default stuff, Constructor, etc.

void UBaseInventoryComponent::Add( AActor *Item )
{
	Items.Add( Item );
}


MyProject7Character.h



#pragma once
#include "GameFramework/Character.h"
#include "BaseInventoryComponent.h"
#include "MyProject7Character.generated.h"

UCLASS(config=Game)
class AMyProject7Character : public ACharacter
{
	GENERATED_BODY()

	// Default stuff

protected:
	void GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const override;

public:	
	UPROPERTY( /*Instanced,*/ Replicated )
		UBaseInventoryComponent* Inventory;
};


MyProject7Character.cpp



#include "MyProject7.h"
#include "UnrealNetwork.h"
#include "MyProject7Character.h"

//////////////////////////////////////////////////////////////////////////
// AMyProject7Character

AMyProject7Character::AMyProject7Character()
{
	// Default stuff

	Inventory = NewObject<UBaseInventoryComponent>();
}

void AMyProject7Character::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const
{
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );

	DOREPLIFETIME( AMyProject7Character, Inventory );
}

void AMyProject7Character::BeginPlay()
{
	Super::BeginPlay();

	// **** If I move the initialization of the Inventory here, it actually works. But I have the feeling that is still wrong.
	//Inventory = NewObject<UBaseInventoryComponent>();

	for ( int i = 0; i < 3; i++ )
	{
		AActor* Item = GetWorld()->SpawnActor<AActor>();
		Inventory->Add( Item );
	}

	UE_LOG( LogTemp, Warning, TEXT( "Inventory spawned. Inventory size: %d" ), Inventory->Items.Num() );
}


With this, every time you start a new “Play”, the Inventory somehow persists over the sessions. “Inventory size” is 3, 6, 9, 12, 15, … etc.

However, if I move the initialization of the Inventory into “BeginPlay()”, it actually says “Inventory size: 3” all the time. So I could consider this working, but I doubt that this is the right way and place to do it.

EDIT

If I let the Inventory just be a TArray<AActor*> it works too. It only starts behaving weird if I wrap that array in a class. :confused:

Okay, I see the issue, though I’m surprised it didn’t flag it as an error.
In a constructor, you need to use CreateDefaultSubobject instead of NewObject.


Inventory = CreateDefaultSubobject<UBaseInventoryComponent>();

I’m sure doing what you did used to produce an assert, perhaps it’s because you didn’t specify the outer object, that it can’t detect the issue.

For future reference, if you want to create subobjects outside the constructor (because you need them to be procedural based on some runtime values), you can indeed do it in BeginPlay, or anywhere else, as follows:


Inventory = NewObject<UBaseInventoryComponent>(this /* Outer (containing) object */);
// If derived from UActorComponent, you also need this
Inventory->RegisterComponent();

Generally though, creates components within the constructor when you can. I will make them available for configuration in the editor, which is a really useful workflow improvement.

Great. :slight_smile: Thank you very much for your help!

This works:



ABaseCharacter::ABaseCharacter()
{
	// ...

	Inventory = CreateDefaultSubobject<UBaseInventoryComponent>( TEXT( "Inventory" ) );
	// Inventory->RegisterComponent(); ** Do I need this? Because I swear I could remember to have tried this and it didn't work ... :D
}


“NewObject( this )” gave me the assertion you mentioned:


NewObject with empty name can't be used to create default subobjects (inside of UObject derived class constructor) as it produces inconsistent object names. Use ObjectInitializer.CreateDefaultSuobject<> instead.

Okay yeah, that figures.
And no, when you create it in the constructor, you don’t need to call RegisterComponent. Only do that when you create one procedurally outside the constructor.