This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

GS_Core

The foundation gem for the GS_Play framework — game lifecycle, save system, stage management, input, actions, and utility libraries.

Overview

GS_Core is the foundation of the GS_Play framework. Every other GS gem depends on it. It provides the game lifecycle management, persistence, level loading, input handling, triggerable actions, and a rich utility library that all other systems build upon.

If you are building anything with GS_Play, you are using GS_Core.


Quick Start

  1. Follow the Installation Guide to add GS_Core to your project.
  2. Set up your project environment (collision layers, physics config).
  3. Create a Game Manager prefab and place it in your level.
  4. Add the managers you need (Save, Stage, Options) to the Game Manager’s startup list.
  5. Refer to the Core Set Up Guide for detailed walkthrough.

Features

SubsystemComponents
GS_ManagersGame Manager, Manager (Base)
GS_SaveSave Manager, Savers, Record Keeper
GS_StageManagerStage Manager, Stage Data
GS_OptionsOptions Manager, Input Profiles
GS_ActionsAction (Base), Built-in Actions
UtilitiesPhysics Trigger Volume, Springs, Curves

See Also

Subsystems

1 - Setup

Setting up the gem.

Overview

GS_Core includes template files that give you a head start when creating custom components. Each template provides a pre-structured C++ class (header, implementation, and interface) or a Script Graph file that follows the GS_Play patterns and conventions.

All C++ templates are unregistered — after copying them into your project, you need to complete the standard O3DE Component Setup: update your module descriptor and CMakeLists.txt.

2 - GS_Managers

The game lifecycle management system — startup sequencing, systemic navigation, and the extensible manager pattern.

Overview

The Managers system is the backbone of every GS_Play project. It provides a controlled startup sequence that initializes game systems in the right order, global access to those systems via EBus, and systemic navigation (New Game, Load Game, Quit) from a single point of control.

Every GS_Play gem that has a manager component (Save, Stage, UI, Unit, Camera, Audio, etc.) plugs into this system automatically.


Architecture

GameManager Entity Hierarchy

GS_GameManager                   Top-level lifecycle controller
  ├── GS_SaveManager             Persistence (save/load game data)
       ├── GS_Saver             Per-entity save components
       └── RecordKeeper         Simple key-value progression records
  ├── GS_StageManager            Level loading and navigation
       ├── GS_StageData         Per-level anchor and spin-up controller
       └── StageExitPoint       Spawn/exit position markers
  ├── GS_OptionsManager          Options and input profile management
       └── GS_InputProfile      Data asset for input bindings
  └── (Your Custom Managers)     Extend GS_Manager for your own systems

All managers follow the same pattern: they are spawned by the Game Manager, go through a two-stage initialization, and are globally accessible via their EBus interfaces.

Staged Initialization

GS_GameManager (singleton, placed in every level as a prefab)
  ├── Spawns manager prefabs from its "Startup Managers" list
  ├── Stage 1: Initialize
  │     Each manager runs Activate(), then reports OnRegisterManagerInit()
  │     GM waits for ALL managers to report before proceeding
  ├── Stage 2: Startup
  │     GM broadcasts OnStartupManagers()
  │     Each manager connects to other managers, then reports OnRegisterManagerStartup()
  │     GM waits for ALL managers to report before proceeding
  └── Stage 3: Complete
        GM broadcasts OnStartupComplete()
        Game systems are fully ready — UI can spawn, gameplay can begin

This two-stage initialization guarantees that during Stage 2, every manager is already initialized and safe to reference. This eliminates race conditions between interdependent systems.


Components

ComponentPurposeDocumentation
GS_GameManagerComponentTop-level lifecycle controller. Spawns managers, handles New Game / Load / Quit / Standby.Game Manager
GS_ManagerComponentBase class for all game system managers. Handles the two-stage init pattern automatically.Manager

Quick Start

In-level Assembly

  1. Create a Game Manager prefab with the GS_GameManagerComponent.
  2. Create Manager prefabs for each system you need.
  3. Add the manager prefab spawnables to the Game Manager’s Startup Managers list.
  4. Place the Game Manager prefab at the top of every level.
  5. The system handles initialization ordering automatically.

Creating Unique Managers

  1. Use the Manager ClassWizard template, or extend the GS_Manager class manually.
  2. Override any initialization stage methods, or utility methods.
  3. Be sure to call parent methods in any override, otherwise initialization will be missed.
  4. Create a new Prefab with your new Manager, add to the GameManager Prefab Managers list.

See Also

2.1 - Game Manager

The top-level game lifecycle controller — startup sequencing, systemic navigation, standby mode, and debug support.

Image showing the Game Manager component, with added manager prefabs, as seen in the Entity Inspector.

Overview

The Game Manager is the root component of every GS_Play project. It controls the game lifecycle from startup through shutdown: spawning and initializing all Manager components in the correct order, providing systemic navigation (New Game, Load Game, Return to Title, Quit), and coordinating standby mode for pausing gameplay during level transitions or other blocking operations.

Every level in your project should contain the same Game Manager prefab. This allows you to start the game from any level — in debug mode it stays in the current level, in release mode it navigates to your title stage.

How It Works

Startup Sequence

When the project starts, the Game Manager executes a three-stage startup:

  1. Spawn Managers — The Game Manager instantiates every prefab in its Startup Managers list. Each manager component runs its Activate() and reports back via OnRegisterManagerInit().
  2. Startup Managers — Once all managers report initialized, the Game Manager broadcasts OnStartupManagers(). Managers now connect to each other and report back via OnRegisterManagerStartup().
  3. Startup Complete — Once all managers report started up, the Game Manager broadcasts OnStartupComplete(). The game is fully initialized and ready to run.

At each stage, the Game Manager counts reports and only proceeds when every spawned manager has reported. This guarantees safe cross-referencing between managers in Stage 2.

Systemic Navigation

The Game Manager provides the high-level game flow methods that drive your project:

  • NewGame / NewGame(saveName) — Starts a new game, optionally with a named save file.
  • ContinueGame — Loads the most recent save and continues.
  • LoadGame(saveName) — Loads a specific save file.
  • ReturnToTitle — Returns to the title stage, tearing down the current game session.
  • SaveAndExitGame / ExitGame — Saves and/or exits the application.

These methods coordinate with the Save Manager and Stage Manager automatically.

Standby Mode

Standby is a global pause mechanism. When the Game Manager enters standby, it broadcasts OnEnterStandby() to all managers, which propagate the signal to their subsystems. This halts timers, physics, gameplay ticks, and other simulation while a blocking operation (like a stage change) completes. Calling ExitStandby reverses the process.

Debug Mode

When Debug Mode is enabled in the Inspector, the Game Manager skips navigating to the title stage and remains in the current level. This allows rapid iteration — you can start the game from any level without going through the full title-to-gameplay flow. If save data and unit management are enabled, it loads default save data but overrides position data with the current level’s default spawn point.


Setup

Image showing the Game Manager wrapper entity set as Editor-Only inside Prefab Edit Mode.

  1. Create an entity and attach the GS_GameManagerComponent (or your extended version).
  2. Turn the entity into a prefab.
  3. Enter prefab edit mode. Set the wrapper entity (the parent of your Game Manager entity) to Editor Only. Save the prefab.
  4. Create Manager prefabs for the systems you need (Save, Stage, Options, or custom managers).
  5. Add each Manager .spawnable to the Game Manager’s Startup Managers list in the Entity Inspector.
  6. Push your overrides into the prefab to propagate across all levels.

Place your Game Manager prefab at the top of every level in the Entity Outliner.

The premade manager prefabs for each GS_Play gem are located in that gem’s Assets/Prefabs directory.


Inspector Properties

PropertyTypeDescription
Project PrefixAZStd::stringSets the project prefix for generated files (e.g. save files). Example: "GS" produces GS_SaveGame.json.
Startup ManagersAZStd::vector<SpawnableAssetRef>The list of Manager prefab spawnables to instantiate on game start. Order does not matter — the two-stage init handles dependencies.
Debug ModeboolWhen enabled, the Game Manager stays in the current level instead of navigating to the title stage. Useful for rapid iteration.

API Reference

Request Bus: GameManagerIncomingEventBus

Commands you send to the Game Manager. This is a singleton bus (Single address, Single handler).

MethodParametersReturnsDescription
IsInDebugboolReturns whether debug mode is active.
IsStartedboolReturns whether the startup sequence has completed.
GetProjectPrefixAZStd::stringReturns the configured project prefix string.
EnterStandbyvoidBroadcasts standby to all managers, pausing gameplay.
ExitStandbyvoidBroadcasts standby exit, resuming gameplay.
NewGamevoidStarts a new game with default save name.
NewGameconst AZStd::string& saveNamevoidStarts a new game with a specified save file name.
ContinueGamevoidLoads the most recent save file and continues.
LoadGameconst AZStd::string& saveNamevoidLoads a specific save file by name.
ReturnToTitlevoidReturns to the title stage, tearing down the current session.
SaveAndExitGamevoidSaves the current game state and exits the application.
ExitGamevoidExits the application without saving.

Notification Bus: GameManagerOutgoingEventBus

Events broadcast by the Game Manager that other systems listen to. This is a multiple handler bus — any number of components can subscribe.

EventDescription
OnSetupManagersFired when all managers have reported initialized. Managers should now connect to each other.
OnStartupCompleteFired when the full startup sequence is complete. Safe to begin gameplay.
OnShutdownManagersFired when the game is shutting down. Managers should clean up.
OnBeginGameFired when a new game or loaded game begins.
OnEnterStandbyFired when entering standby mode. Pause your systems.
OnExitStandbyFired when exiting standby mode. Resume your systems.

ScriptCanvas Reflection

These methods are available as ScriptCanvas nodes for visual scripting:

NodeDescription
TriggerNewGameStarts a new game with default save.
TriggerNewGameWithName(saveName)Starts a new game with a named save.
TriggerContinueGameContinues from the most recent save.
TriggerLoadGame(saveName)Loads a specific save file.
TriggerReturnToTitleReturns to the title stage.
TriggerSaveAndExitGameSaves and exits.
TriggerExitGameExits without saving.

Local / Virtual Methods

Methods available when extending the Game Manager. Override these to customize behavior.

MethodDescription
InitializeManagers()Spawns the Startup Managers list. Override to add custom spawn logic.
ProcessFallbackSpawn()Handles fallback when a manager fails to spawn.
StartupManagers()Broadcasts OnStartupManagers. Override to inject logic between init and startup.
CompleteStartup()Final processing after startup. Broadcasts OnStartupComplete.
BeginGame()Called when transitioning into active gameplay.

Usage Examples

Starting a New Game from C++

#include <GS_Core/GS_CoreBus.h>

// Start a new game with a named save file
GS_Core::GameManagerIncomingEventBus::Broadcast(
    &GS_Core::GameManagerIncomingEventBus::Events::NewGame,
    AZStd::string("MySaveFile")
);

Listening for Startup Complete

#include <GS_Core/GS_CoreBus.h>

class MyComponent
    : public AZ::Component
    , protected GS_Core::GameManagerOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::GameManagerOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::GameManagerOutgoingEventBus::Handler::BusDisconnect();
    }

    // Called once the game is fully initialized
    void OnStartupComplete() override
    {
        // Safe to access all managers and begin gameplay logic
    }

    // Called when entering standby (e.g. during level transitions)
    void OnEnterStandby() override
    {
        // Pause your gameplay systems
    }

    void OnExitStandby() override
    {
        // Resume your gameplay systems
    }
};

Extending the Game Manager

You can extend the Game Manager to add custom startup logic, additional lifecycle events, or project-specific behavior.

Header (.h)

#pragma once
#include <GS_Core/GS_CoreBus.h>
#include <Source/Managers/GS_GameManagerComponent.h>

namespace MyProject
{
    class MyGameManager : public GS_Core::GS_GameManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyGameManager);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        void InitializeManagers() override;
        void StartupManagers() override;
        void CompleteStartup() override;
        void BeginGame() override;
    };
}

Implementation (.cpp)

#include "MyGameManager.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyGameManager, "MyGameManager", "{YOUR-UUID-HERE}");

    void MyGameManager::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyGameManager, GS_Core::GS_GameManagerComponent>()
                ->Version(0);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyGameManager>("My Game Manager", "Custom game manager for MyProject")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }

    void MyGameManager::InitializeManagers()
    {
        // Call base to spawn the standard manager list
        GS_GameManagerComponent::InitializeManagers();

        // Add custom initialization logic here
    }

    void MyGameManager::StartupManagers()
    {
        // Call base to handle standard startup
        GS_GameManagerComponent::StartupManagers();
    }

    void MyGameManager::CompleteStartup()
    {
        // Call base to broadcast OnStartupComplete
        GS_GameManagerComponent::CompleteStartup();

        // Your post-startup logic here (e.g. connect to analytics, initialize online services)
    }

    void MyGameManager::BeginGame()
    {
        GS_GameManagerComponent::BeginGame();

        // Custom game start logic
    }
}

Module Registration

Register your custom Game Manager in your project’s module:

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyGameManager::CreateDescriptor(),
});

See Also

2.2 - Manager

The base class for all game system managers — automatic two-stage initialization and lifecycle integration with the Game Manager.

Image showing a Manager prefab with the wrapper entity set as Editor-Only inside Prefab Edit Mode.

Overview

The GS_ManagerComponent is the base class that all game system managers inherit from. It handles the two-stage initialization pattern with the Game Manager automatically, so you can focus on your system’s logic without worrying about startup ordering.

Every built-in GS_Play manager (Save, Stage, Options, UI, Unit, Camera, Audio, Performer) extends this class. When you need a custom game system manager, you extend it too.

How It Works

When the Game Manager spawns its list of manager prefabs, each Manager component goes through this lifecycle:

  1. Activate — The component initializes itself (connects to buses, sets up internal state). When done, it broadcasts OnRegisterManagerInit() to tell the Game Manager it is ready.

  2. OnStartupManagers — Called by the Game Manager after all managers have reported initialized. This is where you connect to other managers and set up cross-references. When done, broadcasts OnRegisterManagerStartup().

  3. OnStartupComplete — The Game Manager broadcasts this after all managers report started up. The game is fully ready.

  4. OnEnterStandby / OnExitStandby — Called when the Game Manager enters or exits standby mode. Pause and resume your subsystem here.

  5. OnShutdownManagers — Called when the game is shutting down. Clean up your subsystem.

The base class handles steps 1-2 automatically — you override them to add your own logic, then call the base implementation to complete the handshake.


Setup

Image showing the Manager wrapper entity set as Editor-Only inside Prefab Edit Mode.

  1. Create an entity. Attach your Manager component (built-in or custom) to it.
  2. Configure any properties needed in the Entity Inspector.
  3. Turn the entity into a prefab.
  4. Enter prefab edit mode. Set the wrapper entity (parent of your Manager entity) to Editor Only. Save.
  5. Delete the Manager entity from the level (it will be spawned by the Game Manager at runtime).
  6. In the Game Manager prefab, add your Manager .spawnable to the Startup Managers list.

The premade manager prefabs for each GS_Play gem are located in that gem’s Assets/Prefabs directory.


Inspector Properties

The base GS_ManagerComponent exposes no inspector properties of its own. Properties are defined by each inheriting manager component (e.g., the Save Manager exposes a Save System Version Number, the Stage Manager exposes a Stages list).


API Reference

Lifecycle Bus: GameManagerOutgoingEventBus

The Manager base class automatically subscribes to these events from the Game Manager:

EventWhen It FiresWhat You Do
OnStartupManagersAll managers are initializedConnect to other managers, set up cross-references, then call base to report.
OnStartupCompleteAll managers are started upBegin runtime operation.
OnEnterStandbyGame is entering standbyPause your subsystem (stop ticks, halt processing).
OnExitStandbyGame is exiting standbyResume your subsystem.
OnShutdownManagersGame is shutting downClean up resources, disconnect buses.

Registration Methods

These are called internally by the Manager base class to report back to the Game Manager:

MethodDescription
OnRegisterManagerInit()Called at the end of Activate(). Reports to the Game Manager that this manager has initialized.
OnRegisterManagerStartup()Called at the end of OnStartupManagers(). Reports to the Game Manager that this manager has started up.

Usage Examples

Checking if the Game Manager is Ready

#include <GS_Core/GS_CoreBus.h>

bool isStarted = false;
GS_Core::GameManagerIncomingEventBus::BroadcastResult(
    isStarted,
    &GS_Core::GameManagerIncomingEventBus::Events::IsStarted
);

if (isStarted)
{
    // Safe to access all managers
}

Extending the Manager Class

Create a custom manager whenever you need a singleton game system that initializes with the rest of the framework.

Header (.h)

#pragma once
#include <Source/Managers/GS_ManagerComponent.h>

// Define your EBus interface for other systems to call your manager
class MyManagerRequests
{
public:
    AZ_RTTI(MyManagerRequests, "{YOUR-UUID-HERE}");
    virtual ~MyManagerRequests() = default;

    virtual int GetSomeValue() = 0;
    virtual void DoSomething() = 0;
};

class MyManagerBusTraits : public AZ::EBusTraits
{
public:
    static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
    static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};

using MyManagerRequestBus = AZ::EBus<MyManagerRequests, MyManagerBusTraits>;

namespace MyProject
{
    class MyManagerComponent
        : public GS_Core::GS_ManagerComponent
        , protected MyManagerRequestBus::Handler
    {
    public:
        AZ_COMPONENT_DECL(MyManagerComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // Manager lifecycle
        void Activate() override;
        void Deactivate() override;
        void OnStartupManagers() override;
        void OnEnterStandby() override;
        void OnExitStandby() override;

        // Your bus implementation
        int GetSomeValue() override;
        void DoSomething() override;

    private:
        int m_someValue = 0;
    };
}

Implementation (.cpp)

#include "MyManagerComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyManagerComponent, "MyManagerComponent", "{YOUR-UUID-HERE}");

    void MyManagerComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyManagerComponent, GS_Core::GS_ManagerComponent>()
                ->Version(0)
                ->Field("SomeValue", &MyManagerComponent::m_someValue);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyManagerComponent>("My Manager", "A custom game system manager")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &MyManagerComponent::m_someValue, "Some Value", "Description of this property");
            }
        }
    }

    void MyManagerComponent::Activate()
    {
        // Connect your bus
        MyManagerRequestBus::Handler::BusConnect();

        // IMPORTANT: Call base Activate last — it reports OnRegisterManagerInit()
        GS_ManagerComponent::Activate();
    }

    void MyManagerComponent::Deactivate()
    {
        MyManagerRequestBus::Handler::BusDisconnect();
        GS_ManagerComponent::Deactivate();
    }

    void MyManagerComponent::OnStartupManagers()
    {
        // Access other managers here — they are all initialized by now
        // Example: query the Save Manager, connect to the Stage Manager, etc.

        // IMPORTANT: Call base last — it reports OnRegisterManagerStartup()
        GS_ManagerComponent::OnStartupManagers();
    }

    void MyManagerComponent::OnEnterStandby()
    {
        // Pause your subsystem
    }

    void MyManagerComponent::OnExitStandby()
    {
        // Resume your subsystem
    }

    int MyManagerComponent::GetSomeValue()
    {
        return m_someValue;
    }

    void MyManagerComponent::DoSomething()
    {
        // Your game system logic
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyManagerComponent::CreateDescriptor(),
});

Then create a prefab for your manager and add it to the Game Manager’s Startup Managers list.


See Also

3 - GS_Save

The persistence system — save files, load data, and track progression with managers, savers, and record keepers.

Overview

The Save system is the universal means of storing, loading, and handling game data. It provides a centralized Save Manager for file operations, entity-level Saver components for automatic data capture, and a Record Keeper for lightweight progression tracking — all backed by JSON-formatted save files that work across PC, console, and mobile platforms.

Every component that needs to persist data plugs into this system through one of two patterns: extend the Saver base class for complex per-entity data, or use the Record Keeper for simple key-value records.

Architecture

GS_SaveManagerComponent (singleton, spawned by Game Manager)
  
  ├── Manages save files via O3DE SaveData gem
       CoreSaveData file tracks all saves + metadata
       Individual save files store game state as JSON
  
  ├── SaveManagerOutgoingEventBus
       Broadcasts OnSaveAll / OnLoadAll to all Savers
  
  ├── GS_SaverComponent (base class, on any entity)
       Listens for global save/load events
       Calls BuildSaveData()  SaveData() to persist
       Calls LoadLocalData()  ProcessLoad() to restore
       
       ├── BasicEntitySaverComponent (saves Transform)
       ├── BasicPhysicsEntitySaverComponent (saves Transform + Rigidbody)
       └── Your custom savers...
  
  └── RecordKeeperComponent (extends GS_SaverComponent)
        Simple key-value records (string  int)
        Lives on the Save Manager prefab entity

Components

ComponentPurposeDocumentation
GS_SaveManagerComponentCentral save/load controller. Manages save files, triggers global save/load events, provides data access to all savers.Save Manager
GS_SaverComponentBase class for entity-level save handlers. Override BuildSaveData() and ProcessLoad() to persist any component data.Savers
RecordKeeperComponentLightweight key-value store for progression tracking (string name → integer value). Extends GS_SaverComponent.Record Keeper
BasicEntitySaverComponentPre-built saver that persists an entity’s Transform (position, rotation).List of Savers
BasicPhysicsEntitySaverComponentPre-built saver that persists an entity’s Transform and Rigidbody state (velocity).List of Savers

Quick Start

  1. Create a Save Manager prefab with the GS_SaveManagerComponent.
  2. Optionally add a Record Keeper to the same prefab for progression tracking.
  3. Add the Save Manager .spawnable to the Game Manager’s Startup Managers list.
  4. Attach Saver components to any entities that need to persist data.
  5. Call NewGame / LoadGame through the Game Manager — the Save Manager handles the rest.

See Also

3.1 - Save Manager

The central save/load controller — manages save files, triggers global persistence events, and provides data access for all savers.

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:

  1. The Save Manager creates a new save file — either defaultSaveData (no name given) or a custom name via NewGame(saveName).
  2. The CoreSaveData index is updated with the new file entry.

Save Flow

  1. Game systems call SaveData(uniqueName, data) on the SaveManagerIncomingEventBus to store their data into the live save document.
  2. When a full save is triggered (via method call, OnSaveAll broadcast, or save-on-exit logic), the Save Manager serializes the complete document to disk.

Load Flow

  1. The Save Manager reads the target save file from disk into memory.
  2. It broadcasts OnLoadAll via the SaveManagerOutgoingEventBus.
  3. 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.

  1. Create an entity. Attach the GS_SaveManagerComponent to it.
  2. Set the Save System Version Number (increment this when your save format changes to avoid loading incompatible data).
  3. Optionally add a RecordKeeperComponent to the same entity for progression tracking.
  4. Turn the entity into a prefab.
  5. Enter prefab edit mode. Set the wrapper entity (parent) to Editor Only. Save.
  6. Delete the Save Manager entity from the level.
  7. In the Game Manager prefab, add the Save Manager .spawnable to the Startup Managers list.

Inspector Properties

PropertyTypeDefaultDescription
Save System Version Numberint0Version 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 DestroybooltrueWhen 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.

MethodParametersReturnsDescription
NewGameSaveconst AZStd::string& uniqueNamevoidCreates a new save file with the given name. Pass empty string for default name.
LoadGameconst AZStd::string& uniqueNamevoidLoads the specified save file into memory and triggers data restoration.
SaveDataconst AZStd::string& uniqueName, const rapidjson::Value& datavoidStores a named data block into the live save document. Called by Saver components during save operations.
LoadDataconst AZStd::string& uniqueName, rapidjson::Value& outDataboolRetrieves a named data block from the loaded save document. Returns true if the data was found.
GetOrderedSaveListAZStd::vector<AZStd::pair<AZStd::string, AZ::u64>>Returns all save files ordered by timestamp (newest first). Each entry is a name + epoch timestamp pair.
ConvertEpochToReadableAZ::u64 epochSecondsAZStd::stringConverts an epoch timestamp to a human-readable date string.
GetEpochTimeNowAZ::u64Returns the current time as an epoch timestamp.
GetAllocatorrapidjson::Document::AllocatorType*Returns the JSON allocator for constructing save data values.
HasDataconst AZStd::string& uniqueNameboolChecks whether the specified data block exists in the current save.
IsContinuingboolReturns true if previous save data was found on startup.
RegisterSavingvoidRegisters 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.

EventParametersDescription
OnSaveAllBroadcast when a full save is triggered. All savers should gather and submit their data.
OnLoadAllBroadcast 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.

MethodDescription
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

3.2 - Savers

The base class for entity-level save handlers — override BuildSaveData() and ProcessLoad() to persist any component data automatically.

Image showing the Basic Physics Saver component, an example of a Saver-inherited class, as seen in the Entity Inspector.

Overview

The GS_SaverComponent is the base class for all entity-level save handlers. By extending it, you can create companion components that save and load data alongside your entities, or inherit directly into gameplay components for built-in persistence (as done by GS_Inventory).

Savers automatically participate in the global save/load cycle — when the Save Manager broadcasts OnSaveAll or OnLoadAll, every active Saver component responds by gathering or restoring its data.

How It Works

Save Cycle

When a save event fires (global OnSaveAll, local trigger, or saveOnDestroy):

  1. BeginSave() — Primes the save data container.
  2. BuildSaveData() — Your override. Gather your component’s data and write it into localData using the Save Manager’s JSON allocator.
  3. CompleteSave() — Submits the data to the Save Manager via SaveData(uniqueName, localData).

Load Cycle

When a load event fires (global OnLoadAll or loadOnActivate):

  1. LoadLocalData() — Retrieves this Saver’s data block from the Save Manager via LoadData(uniqueName, outData).
  2. ProcessLoad() — Your override. Read the retrieved data and restore your component’s state.

Automatic Identity

Each Saver generates a unique save key from the entity name and GetSubComponentName(). This ensures multiple Savers on different entities (or multiple Saver types on the same entity) don’t collide.


Setup

  1. Attach a Saver component (built-in or custom) to any entity that needs to persist data.
  2. Configure the Saver properties:
    • Load On Activate — restore data automatically when the entity spawns.
    • Save On Destroy — save data automatically when the entity is destroyed.
  3. Ensure the Save Manager is set up and running (it handles the file I/O).

Inspector Properties

PropertyTypeDefaultDescription
Load On ActivatebooltrueAutomatically loads and restores this Saver’s data when the component activates. Useful for entities that spawn mid-game and need to resume their saved state.
Save On DestroybooltrueAutomatically saves this Saver’s data when the component is destroyed. Ensures data is captured even if a global save hasn’t been triggered.

API Reference

Global Event Handlers

These are called automatically when the Save Manager broadcasts global save/load events. Override to customize behavior.

MethodDescription
OnSaveAll()Called when the Save Manager broadcasts a global save. Default implementation calls the full save cycle (BeginSave → BuildSaveData → CompleteSave).
OnLoadAll()Called when the Save Manager broadcasts a global load. Default implementation calls the full load cycle (LoadLocalData → ProcessLoad).

Save Methods

Override these to control how your component’s data is saved.

MethodDescription
BeginSave()Prepares the save data container. Called before BuildSaveData().
BuildSaveData()Your primary override. Write your component’s data into localData using the JSON allocator.
CompleteSave()Submits localData to the Save Manager. Called after BuildSaveData().

Load Methods

Override these to control how your component’s data is restored.

MethodDescription
LoadLocalData()Retrieves this Saver’s data block from the Save Manager into localData.
ProcessLoad()Your primary override. Read localData and restore your component’s state.

Utility Methods

MethodReturnsDescription
SetUniqueName()voidGenerates the unique save key from the entity name and sub-component name. Override for custom key generation.
GetSubComponentName()AZStd::stringReturns "Saver" by default. Override to distinguish multiple Saver types on the same entity.

Components

Pre-built Saver components included in GS_Core:

ComponentSavesDocumentation
BasicEntitySaverComponentEntity Transform (position, rotation)List of Savers
BasicPhysicsEntitySaverComponentEntity Transform + Rigidbody (position, rotation, linear velocity, angular velocity)List of Savers
RecordKeeperComponentKey-value records (string → integer)Record Keeper

Usage Examples

Responding to Global Save/Load Events

If your component doesn’t extend GS_SaverComponent but still needs to react to save/load events, connect to the SaveManagerOutgoingEventBus:

#include <GS_Core/GS_CoreBus.h>

class MyComponent
    : public AZ::Component
    , protected GS_Core::SaveManagerOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::SaveManagerOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::SaveManagerOutgoingEventBus::Handler::BusDisconnect();
    }

    void OnSaveAll() override
    {
        // Gather and submit data to the Save Manager
    }

    void OnLoadAll() override
    {
        // Retrieve and restore data from the Save Manager
    }
};

Extending the Saver Class

Create a custom Saver whenever you need to persist component-specific data that the built-in savers don’t cover.

Header (.h)

#pragma once
#include <Source/SaveSystem/GS_SaverComponent.h>

namespace MyProject
{
    class MyEntitySaverComponent
        : public GS_Core::GS_SaverComponent
    {
    public:
        AZ_COMPONENT_DECL(MyEntitySaverComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // Saver overrides
        void BuildSaveData() override;
        void ProcessLoad() override;
        AZStd::string GetSubComponentName() const override { return "MyEntitySaver"; }
    };
}

Implementation (.cpp)

#include "MyEntitySaverComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <GS_Core/GS_CoreBus.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyEntitySaverComponent, "MyEntitySaverComponent", "{YOUR-UUID-HERE}",
        GS_Core::GS_SaverComponent);

    void MyEntitySaverComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyEntitySaverComponent, GS_Core::GS_SaverComponent>()
                ->Version(0);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyEntitySaverComponent>(
                    "My Entity Saver", "Saves custom entity data")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }

    void MyEntitySaverComponent::BuildSaveData()
    {
        // Get the JSON allocator from the Save Manager
        rapidjson::Document::AllocatorType* allocator = nullptr;
        GS_Core::SaveManagerIncomingEventBus::BroadcastResult(
            allocator,
            &GS_Core::SaveManagerIncomingEventBus::Events::GetAllocator
        );

        if (!allocator) return;

        // Write your data into localData (inherited member)
        localData.SetObject();
        localData.AddMember("myValue", 42, *allocator);
        localData.AddMember("myFlag", true, *allocator);

        // Example: save a string
        rapidjson::Value nameVal;
        nameVal.SetString("hello", *allocator);
        localData.AddMember("myString", nameVal, *allocator);
    }

    void MyEntitySaverComponent::ProcessLoad()
    {
        // Read your data from localData (populated by LoadLocalData)
        if (localData.HasMember("myValue"))
        {
            int myValue = localData["myValue"].GetInt();
            // ... restore state ...
        }

        if (localData.HasMember("myFlag"))
        {
            bool myFlag = localData["myFlag"].GetBool();
            // ... restore state ...
        }
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyEntitySaverComponent::CreateDescriptor(),
});

Attach your custom Saver to any entity that needs persistence. The save/load cycle handles the rest automatically.


See Also

3.2.1 - List of Savers

Pre-built saver components included in GS_Core — ready-to-use entity persistence without writing code.

Overview

GS_Core includes pre-built Saver components that handle common persistence scenarios out of the box. Attach one to any entity that needs to remember its state between save/load cycles — no custom code required.

All built-in Savers inherit from GS_SaverComponent and participate in the global save/load cycle automatically.


Basic Entity Saver

A companion component that saves and loads an entity’s Transform data: position and rotation.

When to Use

Use the Basic Entity Saver for any entity that can be moved or rotated during gameplay and needs to retain its position across saves — collectibles, furniture, doors, NPCs with fixed patrol points, etc.

Inspector Properties

PropertyTypeDefaultDescription
Load On ActivatebooltrueInherited from GS_SaverComponent. Automatically restores the saved Transform on activation.
Save On DestroybooltrueInherited from GS_SaverComponent. Automatically saves the current Transform on destruction.

What It Saves

DataTypeDescription
PositionAZ::Vector3World-space position of the entity.
RotationAZ::QuaternionWorld-space rotation of the entity.

Setup

  1. Attach the BasicEntitySaverComponent to any entity that needs Transform persistence.
  2. Ensure the Save Manager is running.
  3. That’s it — the component saves and loads automatically.

Basic Physics Entity Saver

Image showing the Basic Physics Saver component, as seen in the Entity Inspector.

A companion component that saves and loads an entity’s Transform and Rigidbody physics state.

When to Use

Use the Basic Physics Entity Saver for physics-driven entities that need to retain both their position and their motion state across saves — throwable objects, rolling boulders, physics puzzles, ragdolls, etc.

Inspector Properties

PropertyTypeDefaultDescription
Load On ActivatebooltrueInherited from GS_SaverComponent. Automatically restores saved state on activation.
Save On DestroybooltrueInherited from GS_SaverComponent. Automatically saves current state on destruction.

What It Saves

DataTypeDescription
PositionAZ::Vector3World-space position of the entity.
RotationAZ::QuaternionWorld-space rotation of the entity.
Linear VelocityAZ::Vector3Current linear velocity of the rigidbody.
Angular VelocityAZ::Vector3Current angular velocity of the rigidbody.

Setup

  1. Attach the BasicPhysicsEntitySaverComponent to any entity with a Rigidbody component.
  2. Ensure the Save Manager is running.
  3. The component saves and loads automatically.

Creating Your Own Saver

Need to persist data that the built-in Savers don’t cover? See the Extending the Saver Class guide for a complete walkthrough with header, implementation, and module registration.

See Also

  • Savers — Base class documentation and extension guide
  • Save Manager — Central save/load controller
  • Record Keeper — Lightweight key-value records for simple progression data

3.3 - Record Keeper

Lightweight key-value progression tracking — store and retrieve named integer records without writing a custom saver.

Image showing the Record Keeper component with its unique variables and inherited Saver properties, as seen in the Entity Inspector.

Overview

The Record Keeper is a companion component that provides a simple key-value store for tracking progression, switch states, quest stages, or any other data that doesn’t require a complex Saver implementation. Each record is a SaveRecord — a name/value pair of recordName (string) and recordProgress (integer).

Because it extends GS_SaverComponent, the Record Keeper saves and loads automatically with the rest of the save system. No custom serialization code needed.

How It Works

  1. The Record Keeper lives on your Save Manager prefab entity (recommended) or any entity with save system access.
  2. Game systems call SetRecord, GetRecord, HasRecord, and DeleteRecord via the RecordKeeperIncomingEventBus.
  3. On each call to SetRecord, the Record Keeper broadcasts RecordChanged on the RecordKeeperOutgoingEventBus so listeners can react.
  4. When a global save event fires (OnSaveAll), the Record Keeper serializes all its records into the save file automatically.
  5. On load, it deserializes its records and makes them available immediately.

SaveRecord Data Structure

struct SaveRecord
{
    AZStd::string recordName;    // Unique key (e.g., "quest_village_rescue", "switch_bridge_01")
    AZ::s32       recordProgress; // Integer value (progression stage, state, count, etc.)
};

TypeId: {F6F4F258-819A-468A-B015-CAF51D8289BF}


Setup

  1. Open your Save Manager prefab in prefab edit mode.
  2. Add the RecordKeeperComponent to the Save Manager entity.
  3. Set the Record Keeper Name — this drives unique save/load identification and allows multiple Record Keepers if needed.
  4. Enable the Saver booleans as needed:
    • Load On Activate — automatically loads records when the component activates (recommended: on).
    • Save On Destroy — automatically saves records when the component is destroyed (recommended: on).

Inspector Properties

PropertyTypeDefaultDescription
Record Keeper NameAZStd::string""Unique identifier for this Record Keeper. Drives the save/load key. Required if using multiple Record Keepers.
Load On ActivatebooltrueInherited from GS_SaverComponent. Automatically loads records from save data on activation.
Save On DestroybooltrueInherited from GS_SaverComponent. Automatically saves records to the save system on destruction.

API Reference

Request Bus: RecordKeeperIncomingEventBus

The primary interface for reading and writing records.

MethodParametersReturnsDescription
HasRecordconst AZStd::string& recordNameboolReturns true if a record with the given name exists.
SetRecordconst AZStd::string& recordName, AZ::s32 recordProgressvoidCreates or updates a record. Broadcasts RecordChanged on success.
GetRecordconst AZStd::string& recordNameAZ::s32Returns the value of the named record. Returns 0 if the record does not exist.
DeleteRecordconst AZStd::string& recordNamevoidRemoves the named record from the store.

Notification Bus: RecordKeeperOutgoingEventBus

Connect to this bus to react when records change.

EventParametersDescription
RecordChangedconst AZStd::string& recordName, AZ::s32 recordValueBroadcast whenever SetRecord is called. Use this to update UI, trigger gameplay events, or log progression.

Usage Examples

Setting a Progression Record

#include <GS_Core/GS_CoreBus.h>

// Mark quest stage 2 as complete
GS_Core::RecordKeeperIncomingEventBus::Broadcast(
    &GS_Core::RecordKeeperIncomingEventBus::Events::SetRecord,
    "quest_village_rescue",
    2
);

Reading a Record

#include <GS_Core/GS_CoreBus.h>

AZ::s32 questStage = 0;
GS_Core::RecordKeeperIncomingEventBus::BroadcastResult(
    questStage,
    &GS_Core::RecordKeeperIncomingEventBus::Events::GetRecord,
    "quest_village_rescue"
);

if (questStage >= 2)
{
    // The player has completed stage 2 — unlock the bridge
}

Checking if a Record Exists

#include <GS_Core/GS_CoreBus.h>

bool exists = false;
GS_Core::RecordKeeperIncomingEventBus::BroadcastResult(
    exists,
    &GS_Core::RecordKeeperIncomingEventBus::Events::HasRecord,
    "switch_bridge_01"
);

if (!exists)
{
    // First time encountering this switch — initialize it
    GS_Core::RecordKeeperIncomingEventBus::Broadcast(
        &GS_Core::RecordKeeperIncomingEventBus::Events::SetRecord,
        "switch_bridge_01",
        0
    );
}

Listening for Record Changes

#include <GS_Core/GS_CoreBus.h>

// In your component header:
class MyQuestTrackerComponent
    : public AZ::Component
    , protected GS_Core::RecordKeeperOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::RecordKeeperOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::RecordKeeperOutgoingEventBus::Handler::BusDisconnect();
    }

    // RecordKeeperOutgoingEventBus
    void RecordChanged(const AZStd::string& recordName, AZ::s32 recordValue) override
    {
        if (recordName == "quest_village_rescue" && recordValue >= 3)
        {
            // Quest complete — trigger reward
        }
    }
};

Extending the Record Keeper


See Also

4 - GS_StageManager

The level navigation system — handles loading, unloading, and staged startup of game levels with exit point traversal.

Overview

The Stage Manager system handles the process of changing levels, loading and unloading their assets, and starting up levels in the correct sequence. It supports incremental loading so that complex levels with generative components can spin up reliably without massive frame-time spikes.

Each level contains a Stage Data component that acts as the level’s anchor — connecting the Stage Manager to level-specific references, startup sequences, and navigation data. Exit Points mark spawn positions for player placement when transitioning between stages.

Architecture

GS_StageManagerComponent (singleton, spawned by Game Manager)
  
  ├── Stages list: maps stage names  spawnable prefabs
  
  ├── ChangeStageRequest(stageName, exitName)
       1. Unloads current stage (if any)
       2. Spawns target stage prefab
       3. Queries for GS_StageDataComponent in the new level
       4. Stage Data runs layered startup sequence
  
  ├── GS_StageDataComponent (one per level, in the level prefab)
       Holds level-specific settings, NavMesh ref, priority layers
       Runs staged activation: SetUpStage  ActivateByPriority  Complete
       
       └── StageLazyLoaderComponent (optional, priority-based activation)
  
  └── StageExitPointComponent (multiple per level)
        Marks spawn/exit positions for entity placement
        Can be flagged as Default for fallback positioning

Components

ComponentPurposeDocumentation
GS_StageManagerComponentSingleton level controller. Manages the Stages list, handles change requests, coordinates loading/unloading.Stage Manager
GS_StageDataComponentPer-level anchor. Holds stage name, NavMesh reference, and runs the level’s startup sequence.Stage Data
StageExitPointComponentMarks spawn/exit positions within a level. Registered by name for cross-stage entity placement.Stage Manager: Exit Points
StageLazyLoaderComponentPriority-based entity activation during level load. Spreads heavy initialization across frames.Stage Data: Priority Loading

Quick Start

  1. Create a Stage Manager prefab with the GS_StageManagerComponent.
  2. Add stage entries to the Stages list (name + spawnable prefab for each level).
  3. Set the Default Stage to your starting level.
  4. Add the Stage Manager .spawnable to the Game Manager’s Startup Managers list.
  5. In each level prefab, add a Stage Data component as the level’s anchor.
  6. Optionally place Exit Points in each level for spawn positioning.

See Also

4.1 - Stage Manager

The singleton level controller — manages a list of stages, handles level loading/unloading, and coordinates staged startup with the Stage Data component.

Image showing the Stage Manager component, as seen in the Entity Inspector.

Overview

The Stage Manager is the singleton that handles all the logistics of loading and unloading game levels. It maintains a list of stages (name-to-spawnable mappings) and processes change requests by unloading the current stage and spawning the target. Once loaded, it connects to the level’s Stage Data component to run the staged startup sequence.

The system operates as a “one in, one out” format — loading one level automatically unloads the previous one.

How It Works

Startup

At game startup, the Stage Manager checks the Game Manager’s Debug Mode flag:

  • Debug Mode OFF — The Stage Manager loads the Default Stage automatically. This is the normal game flow.
  • Debug Mode ON — The Stage Manager stays connected to whatever level is already loaded in the editor. This lets developers iterate within a stage without waiting for a full level load cycle.

Change Stage Flow

When ChangeStageRequest(stageName, exitName) is called:

  1. Unload — The current stage is destroyed (if one is loaded).
  2. Spawn — The Stage Manager looks up stageName in the Stages list and spawns the matching prefab.
  3. Connect — The Stage Manager queries the newly spawned level for its Stage Data component.
  4. Startup — The Stage Data component runs the level’s staged activation sequence (priority layers, NavMesh generation, etc.).
  5. Notify — The Stage Manager broadcasts LoadStageComplete when the level is fully ready.

Exit Points

Exit Points are simple components placed within a level that mark positions for entity placement after a stage loads. They support cross-stage traversal — when changing stages, you specify an exit point name, and entities can be repositioned to that point.

  • Exit Points register themselves with the Stage Manager by name via RegisterExitPoint.
  • One Exit Point can be flagged as Default — it serves as the fallback if no specific exit point is requested.
  • Call GetExitPoint(exitName) to retrieve the entity at a named position.

Setup

Image showing the Manager wrapper entity set as Editor-Only inside Prefab Edit Mode.

  1. Create an entity. Attach the GS_StageManagerComponent to it.
  2. Configure the Stages list — add an entry for each level (stage name + spawnable prefab reference).
  3. Set the Default Stage to your starting level. This name must also exist in the Stages list.
  4. Turn the entity into a prefab.
  5. Enter prefab edit mode. Set the wrapper entity (parent) to Editor Only. Save.
  6. Delete the Stage Manager entity from the level.
  7. In the Game Manager prefab, add the Stage Manager .spawnable to the Startup Managers list.

Inspector Properties

PropertyTypeDefaultDescription
StagesAZStd::vector<StageEntry>[]List of stage entries. Each entry maps a stage name to a spawnable prefab asset.
Default StageAZStd::string""The stage to load automatically on game startup (when not in Debug Mode). Must match a name in the Stages list.

API Reference

Request Bus: StageManagerIncomingEventBus

The primary interface for level navigation. Singleton bus — call via Broadcast.

MethodParametersReturnsDescription
ChangeStageRequestAZStd::string stageName, AZStd::string exitNamevoidUnloads the current stage and loads the named stage. The exitName specifies which Exit Point to use for entity placement. Pass empty string for default.
LoadDefaultStagevoidLoads the Default Stage. Typically called internally during game startup.
RegisterExitPointAZStd::string exitName, AZ::EntityId exitEntityvoidRegisters a named exit point entity. Called by StageExitPointComponents on activation.
UnregisterExitPointAZStd::string exitNamevoidRemoves a named exit point. Called by StageExitPointComponents on deactivation.
GetExitPointAZStd::string exitName = ""AZ::EntityIdReturns the entity ID of the named exit point. Pass empty string to get the default exit point.

Notification Bus: StageManagerOutgoingEventBus

Connect to this bus to react to level loading events.

EventParametersDescription
BeginLoadStageBroadcast when a stage change has started. Use this to show loading screens, disable input, etc.
LoadStageCompleteBroadcast when the new stage is fully loaded and its startup sequence is complete. Safe to begin gameplay.
StageLoadProgressfloat progressBroadcast during the loading process with a 0.0–1.0 progress value. Use for loading bar UI.

Local / Virtual Methods

These methods are available when extending the Stage Manager.

MethodDescription
ChangeStage(stageName, exitName)Internal stage change logic. Override to add custom behavior around stage transitions.
LoadStage()Spawns the target stage prefab. Override for custom spawning logic.
ContinueLoadStage()Called after the stage prefab has spawned. Queries for Stage Data and starts the level’s activation sequence.
UpdateStageData()Connects to the Stage Data component in the loaded level.
GetStageByName(stageName)Returns the spawnable asset reference for a named stage from the Stages list.

Usage Examples

Changing Stages

#include <GS_Core/GS_CoreBus.h>

// Load "ForestVillage" and place the player at the "main_entrance" exit point
GS_Core::StageManagerIncomingEventBus::Broadcast(
    &GS_Core::StageManagerIncomingEventBus::Events::ChangeStageRequest,
    AZStd::string("ForestVillage"),
    AZStd::string("main_entrance")
);

Getting an Exit Point Entity

#include <GS_Core/GS_CoreBus.h>

AZ::EntityId exitEntity;
GS_Core::StageManagerIncomingEventBus::BroadcastResult(
    exitEntity,
    &GS_Core::StageManagerIncomingEventBus::Events::GetExitPoint,
    AZStd::string("cave_exit")
);

if (exitEntity.IsValid())
{
    // Reposition the player entity to the exit point's transform
}

Listening for Stage Load Events

#include <GS_Core/GS_CoreBus.h>

class MyLoadScreenComponent
    : public AZ::Component
    , protected GS_Core::StageManagerOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::StageManagerOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::StageManagerOutgoingEventBus::Handler::BusDisconnect();
    }

    void BeginLoadStage() override
    {
        // Show loading screen, disable player input
    }

    void LoadStageComplete() override
    {
        // Hide loading screen, enable player input
    }

    void StageLoadProgress(float progress) override
    {
        // Update loading bar: progress is 0.0 to 1.0
    }
};

Extending the Stage Manager

Extend the Stage Manager when you need custom stage transition logic, procedural level generation, or multi-stage loading.

Header (.h)

#pragma once
#include <Source/StageManager/GS_StageManagerComponent.h>

namespace MyProject
{
    class MyStageManagerComponent
        : public GS_Core::GS_StageManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyStageManagerComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // Override stage change behavior
        void ChangeStage(AZStd::string stageName, AZStd::string exitName) override;
        void ContinueLoadStage() override;
    };
}

Implementation (.cpp)

#include "MyStageManagerComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyStageManagerComponent, "MyStageManagerComponent", "{YOUR-UUID-HERE}",
        GS_Core::GS_StageManagerComponent);

    void MyStageManagerComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyStageManagerComponent, GS_Core::GS_StageManagerComponent>()
                ->Version(0);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyStageManagerComponent>(
                    "My Stage Manager", "Custom stage manager with transition effects")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }

    void MyStageManagerComponent::ChangeStage(AZStd::string stageName, AZStd::string exitName)
    {
        // Example: trigger a fade-out transition before changing stages
        // ... your transition logic ...

        // Call base to perform the actual stage change
        GS_StageManagerComponent::ChangeStage(stageName, exitName);
    }

    void MyStageManagerComponent::ContinueLoadStage()
    {
        // Call base to connect to Stage Data and start level activation
        GS_StageManagerComponent::ContinueLoadStage();

        // Example: trigger a fade-in transition after loading
        // ... your transition logic ...
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyStageManagerComponent::CreateDescriptor(),
});

Then create a prefab for your custom Stage Manager and add it to the Game Manager’s Startup Managers list (replacing the default).


See Also

4.2 - Stage Data

The per-level anchor component — holds stage configuration, NavMesh references, and runs the level’s staged activation sequence.

Image showing the Stage Data component, as seen in the Entity Inspector.

Overview

The Stage Data component is the anchor that connects the Stage Manager to a loaded level. It holds level-specific configuration — stage name, NavMesh reference, priority layers — and runs the staged activation sequence that brings the level online incrementally rather than all at once.

Every level that loads through the Stage Manager should have exactly one Stage Data component at the root of its prefab hierarchy.

How It Works

Connection

When the Stage Manager spawns a stage prefab, it queries the level for the Stage Data component via StageDataIncomingEventBus. The Stage Data returns its reference, and the Stage Manager hands off the startup process.

Staged Activation

Rather than activating every entity in the level at once (which would cause a massive frame spike), the Stage Data component supports priority-based activation:

  1. SetUpStage() — Initial stage configuration. Sets up references, connects internal systems.
  2. ActivateByPriority — Entities tagged with priority layers activate in sequence, spreading heavy initialization across multiple frames. This allows NavMesh generation, procedural content, and complex entity hierarchies to spin up without blocking the main thread.
  3. LoadStageComplete — The Stage Manager broadcasts this once all priority layers have finished and the level is fully ready.

The Stage Data component holds a reference to the level’s Recast NavMesh entity. This allows the Stage Manager to trigger NavMesh generation at the appropriate point during the startup sequence — after the level geometry is loaded but before AI systems begin pathfinding.


Setup

  1. In your level prefab, create an entity at the root level.
  2. Attach the GS_StageDataComponent to it.
  3. Set the Stage Name to match the name used in the Stage Manager’s Stages list.
  4. Assign the NavMesh entity reference if your level uses Recast Navigation.
  5. Configure priority layers for incremental entity activation if needed.

Inspector Properties

PropertyTypeDefaultDescription
Stage NameAZStd::string""The name of this stage. Must match the corresponding entry in the Stage Manager’s Stages list.
NavMeshAZ::EntityIdInvalidReference to the entity with the Recast Navigation component. Used to trigger NavMesh generation during staged startup.

API Reference

Request Bus: StageDataIncomingEventBus

Used by the Stage Manager to connect to and control the level’s startup.

MethodParametersReturnsDescription
GetStageDataStageData*Returns a reference to this Stage Data component. Used by the Stage Manager during initial connection.
BeginSetUpStagevoidStarts the stage’s setup sequence. Called by the Stage Manager after connecting.
GetStageNameAZStd::stringReturns the configured stage name.
GetStageNavMeshAZ::EntityIdReturns the NavMesh entity reference for this stage.

Notification Bus: StageDataOutgoingEventBus

Connect to this bus for level-specific lifecycle events.

EventParametersDescription
OnLoadStageCompleteBroadcast when this stage’s activation sequence has finished.
OnBeginSetUpStageBroadcast when the stage setup begins.
OnTearDownStageBroadcast when the stage is being unloaded. Use for level-specific cleanup.
ActivateByPriorityint priorityBroadcast for each priority layer during staged activation. Entities at this priority level should activate.

Local / Virtual Methods

These methods are available when extending the Stage Data component.

MethodReturnsDescription
SetUpStage()boolRuns the stage’s initial setup. Override to add custom startup logic. Returns true when setup is complete.
BeginLoadStage()voidCalled when the stage begins loading. Override for custom load-start behavior.
LoadStageComplete()voidCalled when loading is complete. Override for custom post-load behavior.

Usage Examples

Listening for Stage Events in a Level Component

#include <GS_Core/GS_CoreBus.h>

class MyLevelController
    : public AZ::Component
    , protected GS_Core::StageDataOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::StageDataOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::StageDataOutgoingEventBus::Handler::BusDisconnect();
    }

    void OnBeginSetUpStage() override
    {
        // Level setup is starting — initialize level-specific systems
    }

    void OnLoadStageComplete() override
    {
        // Level is fully ready — spawn enemies, start ambient audio, etc.
    }

    void OnTearDownStage() override
    {
        // Level is being unloaded — clean up level-specific resources
    }
};

Priority-Based Activation

#include <GS_Core/GS_CoreBus.h>

class MyPriorityActivator
    : public AZ::Component
    , protected GS_Core::StageDataOutgoingEventBus::Handler
{
    int m_myPriority = 2; // Activate during priority layer 2

protected:
    void Activate() override
    {
        GS_Core::StageDataOutgoingEventBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::StageDataOutgoingEventBus::Handler::BusDisconnect();
    }

    void ActivateByPriority(int priority) override
    {
        if (priority == m_myPriority)
        {
            // This is our turn — initialize heavy resources
            // (procedural generation, AI setup, physics volumes, etc.)
        }
    }
};

Extending the Stage Data Component

Extend Stage Data when you need custom level startup sequences, procedural generation hooks, or level-specific configuration.

Header (.h)

#pragma once
#include <Source/StageManager/GS_StageDataComponent.h>

namespace MyProject
{
    class MyStageDataComponent
        : public GS_Core::GS_StageDataComponent
    {
    public:
        AZ_COMPONENT_DECL(MyStageDataComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // Override to add custom stage setup
        bool SetUpStage() override;
        void LoadStageComplete() override;

    private:
        bool m_enableProceduralContent = false;
    };
}

Implementation (.cpp)

#include "MyStageDataComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyStageDataComponent, "MyStageDataComponent", "{YOUR-UUID-HERE}",
        GS_Core::GS_StageDataComponent);

    void MyStageDataComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyStageDataComponent, GS_Core::GS_StageDataComponent>()
                ->Version(0)
                ->Field("EnableProceduralContent", &MyStageDataComponent::m_enableProceduralContent);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyStageDataComponent>(
                    "My Stage Data", "Custom stage data with procedural generation support")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &MyStageDataComponent::m_enableProceduralContent,
                        "Enable Procedural Content",
                        "Generate procedural content during stage setup");
            }
        }
    }

    bool MyStageDataComponent::SetUpStage()
    {
        if (m_enableProceduralContent)
        {
            // Run procedural generation for this level
            // ... your generation logic ...
        }

        // Call base to continue the standard setup sequence
        return GS_StageDataComponent::SetUpStage();
    }

    void MyStageDataComponent::LoadStageComplete()
    {
        // Custom post-load behavior
        // ... your logic ...

        GS_StageDataComponent::LoadStageComplete();
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyStageDataComponent::CreateDescriptor(),
});

Use your custom Stage Data component in level prefabs instead of the default GS_StageDataComponent.


See Also

5 - GS_Options

The configuration system — input profiles, input readers, and runtime settings management for gameplay and accessibility.

Overview

The Options system is the central pillar for handling background systemic configuration. At its simplest, it lets you set up user-facing options like volume, graphics settings, subtitles, and other standard configurable settings. Beyond that, it manages development-side functionality — language settings, current input device type, and other runtime-aware systems — providing that data to hook components throughout the game.

Currently, the Options system’s primary feature is the Input Profile system, which replaces O3DE’s raw input binding files with a more flexible, group-based approach that supports runtime rebinding and per-group enable/disable toggling.

Architecture

GS_OptionsManagerComponent (singleton, spawned by Game Manager)
  ├── Holds the active GS_InputProfile data asset
  ├── GS_InputProfile (data asset, created in Asset Editor)
  │     Contains InputBindingGroups
  │       Each group contains EventInputMappings
  │         Each mapping: eventName + bindings + deadzone + processUpdate
  └── GS_InputReaderComponent (on any entity)
        Reads input from the active profile
        Fires events based on matched bindings
        Can enable/disable input groups at runtime

Components

ComponentPurposeDocumentation
GS_OptionsManagerComponentSingleton manager. Holds the active Input Profile and provides it to Input Readers.Options Manager
GS_InputProfileData asset defining input groups, event mappings, and key bindings. Created in the Asset Editor.Input Profiles
GS_InputReaderComponentReads input through the active profile and fires gameplay events. Can be extended for specialized input handling.Input Options

Quick Start

  1. Create an Options Manager prefab with the GS_OptionsManagerComponent.
  2. Create a GS_InputProfile data asset in the Asset Editor.
  3. Add input groups and event mappings to the profile.
  4. Assign the Input Profile to the Options Manager.
  5. Add the Options Manager .spawnable to the Game Manager’s Startup Managers list.
  6. Attach GS_InputReaderComponents to entities that need to process input.

See Also

5.1 - Options Manager

The singleton options controller — holds the active Input Profile and provides runtime configuration data to game systems.

Image showing the Options Manager component, as seen in the Entity Inspector.

Overview

The Options Manager is the central anchor of the Options system. It holds the active Input Profile data asset and provides it to Input Readers and other game systems that need runtime configuration data.

As a Manager, it is spawned by the Game Manager and participates in the standard two-stage initialization lifecycle.

How It Works

  1. The Options Manager initializes during the Game Manager’s startup sequence.
  2. It loads the configured Input Profile data asset.
  3. Input Readers across the game query the Options Manager for the active profile via OptionsManagerIncomingEventBus.
  4. The Options Manager responds to standby events — pausing and resuming input processing when the game enters or exits standby mode.

Setup

Image showing the Manager wrapper entity set as Editor-Only inside Prefab Edit Mode.

  1. Create an entity. Attach the GS_OptionsManagerComponent to it.
  2. Assign your Input Profile data asset.
  3. Turn the entity into a prefab.
  4. Enter prefab edit mode. Set the wrapper entity (parent) to Editor Only. Save.
  5. Delete the Options Manager entity from the level.
  6. In the Game Manager prefab, add the Options Manager .spawnable to the Startup Managers list.

Inspector Properties

PropertyTypeDefaultDescription
Input ProfileAZ::Data::Asset<GS_InputProfile>NoneThe active Input Profile data asset. Input Readers across the game will use this profile for event-to-binding mappings.

API Reference

Request Bus: OptionsManagerIncomingEventBus

Singleton bus — call via Broadcast.

MethodParametersReturnsDescription
GetActiveInputProfileAZ::Data::Asset<GS_InputProfile>Returns the currently active Input Profile data asset. Called by Input Readers on activation.

Lifecycle Events (from GameManagerOutgoingEventBus)

EventDescription
OnStartupCompleteThe Options Manager is fully ready. Input Readers can now query for the active profile.
OnEnterStandbyPause input processing.
OnExitStandbyResume input processing.

Usage Examples

Getting the Active Input Profile

#include <GS_Core/GS_CoreBus.h>

AZ::Data::Asset<GS_Core::GS_InputProfile> profile;
GS_Core::OptionsManagerIncomingEventBus::BroadcastResult(
    profile,
    &GS_Core::OptionsManagerIncomingEventBus::Events::GetActiveInputProfile
);

if (profile.IsReady())
{
    // Use the profile to look up bindings, check group states, etc.
}

Extending the Options Manager

Extend the Options Manager when you need additional runtime settings (graphics quality, audio levels, accessibility), custom options persistence, or platform-specific configuration.

Header (.h)

#pragma once
#include <Source/OptionsSystem/GS_OptionsManagerComponent.h>

namespace MyProject
{
    class MyOptionsManagerComponent
        : public GS_Core::GS_OptionsManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyOptionsManagerComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        void OnStartupComplete() override;

    private:
        float m_masterVolume = 1.0f;
        int m_graphicsQuality = 2; // 0=Low, 1=Med, 2=High
    };
}

Implementation (.cpp)

#include "MyOptionsManagerComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(MyOptionsManagerComponent, "MyOptionsManagerComponent", "{YOUR-UUID-HERE}",
        GS_Core::GS_OptionsManagerComponent);

    void MyOptionsManagerComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<MyOptionsManagerComponent, GS_Core::GS_OptionsManagerComponent>()
                ->Version(0)
                ->Field("MasterVolume", &MyOptionsManagerComponent::m_masterVolume)
                ->Field("GraphicsQuality", &MyOptionsManagerComponent::m_graphicsQuality);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyOptionsManagerComponent>(
                    "My Options Manager", "Extended options with audio and graphics settings")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Slider,
                        &MyOptionsManagerComponent::m_masterVolume, "Master Volume", "Global audio volume")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
                        ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
                    ->DataElement(AZ::Edit::UIHandlers::ComboBox,
                        &MyOptionsManagerComponent::m_graphicsQuality, "Graphics Quality", "Rendering quality preset");
            }
        }
    }

    void MyOptionsManagerComponent::OnStartupComplete()
    {
        // Apply saved options on startup
        // ... load from save system and apply settings ...

        GS_OptionsManagerComponent::OnStartupComplete();
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::MyOptionsManagerComponent::CreateDescriptor(),
});

See Also

5.2 - GS_InputOptions

The input subsystem — Input Profiles for grouped event-to-binding mappings and Input Readers for processing input in gameplay.

Overview

Input Options is the input subsystem within the Options system. It provides two core pieces:

  • Input Profiles — Data assets that map input bindings to named events, organized into toggleable groups. These replace O3DE’s raw input binding files with a more flexible system that supports runtime rebinding and per-group enable/disable toggling.
  • Input Readers — Components that read input through the active profile and fire gameplay events. They can be extended for specialized input handling (player controllers, UI navigation, etc.).

Architecture

GS_OptionsManagerComponent
  └── Active GS_InputProfile (data asset)
        ├── InputBindingGroup: "Movement"
        │     ├── EventInputMapping: "MoveForward" → [keyboard_key_alphanumeric_W]
        │     ├── EventInputMapping: "MoveBack"    → [keyboard_key_alphanumeric_S]
        │     └── EventInputMapping: "Sprint"      → [keyboard_key_modifier_shift_l]
        ├── InputBindingGroup: "Combat"
        │     ├── EventInputMapping: "Attack"   → [mouse_button_left]
        │     └── EventInputMapping: "Block"    → [mouse_button_right]
        └── InputBindingGroup: "UI"
              ├── EventInputMapping: "Confirm"  → [keyboard_key_alphanumeric_E]
              └── EventInputMapping: "Cancel"   → [keyboard_key_navigation_escape]

GS_InputReaderComponent (on player entity, UI entity, etc.)
  ├── Queries OptionsManager for active profile
  ├── Listens for raw O3DE input events
  ├── Matches bindings → fires named events
  └── EnableInputGroup / DisableInputGroup for runtime control

Components

ComponentPurposeDocumentation
GS_InputProfileData asset defining input groups, event mappings, and key bindings.Input Profiles
GS_InputReaderComponentReads input through the active profile and fires gameplay events.See below

Input Reader

The Input Reader component sits on any entity that needs to process input. It queries the Options Manager for the active Input Profile, then listens for raw O3DE input events and matches them against the profile’s bindings to fire named gameplay events.

Key Features

  • Group toggling — Enable or disable entire input groups at runtime. For example, disable “Combat” inputs during a dialogue sequence, or disable “Movement” inputs during a cutscene.
  • Claim input — The ClaimAllInput flag causes the reader to absorb matched input events, preventing them from reaching other readers. Useful for layered input (e.g., UI absorbs input before gameplay).
  • Extensible — Extend the Input Reader for specialized input handling. GS_Play uses this internally for player controller input and UI navigation input.

API: InputReaderIncomingEventBus

Entity-addressed bus — call via Event(entityId, ...).

MethodParametersReturnsDescription
EnableInputGroupconst AZStd::string& groupNamevoidEnables a named input group for this reader. Events in this group will fire on matched input.
DisableInputGroupconst AZStd::string& groupNamevoidDisables a named input group. Events in this group will be ignored until re-enabled.
IsGroupDisabledconst AZStd::string& groupNameboolReturns true if the named group is currently disabled.

Usage Example

#include <GS_Core/GS_CoreBus.h>

// Disable combat inputs during dialogue
GS_Core::InputReaderIncomingEventBus::Event(
    playerEntityId,
    &GS_Core::InputReaderIncomingEventBus::Events::DisableInputGroup,
    AZStd::string("Combat")
);

// Re-enable when dialogue ends
GS_Core::InputReaderIncomingEventBus::Event(
    playerEntityId,
    &GS_Core::InputReaderIncomingEventBus::Events::EnableInputGroup,
    AZStd::string("Combat")
);

Setup

  1. Create a GS_InputProfile data asset in the Asset Editor.
  2. Add input groups and event mappings to the profile.
  3. Assign the profile to the Options Manager.
  4. Attach a GS_InputReaderComponent to any entity that needs to process input.
  5. The Input Reader automatically queries the Options Manager for the active profile on activation.

See Also

5.2.1 - Input Profiles

Data assets for input binding configuration — map key bindings to named events, organized into toggleable groups for advanced runtime input control.

Image showing the InputProfile data asset, as seen in the Asset Editor.

Overview

Input Profiles are data assets that map key bindings to named events, organized into groups that can be enabled and disabled at runtime. They replace O3DE’s raw input binding files with a more flexible system that supports runtime rebinding, per-group toggling, and options menu customization.

How It Works

An Input Profile contains a list of InputBindingGroups. Each group contains EventInputMappings that define the relationship between a named event, its key bindings, and processing options.

Data Structure

GS_InputProfile
  └── InputBindingGroups[]
        ├── groupName: "Movement"
        └── eventMappings[]
              ├── EventInputMapping
              │     ├── eventName: "MoveForward"
              │     ├── inputBindings: ["keyboard_key_alphanumeric_W"]
              │     ├── deadzone: 0.0
              │     └── processUpdate: true
              └── EventInputMapping
                    ├── eventName: "Jump"
                    ├── inputBindings: ["keyboard_key_alphanumeric_Space"]
                    ├── deadzone: 0.0
                    └── processUpdate: false

EventInputMapping

FieldTypeDescription
eventNameAZStd::stringThe name used to fire this event in gameplay through Input Readers. This is the identifier your gameplay code listens for.
inputBindingsAZStd::vector<AZStd::string>One or more raw O3DE input binding names (e.g., keyboard_key_alphanumeric_W, mouse_button_left, gamepad_button_a). Multiple bindings allow the same event to fire from different input sources.
deadzonefloatDead zone threshold for analog inputs (joysticks, triggers). Values below this threshold are treated as zero. Set to 0.0 for digital inputs (keys, buttons).
processUpdateboolWhen true, the event fires continuously while the input is held. When false, the event fires only on initial press and release. Use true for movement and camera input; false for discrete actions like jumping or interacting.

TypeId: {61421EC2-7B99-4EF2-9C56-2A7F41ED3474}

InputBindingGroup

FieldTypeDescription
groupNameAZStd::stringName of the group (e.g., “Movement”, “Combat”, “UI”). Used by Input Readers to enable/disable groups at runtime.
eventMappingsAZStd::vector<EventInputMapping>The event mappings belonging to this group.

TypeId: {37E880D1-9AB4-4E10-9C3C-020B5C32F75B}


Creating and Using an Input Profile

For initial startup instructions refer to the Core Set Up Guide.

  1. In the Asset Editor, open the New menu and select GS_InputProfile. This creates a blank Input Profile.
  2. Add Input Groups — Create groups that cluster related input events (e.g., “Movement”, “Combat”, “UI”). Groups can be toggled on/off at runtime by Input Readers.
  3. Add Event Mappings — Within each group, add EventInputMappings. Set the Event Name to the identifier your gameplay code will listen for.
  4. Set Bindings — Add the raw O3DE input bindings that should trigger each event. These are standard O3DE raw mapping names.
  5. Configure Deadzone — For gamepad joystick or trigger inputs, set an appropriate deadzone value (e.g., 0.15). Leave at 0.0 for keyboard and mouse inputs.
  6. Set Process Update — Enable for continuous input (movement, camera look). Disable for discrete actions (jump, interact, attack).
  7. Assign to Options Manager — Add the profile to the Options Manager’s Input Profile property.

API Reference

These methods are available on the GS_InputProfile data asset class for runtime binding queries and modifications.

MethodParametersReturnsDescription
GetMappingFromBindingconst AZStd::string& binding, const AZStd::string& groupName = ""const EventInputMapping*Looks up the event mapping associated with a raw binding. Optionally restrict the search to a specific group. Returns nullptr if not found.
GetGroupNameFromBindingconst AZStd::string& bindingconst AZStd::string*Returns the group name that contains the given binding. Returns nullptr if not found.
HasBindingconst AZStd::string& bindingboolReturns true if the binding exists anywhere in the profile.
ReplaceEventInputBindingconst AZStd::string& eventName, const AZStd::string& newBindingboolReplaces the existing binding for the named event with a new one. For runtime rebinding in options menus. Returns true on success.
AddEventInputBindingconst AZStd::string& eventName, const AZStd::string& newBindingboolAdds an additional binding to the named event. Returns true on success.
RemoveEventBindingconst AZStd::string& eventName, const AZStd::string& bindingToRemoveboolRemoves a specific binding from the named event. Returns true on success.

Usage Examples

Runtime Rebinding (Options Menu)

#include <GS_Core/GS_CoreBus.h>

// Get the active input profile
AZ::Data::Asset<GS_Core::GS_InputProfile> profile;
GS_Core::OptionsManagerIncomingEventBus::BroadcastResult(
    profile,
    &GS_Core::OptionsManagerIncomingEventBus::Events::GetActiveInputProfile
);

if (profile.IsReady())
{
    // Rebind the "Jump" event from Space to E
    profile.Get()->ReplaceEventInputBinding(
        "Jump",
        "keyboard_key_alphanumeric_E"
    );

    // Add an alternative binding (gamepad A button)
    profile.Get()->AddEventInputBinding(
        "Jump",
        "gamepad_button_a"
    );
}

Checking if a Binding Exists

if (profile.IsReady() && profile.Get()->HasBinding("keyboard_key_alphanumeric_W"))
{
    // This binding is mapped to an event in the profile
}

See Also

6 - Utilities

A collection of utility libraries — easing curves, spring dampers, physics trigger volumes, gradients, and entity helpers.

Overview

GS_Core includes a rich set of utility libraries for common game development patterns. These are header-only (or lightweight) utilities that any component or system can use without additional setup.


Features

6.1 - Common Enums

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Overview

PhysicsTriggeringVolume is a base class that provides everything needed to process physics trigger overlaps and collision contacts. Inherit from it to create interactive volumes — damage zones, pickup areas, dialogue triggers, environmental hazards — without writing boilerplate physics code.

It handles entity tracking (so you only get one enter/exit per entity), supports both trigger overlaps and collision contacts, and provides optional hold/persist callbacks for continuous processing.

TypeId: {A0C4C982-1B31-4FD3-9386-AD1A57ABEF8E}

How It Works

  1. Your component inherits from PhysicsTriggeringVolume (which itself inherits from AZ::Component).
  2. The base class connects to the physics trigger and collision buses automatically.
  3. When an entity enters/exits the volume, the base class tracks it and calls your override methods.
  4. Optionally enable hold updates for continuous processing while entities remain inside.

Entity Tracking

The base class maintains an internal m_entities list that tracks which entities are currently inside the volume. This ensures:

  • TriggerEnter / CollisionEnter fires only once per entity (not on every physics tick).
  • TriggerExit / CollisionExit fires only when an entity that was previously inside leaves.

API Reference

Trigger Overlap Methods

Override these to react to PhysX trigger volume events.

MethodDescription
TriggerEnter()Called when a new entity enters the trigger volume.
TriggerExit()Called when an entity leaves the trigger volume.
TriggerHold()Called on each physics tick while an entity remains inside. Requires EnableTriggerHoldUpdate to be enabled.

Collision Contact Methods

Override these to react to PhysX rigid body collision events.

MethodDescription
CollisionEnter()Called when a new entity begins colliding with this volume.
CollisionExit()Called when an entity stops colliding with this volume.
CollisionHold()Called on each physics tick while collision persists. Requires EnableTriggerHoldUpdate to be enabled via OnCollisionPersist.

Configuration

PropertyDescription
EnableTriggerHoldUpdateEnables the TriggerHold() / CollisionHold() callbacks on each physics tick. Disabled by default for performance.

Extending Common Enums

Header (.h)

#pragma once
#include <GS_Core/Utilities/PhysicsTriggeringVolume.h>

namespace MyProject
{
    class DamageZoneComponent
        : public virtual GS_Core::PhysicsTriggeringVolume
    {
    public:
        AZ_COMPONENT_DECL(DamageZoneComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // IMPORTANT: Call parent Activate/Deactivate
        void Activate() override;
        void Deactivate() override;

        // Trigger overrides
        void TriggerEnter() override;
        void TriggerExit() override;
        void TriggerHold() override;

    private:
        float m_damagePerSecond = 10.0f;
    };
}

Implementation (.cpp)

#include "DamageZoneComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(DamageZoneComponent, "DamageZoneComponent", "{YOUR-UUID-HERE}");

    void DamageZoneComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<DamageZoneComponent, GS_Core::PhysicsTriggeringVolume>()
                ->Version(0)
                ->Field("DamagePerSecond", &DamageZoneComponent::m_damagePerSecond);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<DamageZoneComponent>(
                    "Damage Zone", "Deals damage to entities inside the trigger volume")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &DamageZoneComponent::m_damagePerSecond,
                        "Damage Per Second", "How much damage to deal per second while inside");
            }
        }
    }

    void DamageZoneComponent::Activate()
    {
        // IMPORTANT: Call parent to connect to physics buses
        PhysicsTriggeringVolume::Activate();

        // Enable hold updates for continuous damage
        // EnableTriggerHoldUpdate = true;
    }

    void DamageZoneComponent::Deactivate()
    {
        // IMPORTANT: Call parent to disconnect from physics buses
        PhysicsTriggeringVolume::Deactivate();
    }

    void DamageZoneComponent::TriggerEnter()
    {
        // An entity entered the damage zone
        AZ_TracePrintf("DamageZone", "Entity entered damage zone");
    }

    void DamageZoneComponent::TriggerExit()
    {
        // An entity left the damage zone
        AZ_TracePrintf("DamageZone", "Entity exited damage zone");
    }

    void DamageZoneComponent::TriggerHold()
    {
        // Called each physics tick while entity is inside
        // Apply damage: m_damagePerSecond * deltaTime
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::DamageZoneComponent::CreateDescriptor(),
});

Setup

  1. Create an entity with a PhysX Collider component (set as trigger).
  2. Attach your custom trigger volume component (e.g., DamageZoneComponent).
  3. Configure the collider shape and your component’s properties.
  4. Entities with rigidbodies that enter the collider will trigger your callbacks.

See Also

6.2 - Serialization Helpers

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Entity Utilities

Helper functions for finding entities by name.

FunctionDescription
EntityUtility::GetEntityByName(name)Returns the entity pointer for the named entity.
EntityUtility::GetEntityIdByName(name)Returns the EntityId for the named entity.

6.3 - Entity Helpers

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Overview

PhysicsTriggeringVolume is a base class that provides everything needed to process physics trigger overlaps and collision contacts. Inherit from it to create interactive volumes — damage zones, pickup areas, dialogue triggers, environmental hazards — without writing boilerplate physics code.

It handles entity tracking (so you only get one enter/exit per entity), supports both trigger overlaps and collision contacts, and provides optional hold/persist callbacks for continuous processing.

TypeId: {A0C4C982-1B31-4FD3-9386-AD1A57ABEF8E}

How It Works

  1. Your component inherits from PhysicsTriggeringVolume (which itself inherits from AZ::Component).
  2. The base class connects to the physics trigger and collision buses automatically.
  3. When an entity enters/exits the volume, the base class tracks it and calls your override methods.
  4. Optionally enable hold updates for continuous processing while entities remain inside.

Entity Tracking

The base class maintains an internal m_entities list that tracks which entities are currently inside the volume. This ensures:

  • TriggerEnter / CollisionEnter fires only once per entity (not on every physics tick).
  • TriggerExit / CollisionExit fires only when an entity that was previously inside leaves.

API Reference

Trigger Overlap Methods

Override these to react to PhysX trigger volume events.

MethodDescription
TriggerEnter()Called when a new entity enters the trigger volume.
TriggerExit()Called when an entity leaves the trigger volume.
TriggerHold()Called on each physics tick while an entity remains inside. Requires EnableTriggerHoldUpdate to be enabled.

Collision Contact Methods

Override these to react to PhysX rigid body collision events.

MethodDescription
CollisionEnter()Called when a new entity begins colliding with this volume.
CollisionExit()Called when an entity stops colliding with this volume.
CollisionHold()Called on each physics tick while collision persists. Requires EnableTriggerHoldUpdate to be enabled via OnCollisionPersist.

Configuration

PropertyDescription
EnableTriggerHoldUpdateEnables the TriggerHold() / CollisionHold() callbacks on each physics tick. Disabled by default for performance.

Extending Entity Helpers

Header (.h)

#pragma once
#include <GS_Core/Utilities/PhysicsTriggeringVolume.h>

namespace MyProject
{
    class DamageZoneComponent
        : public virtual GS_Core::PhysicsTriggeringVolume
    {
    public:
        AZ_COMPONENT_DECL(DamageZoneComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // IMPORTANT: Call parent Activate/Deactivate
        void Activate() override;
        void Deactivate() override;

        // Trigger overrides
        void TriggerEnter() override;
        void TriggerExit() override;
        void TriggerHold() override;

    private:
        float m_damagePerSecond = 10.0f;
    };
}

Implementation (.cpp)

#include "DamageZoneComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(DamageZoneComponent, "DamageZoneComponent", "{YOUR-UUID-HERE}");

    void DamageZoneComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<DamageZoneComponent, GS_Core::PhysicsTriggeringVolume>()
                ->Version(0)
                ->Field("DamagePerSecond", &DamageZoneComponent::m_damagePerSecond);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<DamageZoneComponent>(
                    "Damage Zone", "Deals damage to entities inside the trigger volume")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &DamageZoneComponent::m_damagePerSecond,
                        "Damage Per Second", "How much damage to deal per second while inside");
            }
        }
    }

    void DamageZoneComponent::Activate()
    {
        // IMPORTANT: Call parent to connect to physics buses
        PhysicsTriggeringVolume::Activate();

        // Enable hold updates for continuous damage
        // EnableTriggerHoldUpdate = true;
    }

    void DamageZoneComponent::Deactivate()
    {
        // IMPORTANT: Call parent to disconnect from physics buses
        PhysicsTriggeringVolume::Deactivate();
    }

    void DamageZoneComponent::TriggerEnter()
    {
        // An entity entered the damage zone
        AZ_TracePrintf("DamageZone", "Entity entered damage zone");
    }

    void DamageZoneComponent::TriggerExit()
    {
        // An entity left the damage zone
        AZ_TracePrintf("DamageZone", "Entity exited damage zone");
    }

    void DamageZoneComponent::TriggerHold()
    {
        // Called each physics tick while entity is inside
        // Apply damage: m_damagePerSecond * deltaTime
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::DamageZoneComponent::CreateDescriptor(),
});

Setup

  1. Create an entity with a PhysX Collider component (set as trigger).
  2. Attach your custom trigger volume component (e.g., DamageZoneComponent).
  3. Configure the collider shape and your component’s properties.
  4. Entities with rigidbodies that enter the collider will trigger your callbacks.

See Also

6.4 - Gradients

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Gradients

Multi-stop gradient utilities for sampling color, float, or vector values along a 0.0–1.0 range.

GradientDescription
ColorGradientMulti-stop color gradient with ColorGradientMarker entries (color + position). Sample at any point for interpolated color.
FloatGradientFloat value gradient for numeric curves.
Vector2Gradient2D vector gradient.

6.5 - Spline Helper

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Spline Utilities

Helper functions for working with O3DE spline components.

FunctionDescription
SplineUtility::FindClosestWorldPoint(spline, worldPos)Finds the closest point on the spline in world space.
SplineUtility::FindClosestLocalPoint(spline, localPos)Finds the closest point on the spline in local space.
SplineUtility::FindClosestFraction(spline, worldPos)Returns the 0.0–1.0 fraction along the spline closest to the given world position.

6.6 - Curves

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Curves Utility

A library of 40+ easing curve functions for animation, transitions, and interpolation. Each curve type comes in three variants: Ease In, Ease Out, and Ease In-Out.

All functions take and return clamped values between 0.0 and 1.0 (available as both float and double).

When to Use

  • Animation timing — Control the acceleration and deceleration of animated values
  • UI transitions — Fade, slide, and scale with natural-feeling motion
  • Gameplay curves — Difficulty scaling, damage falloff, speed ramps
  • Procedural generation — Shape noise, terrain height curves, gradient falloff

Available Curves

CurveCharacter
LinearConstant speed — no easing
QuadraticGentle acceleration/deceleration
CubicModerate acceleration/deceleration
QuarticStrong acceleration/deceleration
QuinticVery strong acceleration/deceleration
SinusoidalSmooth, natural-feeling motion
ExponentialSharp acceleration, gradual deceleration (or vice versa)
CircularBased on circular arc — distinct feel
ElasticOvershoots and springs back
BackPulls back before moving forward
BounceBounces at the end

Curve Type Enum

The CurveType enum ({A9718DE5-BFD6-4206-A8B8-0DEB7B7162F2}) can be reflected to the inspector, allowing designers to select easing curves from a dropdown in the Entity Inspector.

6.7 - Springs

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Springs Utility

A collection of critically damped spring functions for smooth, controlled blending between current and target values. Springs are ideal for camera follow, UI animations, procedural motion, and any system that needs to track a moving target without oscillation.

Supports float, AZ::Vector2, AZ::Vector3, and AZ::Quaternion data types.

When to Use

  • Camera smoothing — Follow a target position/rotation without jitter
  • UI animation — Smoothly move UI elements to new positions
  • Procedural motion — Smooth velocity changes, weapon sway, head bob
  • Value blending — Smoothly interpolate any numeric value toward a target

Available Springs

SpringDescription
SimpleSpringBasic critically damped spring. Good for most smoothing needs.
AccelerationSpringSpring with acceleration control. Good for smooth starts.
DoubleSpringTwo-pass spring for extra-smooth results. Good for camera follow.
TimedSpringSpring that reaches the target in a specified time. Good for UI transitions.
VelocitySpringSpring with explicit velocity tracking. Good for physics-driven motion.
QuaternionSpringRotation-specific spring. Good for camera and character rotation smoothing.

6.8 - Angles Helper

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Angle/Orientation Utilities

Helper functions for angle-based section mapping and orientation conversion.

FunctionDescription
SectionByAngle::PickByAngle(angle, config)Maps an angle to a section index using a predefined config. Useful for 4-directional or 8-directional animation selection.
SectionByAngle::YawFromDir(direction)Converts a direction vector to a yaw angle.
SectionByAngle::QuatFromYaw(yaw)Converts a yaw angle to a quaternion rotation.

Includes 22 predefined SectionConfig presets for common angle-to-section mapping patterns (4-way, 8-way, etc.).

6.9 - Physics Trigger Volume

A base class for physics trigger and collision handling — inherit to create interactive volumes with enter, exit, and hold callbacks.

Overview

PhysicsTriggeringVolume is a base class that provides everything needed to process physics trigger overlaps and collision contacts. Inherit from it to create interactive volumes — damage zones, pickup areas, dialogue triggers, environmental hazards — without writing boilerplate physics code.

It handles entity tracking (so you only get one enter/exit per entity), supports both trigger overlaps and collision contacts, and provides optional hold/persist callbacks for continuous processing.

TypeId: {A0C4C982-1B31-4FD3-9386-AD1A57ABEF8E}

How It Works

  1. Your component inherits from PhysicsTriggeringVolume (which itself inherits from AZ::Component).
  2. The base class connects to the physics trigger and collision buses automatically.
  3. When an entity enters/exits the volume, the base class tracks it and calls your override methods.
  4. Optionally enable hold updates for continuous processing while entities remain inside.

Entity Tracking

The base class maintains an internal m_entities list that tracks which entities are currently inside the volume. This ensures:

  • TriggerEnter / CollisionEnter fires only once per entity (not on every physics tick).
  • TriggerExit / CollisionExit fires only when an entity that was previously inside leaves.

API Reference

Trigger Overlap Methods

Override these to react to PhysX trigger volume events.

MethodDescription
TriggerEnter()Called when a new entity enters the trigger volume.
TriggerExit()Called when an entity leaves the trigger volume.
TriggerHold()Called on each physics tick while an entity remains inside. Requires EnableTriggerHoldUpdate to be enabled.

Collision Contact Methods

Override these to react to PhysX rigid body collision events.

MethodDescription
CollisionEnter()Called when a new entity begins colliding with this volume.
CollisionExit()Called when an entity stops colliding with this volume.
CollisionHold()Called on each physics tick while collision persists. Requires EnableTriggerHoldUpdate to be enabled via OnCollisionPersist.

Configuration

PropertyDescription
EnableTriggerHoldUpdateEnables the TriggerHold() / CollisionHold() callbacks on each physics tick. Disabled by default for performance.

Extending Physics Trigger Volume

Header (.h)

#pragma once
#include <GS_Core/Utilities/PhysicsTriggeringVolume.h>

namespace MyProject
{
    class DamageZoneComponent
        : public virtual GS_Core::PhysicsTriggeringVolume
    {
    public:
        AZ_COMPONENT_DECL(DamageZoneComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // IMPORTANT: Call parent Activate/Deactivate
        void Activate() override;
        void Deactivate() override;

        // Trigger overrides
        void TriggerEnter() override;
        void TriggerExit() override;
        void TriggerHold() override;

    private:
        float m_damagePerSecond = 10.0f;
    };
}

Implementation (.cpp)

#include "DamageZoneComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(DamageZoneComponent, "DamageZoneComponent", "{YOUR-UUID-HERE}");

    void DamageZoneComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<DamageZoneComponent, GS_Core::PhysicsTriggeringVolume>()
                ->Version(0)
                ->Field("DamagePerSecond", &DamageZoneComponent::m_damagePerSecond);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<DamageZoneComponent>(
                    "Damage Zone", "Deals damage to entities inside the trigger volume")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &DamageZoneComponent::m_damagePerSecond,
                        "Damage Per Second", "How much damage to deal per second while inside");
            }
        }
    }

    void DamageZoneComponent::Activate()
    {
        // IMPORTANT: Call parent to connect to physics buses
        PhysicsTriggeringVolume::Activate();

        // Enable hold updates for continuous damage
        // EnableTriggerHoldUpdate = true;
    }

    void DamageZoneComponent::Deactivate()
    {
        // IMPORTANT: Call parent to disconnect from physics buses
        PhysicsTriggeringVolume::Deactivate();
    }

    void DamageZoneComponent::TriggerEnter()
    {
        // An entity entered the damage zone
        AZ_TracePrintf("DamageZone", "Entity entered damage zone");
    }

    void DamageZoneComponent::TriggerExit()
    {
        // An entity left the damage zone
        AZ_TracePrintf("DamageZone", "Entity exited damage zone");
    }

    void DamageZoneComponent::TriggerHold()
    {
        // Called each physics tick while entity is inside
        // Apply damage: m_damagePerSecond * deltaTime
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::DamageZoneComponent::CreateDescriptor(),
});

Setup

  1. Create an entity with a PhysX Collider component (set as trigger).
  2. Attach your custom trigger volume component (e.g., DamageZoneComponent).
  3. Configure the collider shape and your component’s properties.
  4. Entities with rigidbodies that enter the collider will trigger your callbacks.

See Also

6.10 - GS_Random

Random utilities — weighted random selection and deterministic random value generation for procedural and gameplay systems.

Overview

The Random utilities provide weighted random selection for gameplay and procedural systems. The primary utility is GetRandomWeighted, a template function that selects an item from a collection based on assigned weights — higher weights mean higher probability of selection.

API Reference

RandomUtils (Template Utility)

FunctionDescription
GetRandomWeighted<T>(collection)Selects a random item from a weighted collection. Each item must provide a weight value. Items with higher weights are proportionally more likely to be selected.

Usage Example

#include <GS_Core/Utilities/RandomUtils.h>

// Define weighted items (e.g., loot drops)
struct LootEntry
{
    AZStd::string itemName;
    float weight; // Higher = more common
};

AZStd::vector<LootEntry> lootTable = {
    { "Common Sword",   50.0f },
    { "Rare Shield",    30.0f },
    { "Epic Helmet",    15.0f },
    { "Legendary Ring",  5.0f },
};

// Select a random item weighted by drop rate
auto selected = GS_Core::RandomUtils::GetRandomWeighted(lootTable);
// "Common Sword" is 10x more likely than "Legendary Ring"

See Also

6.11 - GS_Actions

A utility system for triggerable single-purpose actions — fire discrete behaviors from any system without recoding logic for each component.

Overview

The Actions system provides a simple, universal pattern for triggering discrete behaviors. Instead of recoding common functionality (toggle cursor, play sound, print log) into every component that needs it, you attach Action components to an entity and fire them by channel name.

Actions support chaining — when one action completes, it can fire another action on a different channel, enabling lightweight sequences without custom code.

Architecture

Entity with Actions
  ├── ToggleMouseCursor_GSAction (channel: "enter_menu")
  ├── PlaySound_GSAction           (channel: "enter_menu")
  └── PrintLog_GSAction            (channel: "debug")

Any system calls:
  ActionIncomingEventBus::Event(entityId, DoAction, "enter_menu")
    → Both ToggleMouseCursor and PlaySound fire (matching channel)
    → PrintLog does NOT fire (different channel)

Components

ComponentPurposeDocumentation
GS_ActionComponentBase class for all actions. Handles channel matching, completion broadcasting, and action chaining.Action
ToggleMouseCursor_GSActionComponentToggles the mouse cursor on or off.Core Actions
PrintLog_GSActionComponentPrints a message to the console log.Core Actions

Quick Start

  1. Attach one or more Action components to an entity.
  2. Set the Channel on each action to control when it fires.
  3. From any system, call DoAction(channelName) on the entity’s ActionIncomingEventBus to trigger matching actions.
  4. Optionally enable Broadcast On Complete and set an On Complete Channel Chain for action sequencing.

See Also

6.11.1 - Action

The base class for triggerable actions — channel-based firing, completion broadcasting, and action chaining for lightweight behavior sequencing.

Overview

GS_ActionComponent is the base class for all triggerable actions. Actions are single-purpose behaviors that fire when their channel matches an incoming DoAction call. They support completion broadcasting and optional chaining to sequence multiple actions together.

Every GS_Play action (Toggle Mouse Cursor, Print Log, and actions from other gems) extends this class. When you need a custom action, you extend it too.

How It Works

Firing an Action

  1. A system calls DoAction(targetChannel) on an entity’s ActionIncomingEventBus.
  2. Every Action component on that entity evaluates whether its channel matches the targetChannel.
  3. Matching actions call ProcessAction() to execute their behavior.
  4. When done, CompleteAction() handles completion:
    • If Broadcast On Complete is enabled, it fires OnActionComplete on the ActionOutgoingEventBus.
    • If On Complete Channel Chain is set, it fires a new DoAction with that channel name, triggering the next action in the chain.

Chaining Actions

By setting the On Complete Channel Chain to a different channel name, an action can trigger the next step in a sequence when it completes. This enables lightweight behavior chains without custom sequencing code.

Action A (channel: "step1", onCompleteChain: "step2")
  → fires → Action B (channel: "step2", onCompleteChain: "step3")
    → fires → Action C (channel: "step3")

Important: Every custom action must call CompleteAction() when its work is done. If you forget, the action will never complete and chains will break.


Inspector Properties

PropertyTypeDefaultDescription
ChannelAZStd::string""The channel this action responds to. Only DoAction calls with a matching channel will trigger this action.
Broadcast On CompleteboolfalseWhen enabled, broadcasts OnActionComplete on the ActionOutgoingEventBus when this action finishes.
On Complete Channel ChainAZStd::string""When non-empty, fires a new DoAction with this channel name after completing. Enables action chaining.

API Reference

Request Bus: ActionIncomingEventBus

Entity-addressed bus — call via Event(entityId, ...).

MethodParametersReturnsDescription
DoActionAZStd::string targetChannelvoidTriggers all actions on the target entity whose channel matches targetChannel.

Notification Bus: ActionOutgoingEventBus

Entity-addressed bus — connect via BusConnect(entityId).

EventParametersDescription
OnActionCompleteBroadcast when an action with Broadcast On Complete enabled finishes its work.

Local / Virtual Methods

Override these when creating custom actions.

MethodDescription
DoAction(targetChannel)Evaluates whether this action should fire. Default compares channel to targetChannel. Override for custom evaluation logic.
ProcessAction()Your primary override. Executes the action’s behavior. Called when channel evaluation passes.
CompleteAction()Handles completion. Default checks broadcastOnComplete, fires OnActionComplete, then checks onCompleteChannelChain for chaining. Override to add custom completion logic, but always call base or handle chaining manually.

Usage Examples

Firing Actions on an Entity

#include <GS_Core/GS_CoreBus.h>

// Fire all actions on this entity that match the "open_door" channel
GS_Core::ActionIncomingEventBus::Event(
    targetEntityId,
    &GS_Core::ActionIncomingEventBus::Events::DoAction,
    AZStd::string("open_door")
);

Listening for Action Completion

#include <GS_Core/GS_CoreBus.h>

class MyActionListener
    : public AZ::Component
    , protected GS_Core::ActionOutgoingEventBus::Handler
{
protected:
    void Activate() override
    {
        // Listen for action completions on a specific entity
        GS_Core::ActionOutgoingEventBus::Handler::BusConnect(m_targetEntityId);
    }

    void Deactivate() override
    {
        GS_Core::ActionOutgoingEventBus::Handler::BusDisconnect();
    }

    void OnActionComplete() override
    {
        // An action on the target entity has completed
        // React accordingly (advance dialogue, open next door, etc.)
    }

private:
    AZ::EntityId m_targetEntityId;
};

Extending the Action Class

Create a custom action whenever you need a reusable, triggerable behavior that can be attached to any entity.

Header (.h)

#pragma once
#include <Source/ActionSystem/GS_ActionComponent.h>

namespace MyProject
{
    class PlayEffect_GSActionComponent
        : public GS_Core::GS_ActionComponent
    {
    public:
        AZ_COMPONENT_DECL(PlayEffect_GSActionComponent);

        static void Reflect(AZ::ReflectContext* context);

    protected:
        // Action overrides
        void ProcessAction() override;
        void CompleteAction() override;

    private:
        AZStd::string m_effectName;
        float m_duration = 1.0f;
    };
}

Implementation (.cpp)

#include "PlayEffect_GSActionComponent.h"
#include <AzCore/Serialization/SerializeContext.h>

namespace MyProject
{
    AZ_COMPONENT_IMPL(PlayEffect_GSActionComponent, "PlayEffect_GSActionComponent", "{YOUR-UUID-HERE}",
        GS_Core::GS_ActionComponent);

    void PlayEffect_GSActionComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<PlayEffect_GSActionComponent, GS_Core::GS_ActionComponent>()
                ->Version(0)
                ->Field("EffectName", &PlayEffect_GSActionComponent::m_effectName)
                ->Field("Duration", &PlayEffect_GSActionComponent::m_duration);

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                // Pattern: Incoming Channel → Feature Properties → Outgoing Channel
                editContext->Class<PlayEffect_GSActionComponent>(
                    "Play Effect Action", "Triggers a named particle effect")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject/Actions")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &PlayEffect_GSActionComponent::m_effectName, "Effect Name",
                        "Name of the particle effect to trigger")
                    ->DataElement(AZ::Edit::UIHandlers::Default,
                        &PlayEffect_GSActionComponent::m_duration, "Duration",
                        "How long the effect plays (seconds)");
            }
        }
    }

    void PlayEffect_GSActionComponent::ProcessAction()
    {
        // Your action logic — trigger the particle effect
        AZ_TracePrintf("Action", "Playing effect: %s for %.1f seconds",
            m_effectName.c_str(), m_duration);

        // ... spawn particle effect, start timer, etc. ...

        // IMPORTANT: Call CompleteAction when done
        // If the action is instant, call it here.
        // If it takes time (animation, timer), call it when the effect finishes.
        CompleteAction();
    }

    void PlayEffect_GSActionComponent::CompleteAction()
    {
        // Custom completion logic (if any)
        // ...

        // IMPORTANT: Call base to handle broadcast and chaining
        GS_ActionComponent::CompleteAction();
    }
}

Module Registration

m_descriptors.insert(m_descriptors.end(), {
    MyProject::PlayEffect_GSActionComponent::CreateDescriptor(),
});

Reflection Pattern

When reflecting custom action properties in the Edit Context, follow this organization pattern so all actions have a consistent inspector layout:

  1. Incoming Channel — The channel property (inherited, reflected by base class)
  2. Feature Properties — Your action-specific properties (effect name, duration, etc.)
  3. Outgoing Channel — The broadcastOnComplete and onCompleteChannelChain properties (inherited, reflected by base class)

See Also

6.11.1.1 - Core Actions

Pre-built actions included in GS_Core — ready-to-use triggerable behaviors for common game functionality.

Overview

GS_Core includes pre-built Action components for common behaviors. Attach them to any entity, set a channel, and fire them from any system without writing code.

All built-in actions inherit from GS_ActionComponent and follow the same channel-matching, completion, and chaining patterns.


Toggle Mouse Cursor

Toggles the operating system mouse cursor on or off.

When to Use

Use this action when transitioning between gameplay (cursor hidden) and menus (cursor visible). Common triggers include opening an inventory, entering a pause menu, or starting a dialogue sequence.

Inspector Properties

PropertyTypeDefaultDescription
ChannelAZStd::string""Inherited. The channel this action responds to.
Broadcast On CompleteboolfalseInherited. Broadcasts OnActionComplete when the toggle finishes.
On Complete Channel ChainAZStd::string""Inherited. Fires a follow-up DoAction on completion for chaining.

Usage Example

#include <GS_Core/GS_CoreBus.h>

// Toggle the cursor when opening the pause menu
GS_Core::ActionIncomingEventBus::Event(
    uiEntityId,
    &GS_Core::ActionIncomingEventBus::Events::DoAction,
    AZStd::string("toggle_cursor")
);

Prints a configurable message to the console log. Useful for debugging action chains, verifying event flow, or logging gameplay milestones.

When to Use

Use this action during development to verify that action channels fire correctly, test chaining sequences, or log gameplay events without writing custom components.

Inspector Properties

PropertyTypeDefaultDescription
ChannelAZStd::string""Inherited. The channel this action responds to.
MessageAZStd::string""The message to print to the console log.
Broadcast On CompleteboolfalseInherited. Broadcasts OnActionComplete when the print finishes.
On Complete Channel ChainAZStd::string""Inherited. Fires a follow-up DoAction on completion for chaining.

Usage Example

#include <GS_Core/GS_CoreBus.h>

// Trigger a debug log message
GS_Core::ActionIncomingEventBus::Event(
    debugEntityId,
    &GS_Core::ActionIncomingEventBus::Events::DoAction,
    AZStd::string("debug")
);
// Console output: whatever message was configured on the PrintLog action

Creating Your Own Action

Need custom behavior? See the Extending the Action Class guide for a complete walkthrough with header, implementation, Reflect pattern, and module registration.

See Also

  • Action — Base class documentation and extension guide
  • GS_Actions — System overview

7 - Templates

Starter template files for creating custom GS_Core components — C++ classes and Script Graph files for managers, savers, stage data, and actions.

Overview

/Templates/

ClassWizard templaets created for GS_Core specific class creation.