Loading a Saved Game

I am trying to create a save file that basically just remembers the player’s primary and secondary weapons. It is weird because when I first run then game it loads the data fine. However when I travel to a new level, it tries and loads the data again and then the SaveFile suddenly returns null for the PrimaryWeapon and SecondaryWeapon. Below is my code.

UArenaSaveGame.h

UCLASS()
class THEARENA_API UArenaSaveGame : public USaveGame
{
	GENERATED_BODY()

public:

	UPROPERTY(VisibleAnywhere, Category = Basic)
	FString PlayerName;

	UPROPERTY(VisibleAnywhere, Category = Basic)
	FString SaveSlotName;

	UPROPERTY(VisibleAnywhere, Category = Basic)
	uint32 UserIndex;

	/** main weapon */
	UPROPERTY(VisibleAnywhere, Category = Player)
	class AArenaRangedWeapon* PrimaryWeapon;

	/** secondary weapon */
	UPROPERTY(VisibleAnywhere, Category = Player)
	class AArenaRangedWeapon* SecondaryWeapon;

	UArenaSaveGame(const FObjectInitializer& ObjectInitializer);
	
};

The UArenaSaveGame.cpp

UArenaSaveGame::UArenaSaveGame(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SaveSlotName = TEXT("SavedGame");
	UserIndex = 0;
}

Now in MyCharacter. h I added the Save Game as a property of the character.

UCLASS(Abstract)
class AArenaCharacter : public ACharacter
{
	GENERATED_BODY()
	
        //...

	class UArenaSaveGame* LoadGameInstance;

        //...
}

Then in MyCharacter.cpp I create and instance during the character initialization

AArenaCharacter::AArenaCharacter(const class FObjectInitializer& PCIP)
	: Super(PCIP.SetDefaultSubobjectClass<UArenaCharacterMovement>(ACharacter::CharacterMovementComponentName))
{
	
        //...

	LoadGameInstance = Cast<UArenaSaveGame>(UGameplayStatics::CreateSaveGameObject(UArenaSaveGame::StaticClass()));
}

Then I try and load data in the SpawnDefaultInventory() function call

LoadGameInstance = Cast<UArenaSaveGame>(UGameplayStatics::LoadGameFromSlot(LoadGameInstance->SaveSlotName, LoadGameInstance->UserIndex));

	if (LoadGameInstance == NULL)
	{
		// equip first weapon in inventory
		if (Inventory.Num() > 0)
		{
			PrimaryWeapon = Inventory[0];
			PrimaryWeapon->SetIsPrimaryWeapon(true);
			SecondaryWeapon = Inventory[1];

			UArenaSaveGame* SaveGameInstance = Cast<UArenaSaveGame>(UGameplayStatics::CreateSaveGameObject(UArenaSaveGame::StaticClass()));
			SaveGameInstance->PrimaryWeapon = PrimaryWeapon;
			SaveGameInstance->SecondaryWeapon = SecondaryWeapon;
			UGameplayStatics::SaveGameToSlot(SaveGameInstance, SaveGameInstance->SaveSlotName, SaveGameInstance->UserIndex);

			InitializeWeapons(PrimaryWeapon, SecondaryWeapon);
		}
	}
	else
	{
		PrimaryWeapon = LoadGameInstance->PrimaryWeapon;
		PrimaryWeapon->SetIsPrimaryWeapon(true);
		SecondaryWeapon = LoadGameInstance->SecondaryWeapon;
		InitializeWeapons(PrimaryWeapon, SecondaryWeapon);
	}

If anyone has any insight, I would greatly appreciate it.

Well I also care about all the values inside the weapon for example i.e. the current ammo count and current clip count. If I spawn a new instance every time I load or travel to a new level the weapon would reset to its default values which is not what I want. I was wondering if moving the saving into the PlayerController would be better?

If all you care about is the weapon “type” than I would save UClass* instead of a AArenaRangedWeapon*. When you perform your save you can do UClass* PrimaryWeaponClass = PrimaryWeapon->GetClass(). Then when you load you just spawn a new instance.

What you are doing is most likely not working because the stable name reference to the weapon you save is specific to the level it was saved in. For more information reference Ben Zeigler’s answer here

For those values I would recommend marking the properties up with the SaveGame flag, saving them using a FObjectAndNameAsStringProxyArchive with the ArIsSaveGame = true; You can then use the standard serialize logic on the instance of that weapon, save the result to the array of bytes and serialize it back on a default instance to override the default settings.

Reference the link I posted above for more information.

To be specific this is how I handle things:

`struct FMySaveGameArchive : public FObjectAndNameAsStringProxyArchive
{
	FMySaveGameArchive(FArchive& InInnerArchive, bool bInLoadIfFindFails)
		: FObjectAndNameAsStringProxyArchive(InInnerArchive, bInLoadIfFindFails)
	{
		ArIsSaveGame = true;
	}
};`

USTRUCT()
struct FActorRecord
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(SaveGame)
	UClass*		Class;

	UPROPERTY(SaveGame)
	FTransform	Transform;

	UPROPERTY(SaveGame)
	FName		Name;

	UPROPERTY(SaveGame)
	TArray<uint8> ActorData;
};

void SaveActor(FActorRecord& ActorRecord, AActor* Actor)
{
	//Save actor
	ActorRecord.Class = Actor->GetClass();
	ActorRecord.Transform = Actor->GetTransform();
	ActorRecord.Name = Actor->GetFName();

	FMemoryWriter MemoryWriter(ActorRecord.ActorData, true);

	// use a wrapper archive that converts FNames and UObject*'s to strings that can be read back in
	FMySaveGameArchive Ar(MemoryWriter, false);

	// serialize the object
	Actor->Serialize(Ar);

     //save all relevant components
}

void DeSerializeActor(const FActorRecord& ActorRecord, AActor* DynamicActor)
{
	FMemoryReader MemoryReader(ActorRecord.ActorData, true);
	FMySaveGameArchive Ar(MemoryReader, true);
	DynamicActor->Serialize(Ar);

     //load all relevant components
}

You have to loop over the components and pass the Ar to each of their serialize functions. The key with them is looping over the same ones in the same order both when you load and save. You may have dynamic components at the time of save so you have to take care.

Thanks for this reference! Could you give me an example of saving/loading relevant components using this method? Sorry this is just completely different from how they teach it in the documentation.

I realize this is an ancient post, but it is the only one I’ve found to mention serializing components.

I’ve attempted to do this, but so far I’ve been met with infinite loops while loading. I’m looping through all the actor components on save and serializing the components (which is then stored in an array on the actor record), and on load I’m testing to see if the component already exists on the actor, and constructing it (with NewObject) it if it doesn’t. That part is ok, but the deserializing ends up in a loop while trying to deserialize the component’s name

Have either of you done this and can share the portion of code for saving/loading dynamic actor components, or otherwise have some suggestions?

Thanks in advance :slight_smile:

I never saved dynamic components. You can look at the default object of the blueprint as well as use construction scripts to find the default state of an actor. I’d serialize the dynamic components in a specific way, maybe in a seperate buffer so its easier to distinguish them and you can add them to the loaded actors after the fact.

I don’t serialize the component. I only serialize certain parts of the component that I later pass into the constructor. Let me know if that is useful or if you have more questions.