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.