Rolly Docs

Make your plugin's objects save

Declare what an object of your plugin or game saves, and what it does right before a save and after a load, without including a Rolly Save header or adding Rolly Save as a dependency.

Sheet
21 of 64
Rev
1.0.0
Updated
On this sheet

Declare what saves

  • Flag each property to save SaveGame. In a struct, the member and every struct property above it need the flag.
  • An object your object owns (its outer is your object, or an object your object owns) saves with it when a SaveGame property points at it, and comes back with its class, name and SaveGame properties. An asset or a type saves by path.
  • A property bag (FInstancedPropertyBag) in a SaveGame property saves whole: every value in it, struct members without the flag included.
  • For state that is not a property, override Serialize: call Super::Serialize first, then write your values when Ar.IsSaveGame() is true. Actors and scene components save their SaveGame properties only; their Serialize overrides do not run for saves.
  • Editor-only properties and data are left out, in the editor too, since a packaged game has none.
UCLASS()
class UMyQuestLog : public UActorComponent
{
	GENERATED_BODY()

public:
	virtual void Serialize(FArchive& Ar) override
	{
		Super::Serialize(Ar); // first: a save that writes before it cannot be loaded, so it is refused
		if (Ar.IsSaveGame())
		{
			Ar << Seed;
		}
	}

	UPROPERTY(SaveGame)
	TArray<FName> Finished;

	int32 Seed = 0;
};
C++

Which objects save

  • A game instance subsystem or a world subsystem of your plugin saves with every save of the game, with no setup, when it has SaveGame properties of its own or declares one of the three functions below. Rolly Save finds it by its class path, so after renaming the class add +ClassRedirects under [CoreRedirects] in Config/DefaultEngine.ini: a load then gives the saved values to the renamed class.
  • The same holds for the game instance, the Game Mode, the Game State and the Level Blueprint of the game’s map.
  • Any other object saves once the game registers it under a name with Register Object for Saving, such as URollySaveSubsystem::Get(this)->RegisterObject(QuestLog, TEXT("QuestLog")). Your plugin does not call it itself, since that would need Rolly Save.
  • Only the server or a standalone game saves; clients save nothing of the game’s state.
  • A load sets the saved values of a world subsystem and of the map’s objects once the map’s actors are initialized, before Begin Play; a game instance subsystem lives through the map change and takes its values then too. An object registered under a name takes its values when it registers, so a component that the game registers in Begin Play gets them inside that call.
  • An object takes its saved values only when its class is the saved class or a child of it. A record of an object that is not there, such as a subsystem of your plugin while the plugin is turned off, is written back by the next save, so turning the plugin on again finds its state.

Run code before a save and after a load

Declare any of these functions by its exact name, with no parameters and no return value, as a UFUNCTION that is neither static nor a remote call. In a Blueprint, add a function or custom event of that name without inputs or outputs.

Function Runs Use it to
OnRollySavePreSave Right before the object is written: on the saved object first, then on each object it owns Copy state kept outside SaveGame properties into them
OnRollySaveSaved Once the save that holds the object is written: on the saved objects and the objects they own, in the order they were written; not after a failed save Clear a “changed since the last save” flag, or show that the game was saved
OnRollySaveLoaded Once every record of the load applied, so pointers to other saved objects are set: on the owned objects first, innermost first, then on the saved object. After a load of the game that opens its map, before Begin Play Rebuild state derived from the loaded values, such as caches and visuals
UFUNCTION(BlueprintNativeEvent, Category = "MyPlugin|Save")
void OnRollySavePreSave();

UFUNCTION(BlueprintNativeEvent, Category = "MyPlugin|Save")
void OnRollySaveSaved();

UFUNCTION(BlueprintNativeEvent, Category = "MyPlugin|Save")
void OnRollySaveLoaded();
C++

All three run on the game thread. OnRollySaveLoaded does not run for an object whose saved values were refused or did not read to their end. A function of one of these names with parameters, a return value, or as a static or network function is never called; a setup warning in the Message Log names its class.

Stay independent

Your plugin includes no Rolly Save header and lists no Rolly Save dependency in its Build.cs or .uplugin: the SaveGame flag, Serialize and the three function names are all Rolly Save looks for. Games without Rolly Save build and run your plugin unchanged.

Keep saves loadable

A save checks each object the way a load will. One object it refuses ends the whole save with Invalid Data and a setup message that names the object and the reason, and the slot keeps its previous save. It refuses what a load would refuse:

  • a Serialize override that writes before it calls Super::Serialize;
  • values nested more than 64 levels deep (each struct, container element, instanced struct and property bag is one level), texts formatted into each other more than 32 times, or a formatted text that would display more than about a million characters in any language the player may switch to: a translated pattern that formats another formatted text counts each of its characters as a place that text may repeat, so such formats nest two or three deep at most;
  • owned objects nested more than 64 deep, or a property bag of more than 1,024 properties;
  • inside an instanced struct (FInstancedStruct) or a property bag, at any depth, a struct with a Serialize of its own, such as FInstancedStructContainer, or a property type the save cannot read through. They may hold structs of UPROPERTY members, soft object paths, gameplay tag containers, instanced structs, property bags and the engine’s fixed-size math types: vectors, rotators, quaternions, transforms of each precision, integer points and vectors, GUIDs, colors and date times. A property of your class may hold any struct type.

A save file has bounds of its own, counted across every object saved into it: 262,144 owned objects, texts that display about 67 million characters beyond their saved size (an ordinary line such as “Gold: 1,234” counts about 100), 4,096 property bag layouts and 256 MiB of instanced struct and property bag values, the elements of arrays, sets and maps inside them included at their full size. The save stops at the object that would pass one, so keep large collections in arrays of structs rather than objects, and long logs as the values their lines are built from.

A SaveGame property of your class that holds an FInstancedStructContainer, or another engine struct that reads a type for each item in a Serialize of its own, saves and loads, but the check before a load cannot read inside it, and its saved data chooses the type of every item. Before your game loads saves from other players or machines, keep such items in a TArray<FInstancedStruct> instead, whose types and values the check reads and bounds.