Make Save System

I’ve started exploring this topic as well, having started with the most basic C++ documentation at first and moving onto exploring 's compressed binary save system. However, I’m currently having problems getting a custom ALevelScriptActor subclass to compile (the idea is to implement compressed binary saving directly into each level through the level blueprint by subclassing my level blueprint to a custom C++ class that can save the positions and other relevant data of all actors, pickups, etc present in the level). The root of my troubles appears to lie in a faulty understanding of operator overloading.

Here is AutoSavingLevelScriptActor.h:





#pragma once

#include "Engine/LevelScriptActor.h"
#include "AutoSavingLevelScriptActor.generated.h"

/**
 *
 */

class UCustomSaveGame;

//Make as many Unique Overloads as you want!
FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if(!SaveGameData) return;
    //~
    
    Ar << SaveGameData->NumItemsCollected;  //int32
    Ar << SaveGameData->PlayerLocation;  //FVector
    Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>
}

/**
 * 
 */
UCLASS()
class FPSPROJECT_API AAutoSavingLevelScriptActor : public ALevelScriptActor
{
    GENERATED_UCLASS_BODY()
    
    uint32 NumItemsCollected;
    
    TArray<FRotator> ArrayOfRotationsOfTheStars; // Use this for day/night cycle save data
    
    //FArchive is shared base class for FBufferArchive and FMemoryReader
    void SaveLoadData(FArchive& Ar, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);

    bool SaveGameDataToFile(const FString& FullFilePath, FBufferArchive& ToBinary);
    
    bool SaveGameDataToFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);
    
    bool LoadGameDataFromFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);
    
    UFUNCTION(BlueprintCallable, Category = Data)
    void SaveGame();
    
    UFUNCTION(BlueprintCallable, Category = Data)
    void LoadGame();
};


And here is AutoSavingLevelScriptActor.cpp:





#include "FPSProject.h"
#include "AutoSavingLevelScriptActor.h"


AAutoSavingLevelScriptActor::AAutoSavingLevelScriptActor(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{

}

//SaveLoadData
void AAutoSavingLevelScriptActor::SaveLoadData(FArchive& Ar, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    Ar << SaveDataInt32;
    Ar << SaveDataVector;
    Ar << SaveDataRotatorArray;
}

bool AAutoSavingLevelScriptActor::SaveGameDataToFile(const FString& FullFilePath, FBufferArchive& ToBinary)
{
    //note that the supplied FString must be the entire Filepath
    // 	if writing it out yourself in C++ make sure to use the \\
    // 	for example:
    
    // 	FString SavePath = "/MyProject/MySaveDir";
    
    //Step 1: Variable Data -> Binary
    
    //following along from above examples
    // Do this
    FBufferArchive ToBinary;
    SaveLoadData(ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    //presumed to be global var data,
    //could pass in the data too if you preferred
    
    //No Data
    if(ToBinary.Num() <= 0) return false;
    //~
    
    //Step 2: Binary to Hard Disk
    if (FFileHelper::SaveArrayToFile(ToBinary, * FullFilePath))
    {
        // Free Binary Array
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Save Success!"));
        }
        
        //ADebugCameraController::ClientMessage("Save Success!");
        
        return true;
    }
    
    // Free Binary Array
    ToBinary.FlushCache();
    ToBinary.Empty();
    
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Could Not Be Saved!"));
    }
    
    //ADebugCameraController::ClientMessage("File Could Not Be Saved!");
    
    return false;
}

//I am using the sample save data from above examples as the data being loaded
bool AAutoSavingLevelScriptActor::LoadGameDataFromFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    //Load the Compressed data array,
    // 	you do not need to pre-initialize this array,
    //		UE4 C++ is awesome and fills it
    //		with whatever contents of file are,
    //		and however many bytes that is
    TArray<uint8> TheBinaryArray;
    if (!FFileHelper::LoadFileToArray(TheBinaryArray, *FullFilePath))
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("FFILEHELPER:>> Invalid File"));
        }
        
        //ADebugCameraController::ClientMessage("FFILEHELPER:>> Invalid File");
        return false;
        //~~
    }
    
    //Testing
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Loaded File Size"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(TheBinaryArray.Num()));
    }
    
    //ADebugCameraController::ClientMessage("Loaded File Size");
    //ADebugCameraController::ClientMessage(FString::FromInt(TheBinaryArray.Num()));
    
    //File Load Error
    if(TheBinaryArray.Num() <= 0) return false;
    
    //~
    //		  Read the Data Retrieved by GFileManager
    //~
    
    FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true); //true, free data after done
    FromBinary.Seek(0);
    SaveLoadData(&ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    
    //~
    //								Clean up 
    //~
    FromBinary.FlushCache();
    
    // Empty & Close Buffer 
    TheBinaryArray.Empty();
    TheBinaryArray.Close();
    
    return true;
}

bool AAutoSavingLevelScriptActor::SaveGameDataToFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    FBufferArchive ToBinary;
    SaveLoadData(&ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    
    //Pre Compressed Size
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("~ PreCompressed Size ~"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(ToBinary.Num()));
    }
    
    //ADebugCameraController::ClientMessage("~ PreCompressed Size ~");
    //ADebugCameraController::ClientMessage(FString::FromInt(ToBinary.Num()));
    
    //
    
    // Compress File
    //tmp compressed data array
    TArray<uint8> CompressedData;
    FArchiveSaveCompressedProxy Compressor =
    FArchiveSaveCompressedProxy(CompressedData, ECompressionFlags::COMPRESS_ZLIB);
    
    //Send entire binary array/archive to compressor
    Compressor << ToBinary;
    
    //send archive serialized data to binary array
    Compressor.Flush();
    
    //
    
    //Compressed Size
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("~ Compressed Size ~"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(CompressedData.Num()));
    }
    
    //ADebugCameraController::ClientMessage("~ Compressed Size ~");
    //ADebugCameraController::ClientMessage(FString::FromInt(CompressedData.Num()));
    
    
    // if (!GFileManager) return false; // GFileManager no longer exists. TODO: Add an updated check to prevent crash.
    
    //vibes to file, return successful or not
    if (FFileHelper::SaveArrayToFile(CompressedData, * FullFilePath))
    {
        // Free Binary Arrays
        Compressor.FlushCache();
        CompressedData.Empty();
        
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        // Close Buffer
        ToBinary.Close();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Save Success!"));
        }
        
        //ADebugCameraController::ClientMessage("File Save Success!");
        
        return true;
        //
    }
    else
    {
        // Free Binary Arrays 
        Compressor.FlushCache();
        CompressedData.Empty();
        
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        // Close Buffer 
        ToBinary.Close();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Could Not Be Saved!"));
        }
        
        //ADebugCameraController::ClientMessage("File Could Not Be Saved!");
        
        return false;
        //
    }
}

/*
 * User-side save and load game methods, for F6 and F9 respectively
 */

void AAutoSavingLevelScriptActor::SaveGame()
{
    // Call the appropriate saving method here.
}

void AAutoSavingLevelScriptActor::LoadGame()
{
    // Call the appropriate loading method here.
}


All of the above code is an implementation of code from 's tutorial in the wiki. The problems as things stand now are as follows:

AutoSavingLevelScriptActor.h has an error saying non-void function (null) should return a value, most likely in FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData ). Said method is taken from the aforementioned wiki tutorial.

Other compiler errors are due to pointer issues that I’m currently working on.

I can make my project compile by commenting out the bulk of my AAutoSavingLevelScriptActorClass, which means I must use my simple save system based on a USaveGame subclass. My main issue is figuring out how to get all of the relevant data to my USaveGame subclass without having to resort to a custom Level Blueprint superclass, since my simple saving system is actuated through code in my ACharacter subclass. All feedback will be much appreciated.