Download

Saving/Loading TArray of AActor

I’m learning Unreal C++ programming (and Blueprints) and really enjoyed implementing a HUD, menu, inventory system and so on, but I don’t think that I fully grok how to best write a neat and efficient save/load system.

I love the fact that << can be used to both save and load data to a FArchive, but as my code below illustrates I don’t understand how to take advantage of that when dealing with arrays of objects that need to be spawned on loading. When saving, everything is straight forward, but I have to keep track of if I’m loading and then do special handling to spawn the objects. Perhaps this is the only way, but I would be very happy to learn of a better/smarter/neater way to do this as I anticipate that there will be a lot more objects than just inventories.

The code below is based on Rama’s “Custom Save System To Binary Files” tutorial (Thanks!!!) and on saving FArchive& Ar takes a FBufferArchive and saves it using FFileHelper, while on loading it uses FFileHelper to load and passes in the FMemoryReader to the FArchive& AR parameter.


void ACustomController::SaveLoadData(bool bLoading, FArchive& Ar, FVector& playerLocation, FRotator& playerRotation, TArray<AInventoryObject*>& inventoryObjects)
{
	// Saving/Loading player location and rotation
	Ar << playerLocation;
	Ar << playerRotation;
	
	// Saving/Loading number of inventory objects
	int32 InventoryCount = inventoryObjects.Num();
	Ar << InventoryCount;
	
	// Can't really write code that is ambivalent about saving or loading an array of objects... so this cludge is used.
	if (bLoading)
	{
		UWorld* const World = GetWorld();
		if (!World)
			return;

		for (int i = 0; i < InventoryCount; i++)
		{
			int Width, Height;
			Ar << Width;
			Ar << Height;
			
			AInventoryObject * invObject = World->SpawnActor<AInventoryObject>(AInventoryObject::StaticClass());

			if (invObject)
			{
				invObject->Init(0, Width, Height);
				invObject->m_inventorySlots.SetNum(invObject->m_nInvHeightCount * invObject->m_nInvWidthCount);
				inventoryObjects.Add(invObject);
			}
		}
	}
	else
	{
		for (int i = 0; i < InventoryCount; i++)
		{

			Ar << inventoryObjects*->m_nInvWidthCount;
			Ar << inventoryObjects*->m_nInvHeightCount;
		}
	}
}

I also tried to use Serialize for objects, but I couldn’t get that working for loading.

Thanks for reading!

The archive itself knows if it is loading or saving. So Ar.IsLoading() or Ar.IsSaving() will save you the need to pass around the boolean.

That said, I think that you are going to be forced to have a switch that does different things for this kind of saving. Inherently what you are doing on the save step is storing the specific pieces of data necessary to reconstruct the state and then on load you’re actually reconstructing it, we actually do much the same for Actor components when rerunning construction scripts: cache off some state, then reapply after the construction script runs)

Thanks, that at least saves me tracking it and it is reassuring to hear that my approach is reasonable. I should have spotted the function in the FArchive API reference, but appreciate you pointing it out.

I have been looking at the Serialize handling for UObject and its Override Hierarchy, and I’m not sure if I should be touching that for save/load handling. I.e. adding Serialize override functions to the objects I want to save, or just create my own SaveLoad(FArchive& Ar) functions.

So for my inventory objects I might do:


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

	Ar << ID;
	Ar << InvWidthCount;
	Ar << InvHeightCount;

	if (Ar.IsLoading())
	{
		ResetSlots();

	}
}

To be clear I’m trying to understand how the Serialize function was intended used and thereby if I should be using it, or just forget about it and write my own custom save methods.