Why won't my save and load c++ code work?

I thought I was having an issue with saving specific information but apparently I just can’t save or load my game at all… I have followed many different tutorials (Reuben Ward - “Unreal Engine C++ Tutorial - Creating Savegames” on YouTube, DevSquad “Save/Load Game System Intro - #66 Unreal Engine 4 Beginner Tutorial Series” on YouTube, and even the c++ tutorial “Saving and Loading your game” in the Unreal Engine Documentation) and am able to get this working in blueprint but for some reason not c++. I have had other projects save with no issue and am really confused to what I am doing wrong.

I originally thought I was having an issue setting my boolean arrays so I made the UpdateArray function to empty and then refill arrays with a for loop.

When there is no SaveGame.sav present it appears to work correctly. Here are the specific logs that tell me that.

LogTemp: Warning: Game Saved from GameSave
LogTemp: Warning: GAME SAVED FROM GAME INSTANCE. local Hair bool array lenghth was 14. savegame value is 13
LogTemp: Warning: GAME SAVE CREATED FROM GAMEMODE
LogTemp: Warning: GAME MODE BEGIN PLAY CALLED. Hair bool Array length is 14

Closing the game saves the game again, all the values are still correct. When I re-open the game the values have all reset to 0 (of if I try having default values set in the code for DDSaveGame the values are reset to those instead). Here’s the related logs.

   LogTemp: Warning: GAME LOADED FROM GAME INSTANCE! Lenght of hair bool array is 0
    LogTemp: Warning: GAME LOADED FROM GAMEMODE
    LogTemp: Warning: GAME MODE BEGIN PLAY CALLED. Hair bool Array length is 0

I’m sorry to include so much code to read, I have tried making it as neat as possible for ease of reading. I have also limited it to what I believe to be the root of the issue. Thanks in advance for any assistance in fixing this!
DDGameInstance.cpp (all references of ‘this->’ was the last thing I added in efforts to fix things, I thought it was unnecessary and it fixed nothing)

void UDDGameInstance::SaveGame() 
{
	UDDSaveGame* SaveGameRef = Cast<UDDSaveGame>(UGameplayStatics::CreateSaveGameObject(UDDSaveGame::StaticClass()));
	if(SaveGameRef)
	{
		//Send game instance variables to SaveGameRef to be updated there.
		SaveGameRef->FullSave(bHasChest, bHasLegs, bHasFace, bHasHands, bHasHair, bHasBeard, bHasEyebrows, bMaleCharacter, CurrentCoins, SelectedChest, SelectedLegs, SelectedFace, SelectedHands, SelectedHair, SelectedBeard, SelectedEyebrows, SelectedMenuMusic, SelectedGameMusic);
		UGameplayStatics::SaveGameToSlot(SaveGameRef, TEXT("SaveGame"), 0);
		UE_LOG(LogTemp, Warning, TEXT("GAME SAVED FROM GAME INSTANCE. local Hair bool array lenghth was %i. savegame value is %i"), bHasHair.Num(), SaveGameRef->LenghtOfHairArray());
	}
	else{UE_LOG(LogTemp, Error, TEXT("SAVE GAME IS NULL"));}
}

void UDDGameInstance::LoadGame() 
{
	UDDSaveGame* SaveGameRef = Cast<UDDSaveGame>(UGameplayStatics::CreateSaveGameObject(UDDSaveGame::StaticClass()));
	SaveGameRef = Cast<UDDSaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("SaveGame"), 0));
	TArray<bool> TempChest, TempLegs, TempFace, TempHands, TempHair, TempBeard, TempEyebrows;
	bool TempMale;
	int TempCoin, TempChestIndex, TempLegsIndex, TempFaceIndex, TempHandsIndex, TempHairIndex, TempBeardIndex, TempEyebrowsIndex, TempGameMusic, TempMenuMusic;
	// The operation was successful, so LoadedGame now contains the data we saved earlier.
	SaveGameRef->LoadGame(TempChest, TempLegs, TempFace, TempHands, TempHair, TempBeard, TempEyebrows, TempMale, TempCoin, TempChestIndex, TempLegsIndex, TempFaceIndex, TempHandsIndex, TempHairIndex, TempBeardIndex, TempEyebrowsIndex, TempGameMusic, TempMenuMusic);
	this->UpdateArray(bHasChest, TempChest);
	this->UpdateArray(bHasLegs, TempLegs);
	this->UpdateArray(bHasFace, TempFace);
	this->UpdateArray(bHasHands, TempHands);
	this->UpdateArray(bHasHair, TempHair);
	this->UpdateArray(bHasBeard, TempBeard);
	this->UpdateArray(bHasEyebrows, TempEyebrows);
	this->bMaleCharacter = TempMale;
	this->CurrentCoins = TempCoin;
	this->SelectedChest = TempChestIndex;
	this->SelectedLegs = TempLegsIndex;
	this->SelectedFace = TempFaceIndex;
	this->SelectedHands = TempHandsIndex;
	this->SelectedHair = TempHairIndex;
	this->SelectedBeard = TempBeardIndex;
	this->SelectedEyebrows = TempEyebrowsIndex;
	this->SelectedGameMusic = TempGameMusic;
	this->SelectedMenuMusic = TempMenuMusic;
	UE_LOG(LogTemp, Warning, TEXT("GAME LOADED FROM GAME INSTANCE! Lenght of hair bool array is %i"), bHasHair.Num());
}

void UDDGameInstance::UpdateArray(TArray<bool>& ArrayToUpdate, TArray<bool> NewValues) 
{
	ArrayToUpdate.Empty();
	for(int i = 0; i < NewValues.Num() - 1; i++)
	{
		ArrayToUpdate.Add(NewValues[i]);
		UE_LOG(LogTemp, Warning, TEXT("Value is %s at %i"), ArrayToUpdate[i] ? TEXT("true") : TEXT("false"), i);
	}
	UE_LOG(LogTemp, Warning, TEXT("New Array length is %i"), ArrayToUpdate.Num());
}

I’m also including my entire DDSaveGame class code just in case I have done something fundamentally wrong.

DDSaveGame.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "DDSaveGame.generated.h"

/**
 * 
 */
UCLASS()
class DESERTDASH_API UDDSaveGame : public USaveGame
{
	GENERATED_BODY()
	
	public:

	UDDSaveGame();

	void FullSave(TArray<bool> Chests, TArray<bool> Legs, TArray<bool> Faces, TArray<bool> Hands, TArray<bool> Hairs, TArray<bool> Beards, TArray<bool> Eyebrows, bool MaleCharacter, int Coins, int Chest, int Leg, int Face, int Hand, int Hair, int Beard, int Eyebrow, int GameMusic, int MenuMusic);
	void CoinSave(int Coins);

	void LoadGame(TArray<bool>& ChestsOUT, TArray<bool>& LegsOUT, TArray<bool>& FacesOUT, TArray<bool>& HandsOUT, TArray<bool>& HairsOUT, TArray<bool>& BeardsOUT, TArray<bool>& EyebrowsOUT, bool& MaleCharacterOUT, int& CoinsOUT, int& ChestOUT, int& LegOUT, int& FaceOUT, int& HandOUT, int& HairOUT, int& BeardOUT, int& EyebrowOUT, int& GameMusicOUT, int& MenuMusicOUT);

	int LenghtOfHairArray();

	void UpdateArray(TArray<bool>& ArrayToUpdate, TArray<bool> NewValues);

private:


	TArray<bool> SavedChests;
	TArray<bool> SavedLegs;
	TArray<bool> SavedFaces;
	TArray<bool> SavedHands;
	TArray<bool> SavedHairs;
	TArray<bool> SavedBeards;
	TArray<bool> SavedEyebrows;

	bool bSaveMaleCharacter;

	int CurrentCoins;
	int SelectedChest;
	int SelectedLegs;
	int SelectedFace;
	int SelectedHands;
	int SelectedHair;
	int SelectedBeard;
	int SelectedEyebrows;
	int SelectedGameMusic;
	int SelectedMenuMusic;

};

DDSaveGame.cpp (I have commented out the default values as this just hides the issue until changes are made, changes are not saved and will reset to defaults)

#include "System/DDSaveGame.h"

UDDSaveGame::UDDSaveGame() 
{
	SavedChests;/* = {true, false, false, false, false, false, false, true, false, false, false, false};*/
	SavedLegs;/* = {true, false, false, false, false, false, false, true, false, false, false, false};*/
	SavedFaces;/* = {true, true, true, false, false, true, true, true, false, false};*/
	SavedHands;/* = {true, false, false, true, false};*/
	SavedHairs;/* = {false, false, false, false, false, false, false, false, false, false, false, false, false, true};*/
	SavedBeards;/* = {false, false, false, false, true};*/
	SavedEyebrows;/* = {false, false, false, false, false, false, false, true};*/
	bSaveMaleCharacter;/* = true;*/
	CurrentCoins;/* = 0;*/
	SelectedChest;/* = 0;*/
	SelectedLegs;/* = 0;*/
	SelectedFace;/* = 0;*/
	SelectedHands;/* = 0;*/
	SelectedHair;/* = 13;*/
	SelectedBeard;/* = 4;*/
	SelectedEyebrows;/* = 7;*/
	SelectedGameMusic;/* = 3;*/
	SelectedMenuMusic;/* = 0;*/
}

void UDDSaveGame::FullSave(TArray<bool> Chests, TArray<bool> Legs, TArray<bool> Faces, TArray<bool> Hands, TArray<bool> Hairs, TArray<bool> Beards, TArray<bool> Eyebrows, bool MaleCharacter, int Coins, int Chest, int Leg, int Face, int Hand, int Hair, int Beard, int Eyebrow, int GameMusic, int MenuMusic) 
{
	UpdateArray(SavedChests, Chests);
    UpdateArray(SavedLegs, Legs);
    UpdateArray(SavedFaces, Faces);
    UpdateArray(SavedHands, Hands);
    UpdateArray(SavedHairs, Hairs);
    UpdateArray(SavedBeards, Beards);
    UpdateArray(SavedEyebrows, Eyebrows);
    bSaveMaleCharacter = MaleCharacter;
    CurrentCoins = Coins;
    SelectedChest = Chest;
    SelectedLegs = Leg;
    SelectedFace = Face;
    SelectedHands = Hand;
    SelectedHair = Hair;
    SelectedBeard = Beard;
    SelectedEyebrows = Eyebrow;
    SelectedGameMusic = GameMusic;
    SelectedMenuMusic = MenuMusic;
    UE_LOG(LogTemp, Warning, TEXT("Game Saved from GameSave"));
}

void UDDSaveGame::CoinSave(int Coins) 
{
	CurrentCoins = Coins;
}

void UDDSaveGame::LoadGame(TArray<bool>& ChestsOUT, TArray<bool>& LegsOUT, TArray<bool>& FacesOUT, TArray<bool>& HandsOUT, TArray<bool>& HairsOUT, TArray<bool>& BeardsOUT, TArray<bool>& EyebrowsOUT, bool& MaleCharacterOUT, int& CoinsOUT, int& ChestOUT, int& LegOUT, int& FaceOUT, int& HandOUT, int& HairOUT, int& BeardOUT, int& EyebrowOUT, int& GameMusicOUT, int& MenuMusicOUT) 
{
	UpdateArray(ChestsOUT, SavedChests);
	UpdateArray(LegsOUT, SavedLegs);
	UpdateArray(FacesOUT, SavedFaces);
	UpdateArray(HandsOUT, SavedHands);
	UpdateArray(HairsOUT, SavedHairs);
	UpdateArray(BeardsOUT, SavedBeards);
	UpdateArray(EyebrowsOUT, SavedEyebrows);
	MaleCharacterOUT = bSaveMaleCharacter;
	CoinsOUT = CurrentCoins;
	ChestOUT = SelectedChest;
	LegOUT = SelectedLegs;
	FaceOUT = SelectedFace;
	HandOUT = SelectedHands;
	HairOUT = SelectedHair;
	BeardOUT = SelectedBeard;
	EyebrowOUT = SelectedEyebrows;
	GameMusicOUT = SelectedGameMusic;
	MenuMusicOUT = SelectedMenuMusic;
	UE_LOG(LogTemp, Warning, TEXT("GameMusic Value is %i and should be 3"), SelectedGameMusic);
}

int UDDSaveGame::LenghtOfHairArray() 
{
	return SavedHairs.Num();
}

void UDDSaveGame::UpdateArray(TArray<bool>& ArrayToUpdate, TArray<bool> NewValues) 
{
	ArrayToUpdate.Empty();
	for(int i = 0; i < NewValues.Num() - 1; i++)
	{
		ArrayToUpdate.Add(NewValues[i]);
		UE_LOG(LogTemp, Warning, TEXT("Value is %s at %i"), ArrayToUpdate[i] ? TEXT("true") : TEXT("false"), i);
	}
	UE_LOG(LogTemp, Warning, TEXT("New Array length is %i"), ArrayToUpdate.Num());
}

Thank you again for any assistance you can provide! I am unfortunately quite stuck with this issue and don’t know what to do next.

Have you checked whether there are any errors/warnings in the log when it doesn’t work?
Also, UGameplayStatics::SaveGameToSlot returns a bool - what is it returning in your case?

Thanks so much for your response!

I have tried logging many different things and everything seems to be fine when the game saves, I even save again and log the results on exit and they are correct with none of my warning logs appearing ever. When I log SaveGameToSlot() it returns true, the expected code in the if statement also logs its results.

The problem occurs when I load, this always returns default values that are set in my custom save game class and if no defaults are set everything resets to 0 with array lengths of 0, this is the only change in my logs. I have just changed the way my temporary boolean arrays are declared on line 18 of DDGameInstance.cpp to the code seen below.

    TArray<bool> TempChest;
    TArray<bool> TempLegs;
    TArray<bool> TempFace;
    TArray<bool> TempHands;
    TArray<bool> TempHair;
    TArray<bool> TempBeard;
    TArray<bool> TempEyebrows;

I am still having no success.

for(int i = 0; i < NewValues.Num() - 1; i++)

Why are you subtracting one here?

From my understanding TArray.Num() returns how many elements are in that array. If you have an array with 10 elements the last element is 9 since we start with index 0. From my understanding if we were to not subtract 1 from TArray.Num() the last loop in the for loop will deal with a null element.

Edited- Ooh! I see what you are pointing out though! I am not doing <= so it won’t actually do the last element… Cheers for that!! I checked my other for loops and it was just these 2 I missed the <=

I was having the exact same issue before and after using the for loop, I added it as a potential fix after being told that the below code could cause issues.

    OldBoolArray = NewBoolArray;

In another project where I am trying to troubleshoot this my code works with out using the for loop. I can save and load the game. Loads are successful in that test project on launch as expected also. After writing the exact same code but with all of the required variables I start having the same issue where saving gives me no errors but when I load it only grabs default values from DDSaveGame (if set).

I am going to add the variables one by one today to see when this starts.

Yes, but the condition to keep looping is when i is smaller than that value. So if array size is 3 the last index is 2, and your loop will only execute up to i < 3 - 1 = 2, so only for i = 0 and 1 and not 2.

Hold up - does the UE4 Save System actually support serializing arrays automatically? Can you inspect the contents of the save file on disk before loading it to verify that the values are actually saved? I would be impressed if it does.

They do work, I was just declaring them wrong. If working with them make sure that you declare the variable as a UPROPERTY() as this is the only thing that was different between my projects.

Thinking back to my c++ for UE course I can remember being told that ALL variables of types created in UE should be declared as a UPROPERTY() otherwise issues can pop up… Hindsight is always 20/20, eh?

Thanks for your help!

I did not declare my boolean arrays correctly. Since TArrays are defined in the Unreal Engine I should have been declaring them as follows

UPROPERTY()
TArray<bool> SavedChests;
UPROPERTY()
TArray<bool> SavedLegs;
UPROPERTY()
TArray<bool> SavedFaces;
UPROPERTY()
TArray<bool> SavedHands;
UPROPERTY()
TArray<bool> SavedHairs;
UPROPERTY()
TArray<bool> SavedBeards;
UPROPERTY()
TArray<bool> SavedEyebrows;

but instead I was declaring them without the UPROPERTY() macro as seen on line 31 of DDSaveGame.h.

I found this when I was going through a test project that was successfully saving a boolean array that was exposed to the editor, this jogged my memory and that’s when I realised I probably needed the just the UPROPERTY() even though the save game was not being handled by blueprint.

Note to self - declare ALL variables of UE defined types as a UPROPERTY() to avoid any issues

1 Like

Ah, happy you figured it out. Yes, the UPROPERTY() decorator ensures that the garbage collector doesn’t destroy the objects.

To avoid any confusion from my answer if anyone stumbles across this trying to solve their issues, I also needed to declare all the variables intended on saving as UPROPERTY() in DDSaveGame.h

As mentioned by staticvoidlol any variable without the UPROPERTY() decorator will be destroyed by the garbage collector. After declaring my variables correctly all issues related to saving or loading my game are solved!

1 Like

This saved me after a day of frustration… its ALWAYS something small…