In my game, I save the game in the exact same way you would with Blueprint - using a save game object, the Save Game To/Load Game From Slot nodes, etc… except I do it in C++. That functionality is totally available in C++, and this question may have just helped me with this exact problem in my game. Since I’m using the same system to save my game as you are (except in C++ instead of BP), it stands to reason that you actually can store, save and instantiate a TSubclassOf<UMyClass>
in C++. If not, you wouldn’t have just read that.
So, first of all, create a new C++ Class for your save game object. In my case, I named mine UPeacenetSaveGame
since my game is called “The Peacenet.” In the UE4 Editor, when you create the class, select “None” as the type of class. We’ll set that up manually.
That should generate a header file that looks something like this:
#pragma once
#include "CoreMinimal.h"
class YOURGAME_API UPeacenetSaveGame
{
public:
UPeacenetSaveGame();
~UPeacenetSaveGame();
};
and a C++ file like this:
#include "UPeacenetSaveGame.h"
UPeacenetSaveGame::UPeacenetSaveGame()
{
}
UPeacenetSaveGame::~UPeacenetSaveGame()
{
}
You can remove the constructor and destructor methods, you don’t really need them. Constructor could be useful if you want to initialize the save game object with some default values, however.
This class, right now, isn’t visible to Unreal Engine in any way. We’re going to change that. Usually UE4 does this stuff for you but we selected “None” as our class type so we’ve got to do it on our own.
The usually steps are:
- Include your .generated.h file for your class
- Make the class a
UCLASS()
, or UCLASS(BlueprintType)
if you want full BP access to it.
- Make the class inherit from
UObject
- all UCLASS()
es must inherit from UObject at some level.
- Add a
GENERATED_BODY()
for UE4-generated code.
- Good to go.
So this is what we have now:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "UPeacenetSaveGame.generated.h"
class YOURGAME_API UPeacenetSaveGame : public USaveGame
{
GENERATED_BODY()
public:
UPeacenetSaveGame();
~UPeacenetSaveGame();
};
That’ll make the class visible to UE4. I also took the liberty of making it visible to the save game system by making it a USaveGame
, the C++ version of the Blueprint Save Game Object. So, now you can add your subclass variable to this object:
UPROPERTY(VisibleAnywhere, Category="Save Game")
TSubclassOf<USomeClass> SomeSavedClass;
Then, when you want to access/modify that:
// Access to the various save game object nodes as C++ functions
#include "Kismet/GameplayStatics.h"
// Check if save game exists
bool doesItExist = UGameplayStatics::DoesSaveGameExist(TEXT("MySlotName"), 0); // 0 = user index
// This is where we'll load our save into.
UPeacenetSaveGame* MySave = nullptr;
// If it exists, load from the slot.
if(doesItExist)
{
MySave = Cast<UPeacenetSaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("MySlotName"), 0));
}
else
{
// Construct a new save game
MySave = NewObject<UPeacenetSaveGame>();
// Initialize the class reference inside it to something simple:
MySave->SomeSavedClass = TSubclassOf<USomeClass>(USomeClass::StaticClass()); // Initializes a new TSubClassOf<USomeClass> representing USomeClass itself.
}
// Create a new instance of the class stored in the save file.
USomeClass* MyClass = NewObject<USomeClass>(this, MySave->SomeSavedClass.Get()); // You can only do this if you specify an owning object for the new object. But this creates a new instance of the class stored in the save game object, returning a pointer of type USomeClass to you. If you know what sub-class is being created, you can Cast the pointer to it and access methods/variables specific to that class.
// Update the class stored in the save to another class
MySave->SomeSavedClass = TSubclassOf<USomeClass>(USomeDerivingClass::StaticClass()); // Same as what we did when we created the save file, but we're storing a class reference to USomeDerivingClass instead. This class MUST INHERIT FROM USOMECLASS!
// Save the game back to the slot.
UGameplayStatics::SaveGameToSlot(MySave, TEXT("MySlotName"), 0);
I’ve just tested this in my game, and it’s loading back the class reference perfectly. Hopefully this helps!