
Image showing the Save Manager component, as seen in the Entity Inspector.
Overview
The Save Manager is the central controller of the save system. It manages save file creation, triggers global save/load events that all Saver components respond to, and provides data access methods for storing and retrieving serialized game state.
Save files use JSON formatting for easy human interpretation, and the system uses the O3DE SaveData gem to write to the target platform’s default user data directory — PC, console, or mobile with no additional configuration.
How It Works
Save File Structure
The Save Manager maintains a CoreSaveData file that acts as an index of all save files for the project. This file is identified by the combination of the Game Manager’s Project Prefix and the Save Manager’s Save System Version Number. Changing either value starts a fresh save data pass.
Each individual save file stores the full game state as a JSON document, including timestamped metadata for ordering.
Initialization
On startup, the Save Manager checks for existing save data using the Project Prefix + Version combination. It sets its internal IsContinuing flag to indicate whether previous save data is available to load. Other systems can query this to determine whether to show “Continue” or “Load Game” options.
New Game Flow
When the Game Manager calls NewGame:
- The Save Manager creates a new save file — either
defaultSaveData(no name given) or a custom name viaNewGame(saveName). - The CoreSaveData index is updated with the new file entry.
Save Flow
- Game systems call
SaveData(uniqueName, data)on theSaveManagerIncomingEventBusto store their data into the live save document. - When a full save is triggered (via method call,
OnSaveAllbroadcast, or save-on-exit logic), the Save Manager serializes the complete document to disk.
Load Flow
- The Save Manager reads the target save file from disk into memory.
- It broadcasts
OnLoadAllvia theSaveManagerOutgoingEventBus. - Each Saver component responds by calling
LoadData(uniqueName, outData)to retrieve its portion of the save data.
Setup

Image showing the Manager wrapper entity set as Editor-Only inside Prefab Edit Mode.
- Create an entity. Attach the GS_SaveManagerComponent to it.
- Set the Save System Version Number (increment this when your save format changes to avoid loading incompatible data).
- Optionally add a RecordKeeperComponent to the same entity for progression tracking.
- Turn the entity into a prefab.
- Enter prefab edit mode. Set the wrapper entity (parent) to Editor Only. Save.
- Delete the Save Manager entity from the level.
- In the Game Manager prefab, add the Save Manager
.spawnableto the Startup Managers list.
Inspector Properties
| Property | Type | Default | Description |
|---|---|---|---|
| Save System Version Number | int | 0 | Version stamp for save file compatibility. Increment when your save data format changes — the system will treat saves from a different version as a fresh start. |
| Full Save On Destroy | bool | true | When enabled, the Save Manager performs a full save when the component is destroyed (e.g., on level exit or game shutdown). |
API Reference
Request Bus: SaveManagerIncomingEventBus
The primary interface for all save/load operations. Singleton bus — call via Broadcast.
| Method | Parameters | Returns | Description |
|---|---|---|---|
NewGameSave | const AZStd::string& uniqueName | void | Creates a new save file with the given name. Pass empty string for default name. |
LoadGame | const AZStd::string& uniqueName | void | Loads the specified save file into memory and triggers data restoration. |
SaveData | const AZStd::string& uniqueName, const rapidjson::Value& data | void | Stores a named data block into the live save document. Called by Saver components during save operations. |
LoadData | const AZStd::string& uniqueName, rapidjson::Value& outData | bool | Retrieves a named data block from the loaded save document. Returns true if the data was found. |
GetOrderedSaveList | — | AZStd::vector<AZStd::pair<AZStd::string, AZ::u64>> | Returns all save files ordered by timestamp (newest first). Each entry is a name + epoch timestamp pair. |
ConvertEpochToReadable | AZ::u64 epochSeconds | AZStd::string | Converts an epoch timestamp to a human-readable date string. |
GetEpochTimeNow | — | AZ::u64 | Returns the current time as an epoch timestamp. |
GetAllocator | — | rapidjson::Document::AllocatorType* | Returns the JSON allocator for constructing save data values. |
HasData | const AZStd::string& uniqueName | bool | Checks whether the specified data block exists in the current save. |
IsContinuing | — | bool | Returns true if previous save data was found on startup. |
RegisterSaving | — | void | Registers that a save operation is in progress (used internally by the save counting system). |
Notification Bus: SaveManagerOutgoingEventBus
Broadcast to all Saver components. Connect to this bus to participate in global save/load events.
| Event | Parameters | Description |
|---|---|---|
OnSaveAll | — | Broadcast when a full save is triggered. All savers should gather and submit their data. |
OnLoadAll | — | Broadcast when a save file has been loaded into memory. All savers should retrieve and restore their data. |
Local / Virtual Methods
These methods are available when extending the Save Manager. Override them to customize save file handling.
| Method | Description |
|---|---|
SaveToFile(fileName, docFile) | Serializes a JSON document to disk using the O3DE SaveData gem. |
LoadFromFile(fileName, docFile) | Deserializes a save file from disk into a JSON document. |
FullSave() | Triggers a complete save of all game data to disk. |
UpdateCoreData(saveName) | Updates the CoreSaveData index with the current save file entry. |
FileExists(dataBufferName, localUserId) | Static utility — checks if a save file exists on disk. |
GetSaveFilePath(dataBufferName, localUserId) | Static utility — returns the platform-appropriate file path for a save file. |
Usage Examples
Saving Data from a Component
#include <GS_Core/GS_CoreBus.h>
// Store your component's data into the live save document
rapidjson::Document::AllocatorType* allocator = nullptr;
GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
allocator,
&GS_Core::SaveManagerIncomingEventBus::Events::GetAllocator
);
if (allocator)
{
rapidjson::Value myData(rapidjson::kObjectType);
myData.AddMember("health", m_health, *allocator);
myData.AddMember("level", m_level, *allocator);
GS_Core::SaveManagerIncomingEventBus::Broadcast(
&GS_Core::SaveManagerIncomingEventBus::Events::SaveData,
"MyComponent_PlayerStats",
myData
);
}
Loading Data into a Component
#include <GS_Core/GS_CoreBus.h>
rapidjson::Value outData;
bool found = false;
GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
found,
&GS_Core::SaveManagerIncomingEventBus::Events::LoadData,
"MyComponent_PlayerStats",
outData
);
if (found)
{
if (outData.HasMember("health")) m_health = outData["health"].GetInt();
if (outData.HasMember("level")) m_level = outData["level"].GetInt();
}
Checking if a Save Exists
#include <GS_Core/GS_CoreBus.h>
bool hasSave = false;
GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
hasSave,
&GS_Core::SaveManagerIncomingEventBus::Events::IsContinuing
);
if (hasSave)
{
// Show "Continue" / "Load Game" in the main menu
}
Getting the Save File List
#include <GS_Core/GS_CoreBus.h>
AZStd::vector<AZStd::pair<AZStd::string, AZ::u64>> saves;
GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
saves,
&GS_Core::SaveManagerIncomingEventBus::Events::GetOrderedSaveList
);
for (const auto& [name, epoch] : saves)
{
AZStd::string readable;
GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
readable,
&GS_Core::SaveManagerIncomingEventBus::Events::ConvertEpochToReadable,
epoch
);
AZ_TracePrintf("Save", "Save: %s — %s", name.c_str(), readable.c_str());
}
Extending the Save Manager
Extend the Save Manager when you need custom save file formats, encryption, cloud save integration, or platform-specific serialization.
Header (.h)
#pragma once
#include <Source/SaveSystem/GS_SaveManagerComponent.h>
namespace MyProject
{
class MySaveManagerComponent
: public GS_Core::GS_SaveManagerComponent
{
public:
AZ_COMPONENT_DECL(MySaveManagerComponent);
static void Reflect(AZ::ReflectContext* context);
protected:
// Override save/load to add custom behavior (e.g., encryption, compression)
void SaveToFile(AZStd::string fileName, rapidjson::Document& docFile) override;
void LoadFromFile(AZStd::string fileName, rapidjson::Document& docFile) override;
// Override to customize the full save sequence
void FullSave() override;
};
}
Implementation (.cpp)
#include "MySaveManagerComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
namespace MyProject
{
AZ_COMPONENT_IMPL(MySaveManagerComponent, "MySaveManagerComponent", "{YOUR-UUID-HERE}",
GS_Core::GS_SaveManagerComponent);
void MySaveManagerComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MySaveManagerComponent, GS_Core::GS_SaveManagerComponent>()
->Version(0);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<MySaveManagerComponent>(
"My Save Manager", "Custom save manager with encryption support")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "MyProject")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
}
}
}
void MySaveManagerComponent::SaveToFile(AZStd::string fileName, rapidjson::Document& docFile)
{
// Example: encrypt the JSON before writing
// ... your encryption logic ...
// Call base to perform the actual file write
GS_SaveManagerComponent::SaveToFile(fileName, docFile);
}
void MySaveManagerComponent::LoadFromFile(AZStd::string fileName, rapidjson::Document& docFile)
{
// Call base to perform the actual file read
GS_SaveManagerComponent::LoadFromFile(fileName, docFile);
// Example: decrypt the JSON after reading
// ... your decryption logic ...
}
void MySaveManagerComponent::FullSave()
{
// Example: add a timestamp or checksum before saving
// ... your custom logic ...
GS_SaveManagerComponent::FullSave();
}
}
Module Registration
m_descriptors.insert(m_descriptors.end(), {
MyProject::MySaveManagerComponent::CreateDescriptor(),
});
Then create a prefab for your custom Save Manager and add it to the Game Manager’s Startup Managers list (replacing the default Save Manager).
See Also
- Game Manager — Drives New Game / Load Game / Continue flows
- Savers — Entity-level save handlers that plug into global save/load events
- Record Keeper — Lightweight key-value records for progression
- Manager — Base class lifecycle pattern
- Templates — Starter files for custom save components