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

Return to the regular view of this page.

GS_Play Gameplay Framework for O3DE

GS_Play is a modular gameplay framework for O3DE — a full production-ready set of feature gems you can enable individually to build exactly the game you need. Each gem is independently togglable, so you bring in only what’s relevant to your project.

New to GS_Play? Start with Get Started for installation steps, GS_Core basics, and picking your first toolsets.

Intermediate developers and designers can jump straight to The Basics — editor setup, Script Canvas nodes, and EBus events for each feature set, no architecture knowledge required.

Need deep technical detail? The Framework API covers full component references, EBus interfaces, extension patterns, and class internals.

Want to follow along? Learn has video tutorials and step-by-step lessons for targeted topics and full project walkthroughs.

Licensing, privacy, branding, or press? Legal & Press holds the free-tier licence, the privacy policy, the branding guidelines, and the press fact sheet.

If you find mistakes, gaps, or anything unclear in the documentation, don’t hesitate to reach out.


Contact & Support

Live Support
Real-time help in the #support channel
Join Discord
Email
We typically respond within 1-2 business days
support@genomestudios.ca
Genome Studios
Studio website and other projects
genomestudios.ca

 

Follow Us


Sections

1 - Get Started

Start here — orientation, project setup, and best practices for working with GS_Play.

GS_Play is an intermediate-to-advanced gameplay framework for O3DE. It is a modular, production-ready set of feature gems you enable individually — bring in only what your project needs. Every feature set is built on simple, consistent patterns so you can reason about what the system can do and extend it precisely, without having to understand every internal detail first.

This section covers the essentials: what GS_Play is designed for, how to get a project running, and the conventions that will help you work with the framework effectively.


How This Section Is Organized

Understanding GS_Play — What GS_Play is, what genres and use cases it targets, and how to think about its modularity and pattern-first design. A good first read if you are new to the framework or evaluating whether it suits your project.

Project Quick Start — A step-by-step lesson covering the minimum setup for a working GS_Play project: gem installation, manager configuration, camera setup, and startup sequence. Follow this to get from a blank O3DE project to a running game loop.

Best Practices — Conventions and instincts worth developing early. Covers prefab wrapper structure, how to orient yourself using the Patterns index, and other framework-level habits that prevent common friction points.


Sections

1.1 - Understanding GS_Play

What is GS_Play and what does it do?

1.2 - Project Quick Start

Easy set up to get started.

1.3 - Best Practices

Conventions and habits that make working with GS_Play faster and less frustrating.

A short list of habits worth building early. None of these are gatekept by the framework, but skipping them tends to create friction that is annoying to untangle later.


Follow the Feature Patterns

Every GS_Play feature set has a defined pattern — a simple, consistent way to think about how its pieces connect. Before diving into a new feature, spend a few minutes with its pattern diagram. Once the pattern clicks, the rest of the documentation becomes much easier to navigate and the editor setup becomes obvious.

The Patterns Index has diagrams and breakdowns for every major feature. If you are ever confused about why something is structured the way it is, the pattern page is the first place to look.


Configure Every Level the Same Way

Every level in your project should follow the same base configuration as established in the Simple Project Setup guide:

  • A Game Manager prefab placed in the level.
  • A StageData component configured for the level.
  • An inactive child Level entity holding the level’s content, activated by the Stage Manager startup sequence.

This is the standard GS_Play level structure. Deviating from it will cause the startup sequence, stage transitions, and manager initialization to behave unexpectedly.

Leave Debug Mode on in the Game Manager prefab during development. Debug Mode bypasses the normal startup sequence and drops you directly into the level in a pre-started state, which is far faster for iteration. Disable it only when you need to test the full startup flow.


Use the Class Creation Wizard and Templates

GS_Play extension types — custom conditions, effects, performances, pulse types, trigger sensors, and more — should always be generated using the O3DE Class Creation Wizard with the GS_Play templates, not written from scratch.

The wizard handles UUID generation, cmake file-list registration, and module descriptor injection automatically. Hand-written extension classes frequently miss one of these steps, causing hard-to-diagnose startup failures.

The full template list is in the Templates Index.


Seed LLM Agents with the Agentic Guidelines

If you are using an LLM coding agent to work with GS_Play code, provide it with the Agentic Guidelines document as a bootstrap seed at the start of the session.

The document is structured for agent parsing — it establishes the GS_Play domain model, hard invariants that override general O3DE knowledge, and context anchors for specific subsystems. An agent working without it will frequently make plausible-but-wrong assumptions about how GS_Play components and EBuses are structured.


Ask for Help

If you are blocked on anything — setup, a framework concept, a bug, or something the documentation does not cover clearly — reach out. We want to make sure you are never stuck long enough for it to derail your project.

The fastest route is the #support channel on Discord. You will get a real response, not a bot. No question is too small.


Prefab Wrappers Are Editor Only

Whenever GS_Play documentation refers to a Prefab, it is implied that the Prefab wrapper entity (the blue root entity in the O3DE editor) is set to Editor Only.

This applies universally — and is especially important for GS_Managers. The wrapper entity is for editor organization and prefab identity. It should never be a live runtime entity.

Set this in the Entity Inspector: Entity Status → Editor Only.

1.4 - Agentic Guidelines

Agent bootstrap document for GS_Play framework contextualization. Optimized for LLM parsing, not human reading.

BOOTSTRAP PROTOCOL

On first load of this document, execute in order:

  1. Read all [INVARIANT] entries. Hard constraints. Load before any code generation.
  2. Read SYSTEM ONTOLOGY and GEM INDEX to establish the domain model.
  3. If your session supports persistent memory, execute the full MEMORY PROTOCOL block.
  4. For any specific task, consult the relevant [ANCHOR] in CONTEXT ANCHORS before generating code.

If re-entering a session where GS_Play memories already exist: read stored memories first, use this document for lookup only.


INVARIANTS

Non-negotiable. Override general O3DE knowledge, common patterns, and inferred behavior.

[INVARIANT] No Unicode. Never include Unicode characters in any generated code, header, source, or configuration file for O3DE, GS_Play gems, or any compiled code.

[INVARIANT] Polymorphic containers require raw pointers. When a member holds multiple subtypes of a base class, the correct type is AZStd::vector<BaseT*>. Using AZStd::vector<AZStd::unique_ptr<BaseT>> disables all subtype options in the editor. No exceptions.

[INVARIANT] EnableForAssetEditor lives only in SerializeContext. It must appear directly under ->Version() in the SerializeContext block. Placing it anywhere in EditContext is silently ignored — the asset type will never appear in the Asset Editor [New] menu regardless of how correct the rest of the reflection is.

sc->Class<MyAsset, AZ::Data::AssetData>()
    ->Version(1)
    ->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)  // SerializeContext ONLY
    ->Field("MyField", &MyAsset::m_myField);

[INVARIANT] Use the O3DE Class Creation Wizard for new components. Generate skeleton from wizard, then use find-and-replace to customize. Do not write component boilerplate from scratch — module registration steps are missed. Unnecessary tokens are spent.

[INVARIANT] EBus interface names are exact. Never infer or guess an EBus name. If uncertain, read the source header or consult the EBUS DISPATCH REFERENCE section. Wrong names fail silently at runtime.

[INVARIANT] Namespace all GS_Play code. Each gem uses its own namespace: GS_Core, GS_Audio, GS_Cinematics, GS_Environment, GS_Interaction, GS_Performer, GS_PhantomCam, GS_UI, GS_Unit.

[INVARIANT] Using O3DE code sensibilities. Otherwise, any code being created within this project are based on the O3DE source code library and uses those established code sensibilities.

[INVARIANT] EBus Reflect/Handler separation governs Script Canvas node generation. In Script Canvas: reflected EBus events become caller nodes (invoke the bus). Handler class declarations become notification nodes (listen for events). Apply the minimum necessary:

  • Respond-only — Inherit from {BusName}::Handler, override virtual methods. Do NOT reflect caller events. No SC caller node is generated or needed.
  • Call-only — Reflect the EBus events in Reflect(). Do NOT inherit Handler in the component. No SC notification node is generated or needed.
  • Both — Inherit Handler (generates notification nodes) AND reflect events (generates caller nodes).

Generating both when only one is needed pollutes the SC node palette and signals incorrect intent to maintainers.


MEMORY PROTOCOL

[MEMORY] If your system supports persistent memory, create or verify these entries at session start:

Memory KeySource Section
gs_play_invariantsINVARIANTS section above
gs_play_gem_indexGEM INDEX table
gs_play_ebus_refEBUS DISPATCH REFERENCE section
gs_play_hot_pathsHOT PATHS section

At session end: If you discovered or confirmed any EBus method signatures, component property names, or TypeIds, ask the user: “Should I save what I learned about [topic] to memory?”

Memory freshness rule: If a stored memory conflicts with source code you have read during a session, source code is authoritative. Update the memory.

If no memory system is available: Re-read this document at the start of every new GS_Play session before generating any code.


SYSTEM ONTOLOGY

What this system is: gs_play_gems is a modular O3DE gameplay framework in C++17. It provides game systems as base classes for developer extension. It is a framework, not a library — the expected pattern is: extend base classes, override virtual methods, communicate exclusively via EBus.

Core architectural rules:

  • Each Gem is a self-contained feature module. GS_Complete is the framework’s reference integration layer demonstrating cross-gem patterns — it is not a required home for your project code.
  • Runtime cross-gem communication between GS_Play framework components uses EBus. Your own project code may include and combine gem headers as needed.
  • All game systems are singleton Manager Components responding to GameManagerNotificationBus lifecycle events.
  • Extensible type systems (dialogue effects, pulse types, motion tracks) use polymorphic base classes. Extend the base class and reflect it — types are discovered automatically through O3DE serialization at startup.
  • All user-facing data must be reflected via SerializeContext + EditContext.

CLASS WIZARD PROTOCOL

[INVARIANT] Always use the Class Creation Wizard CLI for new component, asset, and system class generation. The wizard handles file scaffolding, CMake registration, module descriptor insertion, and system component wiring. Manual boilerplate misses registration steps and wastes tokens.

CLI invocation (agents use CLI mode only)

python ClassWizard.py \
  --engine-path  <engine-path> \
  --project-path <project-path> \
  --template     <template_name> \
  --component-name <Name> \
  --namespace      <GemNamespace> \
  --automatic-register \
  [<template-specific flags>]

--automatic-register is required for build integration (CMake file lists, module descriptors, system component entries). Omitting it generates files only.

Available templates — O3DE base

Template--template valueSuffixWhat it createsTemplate-specific flags
Basic Componentdefault_componentComponentStandard game component--skip-interface, --include-editor (requires editor module)
Level Componentlevel_componentComponentComponent on the level entity--skip-interface
System Componentsystem_componentComponentGlobal system entity component, auto-activates--skip-interface
LyShine Componentlyshine_componentComponentUI component, adds Gem::LyShine.API dependency(none)
Data Assetdata_assetAssetAsset class + system component + .setreg config--file-extension <ext> (required), --asset-group <group>
AttimageattimageAttimageRender pipeline attachment image(none)

Available templates — GS_Play extensions

GS_Play provides gem-specific templates that extend the above. These handle GS_ base class wiring, EBus interface generation, and framework-specific registration steps automatically. Full reference: Templates List.

TemplateGemGeneratesUse For
GS_ManagerComponentGS_Core${Name}ManagerComponent + optional busGS_Play manager with startup lifecycle hooks
SaverComponentGS_Core${Name}SaverComponentCustom save/load handler for GS_Save
GS_InputReaderComponentGS_Core${Name}InputReaderComponentController-side hardware input reader
PhysicsTriggerComponentGS_Core${Name}PhysicsTriggerComponentPhysX trigger volume with enter/hold/exit callbacks
PulsorPulseGS_Interaction${Name}_PulseNew pulse type for Pulsor emitter/reactor system
PulsorReactorGS_Interaction${Name}_ReactorNew reactor type responding to a named channel
WorldTriggerGS_Interaction${Name}_WorldTriggerNew world trigger response type
TriggerSensorGS_Interaction${Name}_TriggerSensorNew trigger sensor condition type
UnitControllerGS_Unit${Name}ControllerComponentCustom player or AI controller
InputReactorGS_Unit${Name}InputReactorComponentUnit-side input translation to bus calls
MoverGS_Unit${Name}MoverComponentCustom locomotion mode
GrounderGS_Unit${Name}GrounderComponentCustom ground detection
PhantomCameraGS_PhantomCam${Name}PhantomCamComponentCustom camera behaviour type
UiMotionTrackGS_UI${Name}TrackNew LyShine property animation track
FeedbackMotionTrackGS_Juice${Name}TrackNew world-space entity property animation track
DialogueConditionGS_Cinematics${Name}_DialogueConditionDialogue branch gate — return true to allow
DialogueEffectGS_Cinematics${Name}_DialogueEffectWorld event from Effects node; optionally reversible
DialoguePerformanceGS_Cinematics${Name}_DialoguePerformanceAsync NPC action; sequencer waits for completion

[ANCHOR] Registration requirements per template (some require a manual Reflect() call after generation) → Templates List: Registration Quick Reference

What the wizard generates

The wizard output for --template default_component --component-name PlayerHealth --namespace GS_Core produces:

  • Source/PlayerHealthComponent.h / .cpp — component with Reflect(), Activate(), Deactivate(), AZ_COMPONENT_IMPL
  • Include/GS_Core/PlayerHealthInterface.h — EBus interface header (unless --skip-interface)
  • CMake file list entries, module descriptor registration (with --automatic-register)

Post-wizard agent workflow

After the wizard runs:

  1. Add GS_ base class to the inheritance list alongside AZ::Component.
  2. Add EBus handler inheritance for any buses this component listens to.
  3. Wire BusConnect() / BusDisconnect() in Activate() / Deactivate().
  4. Override virtual methods from the GS_ base class.
  5. Add reflected fields in SerializeContext → EditContext.
  6. Add gem dependency if the component depends on another GS_ gem: add_gem_dependency is automatic for some templates (e.g., LyShine), but cross-GS-gem dependencies must be added manually to CMake.

[ANCHOR] Before customizing wizard output → read the target base class API at /docs/framework/{gem}/.

Discovery commands

--list-templates                   # Print all available templates
--template-help <template_name>    # Print full command shape, all flags, and command list

Use --template-help to discover template-specific flags before invoking.


GEM INDEX

GemNamespaceRoleManager ComponentPrimary Incoming BusDoc Path
GS_CoreGS_CoreFoundation — managers, save, stages, input, actions, utilitiesGS_GameManagerComponentGameManagerRequestBus/docs/framework/core/
GS_AudioGS_AudioAudio engine, mixing, events, Klatt voice synthesisGS_AudioManagerComponentAudioManagerRequestBus/docs/framework/audio/
GS_CinematicsGS_CinematicsDialogue system, cinematics control, stage/performer markersGS_DialogueManagerComponent GS_CinematicsManagerComponentDialogueManagerRequestBus/docs/framework/cinematics/
GS_EnvironmentGS_EnvironmentTime of day, sky, world tickGS_TimeManagerComponentTimeManagerRequestBus/docs/framework/environment/
GS_InteractionGS_InteractionPulsors, targeting, world triggers, cursor(none)—/docs/framework/interaction/
GS_PerformerGS_PerformerCharacter skins, paper-facing, locomotion visualsGS_PerformerManagerComponentPerformerManagerRequestBus/docs/framework/performer/
GS_PhantomCamGS_PhantomCamVirtual cameras, blending, influence fieldsGS_CamManagerComponentCamManagerRequestBus/docs/framework/phantomcam/
GS_UIGS_UIUI framework — single-tier Manager/Page navigation, GS_Motion animations, input interception, load screenGS_UIManagerComponentUIManagerRequestBus/docs/framework/ui/
GS_UnitGS_UnitUnit/character controllers, movers, input, AIGS_UnitManagerComponentUnitManagerRequestBus/docs/framework/unit/
GS_CompleteGS_CompleteIntegration layer — cross-gem components, proves patterns(none)GS_CompleteRequestBus—

EBUS DISPATCH REFERENCE

Dispatch syntax

// Broadcast (no address, singleton bus)
GS_Core::GameManagerRequestBus::Broadcast(
    &GS_Core::GameManagerRequestBus::Events::NewGame);

// Entity-addressed
GS_Unit::UnitRequestBus::Event(
    entityId, &GS_Unit::UnitRequestBus::Events::Possess, controllerEntityId);

// Broadcast with return value
bool isStarted = false;
GS_Core::GameManagerRequestBus::BroadcastResult(
    isStarted, &GS_Core::GameManagerRequestBus::Events::IsStarted);

Bus naming convention

SuffixDirectionHandler policyTypical use
RequestBusCaller → HandlerSingle (singleton) or ById (entity)Commands and queries
NotificationBusBroadcaster → ListenersMultipleState change notifications

GS_Core buses

BusDispatchKey Methods
GameManagerRequestBusBroadcastIsInDebug, IsStarted, NewGame, LoadGame, ContinueGame, EnterStandby, ExitStandby, ReturnToTitle, ExitGame
GameManagerNotificationBusListenOnSetupManagers, OnStartupComplete, OnShutdownManagers, OnBeginGame, OnEnterStandby, OnExitStandby
SaveManagerRequestBusBroadcastNewGameSave, LoadGame, SaveData, LoadData, GetOrderedSaveList
SaveManagerNotificationBusListenOnSaveAll, OnLoadAll
RecordKeeperRequestBusByIdHasRecord, SetRecord, GetRecord, DeleteRecord
RecordKeeperNotificationBusById ListenRecordChanged
StageManagerRequestBusBroadcastChangeStageRequest, LoadDefaultStage, RegisterExitPoint, GetExitPoint
StageManagerNotificationBusListenBeginLoadStage, LoadStageComplete, StageLoadProgress
OptionsManagerRequestBusBroadcastGetActiveInputProfile
ActionRequestBusByIdDoAction
ActionNotificationBusById ListenOnActionComplete

GS_Cinematics buses

BusDispatchKey Methods
CinematicsManagerRequestBusBroadcastBeginCinematic, EndCinematic, RegisterStageMarker, GetStageMarker
DialogueManagerRequestBusBroadcastStartDialogueSequenceByName, ChangeDialogueDatabase, RegisterPerformerMarker, GetPerformer
DialogueSequencerRequestBusBroadcastStartDialogueBySequence, OnPerformanceComplete
DialogueSequencerNotificationBusListenOnDialogueTextBegin, OnDialogueSequenceComplete
DialogueUIBridgeRequestBusByIdRunDialogue, RunSelection, RegisterDialogueUI, CloseDialogue
TypewriterRequestBusByIdStartTypewriter, ForceComplete, ClearTypewriter

GS_Interaction buses

BusDispatchKey Methods
PulseReactorRequestBusByIdReceivePulses, IsReactor
GS_TargetingHandlerRequestBusByIdRegisterTarget, UnregisterTarget, GetInteractTarget
GS_Core::TargetingEmissionBusById ListenOnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteract (was GS_TargetingHandlerNotificationBus)
WorldTriggerRequestBusByIdTrigger, Reset
GS_CursorRequestBusBroadcastRegisterCursorCanvas, HideCursor, SetCursorOffset, SetCursorVisuals, SetCursorPosition

GS_PhantomCam buses

BusDispatchKey Methods
CamManagerRequestBusBroadcastCamera system lifecycle
CamManagerNotificationBusListenEnableCameraSystem, DisableCameraSystem, SettingNewCam
GS_Core::CamCoreEmissionBusListenUpdateCameraPosition, OnCamCoreRegistered, OnCamCoreUnregistered (was CamCoreNotificationBus)
GS_Core::CamCoreExchangeBusByIdGetCamCore (was CamCoreRequestBus::GetCamCore)
PhantomCameraRequestBusByIdPer-camera control

GS_UI buses

BusDispatchKey Methods
UIManagerRequestBusBroadcastLoadGSUI, UnloadGSUI, FocusUI, NavLastUI, RegisterPage
UIPageRequestBusByIdShowPage, HidePage, FocusPage, ToggleShow
LoadScreenRequestBusBroadcastStartLoadScreen, EndLoadScreen
PauseMenuRequestBusBroadcastPauseGame, UnPauseGame

Removed / Deprecated (do not use): GS_UIHubComponent, GS_UIHubBus, GS_UIWindowComponent — these were part of a removed three-tier Hub/Window/Page hierarchy. The current architecture is single-tier: Manager → Page.

GS_Unit buses

BusDispatchKey Methods
UnitManagerRequestBusBroadcastRequestSpawnNewUnit, RegisterPlayerController, CheckIsUnit
UnitManagerNotificationBusListenReturnNewUnit, EnterStandby, ExitStandby
UnitRequestBusByIdPossess, DePossess, GetController, GetUniqueName
UnitNotificationBusById ListenUnitEnteringStandby, UnitExitingStandby (possession moved to GS_Core::PossessionEmissionBus)
GS_Core::PossessionEmissionBusById ListenOnPossessedUnit, OnReleasedUnit (controller entity); OnPossessedByController, OnReleasedByController (unit entity)

GS_Environment buses

BusDispatchKey Methods
TimeManagerRequestBusBroadcastSetMainCam, Set/GetTimeOfDay, SetTimePassageSpeed, GetWorldTime, IsDay
GS_Core::TimeEmissionBusListenWorldTick, DayNightChanged (was TimeManagerNotificationBus)

GS_Audio buses

BusDispatchKey Methods
AudioManagerRequestBusBroadcastAudio engine control
KlattVoiceRequestBusByIdVoice synthesis control per-entity
KlattVoiceSystemRequestBusBroadcastSystem-level voice engine

HOT PATHS

Canonical patterns for the most common agent tasks. Follow exactly.


HOT PATH 1 — Create a component extending a GS_ base

(Incorrect - Templates satisfy much of this.)

  1. Identify target gem namespace and base class.
  2. Generate skeleton via O3DE Class Creation Wizard.
  3. Replace generated names. Add GS_ base class alongside AZ::Component in inheritance list.
  4. In Activate(): call BusConnect() for all buses this component handles.
  5. In Deactivate(): call BusDisconnect() for all buses.
  6. In Reflect(): SerializeContext first, EditContext nested inside it. EnableForAssetEditor in SerializeContext only, directly under ->Version().

[ANCHOR] For any specific gem’s base class API → read /docs/framework/{gem}/ before writing the override body.


HOT PATH 2 — Dispatch an EBus command

(Incorrect - BusInterfaceName)

// Fire and forget, singleton bus
GS_{Gem}::{BusName}::Broadcast(
    &GS_{Gem}::{BusName}::Events::{MethodName},
    arg1, arg2);

// Entity-addressed bus
GS_{Gem}::{BusName}::Event(
    targetEntityId, &GS_{Gem}::{BusName}::Events::{MethodName}, arg1);

// With return value
ReturnType result{};
GS_{Gem}::{BusName}::BroadcastResult(
    result, &GS_{Gem}::{BusName}::Events::{MethodName});

HOT PATH 3 — Listen to an EBus notification

  1. Inherit from {BusName}::Handler (protected inheritance).
  2. In Activate(): {BusName}::Handler::BusConnect();
    • For entity-addressed buses: {BusName}::Handler::BusConnect(GetEntityId());
  3. In Deactivate(): {BusName}::Handler::BusDisconnect();
  4. Override the virtual notification methods.

HOT PATH 4 — Create a new Manager Component

  1. Inherit from GS_Core::GS_ManagerComponent.
  2. Connect to GameManagerNotificationBus in Activate().
  3. Implement lifecycle hooks: OnSetupManagers, OnShutdownManagers, OnEnterStandby, OnExitStandby.
  4. Define {Name}RequestBus (Single/Single policy) for commands.
  5. Define {Name}NotificationBus (Multiple policy) for notifications.
  6. Register descriptor in your gem’s module constructor.

[ANCHOR] Manager lifecycle and initialization order → /docs/framework/core/gs_managers/manager/


HOT PATH 5 — Create a Generic Data Asset

  1. Inherit from AZ::Data::AssetData.
  2. Include GS_Core Utility: GS_AssetReflectionIncludes.h
  3. In Reflect(), SerializeContext block:
    sc->Class<MyAsset, AZ::Data::AssetData>()
        ->Version(1)
        ->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
        ->Field("Field", &MyAsset::m_field);
    
  4. Register asset type in a system component’s Reflect().
  5. EnableForAssetEditor goes only in SerializeContext. Never in EditContext.

HOT PATH 6 — Polymorphic container of subtypes

// CORRECT — all subtypes selectable in editor
AZStd::vector<BaseClass*> m_items;

// WRONG — disables all subtype options in editor
AZStd::vector<AZStd::unique_ptr<BaseClass>> m_items;

Reflect with ->Field("Items", &MyClass::m_items). O3DE handles subtype selection automatically with raw pointers.


HOT PATH 7 — Create a custom Trigger Sensor or World Trigger

Two distinct extension points. Choose the correct base:

Custom Trigger Sensor (condition side — detects when to fire):

  1. Inherit from GS_Interaction::TriggerSensorComponent.
  2. Override EvaluateSensor() — call Trigger() on all WorldTriggerRequestBus handlers on the same entity when your condition is met.
  3. Reflect with SerializeContext and EditContext.
  4. Register descriptor in your gem’s module.

Custom World Trigger (response side — what happens when fired):

  1. Inherit from GS_Interaction::WorldTriggerComponent.
  2. Override DoTrigger() — implement the response behavior.
  3. Override DoReset() if the trigger should re-arm.
  4. Reflect with SerializeContext and EditContext.
  5. Register descriptor in your gem’s module.

[ANCHOR] Full WorldTrigger architecture → /docs/framework/interaction/world_triggers/


HOT PATH 8 — Add a custom Dialogue type (effect, condition, performance)

  1. Inherit from the appropriate base: DialogueEffect, DialogueCondition, or DialoguePerformance.
  2. Reflect the class using SerializeContext and EditContext. The system discovers it automatically at startup — no manual registration step.
  3. GS_Complete demonstrates this pattern for cross-gem types (e.g., a dialogue effect controlling a PhantomCam). Use it as a reference, not a required location.

[ANCHOR] Dialogue type system → /docs/framework/cinematics/dialogue_system/


ANTIPATTERN CATALOG

[ANTIPATTERN] EnableForAssetEditor in EditContext. Silent failure — asset never appears in Asset Editor [New] menu. See INVARIANTS.

[ANTIPATTERN] unique_ptr in polymorphic AZStd containers. Disables all subtype selection in the editor. Use BaseT*. See INVARIANTS.

[ANTIPATTERN] Guessing EBus method names. O3DE EBus dispatches fail silently on wrong method names. Always verify against source headers or the EBUS DISPATCH REFERENCE in this document.

[ANTIPATTERN] Dispatching to entity-addressed buses before target entity is active. Buses are not connected before Activate() or after Deactivate(). Sending events before activation is silently dropped.

[ANTIPATTERN] Omitting AZ_COMPONENT_IMPL in the .cpp file. Component will not be reflectable. Causes subtle reflection failures that appear unrelated to the missing macro.

[ANTIPATTERN] Using "Editor" service in AppearsInAddComponentMenu. GS_Play components use AZ_CRC_CE("Game"). The "Editor" category makes components invisible in standard game entity contexts.

[ANTIPATTERN] Writing component boilerplate manually. Module registration steps are reliably missed. Always start from the O3DE Class Creation Wizard output.


CONFIDENCE THRESHOLDS

Proceed without reading source or docs:

  • Standard O3DE component lifecycle: Init, Activate, Deactivate, BusConnect, BusDisconnect
  • AZStd container usage (with the raw pointer rule applied for polymorphic types)
  • CMake target aliases: Gem::{Name}.API, Gem::{Name}.Private.Object, Gem::{Name}.Clients
  • SerializeContext → EditContext nesting order in Reflect()
  • AZ_CRC_CE() for compile-time CRC values
  • azrtti_cast<> for safe type casting in reflection code

Stop and read documentation or ask the user before proceeding:

  • Any EBus method signature you are not 100% certain of
  • Any virtual method signature on a GS_ base class
  • Whether a specific GS_ gem is present in the target project
  • Initialization order requirements for a new Manager component
  • Any TypeId UUID value — never generate a UUID, ask the user or read the TypeIds header

CONTEXT ANCHORS

Conditions that require reading deeper documentation before generating code.

[ANCHOR] Before writing any unit movement or controller code: → Read /docs/framework/unit/ for mover types (3DFree, Strafe, Grid, SideScroller, Slide, Physics), grounder types, and controller hierarchy (Player vs AI). → [ASK] Which mover type does this movement context require?

[ANCHOR] Before configuring any virtual camera: → Read /docs/framework/phantomcam/ for camera types (PhantomCamera, StaticOrbit, Track, ClampedLook) and influence field setup.

[ANCHOR] Before wiring any dialogue sequence: → Read /docs/framework/cinematics/dialogue_system/ for the full chain: Sequencer → UIBridge → DialogueUI component.

[ANCHOR] Before implementing targeting or interaction: → Read /docs/framework/interaction/targeting/ for the TargetingHandler, Target, TargetField setup order and registration timing.

[ANCHOR] Before building any UI screen or menu: → Read /docs/framework/ui/ for the Manager → Page architecture, navigation return policies, companion component pattern, and UIManager registration sequence. The Hub/Window layer has been removed — do not use GS_UIHubComponent or GS_UIWindowComponent.

[ANCHOR] Before integrating save or persistence: → Read /docs/framework/core/gs_save/ for the SaveManager, RecordKeeper, and Saver component chain.

[ANCHOR] Before setting up project physics layers or collision groups: → Read /docs/get_started/configure_project/setup_environment/ for required engine-level physics configuration.

[ANCHOR] Before implementing behavior that combines two or more GS_ gems: → Review GS_Complete — it provides reference implementations of common cross-gem patterns. Reuse or adapt them rather than reimplementing from scratch.


CLARIFICATION TRIGGERS

[ASK] At the start of any GS_Play session, identify the user context:

  • Designer / Scripter — Working in the O3DE editor with components, Script Canvas, or Lua. No C++ generation needed.

    • Guidance: component configuration, property setup, EBus Script Canvas nodes.
    • Documentation: The Basics → /docs/the_basics/
    • Gem-specific: /docs/the_basics/{gem}/ (e.g., /docs/the_basics/unit/ for unit questions)
  • Engineer — Writing C++ to extend the framework with new components, gems, or systems.

    • Guidance: follows HOT PATHS and code patterns in this document.
    • Documentation: Framework API → /docs/framework/
    • Gem-specific: /docs/framework/{gem}/ (e.g., /docs/framework/unit/ for unit questions)

Both sections cover identical gem featuresets. The Basics covers component usage and scripting; Framework API covers C++ interfaces, EBus signatures, and base class architecture. Available gems in both: core, audio, cinematics, environment, interaction, juice, performer, phantomcam, ui, unit.

When answering any gem-specific question, include a link to the appropriate documentation section alongside your response. If the context is not stated, ask before generating any code or providing documentation links.

[ASK] Before creating any new component: Confirm which gem’s namespace and base class this component should use.

[ASK] Before generating any TypeId UUID: Always request the UUID from the user or confirm they want one generated. Never silently generate a UUID.

[ASK] If the task references a system with no memory or documentation coverage: Ask before inferring behavior. GS_Play base class APIs are not reliably guessable from generic O3DE patterns.

[ASK] If a Manager initialization order is being modified: The sequence of OnSetupManagers calls is load-order dependent. Confirm the intended position in the sequence before writing.

[ASK] If asked to implement a new Dialogue type: Extend the appropriate base class (DialogueEffect, DialogueCondition, or DialoguePerformance) and reflect it. Confirm which gem or module owns the new class before writing.


UPCOMING: AGENTIC SITE FEATURES (Post v1)

The GS_Play documentation site will implement formal agent-friendly features after the v1 documentation release. These are not yet live.

Planned additions follow the emerging specifications at:

What this means for agents using this document now:

Until those features are live, this bootstrap document (/docs/get_started/agentic_guidelines/) is the primary machine-readable entry point. Treat it as authoritative. Do not rely on inferred site structure or navigated page discovery — navigate the docs via explicit paths from the GEM INDEX and CONTEXT ANCHORS sections above.

When the agent-friendly site features are released, this section will be updated with the new entry points and structured access patterns.

2 - Index

Fast references to speed up docs navigation — feature lists, patterns, glossary, templates, changelogs, and utilities.

Quick-access references for the GS_Play documentation. Use this section when you know what you are looking for and need a fast lookup — a term definition, the right template name, a feature comparison, or the changelog for a specific gem.


How This Section Is Organized

Change Log — Version history for GS_Play gems and the documentation itself.

Feature List — A complete index of every GS_Play feature, with links to both its Basics usage guide and Framework API reference.

Patterns — Diagrams and explanations of the core structural patterns used across GS_Play feature sets. A useful orientation before diving into a new gem.

Templates List — All ClassWizard extension templates for generating new conditions, effects, performances, trigger types, and other polymorphic extension classes across GS_Play gems.

Powerful Utilities — Cross-cutting utilities available across multiple feature gems.

Glossary — Definitions for GS_Play-specific terminology and commonly used O3DE concepts in the framework context.

Links — External links to community resources, the asset store, and Genome Studios.


Sections

2.1 - Change Logs

Version changelogs for all GS_Play gems and documentation.

Logs


GS_Audio

Latest version: 0.7.0

Change Summary

The free tier of the audio system — polymorphic audio events with procedural sources, SFXR sound profiles, the 22-effect mix layer, and Steam Audio spatialization with a split listener.

GS_Audio Logs


GS_AudioPro

Latest version: 0.7.0

Change Summary

First release of the premium audio tier — the visual Audio Event Graph, procedural scoring, Klatt voice synthesis, and geometry-driven occlusion and reflections.

GS_AudioPro Logs


GS_Cinematics

Latest version: 0.6.0

Change Summary

Dialogue Editor rebuilt on the gs_graphcanvas framework with container database model, visual graph authoring, and runtime graph execution.

GS_Cinematics Logs


GS_Core

Latest version: 0.6.0

Change Summary

The GS_Options settings system, headless managers, the extensible ImGui debug menu, the input reactor mechanism, new noise/envelope/orbital utilities, and the project and component templates.

GS_Core Logs


GS_Environment

Latest version: 0.5.0

Change Summary

First official base release. Time of day management, time passage speed, day/night cycle notifications, and sky color configuration assets.

GS_Environment Logs


GS_Interaction

Latest version: 0.5.0

Change Summary

First official base release. Physics pulse emitters and reactors, proximity targeting and cursor, and composable world trigger sensors.

GS_Interaction Logs


GS_Juice

Latest version: 0.5.0

Change Summary

First official base release (Early Development). Feedback motion system with transform and material tracks for screen shake, bounce, flash, and glow effects.

GS_Juice Logs


GS_Performer

Latest version: 0.5.0

Change Summary

First official base release (Early Development). Performer manager, skin slot swapping, paper billboard rendering, velocity locomotion, head tracking, and babble.

GS_Performer Logs


GS_PhantomCam

Latest version: 0.5.0

Change Summary

First official base release. Priority-based camera system with follow/look-at behaviors, blend profiles, and spatial influence fields.

GS_PhantomCam Logs


GS_UI

Latest version: 0.5.0

Change Summary

First official base release. Single-tier page navigation, GS_Motion UI animation, enhanced buttons, input interception, load screen, and pause menu.

GS_UI Logs


GS_Unit

Latest version: 0.6.0

Change Summary

Unit Action Graph (under construction) — HFSM editor with parallel layers, perimeter connections, transition conditions, and tick-based state evaluation.

GS_Unit Logs


Documentation

Latest version: 1.1.0

Change Summary

GS_GraphCanvas framework reference, Audio Event Graph docs, Dialogue Editor rewrite, Unit Action Graph docs, Building a Graph Tool tutorial, and Dialogue System Setup tutorial.

Docs Logs


2.1.1 - Audio Change Log

GS_Audio version changelog.

Logs


Audio 0.7.0

GS_Audio is now the free tier of the audio system. The visual authoring, music, voice and geometric acoustics features moved to GS_AudioPro; everything below is what the free gem carries.

Audio Events

  • GS_AudioEvent is now an abstract base, and event libraries hold a polymorphic list — the type dropdown offers every registered event kind.
  • GS_AudioEvent_Pool — a pool of sound assets, one picked at random per fire.
  • GS_AudioEvent_Noise — procedural noise with selectable colour and duration.
  • GS_AudioEvent_Sfxr — procedural chiptone driven by a pool of .sfxrprofile assets.
  • Shared machinery on the base: pooling, concurrency limits, re-fire suppression, distance settings, 3D tracking and spatialization back-end resolution.
  • Extension hooks for writing a new event source, with the asset hooks optional so generators need only three methods.
  • RerouteSounds re-points pooled and future instances when a mix target registers late.

SFXR Sound Profiles

  • New .sfxrprofile asset — 24 parameters describing one procedural sound.
  • Import from sfxr.me / jsfxr by pasting the Serialize JSON.
  • 27 profiles ship with the gem: 7 canonical presets and 20 named custom sounds.

Mixing and Mix Effects

  • MixEffectComponent — an ordered effect chain registered under a name, with a configurable output target.
  • 22 mix effect types: 7 biquad filters, Echo, Reverb, Binaural, 8 character and modulation effects, and 4 utility effects.
  • Named routing shared with volume channels, so a source targets either by name.
  • Order-independent wiring — a chain pointing at a target that does not exist yet resolves when it registers.
  • Runtime control: RerouteMixEffect, SetMixEffectParam, SetMixEffectParamOn, plus manual registration and wiring.

Spatialization

  • Steam Audio integration, vendored and loaded at runtime.
  • Per-voice HRTF binaural placement, recomputed each frame, replacing the built-in spatializer rather than stacking with it.
  • Distance attenuation and air absorption via Steam’s direct effect.
  • Bounded distance curve — the direct signal reaches true silence at max distance, and the reverb send eases to zero over the last quarter of the range.
  • HRTF interpolation modes, HRTF volume, and frame-size mode with an automatic match to the device period.

Audio Manager

  • GS_AudioProjectSettings reflected on the manager, making the component panel the configuration surface.
  • The authoritative listener now supports split targets — position from one entity, facing from another, for third-person games.
  • Listener target defaults to the manager entity with a warning when unset.
  • Accessor surface for GS_AudioPro: engine, node graph, endpoint, mixing buses, Steam Audio context, HRTF and frame size.
  • Spatial-simulator registration and reverb send, null in a base-only install.
  • Options apply targets for master volume and named volume channels.
  • The base manager no longer ticks.

Debug

  • GS_AudioImGuiDebugComponent — an Audio Mixer window with master and per-channel sliders.

Audio 0.6.0

Audio Event Graph

  • Visual node-based editor for authoring complex sound events using the gs_graphcanvas framework (DataFlowGraph topology).
  • Entry nodes (Start/Loop/Finish) for phase-gated audio lifecycle.
  • Source nodes: Aud_SoundNode, Aud_AudioPoolNode — single and pooled audio playback with volume, pitch, delay, looping, and 3D spatialization.
  • Filter nodes: LowPass, HighPass, BandPass, Notch, PeakingEQ, LowShelf, HighShelf — miniaudio filter processing.
  • Effect nodes: Aud_EchoNode, Aud_DelayNode.
  • Routing via gs_graphcanvas built-in IfNode and SwitchNode for conditional audio paths.
  • Aud_OutputNode terminal wiring to mixing bus.
  • Variable-driven sound control — runtime variable API for filter parameters, routing, and source selection.
  • AudioGraphTemplateCache — graph instance pooling and caching per asset path.
  • AudioSoundPool — ma_sound pooling with claim/release pattern, configurable pad and trim thresholds.
  • AudioFilterPool — filter node pooling by type.
  • Phase lifecycle management (Start/Loop/Finish) with TransitionToPhase().
  • Runtime API: PlayAudioGraph, AcquireAudioGraph, FireAudioGraph, StopAudioGraph, FinishAudioGraph, SetAudioGraphVariable*, SetAudioGraphLooping, SetAudioGraphEntity, SetAudioGraphPosition, StopAudioGraphFade.
  • 3D spatialization per-instance via AudioGraphSpatialMode (None, Position, Entity).

Audio 0.5.0

First official base release of GS_Audio.

Audio Manager

  • GS_AudioManagerComponent — audio engine lifecycle management, event library loading
  • AudioManagerRequestBus / AudioManagerNotificationBus
  • Master volume control and engine-level audio settings

Audio Events

  • AudioEventLibrary asset — pools of named audio events
  • GS_AudioEvent — pool selection, concurrent instance limiting, and 3D spatialization
  • AudioManagerRequestBus playback interface

Mixing & Effects

  • GS_MixingBus — mixing bus component with configurable effects chain
  • Filters, EQ, and environmental influence effects per bus

Score Arrangement

  • ScoreArrangementTrack — multi-layer music system
  • ScoreLayer — individual music layers toggled by gameplay state
  • TimeSignatures enum for bar-aligned transitions

Klatt Voice Synthesis

  • KlattVoiceSystemComponent — shared SoLoud engine, 3D listener management
  • KlattVoiceComponent — per-entity text-to-speech with phoneme mapping and segment queue
  • KlattVoiceRequestBus / KlattVoiceNotificationBus / KlattVoiceSystemRequestBus
  • Full 3D spatial voice audio with configurable voice parameters

2.1.2 - AudioPro Change Log

GS_AudioPro version changelog.

Logs


AudioPro 0.7.0

First release of GS_AudioPro, the premium half of the audio system. It requires GS_Audio and extends it.

Gem Architecture

  • GS_ProAudioManagerComponent subclasses the base Audio Manager and declares itself incompatible with it, so a project runs one or the other.
  • Pro attaches through the base gem’s accessor surface — engine, node graph, endpoint, mixing buses, shared Steam Audio context and HRTF — rather than reaching into internals.
  • GS_ProAudioSettings adds the geometry configuration alongside the base project settings.
  • Separate buses: AudioGraphRequestBus, AudioGraphPlayerRequestBus, AudioScoreRequestBus and the Klatt voice buses.
  • ScriptCanvas nodes under the GS_Play/GS_AudioPro category.

Audio Event Graph

  • The .audiograph visual editor, built on the gs_graphcanvas framework with a data-flow topology.
  • 31 node types: 4 sources, 7 filters, Echo and Reverb, 7 character and modulation effects, 4 utility effects, Mix and Split, three phase gates and the Output terminal.
  • AudioGraphLibrary (.agl) — named access to graphs, with a source-file picker.
  • AudioGraphPlayerComponent — drop-on playback with auto-play, spatialization and looping, and no handles to manage.
  • Preserve-until-release instance lifecycle: an owned instance quiesces to idle on stop and keeps its variables and configuration until explicitly released.
  • Full transport, variable, spatialization and state surface on AudioGraphRequestBus.
  • Node registration pinned against unity-build symbol stripping, so the launcher deserializes graphs.

Procedural Scoring

  • ScoreArrangementTrack — multi-layer scores with time signature, tempo and fade control.
  • AudioScoreRequestBus: play a score with a chosen layer set, and bring individual layers in and out at runtime.

Klatt Voice Synthesis

  • Formant text-to-speech with 3D spatial audio, backed by SoLoud and relayed into the audio graph through a custom node.
  • Voice profiles and phoneme maps as assets, with per-word pronunciation overrides.
  • Inline KTT tags for changing pitch, speed and waveform partway through a line.
  • Per-entity voice components with a segment queue and completion notifications.

Geometric Acoustics

  • Steam Audio scene and simulator integration, off by default behind a master switch.
  • Occlusion in raycast and volumetric modes, the latter giving partial occlusion at edges.
  • Listener-centric reflections with configurable wet mix, order and smoothing between rooms.
  • Reverb as a distance-independent send, so a room sounds consistent across its volume while the dry signal falls off.
  • Acoustic geometry components for authored volumes and O3DE Shapes.
  • 15 acoustic material presets plus a custom material, including soft barriers that transmit rather than block.
  • Parametric reverb on a worker thread with lock-free direct and occlusion values.

See Also

2.1.3 - Cinematics Change Log

GS_Cinematics version changelog.

Logs


Cinematics 0.6.0

Dialogue Editor — gs_graphcanvas Integration

  • Dialogue Editor rebuilt on the gs_graphcanvas framework (FlowGraph topology).
  • DialogueDatabaseEditorWindow — container editor with sequence sidebar, performer page, and system page via the full-window page system.
  • DialogueGraphDescriptor — systemId = "dialogue", variablesEnabled = true, fileExtension = ".dialogue".
  • DialogueGraphContext — dialogue-specific data type registration.
  • Editor nodes: Dlg_StartNode, Dlg_TextNode, Dlg_SelectionNode, Dlg_RandomNode, Dlg_EffectsNode, Dlg_PerformanceNode, Dlg_EndNode.
  • Container save model — multiple sequences in a single .dialoguedatabase file with OpenGraphFromAsset / CaptureGraphToAsset / ClearAllDocumentDirty.
  • Graph variable support for dialogue state tracking (flags, counters).
  • Runtime graph execution via GraphInstance and FlowGraphEvaluator in DialogueSequencerComponent.

Cinematics 0.5.0

First official base release of GS_Cinematics.

Cinematics Manager

  • GS_CinematicsManagerComponent — cinematic mode lifecycle (BeginCinematic / EndCinematic)
  • CinematicsManagerRequestBus / CinematicsManagerNotificationBus (EnterCinematic / ExitCinematic)
  • CinematicStageMarkerComponent — named world-space anchor, self-registers on activate

Dialogue Manager

  • GS_DialogueManagerComponent — active .dialoguedb asset ownership and performer registry
  • DialogueManagerRequestBus — StartDialogueSequenceByName, ChangeDialogueDatabase, RegisterPerformerMarker, GetPerformer
  • DialoguePerformerMarkerComponent — named NPC world anchor, PerformerMarkerRequestBus

Dialogue Sequencer

  • DialogueSequencerComponent — node graph traversal, runtime token management
  • DialogueUIBridgeComponent — routes active dialogue to whichever UI is registered, decouples sequencer from presentation
  • DialogueSequencerNotificationBus — OnDialogueTextBegin, OnDialogueSequenceComplete

Data Structure

  • DialogueDatabase (.dialoguedb) — named actors and sequences asset
  • DialogueSequence — directed node graph with startNodeId
  • ActorDefinition — actor name, portrait, and metadata
  • Node types: TextNodeData, SelectionNodeData, RandomNodeData, EffectsNodeData, PerformanceNodeData
  • SelectionOption — per-choice text, conditions, and connections

Conditions

  • DialogueCondition (abstract base) — EvaluateCondition()
  • Boolean_DialogueCondition — base boolean comparison
  • Record_DialogueCondition — checks GS_Save records with comparison operators
  • Polymorphic discovery: extend base class, no manual registration

Effects

  • DialogueEffect (abstract base) — DoEffect() / ReverseEffect()
  • SetRecords_DialogueEffect — sets GS_Save records during dialogue
  • ToggleEntitiesActive_DialogueEffect — activates or deactivates entities in the level
  • Polymorphic discovery: extend base class, no manual registration

Performances

  • DialoguePerformance (abstract base, AZ::TickBus::Handler) — DoPerformance() / ExecutePerformance() / FinishPerformance()
  • MoveTo_DialoguePerformance — interpolated movement to stage marker
  • PathTo_DialoguePerformance — navmesh path navigation to stage marker (requires RecastNavigation)
  • RepositionPerformer_DialoguePerformance — instant teleport to stage marker
  • Polymorphic discovery: extend base class, no manual registration

Dialogue UI

  • DialogueUIComponent — screen-space dialogue text, speaker name, and portrait display
  • WorldDialogueUIComponent — world-space speech bubble display
  • DialogueUISelectionComponent — screen-space player choice menu
  • WorldDialogueUISelectionComponent — world-space selection display
  • TypewriterComponent — character-by-character text reveal, configurable speed, OnTypeFired / OnTypewriterComplete
  • BabbleComponent — procedural audio babble synchronized to typewriter output

Localization

  • LocalizedStringId — key + default fallback text, Resolve() method
  • LocalizedStringTable — runtime key-value string lookup table

Dialogue Editor

  • Node-based in-engine GUI for authoring .dialoguedb assets and sequence graphs

Type Registry

  • DialogueTypeRegistry — factory registration for conditions, effects, and performances
  • DialogueTypeDiscoveryBus — external gems register custom types without modifying GS_Cinematics

2.1.4 - Environment Change Log

GS_Environment version changelog.

Logs


Environment 0.5.0

First official base release of GS_Environment.

Time Manager

  • GS_TimeManagerComponent — world time ownership, time of day control
  • TimeManagerRequestBus — SetTimeOfDay, SetTimePassageSpeed, GetTimeOfDay, GetWorldTime, IsDay
  • TimeManagerNotificationBus — WorldTick (per-frame), DayNightChanged (on threshold cross)
  • GS_EnvironmentSystemComponent — system-level environment registration

Sky Configuration

  • SkyColourConfiguration — data asset defining sky color at different times of day
  • Authored as an asset and assigned to the environment for day/night color blending

2.1.5 - Core Change Log

Changelog.

Logs


Core 0.6.0

GS_Options – Settings System

  • New settings framework: option profiles declare a catalog, and the Options Manager merges, applies, persists and restores it.
  • .optionsprofile asset holding a polymorphic option list; multiple profiles stack and merge by key.
  • Four option types: toggle, slider, dropdown and colour picker.
  • OptionApplyTarget composition — an option’s widget and its engine effect vary independently.
  • Shipped apply targets: data flag, console variable, window mode, window resolution, fullscreen, vsync, and a display gamma target in the private module layer.
  • .optionspreset asset holding values only, with automatic Custom resolution when values diverge.
  • Full bus surface: generic and typed value access, catalog queries by category, apply and save, preset control, and an option-changed notification.
  • SettingsRegistry persistence, flushed on save and shutdown rather than per change.
  • Launch-time apply path — console-expressible settings are written to a startup command file and executed before the window is created, so the game opens correctly rather than switching after launch.
  • Apply deferred by a few ticks after startup, because display and render settings applied at startup-complete precede window and pipeline readiness.
  • Accessibility fonts shipped with the gem.

GS_Options – Input Reactors

  • The reactor mechanism now lives in GS_Core: a reactor component driven by a reactor profile asset holding authored reaction types.
  • Reactors are cloned per component, so a reaction type can hold per-entity state.
  • Input events are classified into pressed, held, released and analog phases before dispatch.
  • InputReactorRequestBus for feeding events, clearing state and querying values.
  • InputReactorNotificationBus emits in parallel with dispatch, so scripts can react without writing a reaction type.

Managers

  • Headless mode — a manager can drive its own startup with no Game Manager present, for deploying a single feature into a project not built on GS_Play.
  • Headless managers do not register with the Game Manager, so they take no part in its startup count.
  • OnBeginGame is never self-fired: startup completing is infrastructure, beginning the game is a cue the project owns.
  • Per-manager logging depth for lifecycle diagnostics.

Debug

  • GS_ImGuiMenuComponent — a content-free GS_Play menu anchor that any gem contributes to, with deterministic ordering by sort key.
  • Core menu shipping a live Options editor and a Game Manager window.

Utilities

  • New Noise utility: Perlin 1D/2D/3D, fractal multi-octave, and reflected layered noise stacks.
  • New Envelope utility: a reflected ADSR envelope with evaluation, duration and completion helpers.
  • New orbital solver: pivot-aware position and pose blending in linear, cylindrical and spherical shapes.
  • New comparison helpers for data-authored boolean conditions.
  • DebugLoggingHelper mix-in for per-component logging depth.
  • Utility headers reorganised into Math, Gradients, Physics, Random and Reflection folders.
  • The type registry subsystem was removed. Extensible type sets now use the polymorphic array convention, where the serializer supplies the type picker.

Templates

  • GS_Core Project template — a working project with Game, Options, Save and Stage managers already wired into a manager collection, on the shared starter level.
  • Component templates for managers, savers, input readers, input reactors and physics triggers.
  • Editor-Only Environment prefab for authoring-time lighting and reference setup.

Interfaces

  • FeedbackExchangeBus added to the contract layer.

Core 0.5.0

  • COOOOREEEEE

2.1.6 - Interaction Change Log

GS_Interaction version changelog.

Logs


Interaction 0.5.0

First official base release of GS_Interaction.

Pulsors

  • PulsorComponent — physics trigger volume that broadcasts typed pulse events
  • PulseReactorComponent — receives pulses and executes reactor types
  • PulseType (abstract base) — extensible pulse payload type
  • Built-in types: Debug_Pulse, Destruct_Pulse
  • PulseTypeRegistry — auto-discovery of all reflected pulse types

Targeting

  • GS_TargetComponent — marks an entity as a targetable object
  • GS_InteractTargetComponent — marks an entity as interactable
  • GS_CursorComponent — world-space cursor and proximity scan
  • GS_TargetingHandlerComponent — finds and locks the best interactable in range
  • GS_TargetingHandlerRequestBus / GS_TargetingHandlerNotificationBus

World Triggers

  • TriggerSensorComponent — base for all condition-side trigger sensors
  • WorldTriggerComponent — base for all response-side world triggers
  • Built-in sensors: InteractTriggerSensorComponent, PlayerInteractTriggerSensorComponent, ColliderTriggerSensorComponent, RecordTriggerSensorComponent
  • WorldTriggerRequestBus — trigger enable/disable interface
  • Sensors renamed from TriggerAction to TriggerSensor (2026-03)
  • 22 components total across all subsystems

2.1.7 - Juice Change Log

GS_Juice version changelog.

Logs


Juice 0.5.0

First official base release of GS_Juice. Early Development — full support planned 2026.

Feedback System

  • FeedbackEmitter — component that plays authored feedback motions on its entity
  • FeedbackMotionAsset — data asset containing one or more feedback tracks
  • FeedbackMotion — runtime instance wrapper for a FeedbackMotionAsset
  • FeedbackMotionTrack — domain base class extending GS_Core’s GS_Motion system

Feedback Tracks

  • FeedbackTransformTrack — drives position, scale, and rotation offsets (screen shake, bounce)
  • FeedbackMaterialTrack — drives opacity, emissive intensity, and color (flash, glow, pulse)

PostProcessing

  • Planned for future release. Not yet implemented.

2.1.8 - Performer Change Log

GS_Performer version changelog.

Logs


Performer 0.5.0

First official base release of GS_Performer. Early Development — full support planned 2026.

Performer Manager

  • GS_PerformerManagerComponent — performer registration and name-based lookup
  • PerformerManagerRequestBus
  • GS_PerformerSystemComponent — system-level lifecycle component

Skin Slots

  • PerformerSkinSlotComponent — manages a named visual slot on a character entity
  • SkinSlotHandlerComponent — handles asset swapping for a given slot
  • PerformerSkinSlotsConfigProfile — asset class defining slot configurations
  • SkinSlotData / SkinSlotNameDataPair — slot data types

Paper Performer

  • PaperFacingHandlerBaseComponent — base for billboard-style rendering handlers
  • PaperFacingHandlerComponent — orients a 2.5D billboard character to face the camera correctly

Locomotion

  • VelocityLocomotionHookComponent — reads entity velocity and drives animation blend tree parameters automatically

Head Tracking

  • Procedural head bone orientation toward a world-space look-at target

Babble

  • BabbleComponent — generates procedural vocalisation tones synchronized to GS_Cinematics typewriter output
  • BabbleToneEvent / SpeakerBabbleEvents — tone data structures per speaker identity

2.1.9 - PhantomCam Change Log

GS_PhantomCam version changelog.

Logs


PhantomCam 1.0.0

Major refactor. The “pick one of N subclass cams” model is replaced by a single GS_PhantomCameraComponent that hosts a composable Body → Aim → Reposition additives → Noise additives stage pipeline. A new channel system scales projects from single-cam through 4-player split-screen co-op without changing the per-cam authoring surface.

Stage pipeline (replaces subclass cams)

  • New IBodyStage / IAimStage / IAdditiveStage interfaces — stages are reflected polymorphic types picked from the editor’s type-picker.
  • Body variants: DefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBody.
  • Aim variants: DefaultLookAtAim, ClampedLookAim (plus extensions from gs_performer).
  • Additive variants: CollisionReposition, OcclusionReposition (stub), TugAimListener, TugBodyListener, PerlinNoise, ImpulseNoise.
  • Three staged pose snapshots — m_desiredPose (pre-Reposition), m_stablePose (post-Reposition pre-Noise), m_finalPose (committed).

Retired components

  • ClampedLook_PhantomCamComponent → replaced by ClampedLookAim aim stage variant.
  • StaticOrbit_PhantomCamComponent → replaced by OrbitBody body stage variant.
  • Track_PhantomCamComponent → replaced by TrackBody body stage variant.
  • Their legacy doc URLs redirect to the new stage variant pages via Hugo aliases.
  • AlwaysFaceCameraComponent is unchanged.

Channels and instancing

  • m_enableInstancedChannels master gate on GS_CamManagerComponent. Three authoring tiers (single cam → single-player rig → multi-channel).
  • New CamChannelScope enum (Local / AllChannels / TrueUnique) authored per phantom cam.
  • ChannelStampComponent — runtime plumbing for stamp-walk channel resolution. Author never sees it.
  • Per-channel rig spawning, per-channel target binding via SetChannelTarget(channelId, entity).
  • Cross-channel cinematic dispatch via DispatchCamToCamCore / ReleaseCamCoreDispatch.
  • Active main-view selection via SetActiveChannel / SetActiveCamCore.
  • Channel-aware SettingNewCamOnChannel notification replaces legacy SettingNewCam when instancing is on.

Blend Profiles

  • New asset extension .camblendprofile (registered through PhantomCamDataAssetsSystemComponent).
  • Three new fields on PhantomBlend entries:
    • BlendShape — Linear / Cylindrical / Spherical (powered by the new Orbital Solver).
    • PivotSource — Source / Destination / Shared for cross-cam blend pivot resolution.
    • InheritState — opt-in cam-to-cam pose handoff.
  • PhantomBlend reflected at Version 4 with version converter for pre-v3 and pre-v4 assets.

State Inheritance

  • Universal CamPoseSnapshot pose-handoff struct.
  • Per-body Get / Adopt implementations — Orbit Get-only, DynamicOrbit full ANGULAR+POSITION, LeadingFollow facing-seeded standoff, Track path-project with rejection threshold.
  • Handoff runs before StartBlend so the destination’s body adopts the seed pose before the blend captures source / destination poses.

Mid-blend Interrupt Correction

  • New m_interruptCorrection field on Cam Core (default 0.03).
  • Y-blend correction window between the cam’s snapshot-at-interrupt and the new blend’s natural pose at the velocity-matched curve point.
  • Replaces the legacy BlendFromCore hard-anchor approach.

Group Targets

  • New GroupTargetComponent — weighted-centroid focal entity. Cams point at it like any other target.
  • Centroid modes: WeightedMean / BoundingBoxCenter / BoundingSphereCenter with m_weightBias blend.
  • Orientation modes: Identity / SpreadAxis / WeightedAverageForward.
  • Registered globally via Cam Manager (RegisterGroupTarget / FindGroupTargetByName).
  • Pair with shared TrueUnique cams to trigger OnAllChannelsActivatedSharedCam on collapse.

Tug Fields

  • Three-component decoupled spatial reposition system — CameraTugVolumeComponent, CameraTugSourceComponent, TugFieldProxyComponent.
  • PhysX layer-gated (TugProxy ↔ TugField collision-group pair). No central registry.
  • Listener stages TugAimListener and TugBodyListener consume engagements in the Reposition phase.

Noise & Impulse

  • New asset extension .camnoiseprofile — six-axis layered Perlin noise stacks. Starter Telephoto_Mild and Telephoto_Intense profiles ship in gs_phantomcam/Assets/Noise Profiles/.
  • PerlinNoise additive — continuous handheld feel.
  • ImpulseNoise additive — event-triggered burst, ADSR-gated. Fire via TriggerCameraImpulse(strength) on the cam.

Orbit Profiles

  • New asset extension .camorbit — CameraOrbitShape with three bands (low / mid / high) and a Roundness power-bulge family slider (diamond → sphere → cube). Arc-length reparameterization for constant spatial speed.
  • Consumed by DynamicOrbitBody.

Camera Input Reader

  • New GS_CameraInputReaderComponent implementing the OrbitInputProvider bus.
  • Per-axis Axis vs Delta style for joystick / mouse handling.
  • ResetPendingInput invariant prevents accumulated mouse delta “bursts” after dormancy.

Influence Fields

  • AddCameraInfluence signature changed — now (sourceEntity, targetEntity, camName, influence). Influences are routed to the channel that owns the target entity; influences whose target isn’t channel-bound are silently dropped.

Orbital Solver (GS_Core)

  • New GS_Core::Math utility — BlendPositionAroundPivot, BlendPoseAroundPivot, ResolveCrossCamPivot. Powers the new blend shapes, DynamicOrbitBody damping, and LeadingFollowBody band response. See Orbital Solver.

PhantomCam 0.5.0

First official base release of GS_PhantomCam.

Cam Manager

  • GS_CamManagerComponent — camera system lifecycle, active camera tracking
  • CamManagerRequestBus / CamManagerNotificationBus
  • GS_PhantomCamSystemComponent — system-level registration

Phantom Cameras

  • GS_PhantomCameraComponent — virtual camera with follow target, look-at, FOV, and priority
  • PhantomCameraRequestBus
  • PhantomCamData — camera configuration data structure
  • Priority-based switching: highest priority active phantom camera drives the real camera

Cam Core

  • GS_CamCoreComponent — reads from the active phantom camera each frame and applies to the engine camera
  • CamCoreRequestBus / CamCoreNotificationBus

Camera Behaviors

  • ClampedLook_PhantomCamComponent — look-at with clamped angle limits
  • StaticOrbitPhantomCamComponent — fixed-distance orbit around a target
  • Track_PhantomCamComponent — follows a spline or path track
  • AlwaysFaceCameraComponent — keeps an entity billboard-facing the active camera

Blend Profiles

  • GS_PhantomCamBlendProfile — asset defining camera transition easing and duration
  • Smooth interpolation between camera positions on priority switch

Influence Fields

  • GlobalCameraInfluenceComponent — global persistent camera modifier
  • CameraInfluenceFieldComponent — spatial zone that modifies camera behavior on entry
  • CamInfluenceData — influence configuration data (offset, FOV shift, tilt)

2.1.10 - UI Change Log

GS_UI version changelog.

Logs


UI 0.5.0

First official base release of GS_UI.

UI Manager

  • GS_UIManagerComponent — canvas lifecycle management, focus stack, startup focus assignment
  • UIManagerRequestBus
  • Single-tier page navigation: Manager → Page (no hub layer)
  • GS_UIPageComponent — root canvas registration, nested page management
  • NavigationReturnPolicy — RestoreLast or AlwaysDefault back navigation behavior
  • FocusEntry — focus state data for page transitions
  • Cross-canvas boundary navigation support
  • Companion Component Pattern: domain-specific page logic lives on companion components that react to page bus events

UI Interaction

  • GS_ButtonComponent — enhanced button with hover and select UiAnimationMotion support
  • GS_UIInputInterceptorComponent — input capture and blocking for UI canvases
  • GS_UIInputProfile — input mapping configuration for UI contexts

UI Animation

  • UiMotionTrack (domain base) — extends GS_Core’s GS_Motion system
  • 8 concrete tracks: UiPositionTrack, UiScaleTrack, UiRotationTrack, UiElementAlphaTrack, UiImageAlphaTrack, UiImageColorTrack, UiTextColorTrack, UiTextSizeTrack
  • UiAnimationMotionAsset — authored animation asset
  • UiAnimationMotion — runtime instance wrapper

Load Screen

  • GS_LoadScreenComponent — manages load screen display during level transitions

Pause Menu

  • PauseMenuComponent — pause state management and pause menu page coordination

Removed

  • GS_UIWindowComponent — removed entirely
  • GS_UIHubComponent / GS_UIHubBus — deprecated legacy layer

2.1.11 - Unit Change Log

GS_Unit version changelog.

Logs


Unit 0.6.0

Unit Action Graph (Under Construction)

  • HFSM editor built on the gs_graphcanvas framework (StateMachineGraph topology).
  • UnitActionGraphDescriptor — topology = StateMachineGraph, variablesEnabled = true, transitionConditions = true, parallelLayers = true.
  • UnitActionEditorWindow — multi-layer container editor with layer sidebar and priority configuration.
  • Perimeter connection rendering — engine-level ConnectionCurveType::Perimeter for state machine arrow visuals.
  • Node types: UAG_EntryNode, UAG_StateNode, UAG_CompoundStateNode (nested sub-state-machine).
  • TransitionDescriptor — per-connection priority and polymorphic condition list, auto-managed on connection add/remove.
  • TransitionCondition base class with built-in conditions: VariableCompareCondition, TimeElapsedCondition, VariableTrueCondition.
  • Transition inspector — click connections to edit priority and conditions; node selection shows outgoing transition summary.
  • StateMachineEvaluator — tick-based runtime with OnEnter/OnTick/OnExit lifecycle and __stateTime auto-variable.
  • ParallelLayerEvaluator — wraps multiple evaluators with shared variable context for simultaneous layer evaluation.
  • Not yet implemented: GS_UnitContext integration, compound state sub-graph editing, tag-based transitions, GS_CharacterActionComponent bridge.

Unit 0.5.0

First official base release of GS_Unit.

Unit Manager

  • GS_UnitManagerComponent — unit registration and lifecycle tracking
  • UnitManagerRequestBus

Unit Component

  • GS_UnitComponent — unit identity and state component
  • UnitRequestBus

Controllers

  • GS_UnitControllerComponent — abstract controller base, possess/release pattern
  • GS_PlayerControllerComponent — human player possession
  • GS_AIControllerComponent — AI-driven possession
  • GS_PlayerControllerInputReaderComponent — reads hardware input and routes to the possessed unit

Input Pipeline

  • 3-stage architecture: controller entity reads hardware → routes to InputDataComponent on the unit → reactor components act on the structured state
  • InputDataRequestBus / InputDataNotificationBus
  • GS_InputReactorComponent — reacts to binary input states (button/action)
  • GS_InputAxisReactorComponent — reacts to axis input values (sticks, triggers)

MoverContext

  • GS_MoverContextComponent — transforms raw input into movement intent, manages active mode and profiles
  • MoverContextRequestBus / MoverContextNotificationBus
  • Mode-driven movement: one named mode active at a time; only the mover and grounder matching that mode run

Movers

  • GS_MoverComponent — mode-aware mover base class
  • GS_PhysicsMoverComponent — physics-driven movement via PhysX
  • GS_3DFreeMoverComponent — unconstrained 3D free movement
  • GS_3DSlideMoverComponent — slide-and-collide surface movement

Grounders

  • GS_PhysicsRayGrounderComponent — raycast-based grounding for the “Free” movement mode

Movement Influence

  • MovementInfluenceFieldComponent — spatial zone that modifies unit movement within its bounds
  • GlobalMovementRequestBus — global movement modifier interface

Profiles

  • GS_UnitMovementProfile — per-mode speed, acceleration, and movement parameter configuration

2.1.12 - Docs Change Log

Updated changelog.

Logs


Docs 1.1.0

New: GS_GraphCanvas Framework Reference

  • Added framework/graphcanvas/ section — full reference for the gs_graphcanvas visual graph editor framework.
  • Descriptor & Topology — GraphSystemDescriptor fields, topology decision matrix, GraphContext data types.
  • Nodes — BaseNode, slot macros, auto-registration, rapid creation macro, built-in nodes.
  • Editor Window — MainWindow features, virtual hooks, page system, container editor pattern.
  • Variables — Variable panel, Get/Set nodes, convert-to-reference, drag-to-canvas.
  • Execution Engines — FlowGraphEvaluator, DataFlowGraphEvaluator, StateMachineEvaluator, GraphExecutionContext, GraphInstance.

New: Audio Event Graph

  • Added framework/audio/audio_event_graph/ — usage docs for the visual sound event editor.
  • Node catalog, phase lifecycle, variable control, extending with custom nodes, runtime API reference.
  • Updated GS_Audio index page with Audio Event Graph section.

Updated: Dialogue Editor

  • Rewrote framework/cinematics/dialogue_system/dialogue_editor/ from skeleton to full usage documentation.
  • Database model, editor layout, sequences, performers, node catalog, conditions, extending, variables.

New: Unit Action Graph (Under Construction)

  • Added framework/unit/unit_action_graph/ — HFSM editor docs marked under construction.
  • Multi-layer model, perimeter connections, nodes, transitions, conditions, parallel layers, current status.
  • Updated GS_Unit index page with Unit Action Graph section.

New: Building a Graph Tool Tutorial

  • Added learn/lessons/building_a_graph_tool/ — step-by-step guide for creating a custom graph editor.
  • Covers topology selection, descriptor, context, nodes, editor window, system component, runtime, and pitfalls checklist.

New: Dialogue System Setup Tutorial

  • Added learn/lessons/dialogue_setup_tutorial/ — 6-step tutorial for setting up a complete dialogue pipeline.
  • Covers database creation, conversation authoring, runtime wiring, UI display, player choices, conditions and effects.

Removed

  • Removed learn/lessons/audio_event_graph_tutorial/ — replaced by Dialogue System Setup tutorial.

Site

  • Added SVG light/dark theme support.

Docs 1.0.0

  • Welcome page finalized.

  • Get Started and Index sections finalized.

  • No missing pages. Final organization.

  • Patterns Complete.

  • Feature List, Glossary fully updated.

  • Get Started T1 page filled.

  • Index T1 page filled.

  • Final Logs format and arrangement.

  • Print page + print section links.

  • Homepage complete.

  • About Us complete.

  • Product pages complete.


Docs 0.9.0

Full first implementation of direct documentation formatting and content. With this release we have our first fully working catalogue of featuresets, functionality, and API.

Feature Roots are called “Tier 2”. They are overviews with quick indexes for their subsystems. Content Pages are called “Tier 3”. They carry the body and substance of any given feature or subfeature. Nested pages below these tiers are combinations of both T2, and T3, depending on the depth an overview needs to be to segue into the child pages.

Established base organization of information.

  • Get Started is very thin and for user onboarding.
  • Index replaces Get Started for regular users. Allowing for easy navigation across categorical necessities for users.
  • Basics is conceptual and script driven knowledge for the framework.
  • Framework API is the engineering and veteran user knowledgebase.
  • Learn is for Youtube video funnelling, or text driven Guide access.

Cross linking is prioritized to move users to the knowledge they seek, asap.

Agentic Guidelines is an LLM based seed for how to use GS_Play and navigate the documentation for information access. The website has been optimized behind the scenes for LLM scraping.

Basics

Basics Implemented.

  • Implemented Core
  • Implemented Audio
  • Implemented Cinematics
  • Implemented Environment
  • Implemented Interaction
  • Implemented Juice
  • Implemented Performer
  • Implemented PhantomCam
  • Implemented UI
  • Implemented Unit

Framework API

Framework API Implemented.

  • Implemented Core
  • Implemented Audio
  • Implemented Cinematics
  • Implemented Environment
  • Implemented Interaction
  • Implemented Juice
  • Implemented Performer
  • Implemented PhantomCam
  • Implemented UI
  • Implemented Unit

2.2 - Feature List

Quick index of all GS_Play Features.

Contents


GS_Core

FeatureAPIDoes
GS_ManagersAPIStart a new game, continue from a save, load a specific file, or return to the title screen
GS_SaveAPISave and load game data, or track persistent flags and counters across sessions
GS_StageManagerAPIMove between levels, or configure per-level spawn points and navigation settings
SettingsAPIGive players volume, graphics and accessibility options that apply, save and restore themselves
GS_OptionsAPIRead player input, disable input during menus, or swap control schemes at runtime
Input ReactorsAPITurn named input events into gameplay, and write your own reaction types
DebuggingAPIRun the level you are editing, or inspect and drive a running game from an in-game menu
UtilitiesAPIUse easing curves, detect physics zones, smooth values, pick randomly, or work with splines
GS_ActionsAPITrigger a reusable behavior on an entity from a script, physics zone, or another action
GS_MotionAPIAnimate a transform, color, or value smoothly over time

GS_Audio

Audio is two gems. GS_Audio is free; GS_AudioPro is premium and extends it.

FeatureTierAPIDoes
Signal Model—APIUnderstand how a sound travels from its source to the speakers
Audio ManagerFreeAPIManage the audio engine, set the listener, or control master volume
Audio EventsFreeAPIPlay sounds by name with pooling, distance falloff and concurrency control
SFXR ProfilesFreeAPIMake procedural sound effects with no audio files
Mixing & EffectsFreeAPIGroup sounds into volume channels and process them with 22 DSP effects
Spatial AudioFreeAPIPlace sounds in the world with HRTF and distance falloff
Audio Event GraphProAPIAuthor a sound as a visual node graph with phases and live variables
Score ArrangementProAPILayer music tracks dynamically based on gameplay state
Klatt VoiceProAPIGenerate speech with configurable voice parameters and 3D spatial audio
Geometric AcousticsProAPIHave walls block sound, and get reverb from the room the player is in

GS_Cinematics

FeatureAPIDoes
Cinematics ManagerAPICoordinate cinematic sequences and manage stage markers for actor positioning
Dialogue SystemAPIAuthor branching dialogue with conditions, effects, and performances
Dialogue UIAPIDisplay dialogue text, player choices, and speech babble on screen or in world space
PerformancesAPIMove actors to stage markers during dialogue with navigation or teleport
Timeline ExpansionAPIExtend the sequence timeline with custom track and key types

GS_Environment

FeatureAPIDoes
Time ManagerAPIControl world time, time passage speed, or respond to day/night changes
Sky ConfigurationAPIDefine how the sky looks at different times of day with data assets

GS_Interaction

FeatureAPIDoes
PulsorsAPIBroadcast typed physics events from trigger volumes to receiving entities
TargetingAPIFind and lock onto the best interactable entity in proximity with a cursor overlay
World TriggersAPIFire configurable responses from zones and conditions without scripting

GS_Juice

FeatureAPIDoes
Feedback SystemAPIPlay screen shake, bounce, flash, or material glow effects on entities

GS_Performer

FeatureAPIDoes
Performer ManagerAPIManage registered performers or query them by name
Skin SlotsAPISwap equipment visuals at runtime with modular slot-based assets
Paper PerformerAPIRender billboard-style 2.5D characters that face the camera correctly
LocomotionAPIDrive animation blend parameters from entity velocity automatically
Head TrackingAPIProcedurally orient a character’s head bone toward a world-space look-at target
BabbleAPIGenerate procedural vocalisation tones synchronized to dialogue typewriter output

GS_PhantomCam

FeatureAPIDoes
Cam ManagerAPIManage the camera system lifecycle and know which camera is active
Phantom CamerasAPIPlace virtual cameras with follow targets, look-at, and priority-based switching
Cam CoreAPIControl how the real camera reads from the active phantom camera each frame
Blend ProfilesAPIDefine smooth transitions between cameras with custom easing
Influence FieldsAPICreate spatial zones that modify camera behavior dynamically

GS_UI

FeatureAPIDoes
UI ManagerAPILoad and unload UI canvases, manage focus stack, or set startup focus
Page NavigationAPINavigate between pages, handle back navigation, or cross canvas boundaries
UI InteractionAPIButton animations and input interception for UI canvases
UI AnimationAPIAnimate UI elements with position, scale, rotation, alpha, and color tracks
WidgetsAPIReusable UI building blocks for counters, sliders, and interactive controls

GS_Unit

FeatureAPIDoes
Unit ManagerAPIRegister units, track active controllers, or spawn units at runtime
ControllersAPIPossess and release units with player or AI controllers
Input DataAPICapture raw input and convert it into structured movement intent
MovementAPIMove characters with movers, grounders, and movement influence fields

2.3 - Patterns

Patterns associated with the GS_Play featuresets.

Outline

The strength of the GS_Play framework is in it’s straightforward, and intuitive patterns.

By creating simple ways to look at certain featuresets, you can reliably expand your gameplay knowing you’re always going to properly connect with the underlying system.

Using our extensive list of templates, you can even more rapidly develop the features that make your project unique.

Where features connect across gems, they meet at neutral contracts collected in the GS_Core Interfaces layer — the three contract kinds (TypeBase, Emit, Exchange) are the connective pattern behind most of the diagrams below.

Use this quick list to jump to the features you want to look at.

You can expect a diagram outlining the relationships between feature elements, and then explanations on how the pattern plays out.

Have fun!

 

Legend

Colour Legend for pattern diagrams

 

Contents


Polymorphic Arrays - framework-wide

Breakdown

Wherever GS_Play has an extensible set of things — the events in an audio library, the effects in a mix chain, the options in a profile, the reactions in an input reactor profile, the tracks in a motion asset — it uses the same shape.

Abstract base            reflected, e.g. an option type or an event type
      |
      |  subclasses reflected against it, in any gem
      v
vector<Base*>            the authored field on an asset or component
      |
      |  SerializeContext supplies the "Add" dropdown
      v
Editor type picker       every registered subtype, automatically

There is no registry, no discovery bus and no registration call. Reflecting a subclass against the base is the whole of it — the serializer already knows every type derived from a reflected base, so the property editor can offer them.

The rule that makes it work

An editable polymorphic field must be a vector of raw pointers to the base. A single Base* or a smart pointer is not editable in the property editor. The owning container owns its elements.

If you author an extensible field and the Add dropdown does not appear, this is nearly always why.

Why it is worth knowing as a pattern

Once you recognise the shape, every extensible system in the framework reads the same way, and extending any of them is the same three steps: derive from the base, implement its handful of virtuals, reflect it.

Where you will meet itThe baseThe authored field
Audio eventsaudio eventan event library’s events
Mix effectsmix effecta mix effect component’s chain
Settingsoption typean option profile’s options
Input reactorsreaction typea reactor profile’s reactions
Motionmotion tracka motion asset’s tracks

The input reactor profile is the clearest one to look at first, because the profile asset makes the whole authoring loop visible in one place.


Managers & Startup - GS_Core

Game Manager Startup Pattern Graph

Breakdown

When the project starts, the Game Manager runs three stages before the game is considered ready:

StageBroadcast EventWhat It Means
1. Initialize(internal)Each manager is spawned. They activate, then report ready.
2. SetupOnSetupManagersSetup stage. Now safe to query other managers.
3. CompleteOnStartupCompleteLast stage. Everything is ready.
Do any last minute things. Now safe to begin gameplay.

For most scripts, you only need OnStartupComplete. Wait for this event before doing anything that depends on managers to be completely setup.

Back to top…


Save - GS_Core

Save System Pattern Graph

Breakdown

When a save or load is triggered, the Save Manager broadcasts to every Saver in the scene. Each Saver independently handles its own entity’s state. The Record Keeper persists flat progression data alongside the save file.

PartBroadcast EventWhat It Means
Save ManagerOnSaveAllBroadcasts to all Savers on save. Each serializes its entity state.
Save ManagerOnLoadAllBroadcasts to all Savers on load. Each restores its entity state.
Record KeeperRecordChangedFires when any progression flag is created, updated, or deleted.

Savers are per-entity components — add them to anything that needs to persist across sessions. The Record Keeper is a global singleton for flags and counters.

Back to top…


Stage Change - GS_Core

Stage Change Pattern Graph

Breakdown

When a stage change is requested, the system follows a five-step sequence before gameplay resumes in the new level:

StepBroadcast EventWhat It Means
1. StandbyOnEnterStandbyGame Manager enters standby, halting all gameplay systems.
2. Unload(internal)The current stage’s entities are torn down.
3. Spawn(internal)The target stage prefab is instantiated.
4. Set UpOnBeginSetUpStageStage Data runs its layered startup: SetUpStage → ActivateByPriority → Complete.
5. CompleteLoadStageCompleteStage Manager broadcasts completion. Standby exits. Gameplay resumes.

The Stage Data startup is layered — OnBeginSetUpStage, ActivateByPriority, then OnLoadStageComplete — so heavy levels can initialize incrementally without causing frame-time spikes.

Back to top…


Pulse - GS_Interaction

Pulse Pattern Graph

Breakdown

When an entity enters a Pulsor’s trigger volume, the Pulsor emits its configured pulse type to all Reactors on the entering entity:

StepWhat It Means
1. Collider overlapPhysics detects an entity entering the Pulsor’s trigger volume.
2. Pulse emitPulsorComponent reads its configured PulseType and prepares the event.
3. Reactor queryEach PulseReactorComponent on the entering entity is checked with IsReactor().
4. ReactionReactors returning true have ReceivePulses() called and execute their response.

Pulse types are polymorphic — new types are discovered automatically at startup. Any gem can define custom interaction semantics without modifying GS_Interaction.

Back to top…


World Triggers - GS_Interaction

World Trigger Pattern Graph

Breakdown

World Triggers split conditions from responses. Any number of World Triggers can stack on a single entity — each fires its own response independently when the condition is met.

PartWhat It Means
Trigger SensorMonitors for a condition. When satisfied, calls Trigger() on all WorldTriggerComponent instances on the same entity.
World TriggerExecutes a response when Trigger() is called. Can be reset with Reset() to re-arm for the next activation.

No scripting is required for standard patterns. Compose Trigger Sensors and World Triggers in the editor to cover the majority of interactive world objects without writing any code.

Back to top…


Camera Blend - GS_PhantomCam

Camera Blend Pattern Graph

Breakdown

When the dominant Phantom Camera changes, the Cam Manager queries the Blend Profile to determine transition timing and easing:

StepWhat It Means
1. Priority changeA Phantom Camera gains highest priority or is activated.
2. Profile queryCam Manager calls GetBestBlend(fromCam, toCam) on the assigned GS_PhantomCamBlendProfile asset.
3. Entry matchEntries are checked by specificity: exact match → any-to-specific → specific-to-any → default fallback.
4. InterpolationCam Core blends position, rotation, and FOV over the matched entry’s BlendTime using the configured EasingType.

Because Blend Profiles are shared assets, editing one profile updates every camera that references it.

Back to top…


Unit Possession - GS_Unit

Unit Possession Pattern Graph

Breakdown

Every unit has exactly one controller at a time, or no controller at all. Possession is established by calling Possess on the unit and released by calling DePossess. Ownership changes are observed through the GS_Core cross-gem contract PossessionEmissionBus whenever ownership changes so other systems can react.

ConceptDescription
PossessionA controller attaches to a unit. The unit accepts input and movement commands from that controller only.
DePossessionThe controller releases the unit. The unit halts input processing and enters a neutral state.
Possession eventsFire on the GS_Core PossessionEmissionBus (addressed by entity ID) whenever a unit’s controller changes.
GetControllerReturns the entity ID of the current controller, or an invalid ID if none.
GetUniqueNameReturns the string name assigned by the Unit Manager when this unit was spawned.

Back to top…


Unit Input Handling - GS_Unit

Unit Input Handling Pattern Graph

Breakdown

The input pipeline has three stages. Each stage is a separate component on the unit entity, and they run in order every frame:

StageComponentWhat It Does
1. ReadGS_PlayerControllerInputReaderComponentReads raw input events from the active input profile and writes them into GS_InputDataComponent.
2. StoreGS_InputDataComponentHolds the current frame’s input state — button presses, axis values — as structured data.
3. ReactReactor componentsRead from GS_InputDataComponent and produce intent: movement vectors, action triggers, etc.

All reactor components downstream of the store stage read from the same GS_InputDataComponent, so there is no duplicated hardware polling and no risk of two reactors seeing different input states for the same frame.

Back to top…


Movers & Grounders - GS_Unit

Movers & Grounders Pattern Graph

Breakdown

Movers and Grounders are multiple components on a Unit that are constantly changing activation based on the units state. When a mover is active, it freely processes the unit’s movement based on it’s functionality. At any moment, through internal or external forces, the Movement, Rotation, or Grounding state can change, which disables the old movers, and activates the new one to continue controlling the Unit.

Input Reader
    ↓  InputReactorRequestBus::FeedInputEvent
GS_InputReactorComponent
    ↓  dispatches by event name and phase
Reaction types  (KeyboardMovement / JoypadMovement / Jump)
    ↓  MoverContextRequestBus::SetMoveInputAxis
GS_MoverContextComponent
    ↓  ModifyInputAxis() → camera-relative
    ↓  GroundInputAxis() → ground-projected
    ↓  MoverContextNotificationBus::MovementModeChanged("Free")
GS_3DFreeMoverComponent  [active when mode == "Free"]
    ↓  AccelerationSpringDamper → rigidBody->SetLinearVelocity
GS_PhysicsRayGrounderComponent  [active when grounding mode == "Free"]
    ↓  MoverContextRequestBus::SetGroundNormal / SetContextState("grounding", ...)
    ↓  MoverContextRequestBus::ChangeMovementMode("Slide")  ← when slope too steep
GS_3DSlideMoverComponent  [active when mode == "Slide"]

The Slide mover activates when the Unit is walking on too steep an angle. It takes control over the unit, slides down the hill, then restores the previous movement behaviour.

Back to top…

2.4 - Templates List

All ClassWizard templates for GS_Play gems — one-stop reference for generating extension classes across every gem.

All GS_Play extension types are generated through the ClassWizard CLI. The wizard handles UUID generation, cmake file-list registration, and module descriptor injection automatically. Never create these files from scratch.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


GS_Core

Full details: GS_Core Templates

TemplateGeneratesUse For
GS_ManagerComponent${Name}ManagerComponent.h/.cpp + optional ${Name}Bus.hManager-pattern components with EBus interface
SaverComponent${Name}SaverComponent.h/.cppCustom save/load handlers via the GS_Save system
GS_InputReaderComponent${Name}InputReaderComponent.h/.cppController-side hardware input readers
InputReactor${Name}InputReactor.h/.cppReaction types that turn named input events into gameplay
PhysicsTriggerComponent${Name}PhysicsTriggerComponent.h/.cppPhysX trigger volumes with enter/hold/exit callbacks

GS_Core also ships the GS_Core Project template, and GS_Complete ships GS_Complete Project — full O3DE project templates with the managers already wired up. See GS_Core Templates.


GS_Interaction

Full details: GS_Interaction Templates

TemplateGeneratesUse For
PulsorPulse${Name}_Pulse.h/.cppNew pulse type for the Pulsor emitter/reactor system
PulsorReactor${Name}_Reactor.h/.cppNew reactor type that responds to a named channel
WorldTrigger${Name}_WorldTrigger.h/.cppNew world trigger that fires a named event
TriggerSensor${Name}_TriggerSensor.h/.cppNew trigger sensor that listens and responds to events

GS_Unit

Full details: GS_Unit Templates

TemplateGeneratesUse For
UnitController${Name}ControllerComponent.h/.cppCustom controller for player or AI possession logic
InputReactor${Name}InputReactorComponent.h/.cppUnit-side input translation from named event to bus call
Mover${Name}PhysicsMoverComponent.h/.cpp or ${Name}MoverComponent.h/.cppCustom locomotion mode (walk, swim, climb, etc.)
Grounder${Name}PhysicsRayGrounderComponent.h/.cpp or ${Name}GrounderComponent.h/.cppCustom ground state detection (ray, capsule, custom)

GS_UI

Full details: GS_UI Templates

TemplateGeneratesUse For
UiMotionTrack${Name}Track.h + .cppNew LyShine property animation track for .uiam assets

GS_Juice

Full details: GS_Juice Templates

TemplateGeneratesUse For
FeedbackMotionTrack${Name}Track.h + .cppNew world-space entity property animation track for .feedbackmotion assets

GS_PhantomCam

Full details: GS_PhantomCam Templates

TemplateGeneratesUse For
PhantomCamera${Name}PhantomCamComponent.h/.cppCustom camera behaviour (follow, look-at, tick overrides)

GS_Cinematics

Full details: GS_Cinematics Templates

TemplateGeneratesUse For
DialogueCondition${Name}_DialogueCondition.h/.cppGate dialogue node connections — return true to allow
DialogueEffect${Name}_DialogueEffect.h/.cppFire world events from Effects nodes; optionally reversible
DialoguePerformance${Name}_DialoguePerformance.h/.cppDrive world-space NPC actions; sequencer waits for completion

Registration Quick Reference

TemplateAuto-registered by CLIManual step required
Manager ComponentYes (cmake + module)None
Saver ComponentYes (cmake + module)None
InputReader ComponentYes (cmake + module)None
Physics Trigger ComponentYes (cmake + module)None
Pulsor PulseYes (cmake)None
Pulsor ReactorYes (cmake)None
World TriggerYes (cmake)None
Trigger SensorYes (cmake)None
Unit ControllerYes (cmake + module)None
Input ReactorYes (cmake + module)None
Mover ComponentYes (cmake + module)Set mode name strings in Activate()
Grounder ComponentYes (cmake + module)Set mode name string in Activate()
Phantom CameraYes (cmake + module)None
UiMotionTrackYes (cmake)Add Reflect(context) to UIDataAssetsSystemComponent
FeedbackMotionTrackYes (cmake)Add Reflect(context) to GS_JuiceDataAssetSystemComponent
DialogueConditionYes (cmake)Add Reflect(context) to DialogueSequencerComponent
DialogueEffectYes (cmake)Add Reflect(context) to DialogueSequencerComponent
DialoguePerformanceYes (cmake)Add Reflect(context) to DialogueSequencerComponent

2.5 - Powerful Utilities

Powerful Utilities associated with the GS_Play featuresets.

Powerful Utilities Outline

Powerful Utilities

2.6 - Glossary

Descriptions and resources around common terminology used in the documentation.

GS_Play Definitions

 

Acoustic Material

A preset describing how a surface treats sound — how much it absorbs, scatters and transmits. Applied to acoustic geometry so a curtain muffles where concrete rings. (GS_AudioPro)


Action

An Action is a reusable, entity-scoped behavior triggered by name. Actions are authored as ActionComponent instances and fired via ActionRequestBus. Any trigger source — a physics zone, dialogue effect, another action, or scripted logic — can invoke an action by name without needing a direct reference to the target component.

 

Babble

Babble is procedural audio vocalisation synchronized to the Typewriter text display system in GS_Cinematics. The BabbleComponent generates tones keyed to a speaker’s identity and tone configuration, producing low-fidelity speech sounds as each character is revealed. See Babble API.

 

Controller

See Unit Controller.

 

Apply Target

What an option does to the engine when its value changes — a console variable, a window mode, an audio channel, or nothing at all. Composed onto an option separately from its widget, and the extension point for adding your own settings.


Audio Event

A named sound the game can fire. Carries its own pooling, variation, distance falloff and concurrency settings, so firing one is always just a name.


Coyote Time

Coyote Time is a grace period during which a grounded jump remains valid for a brief window after the unit walks off a ledge. It prevents the frustrating experience of pressing jump a frame too late when stepping off an edge. Configured per mover component as a float duration in seconds.

 

Custom (preset)

The active preset name when the current values no longer match any defined preset. Not a stored state — it is simply what “no preset matches” resolves to.


Debug Mode

Debug Mode is a setting on the Game Manager component that makes the game start in the current editor level instead of navigating to the title stage. All manager initialization and event broadcasting proceed normally — only the startup navigation changes. Use it for rapid iteration on any level without going through the full boot flow.

 

Headless

A manager set to drive its own startup with no Game Manager in the project. The way a single GS feature is deployed into a project that is not built on GS_Play.


Extensible

Extensible means able to be inherited from to augment and expand on the core functionality. Extensible classes and components, identified by the extensible tag in the documentation, are those available on the public Gem API build target and usable across gem and project boundaries. Extensible classes usually have ClassWizard templates to rapidly generate child classes.

 

Feedback Sequence

A Feedback Sequence is a data asset (.feedbackmotion) that drives world-space entity property animations — position, rotation, scale, material parameters — over time. Sequences are played on entities via GS_FeedbackRequestBus. Track types within a sequence are polymorphic and extensible via the FeedbackMotionTrack ClassWizard template. See Feedback System API.

 

Grounder

A Grounder is a component that detects whether a unit is standing on a surface and reports the ground normal, surface material, and grounded state. Grounders feed data to movers and to coyote time logic. The built-in GS_PhysicsRayGrounderComponent uses a downward raycast. Custom grounders can be created with the Grounder ClassWizard template. See Movement API.

 

Input Data

Input Data is a structured snapshot of a player’s current input state: movement vectors, button states, and action flags. It is held by GS_InputDataComponent on the controller entity and updated each frame by an InputReaderComponent. Input Reactor components read from this snapshot to translate intent into unit commands. See Input Data API.

 

Input Profile

An Input Profile is a named mapping between hardware input events and GS_InputData fields. Profiles are defined in the GS_Options system and can be swapped at runtime to support multiple control schemes (gamepad, keyboard/mouse, accessibility layouts). See GS_Options API.

 

Input Reactor

An Input Reactor is a component on the unit entity that reads GS_InputData from the controller and translates it into movement vectors or action bus calls. Input Reactors are the bridge between the controller’s intent and the unit’s subsystems. Custom reactors are created with the InputReactor ClassWizard template. See Input Data API.

 

Input Reader

An Input Reader is a component on the controller entity that polls hardware input events and writes them into GS_InputDataComponent. It is the hardware-facing end of the input pipeline. The built-in GS_PlayerControllerInputReaderComponent handles standard gamepad and keyboard events. Custom readers are created with the GS_InputReaderComponent ClassWizard template. See Input Data API.

 

Interface Contract

An Interface Contract is a neutral, cross-gem agreement collected in GS_Core/Interfaces so feature gems depend on the contract rather than on each other’s internals. There are three kinds: TypeBase (a pickable Strategy base — subclass and reflect to extend), Emit (a one-way *EmissionBus observation signal), and Exchange (a two-way *ExchangeBus query). See Core Interfaces.

 

Manager

A Manager is a component that extends GS_ManagerComponent and registers with the Game Manager at startup. Managers are spawned by the Game Manager, initialized in a guaranteed two-stage sequence, and made globally accessible via EBus. Each GS_Play gem that provides a system component (Save, Stage, UI, Audio, etc.) includes a manager.

 

Modular

Modular describes a system or feature that is self-contained with clear boundaries between its internal structure and external integrations. Base functionality has no outside dependencies beyond GS_Core, ensuring that when you interface with it, no additional gems are required for it to work. GS_Core itself is the sole shared dependency across all GS_Play gems. Some modular features optionally integrate with O3DE subsystems (for example, GS_UI depends on LyShine).

 

Mix Effect

One of the 22 DSP processors in GS_Audio — filters, EQ, delay, reverb, character effects and utilities. Authored as a named, ordered chain on a component, and surfaced again as graph nodes in GS_AudioPro.


Motion Composite

A Motion Composite is a named grouping of motion layers within GS_Motion that play and blend together. Composites allow multiple concurrent animations (e.g., a camera shake layered over a position tween) to be authored, started, and stopped as a unit.

 

Motion Proxy

A Motion Proxy is a lightweight target object used by GS_Motion to animate values that are not directly addressable on a component. Proxies act as intermediate containers; the animation writes into them, and a binding reads the proxy value each frame and applies it to the real target.

 

Motion Track

A Motion Track is a single animatable property lane within a Feedback Sequence or UI Animation asset. Each track drives one property (e.g., position X, material tint alpha) over time using keyframe curves. Custom track types are registered via Reflect(context) and authored with a ClassWizard template (FeedbackMotionTrack or UiMotionTrack). See Feedback API and UI Animation API.

 

Mover

A Mover is a component that implements one locomotion mode for a unit: walking, swimming, climbing, floating, and so on. Each mover processes movement input and outputs a velocity or transform delta. The active mover is selected by name at runtime. Multiple movers can coexist on one entity; only one is active at a time. Custom movers are created with the Mover ClassWizard template. See Movement API.

 

Movement Mode

A Movement Mode is the named identifier of a mover component. Units switch modes by calling SetMovementMode with the mode string defined in the mover’s Activate(). Common examples: "Walk", "Swim", "Fly".

 

MoverContext

GS_MoverContextComponent transforms raw input intent into mode-aware movement commands and manages which movement mode is active at any given moment. It holds movement profiles and routes input to the correct mover. Only one mover runs at a time — mode switches activate a different mover, not a blend of multiple. See Movement API.

 

Option Profile

An asset declaring the settings a game exposes — each with a key, a widget kind and an apply target. Multiple profiles stack and merge by key.


Polymorphic Array

The framework’s convention for an extensible type set: a vector of pointers to a reflected abstract base, where the serializer supplies the type picker. See Patterns.


Paper Performer

A Paper Performer is a billboard-based character rendering approach for 2.5D and top-down games. The PaperFacingHandlerComponent keeps a sprite quad oriented toward the active camera or a configurable facing target each tick. Custom facing strategies can be created by extending PaperFacingHandlerBaseComponent. See Paper Performer API.

 

Performer

A Performer is a character entity registered with the GS_PerformerManagerComponent. Performers are addressable by a unique name string and support skin slot swapping, locomotion hook binding, and optional head tracking. The Performer Manager is the lookup point for retrieving performer entities by name at runtime. See Performer API.

 

Phantom Camera

A Phantom Camera is a virtual camera component (GS_PhantomCameraComponent) that holds a complete camera state: FOV, clip planes, follow target, look-at target, offsets, and priority. Phantom cameras do not render — they are candidate states. The Cam Core blends the real camera toward the highest-priority phantom. Custom behavior is added by extending the component with the PhantomCamera ClassWizard template. See Phantom Cameras API.

 

Possession

Possession is the relationship between a Unit Controller and a Unit. A controller possesses a unit by calling Possess, gaining authority over its movement and actions. A unit can only be possessed by one controller at a time. Possession is released by calling DePossess. Ownership changes are observed through the GS_Core PossessionEmissionBus contract. See Unit Controllers API.

 

Pulse / Pulsor

A Pulse is a typed data payload broadcast from an emitter trigger volume into the Pulsor system. A Pulsor Emitter fires pulses when triggered; Pulsor Reactors on other entities receive pulses on a matching named channel. New pulse types are created with the PulsorPulse ClassWizard template. See Pulsors API.

 

Reactor

A Reactor is a component that receives Pulses on a named channel from the Pulsor system and executes a response. Reactors subscribe to a specific pulse type and channel name. Multiple reactors can listen on the same channel simultaneously. New reactor types are created with the PulsorReactor ClassWizard template. See Pulsors API.

 

Record / RecordKeeper

A Record is a named integer value stored persistently in the GS_Save system as a flat key-value entry. Records are used for progression flags and counters — quest state, NPC disposition, collected items, and similar global data. The RecordKeeperRequestBus provides HasRecord, SetRecord, GetRecord, and DeleteRecord. Records persist alongside the save file and survive level transitions. See GS_Save API.

 

Signal Chain

The fixed order every sound travels: emission, environment, binaural, volume channel, master. See the Audio Signal Model.


Skin Slot

A Skin Slot is one addressable mesh layer in a modular character’s appearance composition. Each slot holds an actor asset and a list of material overrides. PerformerSkinSlotComponent manages one slot; SkinSlotHandlerComponent manages the full set for a character and applies PerformerSkinSlotsConfigProfile presets. See Performer API.

 

Stage Marker

A Stage Marker is a named world-space anchor entity used by GS_Cinematics to position NPCs during dialogue sequences. A Performance navigates or teleports the assigned NPC to the marker’s transform. Markers are registered by name with the Cinematics Manager. See Cinematics Manager API.

 

Standby

Standby is a global pause broadcast to all managers and their subsystems. The Game Manager enters standby automatically during level transitions and other blocking operations, broadcasting OnEnterStandby to halt gameplay systems and OnExitStandby when the operation completes.

 

Startup Sequence

The Startup Sequence is the three-stage lifecycle the Game Manager runs before gameplay is ready: Stage 1 (Initialize) — each manager activates internally; Stage 2 (SetupManagers) — all managers connect to each other; Stage 3 (StartupComplete) — the game is fully ready. Listen to OnStartupComplete on GameManagerNotificationBus to know when it is safe to begin gameplay.

 

Timeline Expansion

Timeline Expansion refers to the extensibility layer of GS_Cinematics that allows new track and key types to be added to the dialogue sequence timeline. Custom tracks register via Reflect(context) and integrate with the sequencer’s playback engine. See Timeline Expansion API.

 

Spatialization

Everything that happens to a sound as it travels from its emission point to the listener — direction, distance, occlusion, environment. A property of the source, not an effect you add.


Trigger Channel

A Trigger Channel is the named string key used to match World Triggers to Trigger Sensors. A trigger fires events on its channel name; any sensor listening to the same channel name receives the event. Channels are free-form strings authored in the component properties.

 

Trigger Sensor

A Trigger Sensor is a component that listens on a named Trigger Channel and executes a response when an event fires on that channel. New sensor types are created with the TriggerSensor ClassWizard template. See World Triggers API.

 

Unit

A Unit is any entity possessable by a controller. The GS_UnitComponent marker transforms an ordinary entity into a unit, registering it with the Unit Manager and exposing the possession interface. The unit provides the body (movement, collision, visuals); the controller provides the brain (input or AI). See Units API.

 

Volume Channel

A named group of sound types — voice, music, environment — with its own level, feeding master. What an options menu’s audio sliders drive.


Unit Controller

A Unit Controller is a component that possesses a unit entity and drives its behavior. The base GS_UnitControllerComponent handles possession, placement helpers, and unique naming. GS_PlayerControllerComponent adds player input integration; GS_AIControllerComponent adds AI hooks. Custom controllers are created with the UnitController ClassWizard template. See Unit Controllers API.

 

World Trigger

A World Trigger is a component that fires a named event on a Trigger Channel when its activation condition is met — volume entry, exit, proximity, or custom logic. New trigger types are created with the WorldTrigger ClassWizard template. See World Triggers API.


O3DE Source Definitions

 

AZ::Component

AZ::Component is the base class for all O3DE components. All GS_Play components ultimately inherit from it, either directly or through a GS_Play base class. It provides the Activate(), Deactivate(), GetProvidedServices(), and Reflect() interface that the O3DE component system calls at runtime.

 

AZ::Data::AssetData

AZ::Data::AssetData is the base class for O3DE asset types. Custom data assets in GS_Play (such as FeedbackMotionAsset, PerformerSkinSlotsConfigProfile, save records) derive from this class and are serialized by the Asset Processor.

 

AZ::EntityId

AZ::EntityId is the unique identifier for an entity in O3DE. It is the primary address type for ById EBus calls. An invalid (null) entity ID evaluates as AZ::EntityId::IsValid() == false. Most GS_Play bus calls take an AZ::EntityId as their address.

 

Class Wizard

The Class creation utility built natively into the O3DE codebase. Use the Class Wizard to create many common classes needed for O3DE project development. GS_Play extends the Class Wizard with gem-specific templates for generating extension classes. See ClassWizard reference.

 

Component

A Component in O3DE is a discrete unit of behavior or data that lives on an Entity. Components are authored in C++ as classes inheriting from AZ::Component, reflected into the engine’s serialization and editing systems, and added to entities in the Editor. GS_Play provides pre-built component types for all its subsystems.

 

EBus

An EBus (Event Bus) is O3DE’s primary inter-component communication mechanism. It is a typed, address-routed publish/subscribe bus. “Request” buses carry method calls to a handler; “Notification” buses broadcast events to listeners. Buses can be addressed by entity ID (ById), by a custom address, or broadcast to all handlers. All GS_Play public APIs are exposed as EBus interfaces.

 

EditContext

AZ::EditContext is the O3DE reflection context that controls how a type appears in the Editor Inspector. EditContext entries define display names, category, tooltip, slider limits, and which fields are visible. It is accessed from inside Reflect(context) after the SerializeContext block. See O3DE Reflection Rules in project instructions for placement constraints.

 

Entity

An Entity in O3DE is a container of components. Entities themselves have no behavior — all behavior comes from the components attached to them. Entities are identified by an AZ::EntityId and can be loaded from prefabs, spawned at runtime via EntitySpawnTicket, or created procedurally.

 

EntitySpawnTicket

An AZ::EntitySpawnTicket is the runtime handle for a spawned entity or prefab instance in O3DE. It keeps the spawned entity alive as long as the ticket is held. Releasing the ticket destroys the spawned entities. GS_Play’s Unit Manager and Stage Manager use spawn tickets to manage spawned unit and manager entities.

 

Gem

A Gem is O3DE’s modular project extension unit — equivalent to a plugin or package. Each GS_Play subsystem is packaged as a gem (GS_Core, GS_Unit, GS_UI, etc.). Gems declare their public API via build targets, and projects opt in by listing gems in their project configuration.

 

Module Descriptor

A Module Descriptor is the O3DE gem entry point — a class derived from AZ::Module that registers component type descriptors with the engine on startup. Every GS_Play gem has a module descriptor, and the ClassWizard templates automatically add new component descriptors to it when generating extension classes.

 

O3DE

Open 3D Engine — the open-source 3D engine on which GS_Play is built. Maintained by the Linux Foundation. O3DE provides the entity-component framework, asset pipeline, physics integration (PhysX), UI system (LyShine), animation (EMotionFX), and scripting (Script Canvas, Lua) that GS_Play builds upon.

 

Prefab

A Prefab in O3DE is a reusable, nestable entity hierarchy saved as a .prefab file. Prefabs are the O3DE equivalent of Unity prefabs or Unreal Blueprints at the entity composition level. GS_Play’s Game Manager, unit templates, and manager entities are typically authored and spawned as prefabs.

 

ReflectContext

AZ::ReflectContext is the base class for all O3DE reflection contexts. The Reflect(AZ::ReflectContext* context) static method on each component is called by the engine during startup and accepts a ReflectContext that may be cast to SerializeContext, EditContext, BehaviorContext, or others. Each context controls a different aspect of how the type is surfaced to the engine.

 

SerializeContext

AZ::SerializeContext is the O3DE reflection context that controls how a type is serialized — to .prefab files, save data, and asset files. It defines which fields are persisted, the type version, and the class hierarchy. It is the first context accessed in a Reflect() implementation. Placing ->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true) in SerializeContext (not EditContext) is required for asset types to appear in the Asset Editor.

 

Templates

Templates are pre-designed packages that allow the generative creation of gems, components, classes, and file types by copying and modifying template files to meet systemic needs. In GS_Play, ClassWizard templates handle UUID generation, cmake file-list registration, and module descriptor injection automatically. See the Templates List for all available GS_Play templates.

 

TypeId / UUID

A TypeId (also called UUID in O3DE context) is the globally unique identifier assigned to every reflected C++ type. It is a 128-bit value expressed as a hex string in braces, e.g. {A1B2C3D4-...}. The ClassWizard generates a new random UUID for each class it creates. UUIDs must be unique across all gems in a project; never duplicate them. They appear in AZ_COMPONENT_IMPL and in SerializeContext ->Class<>() declarations.

2.7 - Links

Useful links.

GS_Play

LinkWhat it is
GS_Play siteProduct overview, features, and downloads
DocumentationThis site — Get Started, The Basics, Framework API, Learn
ChangelogFramework and documentation release history
SupportContact form — technical support, licensing, enterprise
LinkWhat it is
Legal & PressIndex of all licence, privacy, branding, and press material
Free Licence NoticePlain-language summary of the free-tier licence
Free EULAThe binding agreement for the free tier
PrivacyWhat GS_Play collects, and what this site records
GS_Play branding guidelinesName, logo, and colour rules
PressFact sheet, boilerplate, and what you may publish

Community

LinkWhat it is
GS_HUB DiscordLive support in #support, and the wider community
YouTube — @GS_Play_FrameworkTutorials and feature walkthroughs
r/gs_playSubreddit
BlueskyGenome Studios on Bluesky
LinkedInGenome Studios on LinkedIn
FacebookGenome Studios on Facebook

Genome Studios

LinkWhat it is
genomestudios.caStudio site and other projects
support@genomestudios.caDirect email — 1-2 business day response

O3DE

LinkWhat it is
o3de.orgThe Open 3D Engine project
O3DE documentationOfficial engine documentation
O3DE downloadsEngine installers and source
O3DE licenceApache 2.0 — the engine is governed separately from GS_Play
O3DE branding guidelinesEngine trademark and logo rules

3 - The Basics

High-level usage and scripting references for GS_Play feature sets. Drop-in functionality for intermediate developers and designers.

GS_Play is a full production set of modular features that can all be individually toggled on to enable only the features you need for your project. The “Basics” section covers what each feature set does, how to work with it from the editor and scripts, and what events and nodes are available — without requiring deep architectural knowledge.

If you are new to GS_Play, start with Get Started first. If you need architecture-level detail, component internals, or extension guides, use the Framework API section instead.


How This Section Is Organized

Each gem section follows this structure:

Gem overview page — What the gem does, the problems it solves, and a summary of its feature sets with links to feature sub-pages.

Feature sub-pages — One page per feature set. Each covers:

  • What it does and what components are involved.
  • Editor setup for drop-in use.
  • Relevant ScriptCanvas nodes and EBus events.
  • A quick reference table.
  • Links to the Framework API for deeper reference.

Pages in this section deliberately omit architecture internals, extension code, and low-level component details. If you need those, follow the Framework API links at the bottom of each page.


Sections

3.1 - GS_Core

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

GS_Core is the required foundation for every GS_Play enabled project. All other GS gems depend on it to drive their complex behaviour, and utilize its systemic features. It provides the game startup sequence, persistence, level loading, input handling, a triggerable action system, and a shared utility library.

If you have not set up GS_Core yet, start with the Simple Project Setup guide before reading further.

For architecture details, component properties, and extending the system in C++, see the GS_Core API.


Quick Navigation

I want to…FeatureAPI
Start a new game, continue from a save, load a specific file, or return to the title screenGS_ManagersAPI
Save and load game data, or track persistent flags and counters across sessionsGS_SaveAPI
Move between levels, or configure per-level spawn points and navigation settingsGS_StageManagerAPI
Give players volume, graphics and accessibility settings that save and restoreSettingsAPI
Read player input, disable input during menus, or swap control schemes at runtimeGS_OptionsAPI
Run the level you are editing, or inspect and drive a running gameDebuggingAPI
Use easing curves, detect physics zones, smooth values, pick randomly, or work with splinesUtilitiesAPI
Trigger a reusable behavior on an entity from a script, physics zone, or another actionSystems: GS_ActionsAPI
Animate a transform, color, or value smoothly over timeSystems: GS_MotionAPI

Installation

GS_Core is a required gem. It will be added to your project when you enable any other GS_Play gem.

For a complete guided setup, follow the Simple Project Setup guide or video tutorial.

Follow these steps in particular:

  1. Configure Project
  2. Prepare Managers
  3. Prepare Startup

 

Quick Installation Summary

Once the gem is registered to your project:

  1. Create a Game Manager prefab and place it in every level.
  2. Create prefabs of any managers you wish to utilize in your project
  3. Add all the manager prefabs to your GameManager Managers list.
  4. Implement a way to activate “Begin Game”
    • Create a UI to fire New Game, or Load Game.
    • Create a Script to fire New Game, or Load Game OnStartupComplete.
    • Toggle “Debug Mode” on. This skips through the begin game process.

GS_Managers

Controls the game startup lifecycle — spawning and initializing all manager systems in a guaranteed order, then providing top-level game navigation: New Game, Continue, Load Game, Return to Title, and Quit. The starting point for any game-wide behavior.

GS_Managers API


GS_Save

Handles all save and load operations, including entity state persistence across level loads and simple key-value record tracking for global flags and counters.

GS_Save API


GS_StageManager

Manages level loading and navigation. Place named Exit Points in your levels to control where the player arrives, and use Stage Data components to configure per-level settings like NavMesh references and spawn configuration.

GS_StageManager API


Settings

Declare the options your game exposes — volume, graphics quality, resolution, accessibility — as data, and the framework applies them, saves them and restores them next launch. Presets give you Low / Medium / High with no extra work.

Settings API


GS_Options

Manages player input through swappable Input Profile assets and Input Reader components, with group-level enable/disable for suppressing input during menus, cutscenes, or transitions.

GS_Options API


Debugging

Press play on the level you are editing and actually run that level, with Debug Mode. Then inspect and drive the running game from the in-game GS_Play menu — change any setting live, balance the audio mix, or switch cameras.

Debugging API


Systems

Core framework systems used across multiple gems: the GS_Actions triggerable behavior system and the GS_Motion track-based animation engine.

Systems API


Utilities

A collection of shared tools: easing curves (40+ types), spring dampers for smooth value following, weighted random selection, color and float gradients, spline helpers, and Physics Trigger Volume components.

Utilities API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.1 - Managers System

How to work with the GS_Play manager system — startup events, game navigation, and standby mode from scripts.

The Managers system is how GS_Play starts your game. The Game Manager spawns all other managers in a guaranteed order, coordinates their initialization stages, and then broadcasts events that signal when each stage is complete and when the game is fully ready to run.

This gives you the ability to ensure startup happens as expected, can create your own managers of any type, and can toggle full-game standby.

For architecture details, component properties, and extending the system in C++, see the GS_Managers API.

Game Manager component in the O3DE Inspector

 

Contents


Startup Sequence

Game Manager Startup Pattern Graph

Breakdown

When the project starts, the Game Manager runs three stages before the game is considered ready:

StageBroadcast EventWhat It Means
1 — Initialize(internal)Each manager is spawned. They activate, then report ready.
2 — SetupOnSetupManagersSetup stage. Now safe to query other managers.
3 — CompleteOnStartupCompleteLast stage. Everything is ready.
Do any last minute things. Now safe to begin gameplay.

For most scripts, you only need OnStartupComplete. Wait for this event before doing anything that depends on managers to be completely setup.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Headless Managers

There are two ways to run a manager, and the startup sequence above is only one of them.

Under a Game Manager, managers are spawned and coordinated as a stack. Headless is the alternative: tick the checkbox on a manager and it drives its own startup instead — no Game Manager anywhere in the project, no startup sequence to join.

  1. Add the manager component to an entity in your level.
  2. Tick Headless in the Entity Inspector.
  3. Press play. The manager sets itself up and reaches startup-complete on its own.

This is how you deploy a single GS feature into a project that is not built on GS_Play. A project that wants GS_Audio’s audio system and nothing else adds the Audio Manager, ticks Headless, and has working audio — no manager stack, no game-flow layer, no dependency on the Game Manager’s startup. The same applies to a project that already has its own lifecycle, title flow and save system: those stay where they are, and the GS manager simply runs.

It also covers adopting the framework in stages — take one manager headless now, and switch it onto a Game Manager later if the project grows into the full lifecycle.

A headless manager does not report to the Game Manager, so it stays out of the startup count entirely. You can run one alongside a Game Manager that is coordinating everything else without the two interfering.

Two things to know when you go headless. A headless manager starts as soon as it activates, so the guarantee that every manager is initialized before any manager reaches setup does not apply — if you run several headless managers that depend on each other, that sequencing is yours to arrange, and a Game Manager is usually the better answer. And OnBeginGame is never self-fired: reaching startup-complete says the manager is ready, while saying the game has begun stays your project’s call.


Responding to Startup

ScriptCanvas

Connect to GameManagerNotificationBus and handle OnStartupComplete to know when the game is fully ready:

To check at any point whether the game has already finished starting, use the IsStarted request:


Game Navigation

The Game Manager owns the top-level game flow. Call these from title screens, pause menus, and end-game sequences. They coordinate the Save Manager and Stage Manager automatically.

ScriptCanvas NodeWhat It Does
TriggerNewGameStarts a new game with the default save name.
TriggerNewGameWithName(saveName)Starts a new game and writes to a named save file.
TriggerContinueGameLoads the most recent save and continues from it.
TriggerLoadGame(saveName)Loads a specific save file by name.
TriggerReturnToTitleReturns to the title stage, tearing down the current session.
TriggerSaveAndExitGameSaves the current state and exits the application.
TriggerExitGameExits the application without saving.

Standby Mode

Standby is a global pause. The Game Manager enters standby automatically during level transitions and other blocking operations. It broadcasts OnEnterStandby to halt all gameplay systems, and OnExitStandby when the operation completes.

Listen to these in any script that drives continuous logic — timers, ticks, or animation sequences:

EventWhat to Do
OnEnterStandbyPause timers, halt ticks, stop animations.
OnExitStandbyResume timers, re-enable ticks.

Both events are on GameManagerNotificationBus.


Debug Mode

When Debug Mode is enabled on the Game Manager component in the editor, the game starts in the current level instead of navigating to your title stage. This allows rapid iteration on any level without going through the full boot flow.

Debug Mode only changes startup navigation. All manager initialization and event broadcasting proceed normally.


Quick Reference

NeedBusMethod / Event
Know when startup is completeGameManagerNotificationBusOnStartupComplete
Know when the game itself beginsGameManagerNotificationBusOnBeginGame
Deploy a manager with no Game Manager(component property)Headless checkbox
Check if game has startedGameManagerRequestBusIsStarted
Start a new gameGameManagerRequestBusNewGame / TriggerNewGame (SC)
Continue from last saveGameManagerRequestBusContinueGame / TriggerContinueGame (SC)
Load a specific saveGameManagerRequestBusLoadGame / TriggerLoadGame (SC)
Return to titleGameManagerRequestBusReturnToTitle / TriggerReturnToTitle (SC)
Pause all systemsGameManagerRequestBusEnterStandby
Resume all systemsGameManagerRequestBusExitStandby
Know when standby changesGameManagerNotificationBusOnEnterStandby / OnExitStandby

Glossary

TermMeaning
StandbyGlobal pause broadcast to all managers and their subsystems
Startup SequenceThe three-stage lifecycle (Initialize → SetupManagers → StartupComplete) before gameplay is ready
ManagerA component that extends GS_ManagerComponent and registers with the Game Manager
HeadlessA manager that drives its own startup, deployed with no Game Manager in the project
Debug ModeStarts the game in the current editor level instead of navigating to the title stage

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.2 - Save System

How to work with the GS_Play save system — saving game state, loading saves, and tracking progression with the Record Keeper.

The Save system handles all persistence in a GS_Play project. The Save Manager coordinates file operations, Savers serialize per-entity state, and the Record Keeper tracks flat progression data. Together they give you a complete save/load pipeline that works out of the box and extends cleanly for custom data.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Save Manager component in the O3DE Inspector

 

Contents


How Saving Works

Save System Pattern Graph

Breakdown

When a save is triggered, the Save Manager broadcasts OnSaveAll to every Saver component in the scene. Each Saver serializes its entity’s relevant state into the save file. When loading, the Save Manager broadcasts OnLoadAll, and each Saver restores its entity from the save data.

The Save Manager also maintains a list of all save files with metadata (timestamps, names), so you can present a save/load UI to the player.

OperationWhat Happens
New saveCreates a new save file, broadcasts OnSaveAll to all Savers.
Load saveReads save file, broadcasts OnLoadAll to all Savers.
Save dataWrites current game state to the active save file.
Load dataReads data from the active save file into memory.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Responding to Save Events

ScriptCanvas

Connect to SaveManagerNotificationBus to know when save or load operations occur:


Triggering Saves and Loads

These methods are available on SaveManagerRequestBus:

ScriptCanvas NodeWhat It Does
NewGameSaveCreates a fresh save file for a new game.
LoadGame(saveName)Loads a specific save file by name.
SaveDataWrites current state to the active save file.
LoadDataReads the active save file into memory.
GetOrderedSaveListReturns all save files sorted by most recent.
ConvertEpochToReadable(epoch)Converts a save file timestamp to a human-readable string.

Record Keeper

The Record Keeper is a lightweight key-value store for tracking game-wide progression — quest flags, counters, unlock states, completion markers. It lives on the Save Manager prefab and is automatically persisted with the save system.

Unlike Savers (which are per-entity), the Record Keeper is a global singleton. Any script or component can read and write records by name.

ScriptCanvas NodeWhat It Does
HasRecord(name)Returns whether a record with the given name exists.
SetRecord(name, value)Creates or updates a record. Value is a float.
GetRecord(name)Returns the current value of a record.
DeleteRecord(name)Removes a record.

Responding to Record Changes

Listen on RecordKeeperNotificationBus for the RecordChanged event. This fires whenever any record is created, updated, or deleted — useful for UI that displays progression state.


Built-In Savers

Two Savers ship with GS_Core for the most common use cases:

SaverWhat It Saves
BasicEntitySaverEntity transform (position, rotation, scale).
BasicPhysicsEntitySaverEntity transform plus rigidbody velocity and angular velocity.

Add these components to any entity that needs to persist its position across save/load cycles. They handle serialization and restoration automatically.


Quick Reference

NeedBusMethod / Event
Trigger a saveSaveManagerRequestBusSaveData
Trigger a loadSaveManagerRequestBusLoadGame(saveName)
Create a new saveSaveManagerRequestBusNewGameSave
List all savesSaveManagerRequestBusGetOrderedSaveList
Know when savingSaveManagerNotificationBusOnSaveAll
Know when loadingSaveManagerNotificationBusOnLoadAll
Check a progress flagRecordKeeperRequestBusHasRecord(name) / GetRecord(name)
Set a progress flagRecordKeeperRequestBusSetRecord(name, value)
Know when a record changesRecordKeeperNotificationBusRecordChanged

Glossary

TermMeaning
SaverA component that serializes one entity’s state into the save file
Record KeeperA global key-value store for tracking progression flags and counters
Save FileA serialized snapshot of all Saver data plus Record Keeper state

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.3 - Stage Management

How to work with the GS_Play stage management system — level loading, stage transitions, exit points, and stage data.

The Stage Manager handles all level-to-level navigation in a GS_Play project. It owns the master list of stages, processes transition requests, and coordinates with the Game Manager’s standby mode to ensure clean load/unload cycles. Stage Data components in each level control how that level initializes.

For architecture details, component properties, and extension patterns, see the Framework API reference.

Stage Manager component in the O3DE Inspector

 

Contents


How Stage Transitions Work

Stage Change Pattern Graph

Breakdown

When you request a stage change, the system follows this sequence:

StepWhat Happens
1 — StandbyThe Game Manager enters standby, pausing all gameplay systems.
2 — UnloadThe current stage’s entities are torn down.
3 — SpawnThe target stage’s prefab is instantiated.
4 — Set UpThe Stage Data component in the new level runs its layered startup sequence.
5 — CompleteThe Stage Manager broadcasts LoadStageComplete. Standby exits.

The Stage Data startup is layered — SetUpStage, ActivateByPriority, then Complete — so heavy levels can initialize incrementally without causing frame-time spikes.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Stage Data

Stage Data component in the O3DE Inspector

Each level should have a Stage Data component as its root entity. Using a start inactive child “Level” entity, the Stage Data system controls the initialization sequence for that new level. Stage Data holds level-specific configuration, and scripts.

EventWhat It Means
OnBeginSetUpStageThe level is starting its setup. Initialize per-level systems.
ActivateByPriorityActivate heavy entities in priority order (lazy loading).
OnLoadStageCompleteThe level is fully loaded and ready.
OnTearDownStageThe level is being unloaded. Clean up per-level state.

Listen to these on StageDataNotificationBus in any script that needs to react to level lifecycle.


Triggering Stage Changes

ScriptCanvas

The exitPointName parameter is optional. If provided, the system will position the player at the named exit point in the target level.


Exit Points

Stage Exit Point component in the O3DE Inspector

Exit Points are named position markers placed in a level. They define where entities spawn when arriving from another stage. A door in Level A can specify that when transitioning to Level B, the player should appear at Exit Point “DoorB_Entry”.

Exit Points are registered and unregistered with the Stage Manager automatically when they activate and deactivate.

ScriptCanvas NodeWhat It Does
ChangeStageRequest(stageName, exitPoint)Transitions to a stage and positions at the named exit point.
RegisterExitPoint(name, entity)Manually registers an exit point (usually automatic).
UnregisterExitPoint(name)Manually unregisters an exit point.
GetExitPoint(name)Returns the entity ID of a registered exit point.


Responding to Stage Events

ScriptCanvas


Entity Level Configuration

Stage Data entity level setup in the O3DE Editor

The Stage Data entity must live outside of the levels Game Manager prefab. It is left active.

Inside, it has a secondary level “wrapper” entity that you set to “Start Inactive” by default. This enables the Stage Data to control exactly when the level begins loading.


Quick Reference

NeedBusMethod / Event
Change to a different levelStageManagerRequestBusChangeStageRequest(stageName, exitPoint)
Load the default stageStageManagerRequestBusLoadDefaultStage
Know when a load startsStageManagerNotificationBusBeginLoadStage
Track loading progressStageManagerNotificationBusStageLoadProgress
Know when a load finishesStageManagerNotificationBusLoadStageComplete
React to level setupStageDataNotificationBusOnBeginSetUpStage
React to level teardownStageDataNotificationBusOnTearDownStage
Find an exit pointStageManagerRequestBusGetExitPoint(name)

Glossary

TermMeaning
StageA spawnable prefab representing a game level or screen
Exit PointA named position marker in a level that defines where entities arrive from another stage
Stage DataA per-level component that controls level initialization and holds level-specific settings
Default StageThe first stage loaded on application start (typically the title screen)

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.4 - Options & Input

How to work with the GS_Play options system — player settings with profiles and presets, plus input profiles, groups and runtime binding management.

The Options system covers two things a project needs and rarely wants to write itself:

  • Settings — the options a player changes: volume, graphics, resolution, accessibility. You declare them as data and the framework applies, saves and restores them.
  • Input — group-based input binding that can be rebound and toggled at runtime without code changes.

For architecture details, component properties, and extension patterns, see the Framework API reference.

Options Manager component in the O3DE Inspector

 

Contents


Options Manager

The Options Manager is the singleton that owns both halves of this system. It holds your option profiles and presets, and the active Input Profile. It responds to the Game Manager lifecycle automatically.

Assign three things to it: your option profiles, any presets, and your input profile.


Settings

Settings are the options a player changes — volume, graphics, resolution, accessibility. You declare them as data in an Options Profile asset, and the framework applies, saves and restores them.

Settings API


Input Profiles

Input Profile asset in the O3DE Asset Editor

An Input Profile is a data asset created in the O3DE Asset Editor. It contains named input groups, each holding a set of event mappings. Each event mapping binds a gameplay event name to one or more raw input bindings (key presses, axis movements, button presses) with configurable deadzones.

The key advantage over raw O3DE input bindings is the group system. Groups can be enabled and disabled independently at runtime — a pause menu can suppress gameplay input by disabling the “Gameplay” group, without tearing down and rebuilding bindings.


Enabling and Disabling Input Groups

ScriptCanvas

Enabling, Disabling, and Checking State of Input Groups in Script Canvas


How Input Flows

StageWhat Happens
1 — Raw InputO3DE’s input system captures key/axis/button events.
2 — Input ReaderThe GS_InputReaderComponent on the entity matches raw input against the active Input Profile’s event mappings.
3 — Event MappingMatched input fires a named gameplay event (e.g., “Jump”, “MoveForward”).
4 — ConsumerOther components on the entity (controllers, reactors) handle the gameplay event.

Input Readers filter by group — if a group is disabled, none of its event mappings fire.


Quick Reference

NeedBusMethod / Event
Disable an input groupInputReaderRequestBusDisableInputGroup(groupName)
Enable an input groupInputReaderRequestBusEnableInputGroup(groupName)
Check if group is disabledInputReaderRequestBusIsGroupDisabled(groupName)
Get the active profileOptionsManagerRequestBusGetActiveInputProfile

Glossary

TermMeaning
Input ProfileA data asset containing named input groups with event mappings
Input GroupA named collection of event mappings that can be enabled or disabled at runtime
Event MappingA binding from a gameplay event name to one or more raw input sources
Input ReaderA component that matches raw input against the active Input Profile

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.4.1 - Settings

How to declare player settings as data, read and write them from your game, and offer quality presets.

Settings are the options a player changes — volume, graphics quality, resolution, accessibility choices. GS_Play lets you declare them as data and handles the rest: applying each one to the engine, saving them, and restoring them next launch.

You do not write loading code, saving code, or apply code. You author what the options are and what each one drives.

For architecture details and the full API, see the Framework API reference.

 

Contents


Declaring Settings

Settings are authored as data, not code. Create an Options Profile asset and add an option for each thing your game exposes.

Every option carries:

FieldWhat it is
KeyThe name your code and presets use, e.g. audio.master.
Display NameWhat the player sees.
CategoryWhich page it belongs on — Graphics, Audio, Display, Gameplay, Accessibility, Controls.
DescriptionTooltip text.
Apply targetWhat it actually does when changed.

Pick the option kind for the widget you want — a toggle, a slider with min/max/step, a dropdown with labelled entries, or a colour picker.

The apply target is the part worth pausing on. It is what connects the option to the engine: a console variable, the window mode, the resolution, an audio channel’s volume — or nothing at all, if the value is just something your own game code reads.

You can assign more than one profile. They merge by key, so a project can layer its own options over a supplied set.


Reading and Writing Settings

Setting a value applies it, saves it and notifies listeners in one call. You never have to apply or save separately.

NeedMethod
Read a valueGetOptionBool / GetOptionFloat / GetOptionInt / GetOptionString
Change a valueSetOptionBool / SetOptionFloat / SetOptionInt / SetOptionString
Restore one optionResetToDefault
Restore everythingResetAllToDefault
Get every option in a categoryGetOptionsByCategory
React to any changeOnOptionChanged on the notification bus

GetOptionsByCategory is how you build a settings screen without hard-coding it — ask for the Graphics options and build a row per key.

A tip worth taking: the ImGui debug menu renders your entire option catalog live, with the right widget for each option. Author an option, press play, and drive it before you build any UI for it.


Presets

A preset is a named set of values — Low, Medium, High. Create a preset asset, list the keys it sets and the values it sets them to, and assign it to the Options Manager.

Applying a preset overwrites those options, applies and saves in one action.

Presets have no state of their own. A preset counts as active only while every value it names still matches, so the moment a player nudges one slider, the active preset becomes Custom. That is the familiar behaviour, and it happens for free.


Quick Reference

NeedBusMethod / Event
Read a settingOptionsManagerRequestBusGetOptionFloat / Bool / Int / String
Change a settingOptionsManagerRequestBusSetOptionFloat / Bool / Int / String
Restore one settingOptionsManagerRequestBusResetToDefault
Restore everythingOptionsManagerRequestBusResetAllToDefault
List a category’s optionsOptionsManagerRequestBusGetOptionsByCategory
Apply a presetOptionsManagerRequestBusApplyPreset
Know which preset is activeOptionsManagerRequestBusGetActivePresetName
React to a setting changeOptionsManagerNotificationBusOnOptionChanged

Glossary

TermMeaning
Options ProfileAn asset declaring the settings a game exposes
Apply TargetWhat an option does to the engine when its value changes
KeyThe stable name a setting is stored and referred to by
PresetA named set of values, e.g. Low / Medium / High
CustomThe active preset name when values no longer match any preset

See Also

For the full API:

For related systems:


Get GS_Core

GS_Core - Explore this gem on the product page and add it to your project.

3.1.5 - Systems

Core framework systems — the GS_Actions triggerable behavior system and the GS_Motion track-based animation engine.

GS_Core provides a growing number of standalone systems that are used across multiple gems. Currently, the GS_Motion track-based animation engine powers UIAnimation and Juice Feedback playback.

For architecture details, component properties, and C++ extension guides, see the Framework API: Systems.

 

Contents


GS_Motion

Provides tween-style Motion Track components for animating transforms, colors, and values over time. Multiple tracks on the same entity run in parallel; chains are configured by setting an On Complete motion name.

GS_Motion API


See Also

For the full API, component properties, and C++ extension guides:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.5.1 - Actions System

How to work with the GS_Play action system — triggerable, composable behaviors that fire from scripts, triggers, or code.

The Actions system provides a universal pattern for attaching discrete, reusable behaviors to entities and triggering them from any source — ScriptCanvas, World Triggers, UI buttons, or C++ code. Actions are data-driven components that fire on named channels, enabling composition without custom scripting.

For architecture details, component properties, and creating custom actions in C++, see the Framework API reference.

 

Contents


How Actions Work

An Action is a component you attach to an entity. Each Action has a channel name. When something calls DoAction(channelName) on that entity’s bus, every Action component whose channel matches the name will execute.

This decoupling is the core value — the system that fires DoAction does not need to know what kind of action is attached. You can change, add, or remove action components on an entity without modifying any calling code.

ConceptWhat It Means
ChannelA named string. Actions on the same channel fire together.
CompositionMultiple actions on the same channel execute in parallel — stack components to compose behaviors.
ChainingAn action can fire a different channel on completion, enabling lightweight sequences.

Triggering Actions

ScriptCanvas

[ActionRequestBus → DoAction(channelName)]
    └─► All Action components on this entity with matching channel execute

To know when an action completes:

[ActionNotificationBus → OnActionComplete]
    └─► Action has finished executing

Built-In Actions

GS_Core ships with these ready-to-use actions:

ActionWhat It Does
PrintLogLogs a configurable message to the console. Useful for debugging trigger chains.
ToggleMouseCursorShows or hides the system mouse cursor.

Additional actions are available in other gems (e.g., World Trigger actions in GS_Interaction, dialogue effects in GS_Cinematics).


Common Patterns

World Trigger → Action

A World Trigger detects a collision or interaction event and fires DoAction on its entity. Action components on the same entity respond — one might play a sound, another might set a record, another might toggle an entity.

UI Button → Action

A UI button press fires DoAction with a channel name. Actions handle the response — navigate to a different UI page, start a new game, or toggle the pause menu.

Chaining Actions

Set an action’s “Chain Channel” property to fire a different channel when it completes:

Channel "OpenDoor" → [ToggleEntity action] → chains to "PlayDoorSound" → [AudioEvent action]

Quick Reference

NeedBusMethod / Event
Fire an actionActionRequestBusDoAction(channelName)
Know when an action completesActionNotificationBusOnActionComplete

Glossary

TermMeaning
ActionA component that executes a discrete behavior when triggered on a named channel
ChannelA named string identifier that groups actions — all actions on the same channel fire together
ChainingConfiguring an action to fire a different channel on completion, creating lightweight sequences

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.5.2 - Motion System

How to work with GS_Motion — the track-based animation and tween system that powers UI transitions, feedback effects, and custom animations.

GS_Motion is a track-based animation engine built into GS_Core. It drives timed property changes — position, rotation, scale, color, opacity — through authored data assets rather than hand-coded interpolation scripts. Domain gems extend GS_Motion with their own track types: GS_UI adds 8 LyShine-specific tracks for UI animation, and GS_Juice adds transform and material tracks for game feel feedback.

For architecture details, the domain extension pattern, and all track types, see the Framework API reference.

 

Contents


Key Concepts

ConceptWhat It Is
TrackA single animated property — what changes, how long, and which easing curve.
MotionA collection of tracks that play together. Tracks can start at different times within the motion.
Motion AssetA data asset authored in the O3DE Asset Editor containing the tracks and their configuration.
ProxyAn optional entity redirect — lets a track target a child entity instead of the motion’s owner.
CompositeThe runtime instance created from an asset. Each entity gets its own deep copy.

How It Works

  1. Author a motion asset in the Asset Editor. Each domain has its own asset type (.uiam for UI, .feedbackmotion for Juice).
  2. Assign the asset to a component or embed it in a serialized field (e.g., a page’s show/hide transitions).
  3. Play the motion from ScriptCanvas or C++. The system initializes a runtime composite, resolves proxies, and ticks all tracks.
  4. Each track receives an eased progress value (0 → 1) every frame and applies its property change to the target entity.
  5. When all tracks complete, the motion fires its OnComplete callback.

Easing Curves

Every track can use any of the 40+ easing curves from the GS_Core curves library. Curves are configured per-track in the asset editor.

Available families: Linear, Quad, Cubic, Sine, Expo, Circ, Back, Elastic, Bounce — each with In, Out, and InOut variants.


Proxy Targeting

UiAnimationMotion with proxy bindings configured in the Inspector

When a motion asset has tracks with identifiers (named labels), those tracks appear in the proxy list on the component. Proxies let you redirect a track to a different entity in the hierarchy — for example, a page show animation might animate the background separately from the content panel.

Each proxy entry maps a track label to a target entity. If no proxy is set, the track targets the motion’s owner entity.


Domain Extensions

GS_Motion is not used directly — it provides the base system that domain gems extend with concrete track types.

UI Animation (GS_UI)

Eight tracks for LyShine UI elements (position, scale, rotation, alpha, color, text). Asset extension: .uiam. Used for page transitions, button hover/select effects, and standalone UI animation.

UI Animation API


Feedback Motions (GS_Juice)

Two tracks for game feel effects — transform (position, scale, rotation) and material (opacity, emissive, color tint). Asset extension: .feedbackmotion. Used for screen shake, hit flash, and visual feedback.

Feedback Motions API


Quick Reference

NeedWhere
Animate UI elementsUse .uiam assets with UiAnimationMotionComponent or page transitions
Create feedback effectsUse .feedbackmotion assets with FeedbackEmitter component
Change easing curveEdit the curve type on individual tracks in the asset editor
Redirect a track to another entityConfigure proxy entries on the component
Loop an animationEnable loop on the motion asset

Glossary

TermMeaning
TrackA single animated property within a motion — defines what changes, duration, and easing
MotionA collection of tracks that play together as a single animation
Motion AssetA data asset authored in the Asset Editor containing track configurations
ProxyAn entity redirect that lets a track target a child entity instead of the motion’s owner
CompositeThe runtime instance created from a motion asset — each entity gets its own deep copy
Domain ExtensionA gem-specific set of track types that extends GS_Motion for a particular use case

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.6 - Utilities

General-purpose components and math helpers — physics triggers, easing curves, spring dampers, gradients, splines, and more.

GS_Core includes a library of general-purpose components and math helpers available to every system in the framework. These utilities handle common game development tasks — physics overlap detection, value interpolation, animation curves, gradient sampling, and spatial math — so you can focus on gameplay logic rather than reimplementing fundamental patterns.

For full API details and code examples, see the Framework API reference.

 

Contents


Physics Trigger Volume

A reusable physics overlap detector. Handles trigger enter, stay, and exit events with filtering and callback support. Used internally by Pulsors, Targeting Fields, and World Triggers — and available for your own components.

Physics Trigger Volume API


Easing Curves

40+ easing functions organized into families. Every GS_Motion track, spring, and interpolation system in the framework can reference these curves by enum.

FamilyVariants
LinearLinear
QuadIn, Out, InOut
CubicIn, Out, InOut
SineIn, Out, InOut
ExpoIn, Out, InOut
CircIn, Out, InOut
BackIn, Out, InOut
ElasticIn, Out, InOut
BounceIn, Out, InOut

Select a curve type via the CurveType enum in component properties, asset fields, or C++ code.

Curves API


Spring Dampers

15+ spring functions for physically-grounded value interpolation. Springs produce natural-feeling motion that reacts to velocity and acceleration, making them ideal for camera smoothing, UI follow, and any value that should “settle” rather than snap.

FunctionUse Case
Simple SpringBasic spring with damping
Acceleration SpringSpring with acceleration bias
Double SpringTwo-stage spring for overshoot effects
Timed SpringSpring that reaches target in a fixed time
Velocity SpringSpring driven by velocity
Quaternion SpringSpring for rotation values

Springs API


Gradients

Multi-stop gradient types for sampling values over a range. Used extensively by GS_Motion tracks and GS_Juice feedback tracks to define animation curves.

TypeWhat It Samples
ColorGradientRGBA color with positioned markers
FloatGradientSingle float value
Vector2Gradient2D vector

Gradients are editable in the Inspector with visual marker placement.

Gradients API


Entity Helpers

Utility functions for finding entities in the scene by name.

FunctionWhat It Does
GetEntityByName(name)Returns the entity with the given name.
GetEntityIdByName(name)Returns the EntityId of the named entity.

Entity Helper API


Weighted Random

Template-based weighted random selection. Given a collection of items with weights, returns a randomly selected item biased by weight. Useful for loot tables, dialogue variation, and procedural placement.

GS_Random API


Angle Helpers

Functions for angle and orientation math, plus 22 preset section configurations for direction classification.

FunctionWhat It Does
YawFromDir(direction)Extracts yaw angle from a direction vector.
QuatFromYaw(yaw)Creates a quaternion from a yaw angle.
PickByAngle(angle, sections)Maps an angle to a section index using a preset configuration.

Section presets range from 2-section (left/right) to 16-section (compass-style), plus diagonal and cardinal configurations. Useful for animation direction selection and 2D-style facing.

Angle Helper API


Spline Helpers

Utility functions for working with O3DE spline components.

FunctionWhat It Does
FindClosestWorldPoint(spline, point)Returns the closest point on the spline in world space.
FindClosestLocalPoint(spline, point)Returns the closest point in local space.
FindClosestFraction(spline, point)Returns the 0–1 fraction along the spline.

Spline Helper API


Serialization Helpers

Utility functions for common O3DE serialization patterns. Simplifies working with SerializeContext and EditContext in component reflection.

Serialization Helper API


Common Enums

Shared enumeration types used across the framework.

Common Enums API


Glossary

TermMeaning
Easing CurveA function that maps linear progress (0→1) to a shaped output for smooth animation
Spring DamperA physically-modeled interpolation function that settles naturally toward a target
GradientA multi-stop sampler that returns interpolated values (color, float, vector) over a 0→1 range
Physics Trigger VolumeA reusable overlap detector that fires enter, stay, and exit callbacks

For full definitions, see the Glossary.


See Also

For the full API, component properties, and code examples:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.1.7 - Debugging

How to run a level directly with Debug Mode, and how to inspect and drive a running game with the in-game GS_Play menu.

Two things make working on a GS_Play project faster: Debug Mode, which lets you press play on the level you are editing instead of booting the whole game, and the in-game GS_Play menu, which lets you see and change what the framework is doing while it runs.

For architecture details and the extension guide, see the Framework API reference.

 

Contents


Debug Mode

Tick Debug Mode on your Game Manager and pressing play runs this level, right now.

Without it, a project boots the way a shipped game does: it starts up, waits for the game to be begun, then loads the configured starting stage and navigates away from whatever you were editing.

With it, three things change:

SystemNormallyIn Debug Mode
Game ManagerWaits for your title flow to begin the gameBegins the game as soon as startup completes
Stage ManagerLoads the project’s starting stageStays in the current level, if it has a Stage Data component
Unit ManagerWaits for the game flow to place the playerPlaces the player at the debug exit point

The net effect is that you get a running, playable level without a title screen or a stage transition.


The In-Game Menu

Run the game with ImGui enabled and there is a GS_Play entry in the menu bar. Each gem contributes its own section.

MenuWhat you can do
Core ▸ OptionsSee every setting your project declares, grouped by category, and change any of them live. Apply, save, reset individually or all at once, and try presets.
Core ▸ Game ManagerInspect the game manager, and exit the application.
PhantomCam ▸ Camera ManagementPick the active camera, force one to the front, and edit its lens live.
Audio ▸ Audio MixerMaster volume plus a slider for every volume channel.

The Options window is the fastest way to test a new setting. Author the option, press play, and drive it from the menu — before writing any menu UI of your own. Every edit goes through the same bus your UI will use, so if it works there it will work in your game.

The Audio Mixer is similarly useful for balancing: run the game, play the sounds, and move the channel sliders until the mix is right.


Making a Manager Talk

Every manager has a Logging Depth property. Set it above zero and that manager reports its lifecycle as it activates, sets up and shuts down.

This is per-manager on purpose. When you are diagnosing one system’s startup, turn it up on that manager rather than making the whole framework verbose.


Quick Reference

NeedWhere
Run the current level without booting the gameDebug Mode on the Game Manager
Change a setting liveGS_Play ▸ Core ▸ Options
Balance the audio mixGS_Play ▸ Audio ▸ Audio Mixer
Switch or tune a camera liveGS_Play ▸ PhantomCam ▸ Camera Management
See one manager’s startupLogging Depth on that manager
Check debug mode from scriptGameManagerRequestBus ▸ In Debug

Glossary

TermMeaning
Debug ModeA Game Manager property that begins the game immediately and keeps you in the current level
Stage DataThe per-level component that lets the Stage Manager treat a level as a stage
Logging DepthA per-manager verbosity setting for lifecycle logging

See Also

For the full API and the extension guide:

For related systems:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

3.2 - GS_AI

Foundation scaffold for AI-driven entity behavior in the GS_Play framework.

GS_AI provides the structural foundation for AI-driven entity behavior. It defines the base architecture that AI controller implementations build upon, integrating with the Unit system’s controller pattern to drive NPC entities through behavior logic rather than player input.

For architecture details and extension patterns, see the GS_AI API.


How It Works

GS_AI establishes the conventions and base classes for AI in the GS_Play framework. AI controllers extend the Unit Controller pattern — the same mechanism that player controllers use to drive units. Switching an entity between player control and AI control is a standard possession swap with no special handling required.

AI implementations listen to the same game lifecycle events as every other GS_Play system. AI controllers respond to standby mode, stage transitions, and manager lifecycle broadcasts automatically through the manager pattern.


Integration Points

SystemHow AI Connects
Unit ControllersAI controllers extend the unit controller pattern to drive NPC movement and actions.
GS_ManagersAI systems respond to startup, standby, and shutdown lifecycle events.
InteractionAI entities can be targeting targets, trigger world triggers, and emit/receive pulsors.
CinematicsThe Cinematic Controller (in GS_Complete) demonstrates AI-to-cinematic handoff.

See Also

For the full API and extension patterns:

For related systems:


Get GS_AI

GS_AI — Explore this gem on the product page and add it to your project.

3.3 - GS_Audio

Sound for GS_Play — audio events, mixing and effects, spatial audio, and the premium Audio Pro tier with visual graph authoring, music scoring and voice synthesis.

GS_Audio is the audio system for GS_Play. It plays sounds, mixes them, and places them in the world — and it comes in two gems.

GemTierWhat you get
GS_AudioFreeThe audio engine, audio events with pooling and spatial playback, mixing channels, the full effect palette, and HRTF spatial audio with distance falloff.
GS_AudioProPremiumEverything above, plus the visual Audio Event Graph, procedural music scoring, Klatt voice synthesis, and geometry-driven occlusion and reverb.

The dividing line is how you author, not how good the audio is. The free gem carries the complete effect palette and the same spatial audio Pro uses. Pro adds a visual canvas to build sounds on, plus music, speech and real acoustics.

Each feature below is tagged Base or Pro so you can tell which gem it needs.

For architecture details, component properties, and extension patterns, see the GS_Audio API.


Start Here

If you are new to the audio system, read the Audio Signal Model first. It is short, and it explains how a sound travels from its source to your speakers — which stages exist, what order they run in, and what the words source, spatialization and volume channel mean here.

Two things from it save the most time:

  • Spatialization is a property of the sound, not an effect you add. Position a source and it is spatialized. There is no spatializer to drop into a chain.
  • Reverb and real geometric reflections are alternatives, not layers. Use one or the other, never both, or the sound double-reverbs.

Quick Navigation

I want to…TierFeatureAPI
Manage the audio engine, set the listener, or control master volumeBaseAudio ManagerAPI
Play sounds by name, with variation, distance falloff and concurrency limitsBaseAudio EventsAPI
Make procedural sound effects with no audio filesBaseSFXR ProfilesAPI
Configure mixing channels with filters, EQ, and environmental effectsBaseMixing & EffectsAPI
Position sounds in the world and set where the player hears fromBaseSpatial AudioAPI
Author sound events visually with filters, effects, routing and phasesProAudio Event GraphAPI
Layer music tracks dynamically based on gameplay stateProScore ArrangementAPI
Generate speech with configurable voice parameters and 3D audioProKlatt VoiceAPI
Have walls actually block sound, and get reverb from the real roomProGeometric AcousticsAPI

Installation

GS_Audio requires GS_Core and the MiniAudio gem. Add all three to your project’s gem dependencies.

Quick Installation Summary

  1. Enable the GS_Audio gem in your project configuration.
  2. Create an Audio Manager prefab and add it to the Game Manager’s Managers list.

No Game Manager in your project? Add the Audio Manager to your level and tick Headless on it. It will start itself, and you get the audio system with no dependency on the GS_Play startup sequence — useful when audio is the only GS feature you want.

Adding the premium tier

Enable GS_AudioPro alongside GS_Audio and use the Pro Audio Manager component on your manager prefab instead of the base one. Everything else is authored on that component exactly as before.


Audio Manager

Base. The Audio Manager is the singleton controller for the whole audio system. It initializes the engine, owns the mixing channels, loads your audio event libraries, and carries the listener.

The listener is worth knowing about early: there is no separate listener component. The manager holds it and you point it at an entity. You can also split it — proximity following your character while facing follows your camera — which is the recommended setup for third-person games.

Audio Manager API


Audio Events

Base. A named sound your game can fire. Author events in a library asset, assign the library to the Audio Manager, then play sounds by name from script, C++ or a trigger.

Variation, distance falloff, concurrency caps and entity tracking are all configured on the event — the call site is always just a name. Events can play recorded clips, procedural noise, or chiptones, and clip pools mean a footstep never repeats identically.

Audio Events API


SFXR Profiles

Base. Sound effects made entirely of numbers — coins, lasers, jumps, blips — with no recording and no audio files. Design one in the browser, paste it into an asset, and pool a few mutations so the sound varies.

27 profiles ship with the gem, so a new project has working effects immediately.

SFXR Profiles API


Mixing & Effects

Base. Named mixing channels, each with its own volume and an optional chain of effects — filters, EQ, shelving, delay, reverb, and character effects like chorus, flanger and bit-crushing.

Environmental influence lets a world volume push effects onto a channel while the listener is inside it: walk into a cave, everything gains reverb; walk out and it lifts. Influences stack by priority and are keyed to the entity that applied them.

Mixing & Effects API


Spatial Audio

Base. Positioned sounds are placed in the world automatically — direction, distance falloff, and HRTF processing so a sound feels like it comes from somewhere. There is nothing to add to a sound to make it happen.

The one thing you configure is the listener. In third person you split it: distance measured from your character, direction from your camera.

Spatial Audio API


Audio Pro

Pro. The four features below all ship in GS_AudioPro. Start at the Audio Pro overview for what the premium tier adds, how it attaches to the free gem, and where each feature’s bus lives.

Audio Pro Overview API


Audio Event Graph

Pro. A visual node editor for authoring complex sound events. Build a sound as a graph — sources, filters, effects and conditional routing, connected on a canvas — with a Start / Loop / Finish phase lifecycle and runtime variables that let gameplay reshape the sound while it plays.

This is the premium tier’s headline authoring feature. The free tier reaches the same processors through named effect chains on components.

Audio Event Graph API


Score Arrangement

Pro. Multi-layer music assets. An arrangement defines time signature, tempo and fade behaviour, and holds layers that can be brought in and out independently — so the music adds a drum line as combat starts and drops it when it ends.

Score Arrangement API


Klatt Voice Synthesis

Pro. Text-to-speech built on Klatt formant synthesis. It generates voice from parameters rather than playing recordings, so a project can give hundreds of characters distinct voices with no audio assets and no recording sessions. Supports 3D spatial audio and inline tags that change pitch, speed or waveform partway through a line.

Klatt Voice API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.1 - Audio Manager

How to set up the GS_Play Audio Manager — engine startup, volume channels, event libraries, the listener, and running it without a Game Manager.

Base — ships in GS_Audio.

The Audio Manager is the one component the audio system needs. It starts the engine, holds your volume channels, loads your event libraries, and carries the listener that every positioned sound is measured against.

It is a GS manager, so it plugs into the Game Manager’s startup like the rest — or runs on its own if your project does not have one.

For component properties and the full API, see the Framework API reference.

Audio Manager component in the O3DE Inspector

 

Contents


Setting It Up

  1. Create an entity and add the GS_AudioManagerComponent.
  2. Configure your volume channels — a named list such as Music, SFX, Voice, Ambience.
  3. Assign your event libraries.
  4. Turn the entity into a prefab, set the wrapper entity to Editor Only, and save.
  5. Add the prefab to the Game Manager’s Startup Managers list.

This is the same prefab pattern every GS manager uses — see The Basics: GS_Managers if you have not set one up before.


Using Audio Without a Game Manager

If audio is the only GS feature your project uses, you do not need the manager stack at all.

Add the Audio Manager to your level, tick Headless on it, and press play. It starts itself, and everything on these pages works exactly as documented. No Game Manager, no startup sequence, no other managers.

This is the intended path for dropping GS_Audio into a project that is not otherwise built on GS_Play.


What It Manages

ResponsibilityWhat it does
Engine lifecycleStarts and shuts down the audio engine.
Volume channelsHolds the named channel list and their levels.
Event librariesLoads your .aev libraries so events can be fired by name.
The listenerCarries the point the player hears from.
Spatial audio setupProject-wide spatialization mode and distance defaults.

Setting the Listener

There is no listener component. The manager holds the listener and you point it at an entity.

For first person, point it at your camera or player and you are finished. For third person, split it — distance measured from your character, direction from your camera. See Spatial Audio for why that combination is the one you want.

If you never set a listener, the manager uses its own entity and warns. Audio will work, but it will be positioned from wherever your manager prefab happens to be.


Quick Reference

NeedBusMethod
Set master volumeAudioManagerRequestBusChangeMasterVolume
Set a channel’s volumeAudioManagerRequestBusChangeChannelVolume
List channelsAudioManagerRequestBusGetMixingChannelNames
Set the listenerAudioManagerRequestBusSetListener
Split the listenerAudioManagerRequestBusSetListenerSpatialTarget / SetListenerOrientationTarget
Load an event libraryAudioManagerRequestBusAddAudioLibrary

Glossary

TermMeaning
Volume ChannelA named group of sound types with its own level
Event LibraryA .aev asset holding named audio events
ListenerThe point in the world the player hears from
HeadlessA manager that starts itself, with no Game Manager in the project

See Also

For the full API:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.2 - Audio Events

How to author and fire audio events — event libraries, clip pools, procedural noise and chiptones, distance falloff, and concurrency.

Base — ships in GS_Audio.

An audio event is a named sound your game can fire. You author events in an event library asset, assign the library to the Audio Manager, and then play sounds by name from anywhere — script, C++ or a trigger volume.

Everything that makes a sound behave well in a game — variation, distance falloff, a cap on how many can play at once, following a moving object — is configured on the event, not at the call site. Firing one is always just a name.

For component properties and the extension guide, see the Framework API reference.

 

Contents


Creating an Event Library

  1. In the Asset Browser, create a new Audio Event Library (.aev) asset.
  2. Add an event to it and pick a type from the dropdown.
  3. Give it an Event Name — this is what you fire by.
  4. Set its Mixing Channel so it routes to the right volume channel.
  5. Assign the library to your Audio Manager’s library list.

Libraries can also be added and removed while the game is running, which is how you would stream in a level’s sound set.


Choosing an Event Type

TypeUse it forWhat you author
PoolRecorded sound — footsteps, impacts, voices, music stingersA pool of sound files. One is picked at random each time.
NoiseWind, static, hiss, rumbleA noise colour (White, Pink, Brownish, Blueish) and a duration.
SfxrRetro and UI sound — coins, lasers, jumps, blipsA pool of SFXR profiles. The profile’s envelope sets the length.

Noise and Sfxr events need no audio files at all. They generate their sound at runtime, which makes them useful early in a project when the audio pass has not happened yet.


Making Sounds Vary

Both Pool and Sfxr events hold a pool, and pick from it at random on every fire.

This is the single highest-value thing to know about the event system. A footstep event with one clip sounds mechanical after three steps. The same event with five recorded variants sounds like walking — and the code firing it does not change at all.

For Sfxr events the pool is even cheaper to fill: generate a sound at sfxr.me, press mutate a few times, and import each result as a profile. Five mutated explosions in one pool means the explosion never repeats identically.

A pool of one is perfectly valid. It is just a sound that does not vary.


Distance and Concurrency

SettingWhat it controls
Distance Min / MaxFull volume within min, fading out to silence at max. Set min above zero.
Is 2DIgnores position entirely — pure stereo. Use for UI and music.
Concurrent Event LimitThe most voices this event can have playing at once. Stops one emitter flooding the mix.
Event Repeat HoldA short window after firing where the event will not re-fire. Stops a rapid trigger machine-gunning the sound.
LoopRepeat until stopped.

The last two matter more than they look. A collision sound fired from a physics callback can trigger dozens of times in a few frames; the concurrency limit and repeat hold are what keep that from turning into noise, without any logic on your side.


Firing Events

Three calls on AudioManagerRequestBus, all resolving the event by name:

NeedMethod
A sound with no position — UI, music, narrationPlayEvent2D
A sound at a fixed point in the worldPlayEventAtLocation
A sound that follows a moving objectPlayEventOnEntity

PlayEventOnEntity keeps tracking the entity as it moves, so it is the right choice for anything attached to a character, vehicle or projectile.


Quick Reference

NeedBusMethod
Play a non-positional soundAudioManagerRequestBusPlayEvent2D
Play at a world positionAudioManagerRequestBusPlayEventAtLocation
Play attached to an entityAudioManagerRequestBusPlayEventOnEntity
Load an event library at runtimeAudioManagerRequestBusAddAudioLibrary
Unload an event libraryAudioManagerRequestBusRemoveAudioLibrary

Glossary

TermMeaning
Audio EventA named sound the game fires, with its own pooling, distance and concurrency settings
Event LibraryA .aev asset holding a set of events
PoolA set of sources on one event, picked from at random per fire
Concurrency LimitThe maximum number of voices one event may have playing at once

See Also

For the full API and the extension guide:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.3 - SFXR Profiles

How to make procedural sound effects — design a sound in the browser, paste it into a profile asset, and pool profiles for variation.

Base — ships in GS_Audio.

SFXR profiles are sound effects made entirely of numbers. No recording, no audio files, no disk space worth mentioning — a coin pickup, a laser shot or a jump blip described as a parameter set.

They are the fastest way to get real sound into a project, and 27 of them ship with the gem, so a new project has working effects before it has any audio of its own.

For the parameter reference, see the Framework API reference.

 

Contents


Making a Sound

The intended workflow runs through the browser, because that is where the instant preview is.

  1. Open sfxr.me.
  2. Pick a generator — Pickup, Laser, Explosion, Powerup, Hurt, Jump, Blip — and press it until you like what you hear. Or press Mutate on a sound you already like.
  3. Press Serialize and copy the JSON it gives you.
  4. In O3DE, create a new SFXR Profile (.sfxrprofile) asset.
  5. Open its Import from sfxr.me group and paste the JSON into the field.
  6. The parameters populate. Save.

You can also edit the 24 parameters by hand on the asset afterwards — the import is a starting point, not a lock.


Using Profiles

A profile is played through an Sfxr audio event:

  1. Add an event to your event library and set its type to Sfxr.
  2. Add one or more profiles to the event’s profile list.
  3. Fire the event by name like any other sound.

Audio Pro users can also drop an Sfxr node into an Audio Event Graph and feed it profiles the same way.


Building a Variation Pool

An Sfxr event holds a list of profiles and picks one at random every time it fires.

This is where the browser workflow pays off. Make an explosion you like, press Mutate four times, and import all five results. Drop them into one event and your explosion never sounds the same twice — with no logic anywhere in your game.

An event with one profile is fine, and an event with an empty list is simply silent.


What Ships With the Gem

27 profiles in the gem’s Assets/SFXR Profiles/ folder:

  • 7 classic presets — Coin, Laser, Explosion, Powerup, Hurt, Jump, Blip.
  • 20 named custom profiles.

Use them directly, or open one and mutate it into something of your own.


Glossary

TermMeaning
SFXR ProfileA .sfxrprofile asset describing one procedural sound as parameters
SerializeThe sfxr.me button that exports a sound as pasteable JSON
MutateThe sfxr.me button that randomly varies an existing sound

See Also

For the parameter reference:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.4 - Mixing & Effects

How to work with GS_Play audio mixing — volume channels, named effect chains, all 22 effects, and environmental influence zones.

Base — ships in GS_Audio.

Mixing has two jobs, and they are worth keeping separate in your head.

Volume channels group sounds by what kind of sound they are — voice, music, environment, combat — so a player can turn one down without touching the others. This is what your options menu drives.

Effect chains process the signal — filters, EQ, delay, reverb, and character effects like chorus and bit-crushing. You build a chain on a component, name it, and point sounds at that name.

All 22 effects are available in the free gem. Audio Pro authors the same processors on a visual canvas instead.

For component properties and the full effect reference, see the Framework API reference.

 

Contents


Volume Channels

Channels are configured on the Audio Manager as a named list. Each audio event names the channel it routes to.

Set them up the way your options menu will present them — a Voice channel, a Music channel, an SFX channel — and wiring the sliders becomes trivial, because the options system can drive channel levels directly.

Channels are independent of position. A footstep, a spell and a line of dialogue are all spatialized the same way, and all three route to different channels.


Building an Effect Chain

  1. Add a Mix Effect component to an entity.
  2. Give it a Name — this is what sounds and other chains will target.
  3. Add effects to its list. They run top to bottom.
  4. Set its Output Target — another chain’s name, or a volume channel.

Then point sounds at it by setting an event’s Mixing Channel to your chain’s name. Channels and chains share one namespace, so a sound does not care which kind of thing it is aimed at.

Three things that make this easier than it sounds:

  • Order does not matter. If a chain points at something that does not exist yet, the connection waits and completes when it appears. You never have to think about activation order.
  • You can add chains at runtime. Sounds already playing through that channel pick up the new routing.
  • One entity can hold several chains. Give each its own name and point one at another to compose them.

You can put a chain on the emitting entity to colour just that object’s sound, or use shared global chains for project-wide treatments. Both work, and most projects use both.


The Effects

FamilyEffects
FiltersLow Pass, High Pass, Band Pass, Notch, Peaking EQ, Low Shelf, High Shelf
Time & spaceEcho, Reverb, Binaural
CharacterWave Shaper, Lofi, Robotize, Flanger, Chorus, Ring Mod, Tremolo, Pitch
UtilityPanner, Compressor, DC Removal, Amplify

Environmental Influence Zones

A world entity can push effects onto channels while the player is inside it — walk into a cave and everything gains reverb, walk out and it lifts.

Zones stack by priority, so overlapping zones resolve predictably rather than by whichever fired last, and each zone’s contribution is tracked against the entity that applied it so removing it is unambiguous.


Quick Reference

NeedBusMethod
Set a channel’s levelAudioManagerRequestBusChangeChannelVolume
Set master volumeAudioManagerRequestBusChangeMasterVolume
List the configured channelsAudioManagerRequestBusGetMixingChannelNames
Mute a channelGS_MixingRequestBusMuteBus
Change an effect’s parameter liveAudioManagerRequestBusSetMixEffectParamOn
Re-point a chain’s outputAudioManagerRequestBusRerouteMixEffect
Apply an environmental zoneAudioManagerRequestBusApplyAudioEffectInfluence

Glossary

TermMeaning
Volume ChannelA named group of sound types with its own level, feeding Master
Mix Effect ChainAn ordered set of effects on a component, registered under a name
Output TargetWhere a chain’s last effect feeds — another chain, or a channel
Influence ZoneA world entity that imposes effects on channels while it applies

See Also

For the full API and the effect reference:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.5 - Spatial Audio

How sounds get placed in the world — setting the listener, splitting it for third-person, distance falloff, and what the free tier does and does not do.

Base — ships in GS_Audio.

Spatial audio is handled for you. Fire a sound at a position or on an entity and it is placed in the world: direction, distance falloff, and HRTF binaural processing so it sounds like it is coming from somewhere rather than from inside your head.

There is nothing to add to a sound to make this happen, and nothing to add to a chain. The only thing you configure is the listener — where the player is hearing from.

For settings and behaviour detail, see the Framework API reference.

 

Contents


Setting the Listener

There is no listener component to add. The Audio Manager carries the listener, and you point it at an entity.

For a first-person game, that is the whole setup — point it at the camera or the player and you are done.

If you set nothing, the manager falls back to its own entity and warns you. That fallback exists so the system is never silently broken, but it is not where you want your listener.


Third-Person: Splitting the Listener

In third person the camera and the character are in different places, and “where is the player hearing from” has two different right answers.

  • How close a sound is should be measured from the character. Proximity is about your body.
  • Which direction a sound is in should be measured from the camera. Left and right are about where you are looking.

So set them separately:

Set thisTo this
Spatial targetYour character
Orientation targetYour camera

This is the recommended third-person setup, and the difference is immediately audible — sounds sit where you expect instead of drifting with the camera boom.

For first person, use the single-target call and let both follow one entity.


Distance Falloff

Each audio event sets its own Distance Min / Max:

  • Inside min, the sound plays at full volume.
  • Between min and max, it fades.
  • At max it is genuinely silent — not faintly audible forever.

That last point is deliberate. Physically accurate falloff never quite reaches zero, which leaves distant sounds quietly cluttering the mix. Max distance here means what it says.

Reverb behaves slightly differently: it stays constant until three quarters of the way out, then eases off. A room sounds like the same room wherever you stand in it, while the dry sound still gets quieter as you walk away.


What This Does Not Do

Worth knowing before you evaluate the result:

  • Nothing is blocked by geometry. A sound behind a wall is quieter because it is further away, not because the wall is there. Occlusion needs Audio Pro.
  • Up, down, in front and behind are subtle. Left and right are clear and reliable. Elevation and front/back are inherently weak cues with a generic HRTF — that is the technique, not a bug.

Quick Reference

NeedBusMethod
Set the listener (first person)AudioManagerRequestBusSetListener
Set where distance is measured fromAudioManagerRequestBusSetListenerSpatialTarget
Set where direction is measured fromAudioManagerRequestBusSetListenerOrientationTarget
Play a sound that follows an objectAudioManagerRequestBusPlayEventOnEntity

Glossary

TermMeaning
ListenerThe point in the world the player hears from
Spatial targetThe entity distance is measured from
Orientation targetThe entity direction is measured from
HRTFHead-related transfer function — the filtering that makes a sound seem to come from a direction

See Also

For settings and behaviour detail:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.6 - Audio Pro

GS_AudioPro — the premium audio gem. Visual sound authoring, adaptive music, generated speech and real acoustics, added on top of everything the free gem does.

GS_AudioPro is the premium audio gem. It sits on top of GS_Audio — everything in the free gem keeps working exactly as it did, and Pro adds four things of its own.

Added by ProWhat it gives you
Audio Event GraphBuild a sound on a canvas instead of a component list.
Score ArrangementMusic that adds and drops layers as the game changes.
Klatt VoiceGenerated character speech with no recordings.
Geometric AcousticsSounds actually blocked by walls, and reverb from the real room.

 

Contents


Adding Pro to a Project

  1. Enable the GS_AudioPro gem alongside GS_Audio.
  2. Open your Audio Manager prefab and use the Pro Audio Manager component in place of the base one.
  3. Assign your graph libraries and score tracks on it, and save.

The Pro manager does everything the base one does — volume channels, event libraries, listener, project settings are all still there, with Pro’s geometry settings added alongside them.

You cannot run both managers at once. A project uses the base manager or the Pro one.


Where Each Feature Lives

Pro’s features each have their own bus, separate from the Audio Manager’s. Playing sounds, volume and mixing stay exactly where they are on AudioManagerRequestBus.

FeatureBus
Score arrangementsAudioScoreRequestBus
Audio event graphsAudioGraphRequestBus
A graph playing on one entityAudioGraphPlayerRequestBus
VoicesThe Klatt voice buses

Features

Audio Event Graph

Author a sound as a graph. Drop in sources, filters and effects, wire them together, and route the result — with a Start / Loop / Finish lifecycle and variables your game can drive while the sound is playing.

Audio Event Graph

Score Arrangement

Multi-layer music. One arrangement holds several layers with a shared tempo and time signature, and you bring them in and out as the game changes — add percussion for combat, drop it when it ends.

Score Arrangement

Klatt Voice

Speech generated from parameters rather than recordings. Give every character a distinct voice without a recording session, and use inline tags to change pitch or speed partway through a line.

Klatt Voice

Geometric Acoustics

Mark up your level with acoustic volumes and materials, and sounds start being genuinely blocked by what is between them and the player — with reverb that comes from the room they are standing in.

Geometric Acoustics


See Also

For the free gem this builds on:

For the full API:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.6.1 - Audio Event Graph

How to author complex sound events visually using the Audio Event Graph editor — nodes, phase lifecycle, variables, and runtime control.

The Audio Event Graph is a visual node-based editor for authoring complex sound events. Instead of a single clip pool, you connect sources, filters, effects, and routing nodes into a graph that evaluates as a data-flow pipeline. Each .audiograph file is one sound event. At runtime, the graph maps directly to a live audio processing chain.

For component properties and C++ extension, see the Framework API reference.

Audio Event Graph Editor in O3DE Editor

 

Contents


How It Works

Nodes in an Audio Event Graph evaluate left to right in topological order. Connections carry AudioRoute values — handles to a point in the audio processing chain. The signal flows through:

  1. An Entry node gates which phase is active
  2. Source nodes create and output audio
  3. Filter and effect nodes process the signal
  4. Routing nodes (If/Switch) direct the signal based on variables
  5. The Output node wires everything to a mixing bus

Only nodes affected by a change re-evaluate — the rest are skipped. This makes variable-driven sound modification cheap even for complex graphs.


Phase Lifecycle

Audio Event Graph Lifecycle Nodes

Every Audio Event Graph supports up to three phases:

PhaseEntry NodeUse For
StartAud_StartNodeOne-shot intro sounds, attack transient
LoopAud_LoopNodeSustained loops, ambient audio
FinishAud_FinishNodeFade-outs, release tails

Phases transition automatically: Start fires once, Loop repeats while looping is enabled, Finish plays when FinishAudioGraph() is called. Not all phases are required — a simple one-shot only needs Start.

Entry nodes share routing freely. A source wired to both Start and Loop phases produces sound in both.


Nodes

Source Nodes

Source Nodes in the Audio Event Graph Editor

NodeWhat It Does
Aud_SoundNodeSingle audio asset playback
Aud_AudioPoolNodeRandom clip selection from a pool

Both support volume, pitch, delay, looping, and 3D spatialization.

Filter Nodes

Filter Nodes in the Audio Event Graph Editor

LowPass, HighPass, BandPass, Notch, PeakingEQ, LowShelf, HighShelf — all take audio_in and output audio_out. Parameters (cutoff, Q, gain) can be set per-node or bound to graph variables.

Effect Nodes

Echo / Delay Effect Node

NodeWhat It Does
Aud_EchoNodeEcho / reverb tail
Aud_DelayNodeDelay effect

Routing Nodes

IfNode and SwitchNode route AudioRoute values based on conditions or a selector variable. Use these for conditional audio paths — different sounds based on surface type, weather, or character state.

Output Node

Aud_OutputNode wires the final signal to a named mixing bus. Every graph needs exactly one Output node.


Using Variables

Declare variables in the Variable Panel. Bind them to filter parameter slots (right-click > Convert to Reference) or use If/Switch nodes to route based on them.

At runtime, setting a variable re-evaluates only the nodes that depend on it — the rest of the graph is untouched.

Common patterns:

VariableTypeUse
underwaterFloatBind to LowPass cutoff — muffles audio when > 0
surfaceTypeIntDrive a SwitchNode to select different footstep sources
intensityFloatBlend between effect parameters for emotional scoring

Variables are set from gameplay code via the Audio Manager:

SetAudioGraphVariable(instanceId, "underwater", 0.8)
SetAudioGraphVariableBool(instanceId, "isSprinting", true)

Playing at Runtime

All Audio Event Graph playback goes through the Audio Manager.

Fire-and-Forget

For one-shot sounds with no runtime modification needed:

PlayAudioGraph("Assets/Audio/Explosion.audiograph")

Manual Control

For sounds that loop, need variable updates, or need explicit stopping:

  1. AcquireAudioGraph(assetPath) — get an instance ID
  2. SetAudioGraphLooping(id, true) — if it loops
  3. SetAudioGraphEntity(id, entityId) — for 3D tracking
  4. FireAudioGraph(id) — start playback
  5. SetAudioGraphVariable(id, name, value) — update variables any time
  6. StopAudioGraph(id) or FinishAudioGraph(id) — stop cleanly
  7. ReleaseAudioGraph(id) — return the instance to the pool

Quick Reference

TaskHow
Create a new graphFile > New in the Audio Event Graph Editor
Add a phase entry nodeDrag Start / Loop / Finish node from the palette
Add a sourceDrag Aud_SoundNode or Aud_AudioPoolNode
Chain a filterConnect source audio_out → filter audio_in
Wire to outputConnect final node’s audio_out → Aud_OutputNode audio_in
Bind a variable to a slotRight-click the slot > Convert to Reference
Play fire-and-forgetAudioManagerRequestBus::PlayAudioGraph(path)
Play with controlAcquireAudioGraph → FireAudioGraph → ReleaseAudioGraph
Update a variableSetAudioGraphVariable(id, name, value)

Glossary

TermMeaning
Audio Event GraphA .audiograph file defining a sound event as a data-flow node graph
AudioRouteThe value type flowing between nodes — a handle to a point in the miniaudio processing chain
PhaseOne of three lifecycle stages (Start / Loop / Finish) gated by entry nodes
Graph InstanceAn independent runtime copy of a graph, pooled and managed by the Audio Manager
Dirty PropagationThe evaluation model — only nodes downstream of a changed variable re-run

For full definitions, see the Glossary.


See Also


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.6.2 - Geometric Acoustics

How to make sounds get blocked by walls and pick up the reverb of the room they are in — acoustic volumes, materials, occlusion and reflections.

The free gem places sounds by direction and distance — a sound behind a wall is quieter because it is further away, not because the wall is there.

Geometric acoustics makes the wall matter. Mark up your level and sounds get occluded by what is between them and the player, and pick up reverb from the room the player is actually standing in.

For settings and behaviour detail, see the Framework API reference.

 

Contents


Setting It Up

Geometric acoustics is off until you turn it on.

  1. Select your Pro Audio Manager.
  2. Tick Geometry Enabled.
  3. Leave Occlusion and Reflections on unless you specifically want one without the other.

Nothing happens yet — the simulation has no geometry to work with until you mark some up.


Marking Up a Level

You do not hand the system your level meshes. You mark up the parts that matter acoustically — walls, floors, doorways, the big surfaces that shape a room.

Two components do it:

ComponentUse it when
Audio Shape GeometryYou already have a Shape component on the entity. Quads become planes, Boxes become boxes. Usually the easier option.
Audio GeometryYou want to author a box volume directly.

A practical starting point: block out the room a player will spend the most time in, tag its walls and floor, and listen. You will hear the difference immediately, and you can decide from there how much of the rest of the level is worth marking up.


Choosing Materials

Every piece of geometry carries a material preset. Fifteen ship, in three useful groups:

GroupPresetsWhat they do
HardGeneric, Brick, Concrete, Ceramic, Glass, Plaster, Wood, Metal, RockBounce sound. Bright, long reverb — stone corridors, tiled bathrooms.
AbsorptiveGravel, CarpetSoak sound up. Short, dull reverb — carpeted rooms, soft ground.
Soft barriersCurtain, Blanket, Door, DrapeLet sound through, muffled.

The soft-barrier group is worth knowing about. A curtain across a doorway does not silence what is behind it, it dulls it — the effect people usually fake with a low-pass filter. Here you get it by picking the right material.

If none of the presets fit, Custom exposes all seven coefficients directly.


Tuning Occlusion

Two modes:

  • Raycast — one ray. A sound is occluded or it is not. Cheap.
  • Volumetric — samples a sphere around the source, so sound leaks partially around edges. Costs more.

If occlusion sounds like a switch flicking as the player walks past a doorframe, use Volumetric. That partial-occlusion behaviour at edges is exactly what it exists for. Occlusion Radius controls how wide the sampled sphere is.


Tuning Reverb

Reverb comes from the geometry around the player, and updates as they move.

Two behaviours that are deliberate, so you do not go hunting for a bug:

  • It eases between rooms. Walking through a doorway blends the reverb rather than snapping it. Reflection Smooth Time controls how long that takes — set it to 0 if you want an instant change.
  • It does not get quieter as you walk away from a sound. The room sounds like the same room wherever you stand in it, while the dry sound still falls off with distance. That is correct, and it is what makes a large space feel large.

What It Does Not Do

  • Reverb cannot tell your room from the one next door. It is computed around the player, so a sound sealed in an adjacent room still feeds the same reverb.
  • An occluded sound still feeds the reverb. Occlusion muffles the direct sound; it does not stop the source exciting the room.
  • It only knows about geometry you marked up. Render meshes and physics colliders are not consulted.
  • There is no baking. Everything is computed at runtime.

See Also

For settings and behaviour detail:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.6.3 - Score Arrangement

How to work with GS_Play score arrangements — multi-layer dynamic music with configurable time signatures and layer control.

Score Arrangement Tracks are multi-layer music assets for dynamic, adaptive game music. Each arrangement defines a time signature, tempo, fade behavior, and a set of independently controllable Score Layers. This enables music that responds to gameplay — adding percussion during combat, muting melody during dialogue, or transitioning between intensity levels.

For asset structure and playback API, see the Framework API reference.

Score Arrangement asset in the O3DE Asset Editor

 

Contents


How It Works

A Score Arrangement Track is a data asset containing:

FieldWhat It Controls
Time SignatureMusical timing (4/4, 3/4, 6/8, etc.).
BPMTempo in beats per minute.
Fade ControlHow layers fade in and out.
Score LayersIndividual audio tracks that play simultaneously.

Each Score Layer is an independent audio stream within the arrangement. Layers can be enabled or disabled at runtime, creating dynamic music that evolves based on game state.


Supported Time Signatures

4/4, 4/2, 12/8, 2/2, 2/4, 6/8, 3/4, 3/2, 9/8


Glossary

TermMeaning
Score Arrangement TrackA multi-layer music data asset with time signature, tempo, and controllable layers
Score LayerAn individual audio stream within an arrangement that can be enabled or disabled at runtime
BPMBeats per minute — the tempo of the arrangement

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.3.6.4 - Klatt Voice Synthesis

How to work with GS_Play Klatt voice synthesis — text-to-speech with 3D spatial audio, voice profiles, and inline parameter control.

GS_Play includes a built-in text-to-speech system based on Klatt formant synthesis. The KlattVoiceComponent converts text to speech in real time with configurable voice parameters. Voices are positioned in 3D space and attenuate with distance, making synthesized speech feel like it comes from the character speaking.

For component properties, voice parameter details, and the phoneme mapping system, see the Framework API reference.

Klatt Voice Profile asset in the O3DE Asset Editor

 

Contents


How It Works

  1. Configure a voice using a KlattVoiceProfile — set frequency, speed, waveform, formants, and pitch variance.
  2. Assign a KlattPhonemeMap — maps text characters to ARPABET phonemes for pronunciation.
  3. Speak text from ScriptCanvas or C++ — the system converts text to phonemes and synthesizes audio in real time.
  4. Position in 3D — the voice component uses KlattSpatialConfig for 3D audio positioning relative to the entity.

Voice Configuration

ParameterWhat It Controls
FrequencyBase voice pitch.
SpeedSpeech rate.
WaveformVoice quality — Saw, Triangle, Sin, Square, Pulse, Noise, Warble.
FormantsVocal tract resonance characteristics.
Pitch VarianceRandom pitch variation for natural-sounding speech.
DeclinationPitch drop over the course of a sentence.

KTT Tags

KTT (Klatt Text Tags) allow inline parameter changes within speech text for expressive delivery:

"Hello <speed=0.5>world</speed>, how are <pitch=1.2>you</pitch>?"

The KlattCommandParser processes these tags during speech synthesis, enabling mid-sentence changes to speed, pitch, and other voice parameters.

For the complete tag reference — all attributes, value ranges, and reset behavior — see the Framework API: KTT Voice Tags.


Phoneme Maps

Two base phoneme maps are available:

MapDescription
SoLoud_DefaultSimple default mapping.
CMU_FullFull CMU pronunciation dictionary mapping.

Custom phoneme overrides allow project-specific word pronunciations (character names, fantasy terms) without modifying the base map.


3D Spatial Audio

The KlattSpatialConfig controls how synthesized speech is positioned in 3D:

  • Voices attenuate with distance from the listener.
  • The KlattVoiceSystemComponent tracks the listener position and updates all active voices.
  • Multiple characters can speak simultaneously with correct spatial positioning.

Quick Reference

NeedBusMethod
Control a voiceKlattVoiceRequestBusVoice synthesis methods (entity-addressed)
System-level voice controlKlattVoiceSystemRequestBusListener tracking, engine management

Glossary

TermMeaning
Klatt SynthesisA formant-based speech synthesis method that generates voice from frequency parameters
KTT TagsInline text tags that modify voice parameters mid-sentence during synthesis
Phoneme MapA mapping from text characters to ARPABET phonemes for pronunciation
KlattSpatialConfigConfiguration for 3D audio positioning and distance attenuation of synthesized speech

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

3.4 - GS_Cinematics

Node-graph dialogue sequences, cinematic stage management, polymorphic performances, and world-space UI with typewriter and audio babble.

GS_Cinematics is the complete dialogue and cinematic control system for GS_Play. It provides a node-graph authoring tool for branching dialogue sequences, a runtime sequencer with conditions and side effects, a UI layer for text display and player choice, and a Cinematics Manager for scene staging. Custom conditions, effects, and performance types are discovered automatically through O3DE serialization, so project-specific dialogue behaviors can be added without modifying the gem.

For architecture details, component properties, and extending the system in C++, see the GS_Cinematics API.


Quick Navigation

I want to…FeatureAPI
Coordinate cinematic sequences and manage stage markers for actor positioningCinematics ManagerAPI
Author branching dialogue sequences in the visual Dialogue EditorDialogue SystemAPI
Display dialogue text, player choices, and speech babble on screen or in world spaceDialogue UIAPI
Move actors to stage markers during dialogue with navigation or teleportPerformancesAPI

Installation

GS_Cinematics requires GS_Core, LyShine, and RecastNavigation. The node editor tools additionally require GraphCanvas and GraphModel as editor-only dependencies.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Cinematics gem in your project configuration.
  2. Create Cinematics Manager and Dialogue Manager prefabs, add to the Game Manager’s Managers list.
  3. Create .dialoguedatabase assets with the Dialogue Editor (GS Tools > Dialogue Editor).
  4. Place DialogueSequencer and DialogueUI components in your level.
  5. Bake NavMesh in levels where PathTo performances will be used.

Cinematics Manager

The Cinematics Manager coordinates the begin and end of cinematic sequences, broadcasting enter/exit events so other systems know when to yield control. It maintains a registry of stage marker entities placed in each level — named anchor points that performers and cameras look up at runtime to determine positioning during a sequence.

Cinematics Manager API


Dialogue System

The Dialogue System is the authoring and runtime core. Dialogue is authored in the Dialogue Editor — a visual node graph tool — and stored in .dialoguedatabase container files holding actors and sequences. Each sequence is a graph of polymorphic nodes: text, selection, random branch, effects, and performances. At runtime, the sequencer walks the graph, evaluates conditions, executes effects, and emits events for the UI layer.

Dialogue System API


Dialogue UI

The Dialogue UI feature set puts dialogue text and player choices on screen. DialogueUI handles screen-space text display with a Typewriter for character-by-character reveal. DialogueUISelection renders player choices as selectable buttons. World-space variants place speech bubbles above actors. Babble plays audio tied to the active speaker. The DialogueUIBridge routes sequencer events to the correct UI implementation and routes player selection back.

Dialogue UI API


Performances

Performances are polymorphic actions that move or reposition actors during dialogue. MoveTo translates actors to named stage markers, PathTo navigates via NavMesh, and Reposition teleports instantly. All run asynchronously — the sequencer waits for completion before advancing. Custom performance types can be created through extending the class.

Performances API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

3.4.1 - Cinematics Manager

How to coordinate cinematic sequences in GS_Play — beginning and ending cutscenes, registering stage markers, and reacting to cinematic events from scripts.

The Cinematics Manager is the GS_Core-integrated manager component for the GS_Cinematics system. It signals when a cinematic sequence begins and ends, broadcasts events so other systems — UI, movement, input — know when to yield control to a cutscene, and maintains a registry of named CinematicStageMarkerComponent entities placed in each level.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Cinematics Manager component in the O3DE Inspector

 

Contents


Stage Markers

Cinematic Stage Marker component in the O3DE Inspector

Stage markers are named anchor entities placed in each level that serve as spatial reference points for cinematic sequences. Performers and camera systems look up markers by name at runtime to determine where actors should stand, face, or move to during a cutscene.

This design decouples authored dialogue data from level-specific layout. The same sequence asset plays in any level as long as that level provides CinematicStageMarkerComponent entities with the expected names.

ComponentPurpose
CinematicStageMarkerComponentMarks a named position in the level for cinematic staging.

To register a marker, add CinematicStageMarkerComponent to an entity in the level and give it a name. The Cinematics Manager automatically discovers and registers all markers in the level on startup via RegisterStageMarker. At runtime, any system can retrieve a marker entity by name with GetStageMarker.


Cinematic Events

When a cinematic begins and ends, the Cinematics Manager broadcasts events on CinematicsManagerNotificationBus. Listen to these in any system that needs to yield or reclaim control during a cutscene — player input, HUD, AI, camera.

EventWhen It FiresWhat to Do
EnterCinematicA cinematic sequence has started.Disable player input, hide the HUD, suspend AI.
ExitCinematicThe cinematic sequence has ended.Re-enable input, restore HUD, resume AI.

Starting and Ending Cinematics

Call BeginCinematic to signal that a cinematic is starting and EndCinematic when it completes. These calls broadcast EnterCinematic and ExitCinematic respectively. They do not drive animation or sequence playback directly — that is the role of DialogueSequencerComponent. The Cinematics Manager handles the global state change so all listening systems respond in one coordinated broadcast.

BusMethodWhat It Does
CinematicsManagerRequestBusBeginCinematicBroadcasts EnterCinematic to all listeners.
CinematicsManagerRequestBusEndCinematicBroadcasts ExitCinematic to all listeners.
CinematicsManagerRequestBusRegisterStageMarkerAdds a marker to the registry by name.
CinematicsManagerRequestBusGetStageMarkerReturns the entity for a marker by name.

ScriptCanvas Usage

Reacting to Cinematic State

To pause gameplay systems when a cinematic starts and resume them when it ends, connect to CinematicsManagerNotificationBus:

Triggering a Cinematic

To start a cinematic from a trigger or cutscene entity, call BeginCinematic, drive the sequence through the Dialogue Sequencer, then call EndCinematic on completion:

Looking Up a Stage Marker


Quick Reference

NeedBusMethod / Event
Start a cinematicCinematicsManagerRequestBusBeginCinematic
End a cinematicCinematicsManagerRequestBusEndCinematic
Register a stage markerCinematicsManagerRequestBusRegisterStageMarker
Retrieve a stage marker entityCinematicsManagerRequestBusGetStageMarker
Know when a cinematic startsCinematicsManagerNotificationBusEnterCinematic
Know when a cinematic endsCinematicsManagerNotificationBusExitCinematic

Glossary

TermMeaning
Stage MarkerA named anchor entity in a level used as a spatial reference for cinematic positioning
CinematicA global state where the Cinematics Manager has signaled that a cutscene is in progress

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

3.4.2 - Dialogue System

How to author and play back branching dialogue in GS_Play — the Dialogue Editor, node types, conditions, effects, performances, and the runtime sequencer.

The Dialogue System is the authoring and runtime core of GS_Cinematics. Dialogue is authored visually in the Dialogue Editor — a node graph tool where sequences are built from Text, Selection, Effects, and Performance nodes inside a .dialoguedatabase container file. At runtime, GS_DialogueManagerComponent manages the active database and maps performer names to entities in the level, while DialogueSequencerComponent drives playback — walking the graph, evaluating conditions, executing effects, and emitting events that UI components consume.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Dialogue Manager component in the O3DE Inspector

 

Contents


Dialogue Editor

Dialogue is authored in the Dialogue Editor — a visual node graph tool opened from GS Tools > Dialogue Editor. Each .dialoguedatabase file is a container holding all actors and sequences for a dialogue database. Sequences are built as directed node graphs: connect Text, Selection, Random, Effects, and Performance nodes to define the conversation flow. The editor saves the entire container as one file — individual sequences are not separate assets.

Conditions, effects, and performance types from any gem are discovered automatically at startup and appear in the editor without modifying GS_Cinematics.

Dialogue Editor in the O3DE Editor


Database Model

A .dialoguedatabase file is a container managed entirely through the Dialogue Editor:

Container ContentsPurpose
Actors / PerformersCharacter definitions with portrait and metadata. Defined on the Performers page.
SequencesEach sequence is an independent graph, opened as a tab via the Sequence Sidebar.
System settingsDatabase-level defaults, on the System page.

The editor saves and loads the entire container as one file. Individual sequences are not separate assets. Switch the active database at runtime via DialogueManagerRequestBus::ChangeDialogueDatabase.

Authoring a Sequence

  1. Open GS Tools > Dialogue Editor
  2. File > New → save as .dialoguedatabase
  3. Define performers on the Performers page
  4. On the Sequences page, click Add in the sidebar, name the sequence
  5. Build the graph: drag nodes from the palette, connect FlowOut → FlowIn
  6. Ctrl+S to save the database

Node Types

Each sequence is a directed graph of nodes connected by FlowOut → FlowIn slots.

NodeWhat It Does
StartEntry point. Auto-spawned, one per sequence.
TextDisplays a line of dialogue from a speaker. Supports localization.
SelectionPresents the player with choices. One FlowOut slot per option.
RandomSelects a random outgoing branch. Supports weighted randomization.
EffectsExecutes one or more DialogueEffect objects.
PerformanceTriggers a DialoguePerformance action, optionally waits for it to complete.
EndTerminates the sequence.

Dialogue Nodes in the Dialogue Editor


Conditions

Conditions are polymorphic objects added to any node. The sequencer evaluates all conditions before entering a node — failed conditions cause the evaluator to skip it and try the next valid path.

Condition TypeWhat It Evaluates
Boolean_DialogueConditionA base boolean comparison.
Record_DialogueConditionChecks a game record via the RecordKeeper system.

Custom conditions from any gem are discovered automatically — subclass DialogueCondition, reflect it, and it appears in the inspector type picker.

Effects Node with Conditions in the Dialogue Editor


Effects

Effects are polymorphic objects executed when the sequencer reaches an Effects node.

Effect TypeWhat It Does
SetRecords_DialogueEffectSets one or more game records via the RecordKeeper system.
ToggleEntitiesActive_DialogueEffectActivates or deactivates entities in the level.

Custom effects follow the same discovery pattern as conditions.


Performances

Performances are polymorphic actions executed at Performance nodes. The sequencer can optionally wait for completion before advancing.

Performance TypeWhat It Does
MoveTo_DialoguePerformanceSmoothly moves a performer to a named stage marker.
PathTo_DialoguePerformanceNavigates a performer to a marker via NavMesh.
RepositionPerformer_DialoguePerformanceInstantly teleports a performer to a marker (non-blocking).

Custom performance types are discovered automatically.


Runtime Playback

GS_DialogueManagerComponent

The top-level manager for all dialogue. Holds the active database, maps performer names to level entities, and is the entry point for starting sequences.

BusMethodWhat It Does
DialogueManagerRequestBusStartDialogueSequenceByNameStarts a named sequence from the active database.
DialogueManagerRequestBusChangeDialogueDatabaseLoads a different .dialoguedatabase asset.
DialogueManagerRequestBusRegisterPerformerMarkerRegisters a performer entity by name for the current level.
DialogueManagerRequestBusGetPerformerReturns the entity for a named performer.

DialogueSequencerComponent

Drives sequence playback. Walks the graph, evaluates conditions, executes effects, triggers performances, and emits notifications for the UI layer.

BusMethod / EventPurpose
DialogueSequencerRequestBusStartDialogueBySequenceBegins playback of a sequence object directly.
DialogueSequencerNotificationBusOnDialogueTextBeginFires when a Text node executes — carries speaker and text data.
DialogueSequencerNotificationBusOnDialogueSequenceCompleteFires when the sequence reaches its End node.

Localization

Text nodes store lines as LocalizedStringId references — a key plus default fallback text. At runtime Resolve() looks up the key in the active LocalizedStringTable and returns the localized string. If no table is loaded or the key is absent, the fallback text is used.


ScriptCanvas Usage

Starting a Dialogue Sequence

Performer Marker Setup


Quick Reference

NeedBusMethod / Event
Start a sequence by nameDialogueManagerRequestBusStartDialogueSequenceByName
Load a different databaseDialogueManagerRequestBusChangeDialogueDatabase
Register a performerDialogueManagerRequestBusRegisterPerformerMarker
Get a performer entityDialogueManagerRequestBusGetPerformer
Start a sequence directlyDialogueSequencerRequestBusStartDialogueBySequence
React to a text lineDialogueSequencerNotificationBusOnDialogueTextBegin
React to sequence endDialogueSequencerNotificationBusOnDialogueSequenceComplete

Glossary

TermMeaning
Dialogue EditorThe visual gs_graphcanvas-based tool for authoring dialogue sequences
DialogueDatabaseA .dialoguedatabase container asset holding actors and sequences
DialogueSequenceA graph of nodes defining a single dialogue conversation
DialogueConditionA polymorphic evaluator gating node entry
DialogueEffectA polymorphic action executed at Effects nodes
DialoguePerformanceA polymorphic action that moves or repositions performers; optionally blocking
PerformerA named actor entity in the level mapped from the database via DialoguePerformerMarkerComponent

For full definitions, see the Glossary.


See Also


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

3.4.2.1 - Dialogue UI

How to display dialogue text, player choices, and typewriter effects in GS_Play — screen-space and world-space UI components and the bridge that connects them to the sequencer.

The Dialogue UI layer is the display side of the GS_Cinematics system. It receives events from DialogueSequencerComponent through DialogueUIBridgeComponent and routes them to the correct UI implementation — screen-space for HUD-style dialogue or world-space for speech bubbles above actors. Player choices are handled by selection components, and the TypewriterComponent reveals text character-by-character. BabbleComponent optionally plays per-character audio babble to give speakers a voice.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

DialogueUIComponent canvas in the O3DE UI Editor

 

Contents


Component Overview

ComponentSpacePurpose
DialogueUIComponentScreenDisplays the current speaker line on the HUD.
WorldDialogueUIComponentWorldDisplays speech bubbles positioned above actors in 3D space.
DialogueUISelectionComponentScreenRenders player choice options on the HUD.
WorldDialogueUISelectionComponentWorldRenders player choice options in 3D world space.
DialogueUIBridgeComponent—Routes sequencer events to UI and player input back to sequencer.
TypewriterComponent—Reveals text one character at a time with configurable timing.
BabbleComponent—Plays per-character audio babble for the active speaker.

DialogueUIBridgeComponent

The Bridge component is the central connector between the sequencer and the UI. Place it on the same entity as DialogueSequencerComponent. It listens for OnDialogueTextBegin and OnDialogueSequenceComplete from the sequencer and forwards those events to whatever UI components are registered with it. It also receives player selection events from the selection UI and forwards them back to the sequencer.

This design keeps the sequencer and the display fully decoupled — swapping in a new UI implementation only requires registering it with the Bridge.

BusMethodWhat It Does
DialogueUIBridgeRequestBusRunDialogueSends a text line to the registered dialogue UI.
DialogueUIBridgeRequestBusRunSelectionSends selection options to the registered selection UI.
DialogueUIBridgeRequestBusRegisterDialogueUIRegisters a UI entity as the active dialogue display target.
DialogueUIBridgeRequestBusCloseDialogueTells the registered UI to close.
DialogueUIBridgeNotificationBusOnDialogueCompleteFires when the dialogue UI reports it has finished displaying.
DialogueUIBridgeNotificationBusOnSelectionCompleteFires when the player makes a selection.

Dialogue Display Components

Screen-Space Text

DialogueUIComponent handles HUD-style dialogue display. Add it to a UI canvas entity. It exposes the active TypewriterComponent so other systems can check typewriter state or force completion.

World-Space Speech Bubbles

WorldDialogueUIComponent extends DialogueUIComponent for world-space placement. It positions the dialogue panel above the speaking actor’s entity in 3D space. Use this for over-the-shoulder dialogue or conversations where the camera stays in the world rather than cutting to a UI overlay.


Selection Components

Screen-Space Choices

DialogueUISelectionComponent renders the player’s available choices as a list on the HUD. Each choice is backed by a DialogueSelectButtonComponent entity that is configured with option text and a selection index. When the player activates a button, it fires back through the Bridge to the sequencer.

World-Space Choices

WorldDialogueUISelectionComponent extends DialogueUISelectionComponent for world-space display. Choices appear positioned in 3D space rather than as a HUD overlay, useful for games with diegetic UI.


TypewriterComponent

TypewriterComponent in the O3DE Inspector

The TypewriterComponent reveals a string one character at a time. It fires OnTypeFired for each character revealed and OnTypewriterComplete when the full string is displayed. Use ForceComplete to instantly reveal the remaining text — typically wired to a player skip input — and ClearTypewriter to reset the display to empty.

BusMethod / EventWhat It Does
TypewriterRequestBusStartTypewriter(text)Begins revealing the given string character by character.
TypewriterRequestBusForceCompleteInstantly reveals all remaining characters.
TypewriterRequestBusClearTypewriterClears the display and resets state.
TypewriterNotificationBusOnTypeFiredFires each time a character is revealed.
TypewriterNotificationBusOnTypewriterCompleteFires when the full string has been revealed.

BabbleComponent

BabbleComponent pairs with TypewriterComponent to play short audio sounds for each character revealed, giving the impression of a speaker’s voice. Each actor has a SpeakerBabbleEvents record that maps them to a specific babble sound profile. The component returns the correct BabbleToneEvent for the current speaker via GetBabbleEvent, which is called by the typewriter on each OnTypeFired.

BusMethodWhat It Does
BabbleRequestBusGetBabbleEventReturns the babble audio event for the active speaker.

ScriptCanvas Usage

Forcing Typewriter Completion on Skip Input

Wire a player input action to ForceComplete so pressing a button instantly reveals the current line:

Reacting to Typewriter Events


Quick Reference

NeedBusMethod / Event
Route sequencer events to UIDialogueUIBridgeRequestBusRunDialogue / RunSelection
Register a UI entity with the bridgeDialogueUIBridgeRequestBusRegisterDialogueUI
Close the dialogue UIDialogueUIBridgeRequestBusCloseDialogue
React when dialogue UI finishesDialogueUIBridgeNotificationBusOnDialogueComplete
React when player makes a choiceDialogueUIBridgeNotificationBusOnSelectionComplete
Start typewriter revealTypewriterRequestBusStartTypewriter
Skip / instantly reveal textTypewriterRequestBusForceComplete
Clear the typewriter displayTypewriterRequestBusClearTypewriter
React to each character revealTypewriterNotificationBusOnTypeFired
React to full text revealedTypewriterNotificationBusOnTypewriterComplete
Get babble event for speakerBabbleRequestBusGetBabbleEvent

Glossary

TermMeaning
DialogueUIBridgeThe connector component that routes sequencer events to UI and player input back to the sequencer
TypewriterA text reveal component that displays characters one at a time with configurable timing
BabblePer-character audio playback that simulates a speaker’s voice during typewriter text reveal

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

3.4.2.2 - Performances

How to move and reposition actors during dialogue in GS_Play — the polymorphic performance system, built-in movement types, and async completion.

Performances are polymorphic actions that move or reposition actors during dialogue sequences. The sequencer triggers a performance when it reaches a PerformanceNodeData node and waits for OnPerformanceComplete before advancing. This async model lets multi-step actor choreography complete fully before dialogue continues. Three built-in performance types cover direct movement, navmesh navigation, and instant teleportation. Custom types can be created by extending the base class.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Dialogue Performer Marker component in the O3DE Inspector Performance Node in the Dialogue Editor

 

Contents


How Performances Work

When the sequencer encounters a PerformanceNodeData node, it:

  1. Resolves the named performer entity via DialogueManagerRequestBus → GetPerformer.
  2. Instantiates the performance object specified on the node.
  3. Calls DoPerformance() — the public entry point.
  4. Waits. The sequencer does not advance until the performance broadcasts OnPerformanceComplete.
  5. Resumes from the next node once completion is received.

The performance itself handles the movement logic, monitors completion, and signals back through its notification bus. This means a single PerformanceNodeData can represent any action that takes time — walking to a spot, running a path, playing an animation, triggering a VFX sequence — as long as it broadcasts completion when done.


Built-in Performance Types

Performance TypeMovement MethodWhen to Use
MoveTo_DialoguePerformanceDirect translation to marker(s)Open areas, stylized movement, non-physical traversal.
PathTo_DialoguePerformanceRecastNavigation pathfindingRealistic navigation around obstacles and geometry.
RepositionPerformer_DialoguePerformanceInstant teleport to markerOff-screen repositioning between scenes, no visible movement needed.

All three extend DialoguePerformance, the abstract base class. The base class provides DoPerformance(), ExecutePerformance(), and FinishPerformance() hooks, plus TickBus integration for per-frame movement updates.


MoveTo_DialoguePerformance

MoveTo_DialoguePerformance translates an actor toward one or more named stage markers using direct movement — no obstacle avoidance, no navmesh. It broadcasts movement requests over MoveTo_PerformanceNotificationBus. Listening systems (typically the performer’s movement component) receive those requests and execute the actual translation. When the actor reaches the final marker, the performance calls FinishPerformance() and broadcasts completion.

Use this for stylized games where physical path correctness is less important than snappy, predictable actor placement, or for any case where the path between actor and marker is guaranteed to be clear.

BusPurpose
MoveTo_PerformanceRequestBusQuery state of the performance.
MoveTo_PerformanceNotificationBusReceives move-to-marker requests from the performance.

PathTo_DialoguePerformance

PathTo_DialoguePerformance navigates an actor to one or more named stage markers using the RecastNavigation navmesh. It requests a path from the navmesh, walks the actor along that path, and completes when the actor arrives at the final marker. Use this in games with detailed geometry where actors must walk around walls, furniture, and obstacles rather than moving in a straight line.

RecastNavigation must be enabled in the project and a navmesh must be baked in any level where PathTo performances are used.

BusPurpose
PathTo_PerformanceRequestBusQuery state of the performance.
PathTo_PerformanceNotificationBusReceives path-to-marker requests from the performance.

RepositionPerformer_DialoguePerformance

RepositionPerformer_DialoguePerformance teleports an actor instantly to a named stage marker. It does not animate, translate, or navigate — it sets position directly. Useful for placing actors at the start of a new scene, recovering from a previous sequence that ended far from the required starting point, or repositioning actors that are off-camera and do not need visible movement.

BusPurpose
RepositionPerformer_PerformanceNotificationBusReceives teleport requests from the performance.

Async Completion

All performances signal completion asynchronously. The sequencer does not poll or time out — it simply waits for the performance to call FinishPerformance(), which broadcasts OnPerformanceComplete over the appropriate notification bus. This means:

  • A performance can take any amount of time.
  • A performance can be driven by external events — animation callbacks, physics arrival, etc.
  • Multiple performances can run in parallel if the node graph is structured to fork, because each notifies independently.

When writing custom performance types, always call FinishPerformance() when the action is done. Forgetting to do so will stall the sequencer indefinitely.


Extending with Custom Performances

Custom performance types are discovered automatically through O3DE serialization at startup. Extend DialoguePerformance, reflect it, and your custom type appears in the node editor’s performance picker and can be placed on any PerformanceNodeData node.

See the Framework API reference for the full base class interface and extension guide.


ScriptCanvas Usage

Performances are authored in the dialogue node graph and executed automatically by the sequencer. Most projects do not need to drive performances from ScriptCanvas directly. The common scripting patterns involve the movement side — receiving move or path requests from a performance and executing them on the performer entity.

Receiving a MoveTo Request

[MoveTo_PerformanceNotificationBus → StartMoveToMarker(markerEntity)]
    └─► [Move performer toward markerEntity position]
    └─► [When arrived → MoveTo_PerformanceRequestBus → ReportArrival]

Receiving a Reposition Request

[RepositionPerformer_PerformanceNotificationBus → RepositionToMarker(markerEntity)]
    └─► [Set performer transform to markerEntity transform]

Quick Reference

NeedBusMethod / Event
Receive a MoveTo move requestMoveTo_PerformanceNotificationBusStartMoveToMarker
Receive a PathTo navigation requestPathTo_PerformanceNotificationBusStartPathToMarker
Receive a reposition teleport requestRepositionPerformer_PerformanceNotificationBusRepositionToMarker
Query active MoveTo stateMoveTo_PerformanceRequestBus(see Framework API)
Query active PathTo statePathTo_PerformanceRequestBus(see Framework API)

Glossary

TermMeaning
PerformanceA polymorphic action that moves or repositions an actor during a dialogue sequence
PerformanceNodeDataA dialogue graph node that triggers a performance and waits for completion
Async CompletionThe pattern where the sequencer waits for OnPerformanceComplete before advancing

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

3.5 - GS_Environment

World time and environmental systems for GS_Play — time-of-day progression, day/night cycle management, and data-driven sky configuration.

GS_Environment manages the living world — the passage of time, the shift between day and night, and the visual appearance of the sky. It provides a singleton time authority that other systems subscribe to for world tick events and phase-change notifications, decoupling every time-dependent system from managing its own clock. Sky presentation is data-driven through configuration assets.

For architecture details, component properties, and extending the system in C++, see the GS_Environment API.


Quick Navigation

I want to…FeatureAPI
Control world time, time passage speed, or respond to day/night changesTime ManagerAPI
Define how the sky looks at different times of day with data assetsSky ConfigurationAPI

Installation

GS_Environment requires GS_Core and the Atom renderer. Add both to your project.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Environment gem in your project configuration.
  2. Create a Time Manager prefab and add it to the Game Manager’s Managers list.
  3. Create SkyColourConfiguration assets for your sky appearance.

Time Manager

The Time Manager is the singleton authority for world time. It advances time each tick according to a configurable speed, exposes query and control methods, and broadcasts WorldTick and DayNightChanged events so any system can react to time progression without polling.

Time Manager API


Sky Configuration

The Sky Configuration system defines how the sky looks at different times of day through data assets. SkyColourConfiguration assets hold colour values for dawn, midday, dusk, and night — swappable per-region, per-weather, or per-level without modifying entity hierarchies.

Sky Configuration API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

3.5.1 - Time Manager

How to work with the GS_Play Time Manager — world time, day/night cycles, and time passage control.

The Time Manager is the singleton controller for world time in your project. It drives the time-of-day value, controls how fast time passes, determines day/night state, and broadcasts timing events that other systems (lighting, AI schedules, environmental effects) can react to.

For component properties and API details, see the Framework API reference.

Time Manager component in the O3DE Inspector

 

Contents


How It Works

The Time Manager maintains a continuous time-of-day value. Each frame, it advances the time based on the configured passage speed and broadcasts a WorldTick event. When the time crosses the day/night threshold, it broadcasts DayNightChanged.

You set the main camera reference so the Time Manager can position sky effects relative to the player’s view.


Controlling Time

ScriptCanvas NodeWhat It Does
SetTimeOfDay(time)Sets the current time of day directly.
GetTimeOfDayReturns the current time of day.
SetTimePassageSpeed(speed)Controls how fast time advances (multiplier).
GetWorldTimeReturns the total elapsed world time.
IsDayReturns whether it is currently daytime.
SetMainCam(entity)Sets the camera entity for sky positioning.

ScriptCanvas

Time Manager entity setup in the O3DE Editor


Responding to Time Events

Time Manager entity setup in the O3DE Editor


Time Manager Entity Configuration

Time Manager entity setup in the O3DE Editor

In order to drive many of the environment effects and weather systems, the Time Manager needs many entities and components available to it.

EntityWhat It Does
SkyPivotThis entity holds all the rest of the celestial entities. It’s pivot is the pivot of the planet.
Sun / MoonTwo directional lights, pointed in opposite directions. One is disabled at a time and has it’s colours and intensities handled by the Time Manager sky configuration profile.
Moon BodyA graphic, placed in the sky relative to the moon directional light. Purely Cosmetic.
Planet AxisAn entity that carries celestial bodies at an offset. They spin with the rest of the sky. Purely Cosmetic.
StarsThe stars component. If it’s inside the pivot, the stars follow the rotation of the sky.

Quick Reference

NeedBusMethod / Event
Set time of dayTimeManagerRequestBusSetTimeOfDay(time)
Get current timeTimeManagerRequestBusGetTimeOfDay
Check if daytimeTimeManagerRequestBusIsDay
Change time speedTimeManagerRequestBusSetTimePassageSpeed(speed)
React to time ticksTimeManagerNotificationBusWorldTick
React to day/night changeTimeManagerNotificationBusDayNightChanged

Glossary

TermMeaning
World TimeThe continuously advancing time-of-day value maintained by the Time Manager
Time Passage SpeedA multiplier controlling how fast world time advances each frame
Day/Night PhaseThe current phase (day or night) determined by the time-of-day threshold

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

3.5.2 - Sky Configuration

How to work with GS_Play sky configuration — data-driven sky color settings for time-of-day transitions.

Sky Colour Configuration assets define the visual appearance of the sky at different times of day. These are data assets created in the O3DE Asset Editor that the Time Manager references to drive sky color transitions as time progresses.

For asset structure and property details, see the Framework API reference.

Sky Colour Configuration asset in the O3DE Asset Editor

 

Contents


How It Works

A Sky Colour Configuration asset defines color values for key times of day (dawn, midday, dusk, night). The Time Manager samples the configuration based on the current time of day and interpolates between the defined colors to produce smooth transitions.

Different environments (desert, forest, underwater) can use different configuration assets. Swapping the active configuration changes the sky appearance without modifying entity hierarchies.


Glossary

TermMeaning
SkyColourConfigurationA data asset defining sky color values for dawn, midday, dusk, and night

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

3.6 - GS_Interaction

Physics-based pulse events, proximity targeting with cursors, and data-driven world trigger zones.

GS_Interaction provides three independent but composable systems for making the world respond to entities. Pulsors broadcast typed events via physics volumes, the Targeting system finds and locks onto the best interactable in proximity with a cursor overlay, and World Triggers fire configurable responses from zones and conditions without requiring script code.

For architecture details, component properties, and extending the system in C++, see the GS_Interaction API.


Quick Navigation

I want to…FeatureAPI
Broadcast typed physics events from trigger volumes to receiving entitiesPulsorsAPI
Find and lock onto the best interactable entity in proximity with a cursor overlayTargetingAPI
Fire configurable responses from zones and conditions without scriptingWorld TriggersAPI

Installation

GS_Interaction requires GS_Core, LmbrCentral, LyShine, and CommonFeaturesAtom. Add all required gems to your project before placing Interaction components in a level.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Interaction gem in your project configuration.
  2. Configure physics collision layers for trigger volumes.
  3. Place Pulsor, Targeting, or World Trigger components on entities as needed.

Pulsors

The Pulsor system is a physics-driven event broadcast layer. A PulsorComponent on any entity with a trigger collider emits a typed pulse event when another entity enters or exits that volume. PulseReactorComponents on the receiving entity listen for those events and respond. Pulse types are polymorphic and registered at runtime, so project-specific types can be added without modifying GS_Interaction.

Pulsors API


Targeting

The Targeting system handles “which interactable entity is the player looking at or closest to right now?” A TargetingHandler on the player maintains a live registry of nearby targets, evaluates candidates on demand, and exposes the current target via bus events. Cursor components render a tracking reticle on screen that updates based on the selected target’s properties.

Targeting API


World Triggers

World Triggers are a data-driven system for firing game responses when conditions are met, with no script boilerplate required. TriggerSensor components define the condition side (interact, collider overlap, record check), and WorldTrigger components define the response (set record, toggle entity, change stage, print log). The combination lets most interactive world objects be authored entirely in the editor.

World Triggers API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

3.6.1 - Pulsors

How to work with the GS_Interaction Pulsor system — emitting typed physics-based pulse events and reacting to them from scripts.

The Pulsor system is a physics-driven event broadcast layer. A PulsorComponent lives on any entity with a trigger collider and emits a typed pulse event when another entity enters or exits that volume. PulseReactorComponent instances on the receiving entity listen for those events and respond. Because pulse types are polymorphic and registered at runtime, you can define project-specific pulse types without modifying the Pulsor or Reactor components themselves.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Pulsor component in the O3DE Inspector

 

Contents


How Pulses Work

Pulse Pattern Graph

Breakdown

Pulse Reactor component in the O3DE Inspector

When a physics trigger fires, the PulsorComponent iterates all PulseReactorComponent instances on the entering entity and calls ReceivePulses() on each one that returns true from IsReactor() for the emitted pulse type. Each reactor independently decides whether it handles a given pulse, so multiple reactors on one entity can each respond to a different subset of pulse types without interfering with each other.

StepWhat Happens
1 — Collider overlapPhysics system detects entity entering the Pulsor’s trigger volume.
2 — Pulse emitPulsorComponent reads its configured PulseType and prepares the event.
3 — Reactor queryEach PulseReactorComponent on the entering entity is called with IsReactor() for that type.
4 — ReactionReactors that return true have ReceivePulses() called and execute their response.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Pulse Types

A pulse type is a class derived from the abstract PulseType base. It carries no payload — the type itself is the signal. Reactors match on type identity, so adding a new type automatically makes it distinct from all existing ones.

Built-In Pulse Types

TypePurpose
Debug_PulseTest and development use. Logs or prints when received.
Destruct_PulseSignals that the receiving entity should be destroyed or deactivated.

Reactor Types

A reactor type is a class derived from the abstract ReactorType base. It defines what the receiving entity does when a matching pulse arrives. One entity can carry multiple reactors — one for each pulse type it needs to handle.

Built-In Reactor Types

TypePaired WithBehavior
Debug_ReactorDebug_PulseOutputs a debug message when it receives a matching pulse.
Destructable_ReactorDestruct_PulseHandles entity destruction or deactivation on pulse receipt.

Extending with Custom Pulse Types

Custom pulse and reactor types are discovered automatically through O3DE serialization at startup. Any team or plugin can add new interaction semantics — damage types, pickup types, status effects — by extending the base class and reflecting it, without modifying GS_Interaction itself. Custom types appear automatically as options in the editor’s component property dropdowns and are available to all Pulsors and Reactors in the project.

For the full extension guide and C++ interface, see Framework API: Pulsors and Framework API: Pulses.


Components

ComponentRoleKey Bus
PulsorComponentEmits a typed pulse when its trigger volume fires.—
PulseReactorComponentReceives pulses and executes a reaction if the type matches.PulseReactorRequestBus (ById)

ScriptCanvas Usage

The GS_Core PulseReactorEmissionBus exposes the reactor’s state for script-driven queries (the ScriptCanvas node name is preserved from the pre-interfaces bus). A common pattern is to wait for a Reactor to receive a pulse via OnPulseReceived, then process its incoming data for your own pulse processing.


Quick Reference

NeedBusMethod
Check if entity handles a pulse typePulseReactorRequestBus (ById)IsReactor()
Manually trigger pulse processingPulseReactorRequestBus (ById)ReceivePulses()

Glossary

TermMeaning
PulsorA component that emits a typed pulse event when its trigger volume fires
Pulse ReactorA component that receives pulses and executes a response if the type matches
Pulse TypeA polymorphic class that identifies the kind of pulse being emitted

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

3.6.2 - Targeting

How to work with the GS_Interaction Targeting system — selecting interactable entities, managing cursor display, and handling interaction input from scripts.

The Targeting system answers the question “which interactable entity should the player act on right now?” A GS_TargetingHandlerComponent on the player maintains a live registry of nearby targets. Proximity detection volumes populate that registry automatically as the player moves. When the handler evaluates candidates, it selects the best target and broadcasts the result so the cursor layer and any downstream logic stay in sync.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Targeting Handler component in the O3DE Inspector

 

Contents


How Targeting Works

The targeting pipeline runs across four layers — proximity detection, target registration, target selection, and cursor display. Each layer is a separate component so they can be mixed and matched for different entity configurations.

LayerComponentWhat It Does
Proximity detectionGS_TargetingInteractionFieldComponentPhysics trigger volume on targetable entities. Registers and unregisters with the handler as the player enters and exits range.
Target dataGS_TargetComponent / GS_InteractTargetComponentDefines the target’s visual properties — size, spatial offset, colour, and sprite.
Target selectionGS_TargetingHandlerComponentMaintains the candidate list, evaluates it, and exposes the selected target via GetInteractTarget.
Cursor displayGS_CursorComponent + GS_CursorCanvasComponentReads the selected target’s visual properties and renders a cursor on screen or in world space.

Target Components

GS_TargetComponent is the base marker that makes an entity targetable. It carries four properties the cursor layer reads to display correctly:

PropertyPurpose
SizeHow large the targeting cursor appears over this entity.
OffsetSpatial offset from the entity’s origin, used to position the cursor at a sensible point (e.g. centre of a character’s torso).
ColourTint applied to the cursor sprite when this target is selected.
SpriteThe cursor image to display for this target.

GS_InteractTargetComponent extends GS_TargetComponent with specialised interaction semantics. Use it for any entity that can be interacted with via the InteractInputReaderComponent.


Targeting Handler

The GS_TargetingHandlerComponent is the central coordinator. It tracks all currently-registered candidates and determines which one is the best target at any given moment.

Notifications

Subscribe to GS_TargetingHandlerNotificationBus to react to targeting state changes:

NotificationWhen It Fires
OnUpdateInteractTargetThe selected target has changed. Payload is the new target entity id (or invalid if none).
OnEnterStandbyThe targeting handler has suspended evaluation (e.g. during a cutscene or level transition).
OnExitStandbyThe targeting handler has resumed evaluation.

ScriptCanvas — Listening for Target Changes

ScriptCanvas — Querying the Current Target


Interaction Fields

GS_TargetingInteractionFieldComponent is a physics trigger volume placed on targetable entities. When the player’s collider overlaps the field, it calls RegisterTarget on the handler. When the player leaves, it calls UnregisterTarget. The handler never has to poll — the field layer pushes updates automatically.

The field’s volume shape (sphere, box, capsule) determines the interaction range independently of the entity’s visual or physics collision bounds, so you can have a large detection range with a small visible object.


Cursor System

Interact Cursor component in the O3DE Inspector

The cursor layer visualizes the current target selection on screen. It is composed of two components that work together:

ComponentBusRole
GS_CursorComponentGS_CursorRequestBus (Single)Global cursor coordinator. Manages canvas registration, visibility, offset, and position.
GS_CursorCanvasComponentGS_CursorCanvasRequestBus (ById)Per-canvas cursor. Renders the sprite and responds to hide/show instructions.

ScriptCanvas — Cursor Control

[GS_CursorRequestBus → SetCursorVisuals]
    └─► Updates the sprite and colour from the selected target's properties.

[GS_CursorRequestBus → SetCursorPosition]
    └─► Moves the cursor to track the target's world position.

[GS_CursorRequestBus → HideCursor]
    └─► Hides the cursor when no target is selected or targeting is suspended.

UI Cursor Canvas

GS_CursorCanvasComponent UI canvas configuration in the O3DE UI Editor

A LyShine ui canvas that pairs with the in-world CursorComponent.

The cursor canvas applies visuals to the UI image in behalf of the targeting system.


Interaction Input

InteractInputReaderComponent extends GS_Core::GS_InputReaderComponent to map raw button input to interaction events.

Place it on the same entity as the GS_TargetingHandlerComponent. When the player presses the configured interact button, the reader fires an event the handler can act on to confirm the current selection or trigger the target’s interact action.

This keeps input handling decoupled from targeting logic — you can swap input mappings or replace the reader component without touching the handler.


Quick Reference

NeedBusMethod / Event
Get the current targetGS_TargetingHandlerRequestBus (ById)GetInteractTarget
Manually register a targetGS_TargetingHandlerRequestBus (ById)RegisterTarget
Manually unregister a targetGS_TargetingHandlerRequestBus (ById)UnregisterTarget
Know when the target changesGS_TargetingHandlerNotificationBus (ById)OnUpdateInteractTarget
Know when targeting suspendsGS_TargetingHandlerNotificationBus (ById)OnEnterStandby
Know when targeting resumesGS_TargetingHandlerNotificationBus (ById)OnExitStandby
Hide the cursorGS_CursorRequestBusHideCursor
Update cursor positionGS_CursorRequestBusSetCursorPosition
Update cursor appearanceGS_CursorRequestBusSetCursorVisuals

Glossary

TermMeaning
Targeting HandlerThe coordinator component on the player that maintains the candidate list and selects the best target
Interaction FieldA physics trigger volume on a targetable entity that registers/unregisters with the handler automatically
Target ComponentA marker component that makes an entity targetable with visual properties for the cursor
CursorThe visual overlay that tracks the selected target on screen or in world space

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

3.6.3 - World Triggers

How to work with the GS_Interaction World Trigger system — configuring sensor types and trigger types to create event-driven world responses without scripting.

World Triggers are a data-driven system for firing game responses when conditions are met. A TriggerSensorComponent defines the condition side — physics overlap, player interact, record check. A WorldTriggerComponent defines the response side — changing stage, toggling an entity, writing a record, logging a message. Each component holds an array of polymorphic type objects, so any combination of conditions and responses can be composed on a single entity without scripting.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

 

Contents


How World Triggers Work

World Trigger Pattern Graph

Breakdown

Each trigger is split into two container components. Logic lives in type objects held by each component:

PartRole
TriggerSensorComponentHolds andConditions and orConditions arrays of TriggerSensorType objects. When conditions pass, fires WorldTriggerRequestBus::Trigger on the same entity.
WorldTriggerComponentHolds a triggerTypes array of WorldTriggerType objects. On Trigger(), calls Execute() on every type. On Reset(), calls OnReset().

You can add any mix of sensor types and trigger types — for example, a door that requires both a physics overlap AND a record flag (andConditions) before it opens (TriggerType_ToggleEntities), then sets a quest record (TriggerType_SetRecord).

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Sensor Types

Trigger Sensor component in the O3DE Inspector

Add sensor types to the andConditions or orConditions arrays on a TriggerSensorComponent. The four built-in types cover the most common condition patterns:

Sensor TypeConditionNotes
SensorType_InteractAny unit with targeting interacts with this entity.Requires GS_TargetComponent or GS_InteractTargetComponent on the entity.
SensorType_PlayerInteractThe player-controlled unit specifically interacts with this entity.Extends SensorType_Interact; filters to entities with the "Player" tag.
SensorType_ColliderA physics collider enters the entity’s trigger volume.Automatically activates a PhysicsTriggerComponent — no manual setup needed.
SensorType_RecordA game record reaches a configured value.Connects to RecordKeeper automatically; fires without polling.

Trigger Types

World Trigger component in the O3DE Inspector

Add trigger types to the triggerTypes array on a WorldTriggerComponent. The four built-in types handle the most common response patterns:

Trigger TypeResponseNotes
TriggerType_PrintLogPrints a message to the development log.Development and debugging use.
TriggerType_SetRecordWrites a value to the Record Keeper by name.Useful for setting quest flags, unlock states, or counters.
TriggerType_ToggleEntitiesEnables or disables referenced entities. Seeds initial state at startup via startActive.Works with any entity in the level — doors, pickups, blockers.
TriggerType_ChangeStageTransitions the game to a different stage (level).Queued on next tick; coordinates with Stage Manager automatically.

Reset and Re-Arming

Calling Reset() on WorldTriggerRequestBus calls OnReset() on every trigger type in the component. Each type can reverse its effect or prepare for re-activation:

  • TriggerType_ToggleEntities inverts entity states on both Execute and OnReset, enabling toggle behavior.
  • Switches that cycle on repeat interaction.
  • Area triggers that fire each time a player re-enters.
  • Multi-step sequences where earlier steps re-arm later ones.

ScriptCanvas Usage

WorldTriggerRequestBus exposes both core methods directly to scripts:

A common script pattern is to use a SensorType_Record condition to gate a sequence, then script the reset from a separate event so the trigger only re-arms after additional conditions are met:

You can also bypass the sensor component entirely and call Trigger() directly from any script when you need to fire a world trigger response programmatically — for example, from a dialogue completion callback or an animation event.


Editor Setup Pattern

Most interactive world objects follow this assembly in the editor:

  1. Create an entity for the interactive object (door, switch, collectible, zone border).
  2. Add a TriggerSensorComponent to the entity.
  3. Add the appropriate sensor type(s) to andConditions or orConditions (e.g. SensorType_PlayerInteract for interact, SensorType_Collider for proximity).
  4. Add a WorldTriggerComponent to the same entity.
  5. Add the appropriate trigger type(s) to triggerTypes (e.g. TriggerType_ToggleEntities to open a door, TriggerType_SetRecord to record the event).
  6. Configure each type’s properties in the editor — no scripting needed for these patterns.

For interact-based sensors, also add GS_TargetComponent or GS_InteractTargetComponent so the Targeting system can select this entity.


Quick Reference

NeedBusMethod
Fire a trigger response from scriptWorldTriggerRequestBus (ById)Trigger()
Re-arm a trigger after it has firedWorldTriggerRequestBus (ById)Reset()
Fire sensor evaluation from event-driven typeTriggerSensorRequestBus (ById)DoAction() / DoResetAction()

 

Condition TypeSensor Type
Any unit interactsSensorType_Interact
Player only interactsSensorType_PlayerInteract
Physics overlapSensorType_Collider
Record/flag stateSensorType_Record

 

Response TypeTrigger Type
Log a debug messageTriggerType_PrintLog
Write a game recordTriggerType_SetRecord
Toggle an entity on/offTriggerType_ToggleEntities
Change stage/levelTriggerType_ChangeStage

Glossary

TermMeaning
TriggerSensorComponentContainer component that owns sensor type objects and evaluates them on each event
WorldTriggerComponentContainer component that owns trigger type objects and calls Execute on each Trigger()
TriggerSensorTypeA type object that defines one condition — extend this to create custom sensor logic
WorldTriggerTypeA type object that defines one response — extend this to create custom trigger logic
andConditionsArray on TriggerSensorComponent where all types must pass for the trigger to fire
orConditionsArray on TriggerSensorComponent where any one type passing is sufficient
Re-ArmingCalling Reset() on a WorldTriggerComponent so its types can fire again on the next Trigger()

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

3.7 - GS_Juice

Game feel and feedback motion system — screen shake, bounce, flash, and material effects driven by the GS_Motion engine.

GS_Juice is the game feel and feedback system. It provides motion-based visual feedback effects — screen shake, bounce, flash, pulse, material glow — authored as data assets and played on entities at runtime. GS_Juice extends the GS_Motion animation system with feedback-specific track types, so all timing, easing, and proxy targeting features are available for feedback effects.

For architecture details, track types, and the domain extension pattern, see the GS_Juice API.


Quick Navigation

I want to…FeatureAPI
Play screen shake, bounce, flash, or material glow effects on entitiesFeedback SystemAPI

Installation

GS_Juice requires GS_Core. Add both gems to your project.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Juice gem in your project configuration.
  2. Create .feedbackmotion assets in the O3DE Asset Editor.
  3. Place FeedbackEmitter components on entities that need to play effects.

Feedback System

The feedback system is the core of GS_Juice. Feedback Motion assets (.feedbackmotion) define one or more animation tracks that play together as a feedback effect. The Feedback Emitter component plays a feedback motion on itself or on a target entity. Effects are additive — they modify properties relative to the entity’s current state, so multiple effects stack without conflict.

Two track types are included:

  • Transform Track — Animates position offset, scale, and rotation using gradients. Ideal for screen shake, bounce, squash-and-stretch, and recoil.
  • Material Track — Animates material properties (opacity, emissive intensity, color tint) using gradients. Ideal for hit flash, damage glow, and fade effects.

Feedback System API


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

3.7.1 - Feedback System

How to work with GS_Play feedback motions — transform and material animation effects for game feel.

The Feedback System is the core of GS_Juice. Feedback Motion assets define animation tracks that play together as a visual effect — screen shake, bounce, hit flash, glow pulse. The Feedback Emitter component plays these effects on entities, with support for proxy targeting and stacking.

For track type details, asset structure, and component properties, see the Framework API reference.

FeedbackEmitter component in the O3DE Inspector

 

Contents


Track Types

TrackWhat It AnimatesFields
FeedbackTransformTrackPosition offset, scale, rotationVector2Gradient (XY), FloatGradient (scale), FloatGradient (rotation)
FeedbackMaterialTrackOpacity, emissive intensity, color tintFloatGradient (opacity), FloatGradient (emissive), ColorGradient (tint)

Both track types use Gradients from GS_Core, giving you full control over the animation shape at every point in the effect’s duration.


Feedback Emitter

The FeedbackEmitter component lives on any entity and plays a Feedback Motion.

PropertyWhat It Does
FeedbackMotionThe motion asset and proxy configuration.
playOnActivateIf true, plays the motion automatically when the entity activates.
MethodWhat It Does
Play()Plays the feedback motion on the owning entity.
PlayOnTarget(entityId)Plays on a different target entity.
Stop()Stops the currently playing motion.

Authoring Feedback Motions

  1. Create a .feedbackmotion asset in the O3DE Asset Editor.
  2. Add transform and/or material tracks.
  3. Configure each track’s gradients — these define the animation curve shape.
  4. Set timing (start time, duration) and easing for each track.
  5. Optionally set track identifiers for proxy targeting.
  6. Assign the asset to a FeedbackEmitter component.

Stacking Effects

Feedback effects are additive — each track applies its property change relative to the entity’s current state. Multiple effects on the same entity stack naturally without conflict. A bounce effect and a flash effect can run simultaneously on the same entity.


Proxy Targeting

UiAnimationMotion with proxy bindings configured in the Inspector

When a motion asset has tracks with identifiers (named labels), those tracks appear in the proxy list on the component. Proxies let you redirect a track to a different entity in the hierarchy — for example, a page show animation might animate the background separately from the content panel.

Each proxy entry maps a track label to a target entity. If no proxy is set, the track targets the motion’s owner entity.


Glossary

TermMeaning
Feedback MotionA .feedbackmotion data asset containing animation tracks that play as a visual effect
Feedback EmitterA component that plays a feedback motion on its entity or a target entity
FeedbackTransformTrackA track that animates position offset, scale, and rotation via gradients
FeedbackMaterialTrackA track that animates opacity, emissive intensity, and color tint via gradients

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

3.8 - GS_Item

Inventory and equipment systems for GS_Play — container-based item storage, slot management, stacking, and visual equipment integration with GS_Performer.

GS_Item provides the foundational inventory and equipment infrastructure for GS_Play games. It handles how items are stored, organized, and associated with characters — from simple containers that hold collectables to slot-based equipment systems that drive visual changes through GS_Performer.

For architecture details, component properties, and extending the system in C++, see the GS_Item API.


Quick Navigation

I want to…FeatureAPI
Store, stack, and manage items in container-based inventoriesInventoryAPI
Equip items into typed character slots with visual integrationEquipmentAPI

Installation

GS_Item requires GS_Core. GS_Performer is an optional dependency for visual equipment integration.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Item gem in your project configuration.
  2. Define item data assets for your game’s items.
  3. Place Inventory components on entities that need to store items.
  4. Place Equipment components on characters that need to equip items.

Inventory

The Inventory system is a container-based model for item storage. Each inventory is a collection of item slots where each slot holds an item reference and a stack count. The system handles adding, removing, querying, and capacity management. Any entity can host an inventory — a player backpack, a chest, a vendor, or a loot bag.

Inventory API


Equipment

The Equipment system is the slot-based layer that governs which items a character has equipped. Each slot has a defined type and enforces compatibility rules. Equipment integrates with GS_Performer’s Skin Slot system — equipping an item automatically updates the matching visual asset, connecting inventory state to character presentation.

Equipment API


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

3.8.1 - Inventory

How to work with GS_Play inventory — container-based item storage with slots and stacking.

The Inventory system provides container-based item storage for entities. Inventory components define slot-based containers that hold item data, support stacking, and enable item transfer between entities (player to chest, vendor to player).

For component properties and the inventory data model, see the Framework API reference.

 

Contents


How Inventory Works

ConceptWhat It Means
ContainerA component on an entity that holds item slots. Players, chests, vendors, and loot drops are all containers.
SlotAn individual position within a container. Each slot holds one item type.
StackingMultiple units of the same item can occupy a single slot up to a maximum stack size.
TransferItems move between containers via slot references — no intermediate state required.

Glossary

TermMeaning
ContainerA component on an entity that holds item slots
SlotAn individual position within a container that holds one item type
StackingMultiple units of the same item occupying a single slot up to a maximum count

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

3.8.2 - Equipment

How to work with GS_Play equipment — typed equipment slots with visual integration via GS_Performer skin slots.

The Equipment system provides typed equipment slots for characters. Equipment slots enforce type constraints (helmet slot accepts helmets, weapon slot accepts weapons) and integrate directly with the Skin Slot system in GS_Performer to update visual appearance when items are equipped or unequipped.

For component properties and the equipment data model, see the Framework API reference.

 

Contents


How Equipment Works

PhaseWhat Happens
EquipAn item is placed into a typed slot. Equip notifications fire.
Visual UpdateIf linked to a GS_Performer skin slot, the character mesh updates automatically.
Stat ApplicationEquipment stats are applied to the entity (via RPStats integration, when available).
UnequipThe item is removed from the slot. Visual and stat changes revert.

Integration with Skin Slots

Equipment slots can be linked to Performer Skin Slots. When an item is equipped, the corresponding skin slot’s actor mesh and materials are automatically swapped to match the item. No manual wiring is required — the connection is data-driven through slot name matching.


Glossary

TermMeaning
Equipment SlotA typed slot on a character that accepts items matching its type constraint
Skin Slot IntegrationAutomatic visual update when equipping or unequipping items via GS_Performer

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

3.9 - GS_Performer

Character rendering and animation for GS_Play — modular skin slots, billboard 2.5D rendering, and velocity-driven locomotion parameters.

GS_Performer is the character presentation layer for GS_Play. It handles the visual side of any entity that needs to look like a character — whether that is a fully-rigged 3D mesh swapping equipment, a sprite-based billboard character, or an animated unit whose blend parameters track real movement velocity. GS_Performer sits above GS_Unit and GS_Core, consuming movement and game state events to keep visual output in sync with gameplay.

For architecture details, component properties, and extending the system in C++, see the GS_Performer API.


Quick Navigation

I want to…FeatureAPI
Manage registered performers or query them by namePerformer ManagerAPI
Swap equipment visuals at runtime with modular slot-based assetsSkin SlotsAPI
Render billboard-style 2.5D characters that face the camera correctlyPaper PerformerAPI
Drive animation blend parameters from entity velocity automaticallyLocomotionAPI

Installation

GS_Performer requires GS_Core. EMotionFX and GS_Unit are optional dependencies for animation and velocity-driven locomotion.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Performer gem in your project configuration.
  2. Create a Performer Manager prefab and add it to the Game Manager’s Managers list.
  3. Place PerformerSkinSlotComponent and SkinSlotHandlerComponent on character entities.
  4. For 2.5D characters, add a PaperFacingHandlerComponent.

Performer Manager

The Performer Manager is the singleton coordinator for all registered performers in the active scene. It follows the GS_Core manager pattern — spawned by the Game Manager, participates in standby, and is accessible globally via its request bus. Systems that need to enumerate or query active performers route through this component.

Performer Manager API


Skin Slots

The Skin Slot system is the modular equipment and appearance layer for characters. Each PerformerSkinSlotComponent represents an equipment slot holding an actor asset and material overrides. The SkinSlotHandlerComponent manages the full collection for one character, providing batch operations and appearance-changed notifications. This makes character visual configuration composable rather than monolithic.

Skin Slots API


Paper Performer

The Paper Performer system provides billboard-style 2.5D character rendering. The PaperFacingHandlerComponent rotates the entity’s mesh to face the active camera each frame, accounting for movement direction to prevent visual artifacts from naive always-face-camera implementations. Custom facing logic can be substituted without modifying the core component.

Paper Performer API


Locomotion

The Locomotion feature set connects an entity’s physical movement to its animation layer. The VelocityLocomotionHookComponent reads velocity from the physics system each tick and drives animation blend parameters — speed and direction — that EMotionFX or Simple Motion components consume. Animations stay tightly synchronized with gameplay movement without manual state machine scripts.

Locomotion API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.1 - Performer Manager

How to work with the GS_Play Performer Manager — performer registration and lifecycle.

The Performer Manager is the singleton controller for the performer system. It tracks all active performers in the scene, handles registration and lookup, and responds to the Game Manager’s lifecycle events automatically.

For component properties and API details, see the Framework API reference.

PerformerManager component in the O3DE Inspector

 

Contents


How It Works

Performers register with the Performer Manager when they activate. The manager provides lookup by name so that other systems — dialogue, cinematics, AI — can find performers without maintaining their own references.

The Performer Manager responds to standby and shutdown broadcasts from the Game Manager, coordinating performer state across level transitions.


Quick Reference

NeedBusMethod
Query performer managementPerformerManagerRequestBusManager-level queries

Glossary

TermMeaning
PerformerAn entity that represents a character in the world with visual, animation, and interaction capabilities
Performer ManagerThe singleton that tracks all active performers and provides lookup by name

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.2 - Performers

The character rendering entity types in GS_Performer — billboard 2.5D paper performers and fully-rigged 3D avatar performers.

Performers are the character entity types in GS_Performer. Each type defines how a character’s visual layer is structured and rendered — whether a flat sprite quad that rotates to face the camera, or a fully-rigged 3D mesh driven by EMotionFX. Choose the performer type that fits your character pipeline, then add Performer Features to layer in additional capabilities.

For component details and C++ extension, see the Framework API: Performers.


Paper Performer

The Paper Performer is a billboard-based character type for 2.5D and top-down games. The PaperFacingHandlerComponent orients the entity’s sprite quad toward the active camera each frame, accounting for movement direction to prevent visual artifacts. It is the simplest performer type — lightweight, with no animation graph required.

Paper Performer API


Avatar Performer

The Avatar Performer is the full 3D character pipeline. It drives a rigged EMotionFX actor and integrates with Skin Slots and the Velocity Locomotion Hook for a complete character presentation layer. Use this for characters with full 3D rigs, animation blend trees, and runtime equipment swapping.

Avatar Performer API


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.2.1 - Avatar Performer

How to work with GS_Play avatar performers — 3D pipeline, animation, equipment.

Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.2.2 - Paper Performer

How to work with GS_Play paper performers — billboard-style 2.5D character rendering with camera-aware facing.

The Paper Performer system provides billboard-style character rendering for 2.5D games. The Paper Facing Handler rotates sprite-based characters to always face the active camera while maintaining proper orientation relative to movement direction. This prevents the visual artifacts that occur when a flat sprite is viewed from the side.

For component properties and the facing algorithm, see the Framework API reference.

 

Contents


How It Works

The PaperFacingHandlerComponent is placed on any entity that uses sprite-based or flat-mesh character rendering. Each frame, it calculates the optimal facing direction based on:

  1. The active camera’s position (via CamCore notifications).
  2. The entity’s movement direction.
  3. Configurable facing rules.

The result is a character that always presents its front face to the camera without snapping or popping during movement direction changes.


Camera-Aware Variant

The CamCorePaperFacingHandlerComponent (in GS_Performer) extends the base paper facing with direct camera core integration. It listens to the GS_Core CamCoreEmissionBus for camera position updates, ensuring the facing calculation uses the exact camera state rather than a cached position.


Entity Configuration

Performer Entity Configuration in the O3DE Editor

Note this has been put inside a GS_Unit prefab.


Glossary

TermMeaning
Paper PerformerA billboard-style character that always faces the camera for 2.5D rendering
Paper Facing HandlerThe component that calculates optimal facing direction each frame

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.3 - Performer Features

Universal capabilities that extend any performer type — modular equipment via skin slots, and velocity-driven animation via locomotion.

Performer Features are components that layer onto any performer entity, regardless of type. They are not tied to how a character is rendered — they extend what any performer can do. Skin Slots give any character modular equipment slots. Locomotion connects physics movement to the animation graph automatically.

For component details and C++ extension, see the Framework API: Performer Features.


Skin Slots

The Skin Slot system provides modular equipment and appearance management. Each PerformerSkinSlotComponent represents one equipment position on the character — helm, chest, weapon — holding a swappable actor mesh and material set. The SkinSlotHandlerComponent coordinates all slots on a character and supports preset profiles for NPC archetypes or full equipment sets.

Skin Slots API


Locomotion

The Velocity Locomotion Hook connects entity movement to animation automatically. It reads the entity’s physics velocity each frame and writes blend parameters — speed and direction — directly into the EMotionFX animation graph, driving walk, run, idle, and fall transitions without manual state machine scripting.

Locomotion API


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.3.1 - Skin Slots

How to work with GS_Play skin slots — modular character appearance with swappable actor meshes and materials.

The Skin Slot system provides modular character appearance. Each slot represents an equipment position on a character (head, body, arms, weapon) and holds an actor mesh with its materials. The Skin Slot Handler manages the full collection of slots for a character, enabling runtime equipment swapping through data changes rather than entity restructuring.

For component properties and data structures, see the Framework API reference.

Skin Slot Configuration Profile in the O3DE Asset Editor

 

Contents


How It Works

Each character entity has one SkinSlotHandlerComponent and multiple PerformerSkinSlotComponent children — one per equipment position.

ComponentWhat It Does
SkinSlotHandlerManages the full set of skin slots for a character. Provides batch operations.
PerformerSkinSlotIndividual slot. Holds a SkinSlotData (actor asset + material assets).

When you equip a new item, you update the slot’s SkinSlotData — the system swaps the actor mesh and materials automatically.


Skin Slot Data

FieldWhat It Is
Actor AssetThe 3D mesh for this equipment slot.
Material AssetsMaterials applied to the mesh.
Slot NameNamed identifier for the slot (e.g., “Helmet”, “Chest”, “MainHand”).

Skin Slot Configuration Profiles are preset assets that define complete character appearances — useful for NPC archetypes or equipment sets.


Quick Reference

NeedBusMethod
Access a skin slotPerformerSkinSlotRequestBusPer-slot queries and updates
Manage all slotsSkinSlotHandlerRequestBusBatch operations on all slots

Glossary

TermMeaning
Skin SlotAn equipment position on a character that holds an actor mesh and materials
SkinSlotHandlerThe manager component that coordinates all skin slots on a single character
SkinSlotDataThe data structure containing actor asset, materials, and slot name
Skin Slot Configuration ProfileA preset asset defining a complete character appearance for batch application

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.9.3.2 - Locomotion

How to work with GS_Play velocity locomotion — automatic animation parameter driving from entity velocity.

The Velocity Locomotion Hook automatically drives animation blend parameters from an entity’s velocity. Instead of manually scripting animation state transitions, you attach this component and it reads the entity’s physics velocity each frame, writing the appropriate blend values to the animation system.

For component properties, see the Framework API reference.

Velocity Locomotion Hook component in the O3DE Inspector

 

Contents


How It Works

The VelocityLocomotionHookComponent runs on the tick bus. Each frame it:

  1. Reads the entity’s current velocity from the physics system.
  2. Extracts speed, direction, and vertical velocity.
  3. Writes these as blend parameters to the entity’s EMotionFX animation graph.

This creates a seamless connection between movement and animation — walk, run, idle, and fall transitions happen automatically based on physics state.


When to Use

ScenarioResult
Character walkingSpeed parameter drives walk/run blend
Character idleZero velocity triggers idle state
Character fallingVertical velocity drives fall animation
Character stoppingDeceleration naturally transitions to idle

The component is stateless and lightweight — it reads velocity and writes parameters each frame with no internal state machine.


Glossary

TermMeaning
Velocity Locomotion HookA component that reads entity velocity and writes animation blend parameters each frame
Blend ParameterA named value sent to the animation graph that controls state transitions and blending

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

3.10 - GS_PhantomCam

Priority-based virtual camera management — composable stage pipeline, channel-aware instancing, blend profiles, and spatial influence fields.

GS_PhantomCam is the camera management system for GS_Play. Instead of driving a single camera directly, you place lightweight virtual cameras — Phantom Cameras — in your level. Each Phantom Camera carries a priority value and runs a per-tick stage pipeline that produces its candidate pose. The Cam Core drives the real engine camera to match whichever Phantom Camera currently has the highest priority. Transitions between cameras are animated by Blend Profile assets, and Influence Fields let spatial zones or gameplay states adjust priority dynamically.

For multi-player and split-screen, GS_PhantomCam can also run in a channel-aware mode where each player gets its own arbitration slot — Tier 1 single-cam, Tier 2 single-player rig, Tier 3 multi-channel co-op — without changing the per-cam authoring surface.

For architecture details, component properties, and extending the system in C++, see the GS_PhantomCam API.


Quick Navigation

I want to…FeatureAPI
Manage the camera system lifecycle, channel registries, and dispatch overridesCam ManagerAPI
Place virtual cameras with priority, target routing, and snap / focus statePhantom CamerasAPI
Compose camera behavior from Body, Aim, and Additive stagesStage PipelineAPI
Drive the real camera, run blends, handle mid-blend interruptsCam CoreAPI
Define smooth transitions with custom easing, blend shape, and state inheritanceBlend ProfilesAPI
Create spatial zones that modify camera priorityInfluence FieldsAPI

Architecture

Camera Blend Pattern Graph

Breakdown

Each Phantom Camera publishes a candidate pose every tick by running its Body → Aim → Reposition → Noise pipeline. The Cam Manager arbitrates priority across all registered Phantom Cameras inside a channel (channel 0 covers all single-player projects). When the channel’s winner changes, the Cam Core looks up the best blend for the pair and animates the real camera from the outgoing pose to the new one — optionally inheriting pose state across the transition and applying mid-blend interrupt correction when a new winner appears before the current blend completes.

ScenarioWhich Blend Is Used
Camera A becomes inactive, Camera B was waitingThe Cam Core queries the assigned Blend Profile for an A → B entry.
No entry matches the pairThe Cam Core falls back to its own default blend time, easing, and shape.
A new transition starts before the current one finishesThe Cam Core applies a mid-blend correction window so the cam does not snap.
Blend entry’s Inherit State is setThe outgoing cam’s body publishes a pose snapshot; the incoming cam’s body adopts it before the blend executes.

Because Blend Profiles are assets, the same profile can be shared across many Phantom Cameras. A single edit to the asset changes the feel of every camera that references it.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Installation

GS_PhantomCam requires GS_Core only. Add both gems to your project before placing PhantomCam components in a level.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_PhantomCam gem in your project configuration.
  2. Create a Cam Manager prefab and add it to the Game Manager’s Managers list.
  3. Choose your authoring tier:
    • Tier 1 / 2 (single-player) — Assign a rig prefab to the Cam Manager’s Primary Rig Prefab, or hand-place a Cam Core entity in the level.
    • Tier 3 (split-screen / co-op) — Toggle Enable Instanced Channels on the Cam Manager and populate the Channel Configs list. Detailed walkthrough ships with the Channels & Instancing docs.
  4. Place Phantom Camera entities with desired Body, Aim, and Additive stages.
  5. Create Blend Profile assets for camera transitions.

Cam Manager

The Cam Manager is the singleton camera-system controller, integrated with GS_Core’s manager lifecycle. It handles startup and shutdown, owns per-channel registries (cameras, targets, influences, group targets), runs priority arbitration, dispatches cross-channel cinematic overrides, and orchestrates the engine’s active main view. In multi-channel projects, the Cam Manager also spawns rig prefabs at startup and re-spawns them on stage transitions.

Cam Manager API


Phantom Cameras

A Phantom Camera is a single entity component — GS_PhantomCameraComponent — that carries priority, lens, target routing, snap / focus / blendingOut state, and the stage pipeline slot that gives the cam its behavior. Where previous versions of GS_PhantomCam shipped separate components for each behavior (clamped-look, static-orbit, spline track), all those behaviors are now stage variants on the single base component.

Phantom Cameras API


Stage Pipeline

Every Phantom Camera runs the same per-tick pipeline:

Body → Aim → Reposition additives → Noise additives → Finalize

Authors compose a cam by picking a Body stage (follow, orbit, dynamic orbit, leading-follow, track), an Aim stage (default look, clamped look), and zero-or-more Additive stages (collision, tug listeners, Perlin noise, impulse). The same component becomes a follow cam, an orbital cam, a tracking dolly, or a third-person shoulder cam depending on what is slotted.

For step-by-step composition recipes — third-person shoulder cam, orbital boss cam, tracking dolly — see the recipes page.

Stage Composition Recipes API


Cam Core

Cam Core is the runtime bridge between the Phantom Camera system and the actual O3DE camera entity. It receives the channel’s arbitrated winner from the Cam Manager, looks up the matching Blend Profile entry, optionally runs a state-inheritance handoff between the outgoing and incoming cams, and animates the real camera over the blend duration. When a new transition starts before the current one completes, Cam Core applies a mid-blend correction window so the cam does not snap.

Cam Core API


Blend Profiles

Blend Profiles are .camblendprofile data assets that define how the Cam Core transitions between Phantom Cameras. Each entry specifies a From / To pair, a duration, an easing curve, a blend shape (Linear / Cylindrical / Spherical around a pivot), an inherit-state flag, and a pivot source. Authors share one profile across many cameras for visual consistency, then add per-pair overrides where the feel needs to differ.

Blend Profiles API


Influence Fields

Influence Fields are spatial or global modifiers that dynamically shift camera priority. GlobalCameraInfluence applies a constant priority modifier for its entire active lifetime (typically scoped to a level via the StageData entity); CameraInfluenceField applies a priority modifier when an entity enters a PhysX trigger volume. In multi-channel projects, influences are routed to the channel that owns the entity that triggered them — clean isolation between players in co-op.

Influence Fields API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.1 - Cam Manager

How to work with the GS_PhantomCam manager — enabling and disabling the camera system, and responding to active camera changes.

The Cam Manager is the camera system’s singleton manager. It integrates with GS_Core’s standard manager lifecycle, handles the camera system’s startup and shutdown through the two-stage initialization sequence, and maintains awareness of which Phantom Camera is currently dominant. Any time the active camera changes, the Cam Manager broadcasts a notification so dependent systems can react without polling.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Cam Manager component in the O3DE Inspector

 

Contents


Manager Lifecycle

The Cam Manager is a GS_ManagerComponent and participates in the standard GS_Core startup sequence. It activates alongside all other managers during the two-stage initialization, making it safe to call camera system methods any time after OnStartupComplete.

StageWhat Happens
InitializeCam Manager activates and registers with the camera pipeline.
Startup (OnSetupManagers)Manager is initialized. Safe to query camera state.
Complete (OnStartupComplete)All managers ready. Safe to enable or disable the camera system.

Enabling and Disabling the Camera System

The camera system can be suspended and resumed independently of other gameplay systems. This is used during UI-only states, loading screens, or when a cinematic takes direct control of the camera.

NotificationWhat It Does
EnableCameraSystemActivates the camera system. Phantom Cameras resume competing for priority.
DisableCameraSystemSuspends the camera system. The real camera stops receiving updates from Phantom Cameras.

Both events are on CamManagerNotificationBus. Broadcast them to switch the camera system state from any script or component.


Tracking the Active Camera

The Cam Manager broadcasts SettingNewCam on CamManagerNotificationBus whenever a different Phantom Camera becomes dominant. This fires any time a camera with higher priority activates, a dominant camera deactivates, or a blend resolves to a new target.

Use this event in any system that needs to know which camera is currently active — aiming reticles, world-space UI elements, minimaps, or audio listener positioning.

ScriptCanvas


Suspending the Camera System

ScriptCanvas

Broadcast DisableCameraSystem to suspend camera updates, for example when a cutscene takes direct camera control:

Because DisableCameraSystem and EnableCameraSystem are notifications rather than requests, they are fire-and-forget. Any script can broadcast them.


Changing the Camera Target

ScriptCanvas

The follow target, designated by the CamManager, can be set with Get/Set Target:


Three Authoring Tiers

The Cam Manager exposes three top-level fields that control how the camera system is configured for your project. Most single-player projects use Tier 1; multi-player or split-screen projects use Tier 3.

TierCam Manager configurationWhen to choose it
Tier 1 — Single Camm_primaryRigPrefab set, m_enableInstancedChannels = falseSingle-player projects with a normal camera rig. The default for most games.
Tier 2 — Level-placed CamCoreNo primary rig prefab, m_enableInstancedChannels = falseLegacy single-player flow where the Cam Core is hand-placed in each level.
Tier 3 — Multi-Channelm_enableInstancedChannels = true, m_channelConfigs populatedSplit-screen, local co-op, or cinematic projects that need per-player rigs and arbitration.

For the full step-by-step walkthrough of each tier — including common Tier 3 patterns (shared cinematic cams, hero-perspective cams, cross-channel dispatch) and the lobby flow for variable player counts — see the dedicated Channels page:

Channels & Instancing API


Cam Manager Entity Configuration

Cam Manager entity setup in the O3DE Editor

In Tier 1, assign your rig prefab (containing a Cam Core and any cams that should persist with the manager) to the Cam Manager’s Primary Rig Prefab field. The Cam Manager spawns the rig at startup.

In Tier 2, hand-place the Cam Core entity as a child of the Cam Manager. Any phantom cameras that should persist between stage changes can sit alongside the Cam Core.

In Tier 3, populate m_channelConfigs with one entry per supported player slot — see Channels & Instancing.

Across all tiers, the PossessedUnit event on a player controller is a convenient way to push the camera target the moment the player unit spawns.


Quick Reference

NeedBusMethod / Event
Enable the camera systemCamManagerNotificationBusEnableCameraSystem
Disable the camera systemCamManagerNotificationBusDisableCameraSystem
Set the camera target (single-player)CamManagerRequestBusSetTarget(playerEntityId)
Set a channel’s target (multi-channel)CamManagerRequestBusSetChannelTarget(channelId, entity)
Know when the active camera changes (single-player)CamManagerNotificationBusSettingNewCam(newCamEntityId)
Know when a channel’s active cam changes (multi-channel)CamManagerNotificationBusSettingNewCamOnChannel(channelId, cam)
Spawn / despawn a channel mid-sessionCamManagerRequestBusEnableChannel(id) / DisableChannel(id)
Set the engine’s active main viewCamManagerRequestBusSetActiveChannel(id) or SetActiveCamCore(camCore)

Glossary

TermMeaning
Cam ManagerThe singleton manager for the PhantomCam system that tracks active cameras and broadcasts changes
Dominant CameraThe Phantom Camera with the highest priority that currently drives the real camera

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.1.1 - Channels & Instancing

Concept primer for the PhantomCam channel system — what a channel is, which authoring tier to choose, and the pitfalls to know before authoring.

GS_PhantomCam can be configured at three different tiers depending on what your project needs. Most projects use Tier 1 (single-cam) or Tier 2 (single-player rig). Co-op, split-screen, and per-player-cinematic projects use Tier 3 (multi-channel). The per-cam authoring surface is identical across tiers — what changes is the Cam Manager configuration and how many rigs the system spawns.

This page is a concept primer. For step-by-step Tier 1 / Tier 2 / Tier 3 walkthroughs, common Tier 3 patterns, and the lobby-flow recipe for variable player counts, see the recipes collection.

Channel Tier Configurations (Recipes) API

For architecture details and the full bus surface, see the Framework API reference.

 

Contents


What Is a Channel?

A channel is one player viewpoint slot. Each channel owns:

  • An instantiated rig subtree (one Cam Core + zero or more Phantom Cameras).
  • Its own target binding (typically the player unit for that channel).
  • Its own priority table — arbitration is independent per channel.
  • Its own active influence-field records.

Channel 0 is the implicit default for all single-player flows. You only deal with non-zero channels in Tier 3.

Multi-view rendering is pending. Until the AttachmentImage work lands, the engine renders one channel’s view at a time even when multiple channels are arbitrating internally. The Cam Manager’s active main-view API selects which channel reaches the framebuffer. The arbitration / dispatch / target binding all work today — only the simultaneous-rendering piece is gated.


Choosing a Tier

TierCam Manager configurationWhen to choose itRecipe
Tier 1 — Single Camm_primaryRigPrefab set, m_enableInstancedChannels = falseSingle-player projects with a normal camera rig. The default for most games.Tier 1 walkthrough
Tier 2 — Level-placed CamCoreNo primary rig prefab, m_enableInstancedChannels = falseLegacy single-player flow where the Cam Core is hand-placed in each level rather than spawned from a prefab. Supported but not preferred.Tier 2 walkthrough
Tier 3 — Multi-Channelm_enableInstancedChannels = true, m_channelConfigs populatedSplit-screen, local co-op, or cinematic projects that need per-player rigs and arbitration.Tier 3 walkthrough

You can build a project at Tier 1, then add Tier 3 later by toggling the master gate and authoring channel configs — the per-cam configuration and stage compositions you authored at Tier 1 still work.

The Tier 3 walkthrough recipe also covers common Tier 3 patterns (in-rig per-player cams, shared cinematic collapse cams, hero-perspective cams, cross-channel dispatch) and the lobby flow for variable player counts.


Pitfalls

Tug-field channels are not instancing channels

Tug-field m_channels are arbitrary string tags (“Cinematic”, “Combat”) that match tug volumes to listeners. They are unrelated to the integer ChannelIds on this page despite the shared word. See Tug Fields for the unrelated tagging system.

A cam outside any rig prefab in Tier 3

If you place a Local-scope Phantom Camera in the level (not inside a rig prefab) with multi-channel ON, the stamp-walk misses and the cam falls through to legacy channel 0. This may surprise you. To make a level-placed cam channel-aware in Tier 3, set its scope to TrueUnique with explicit binding.

AllChannels outside a rig prefab

AllChannels-scoped cams outside a rig prefab currently warn and fall back to channel 0. The “out-of-rig runtime cloning” path is deferred. For per-player broadcast cams, place the cam inside the rig prefab.

Cam Manager spawns BEFORE HandleStartup

The Cam Manager spawns configured rigs from OnStartupComplete and only then broadcasts HandleStartup. This means cams inside spawned rigs receive the startup wave alongside everything else — your gameplay code can rely on Cam Cores being registered by the time HandleStartup fires. Stage transitions follow the same pattern: respawn before re-broadcasting HandleStartup.


See Also

Recipes:

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.2 - Cam Core

How to work with GS_CamCoreComponent — the rendering bridge that reads the dominant Phantom Camera each frame and drives the real camera entity.

Cam Core is the rendering bridge between the Phantom Camera system and the actual O3DE camera entity. GS_CamCoreComponent runs on tick, reads the dominant Phantom Camera’s configuration each frame, and writes position, rotation, and field of view directly to the camera entity that the renderer uses. Phantom Cameras define intent — Cam Core executes it.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Cam Core component in the O3DE Inspector

 

Contents


What Cam Core Does Each Frame

On every tick, Cam Core follows this sequence:

StepWhat Happens
1 — Query active cameraReads the current dominant Phantom Camera from the Cam Manager.
2 — Read PhantomCamDataFetches the active camera’s position, rotation, FOV, and clip planes.
3 — Apply blendIf a blend is in progress, interpolates between the outgoing and incoming camera values.
4 — Write to real cameraPushes the resolved transform and FOV to the real O3DE camera entity.
5 — Broadcast positionFires UpdateCameraPosition on CamCoreNotificationBus so dependent systems receive the final camera location.

Because Cam Core owns the final write to the camera entity, it is also the correct insertion point for last-mile adjustments such as screen shake offsets, post-processing overrides, or recoil displacement — applied after blend resolution but before the frame renders.


Responding to Camera Position Updates

CamCoreNotificationBus broadcasts UpdateCameraPosition every frame with the resolved camera transform. Connect to this bus in any system that needs per-frame camera location data.

Use CaseWhy UpdateCameraPosition
Audio listener positioningFollow the camera without polling the camera entity.
LOD or culling controllersReceive camera position reliably after blend is applied.
Shadow caster updatesReact to camera movement each frame.
World-space UI anchoringKnow exactly where the camera landed after Cam Core processed its frame.

ScriptCanvas


Cam Core Setup

Cam Core requires exactly one GS_CamCoreComponent in the level, and a separate entity with an O3DE Camera component that Cam Core is configured to drive. The Camera entity is what the renderer uses — Cam Core holds a reference to it and writes to it each frame.

EntityRequired Components
Cam Core entityGS_CamCoreComponent
Camera entityO3DE CameraComponent (standard)

Assign the Camera entity reference in the Cam Core component’s properties in the editor. Both entities should be present in every level that uses the PhantomCam system.


Querying Cam Core State

Use CamCoreRequestBus to read the current camera state or set the camera entity reference at runtime:

ScriptCanvas

Cam Core state in Script Canvas


Quick Reference

NeedBusMethod / Event
Know the camera position each frameCamCoreNotificationBusUpdateCameraPosition(transform)
Get the real camera entity IDCamCoreRequestBusGetCameraEntity
Get the current resolved FOVCamCoreRequestBusGetCurrentFOV
Know when the active camera changesCamManagerNotificationBusSettingNewCam(newCamEntityId)

Glossary

TermMeaning
Cam CoreThe rendering bridge that reads the dominant Phantom Camera each frame and writes to the real O3DE camera
Blend ResolutionThe process of interpolating position, rotation, and FOV between outgoing and incoming cameras

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.2.1 - State Inheritance

One-checkbox author surface for cam-to-cam pose handoff during blends. When to enable it, what each body type does with it, and the common authoring gotcha.

State inheritance is an opt-in checkbox on each entry in a Blend Profile asset. When the box is ticked, the outgoing camera publishes a snapshot of its pose at the moment of transition, and the incoming camera’s Body stage adopts the snapshot through its own kinematic model. The blend then reads as a smooth drift instead of a swing.

When the box is unticked (the default), the incoming camera starts from its authored ideal — useful for cinematic shots where you want the camera to land at a specific framing regardless of where the outgoing camera was.

For the protocol mechanics, per-stage Get / Adopt implementations, and the matching m_inheritState field, see the Framework API reference.

 

Contents


When to Tick the Box

Tick it when…Don’t tick it when…
The two cams have similar framing intent (both follow the same target) and you want a seamless continuation.The destination cam is a cinematic shot authored at a specific pose.
The destination cam would otherwise “snap to its starting yaw / pitch” in a jarring way.The two cams have unrelated targets and the snap is the desired beat.
You’re blending between two orbital cams and want angular continuity.The destination cam is the first cam in a stage and there’s no source to inherit from.

The default is off so cinematic shots land at their authored poses unless you opt in.


What Each Body Does With It

Inheritance is body-only. The destination’s Aim stage runs fresh on the inherited body pose.

Destination BodyBehavior with InheritState = true
DefaultFollowBodyNo special handling — falls through to the standard visual blend.
OrbitBody (static orbit)Does not adopt — the authored yaw / pitch define the static shot’s identity. (It can be the source for inheritance into another orbit cam, providing clean angular state.)
DynamicOrbitBodyAdopts the source’s yaw / pitch directly when the source publishes angular state (e.g. another orbit body). Falls back to back-deriving yaw / pitch from world position when the source doesn’t. Preserves angular continuity across different orbit shape assets.
LeadingFollowBodyInherits the source’s facing at the body’s preferred standoff distance. The destination cam doesn’t lock to the source’s literal position (which could land off-band); instead it stands at band-natural distance in the direction the source was facing.
TrackBodyProjects the source’s position onto the dolly spline. If the source sits farther than the body’s threshold (default 5 m) from the spline, the adoption is rejected and the cam falls back to a standard blend (no teleport-snap).

The Common Gotcha

If the destination cam looks like it’s snapping to its initial-yaw seed instead of inheriting from the source, check the inherit flag on the matched blend entry. The symptom varies by body type:

BodySymptom of inherit-off
LeadingFollowBody“Cam orients behind player’s travel direction regardless of source facing.”
DynamicOrbitBody / OrbitBodyCam swings to authored yaw / pitch then blends from there.
TrackBodyDolly starts at its untouched starting spline parameter.

All three indicate the same root cause: the blend entry exists in the asset, but InheritState is unticked on it. Fix it in the asset.


Author Steps

  1. Open the relevant .camblendprofile asset in the Asset Editor.
  2. Find or add the blend entry for the From / To pair.
  3. Tick the Inherit State checkbox on that entry.
  4. Save the asset.

That’s the entire author surface. Everything else — pose snapshotting, body-stage Get / Adopt selection, ANGULAR vs POSITION derivation, the path-rejection threshold — happens automatically per body type.

For per-pair authoring at scale, share one blend profile across cams that should behave the same way, and use multiple profiles for cams that need different inheritance defaults.


See Also

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.2.2 - Blend Profiles

How to author .camblendprofile assets to control camera transitions — duration, easing, blend shape, pivot source, and the inherit-state toggle.

Blend Profiles are .camblendprofile data assets that define how the Cam Core transitions from one Phantom Camera to another. Without a Blend Profile, transitions are instantaneous. A well-authored Blend Profile is often the single largest contributor to a camera system feeling polished rather than mechanical.

Each entry in a profile defines a single transition rule — a From / To pair plus the parameters that shape the blend: duration, easing curve, blend shape, pivot source, and whether the destination cam should inherit pose state from the source.

For asset structure details and version semantics, see the Framework API reference.

Cam Blend Profile asset in the O3DE Asset Editor

 

Contents


What a Blend Entry Contains

Each entry in a .camblendprofile defines a single transition rule:

FieldPurpose
From CameraEntity name of the outgoing cam. Blank / "any" matches all.
To CameraEntity name of the incoming cam. Blank / "any" matches all.
Blend TimeDuration of the transition in seconds.
Blend TypeThe easing curve. Same CurveType enum used elsewhere in the framework.
Blend ShapeLinear, Cylindrical, or Spherical. See Blend Shape.
Pivot SourceSource, Destination, or Shared. See Pivot Source. Ignored when Blend Shape is Linear.
Inherit StateOpt-in cam-to-cam pose handoff. See Inherit State.

Camera names correspond to the entity names of your Phantom Camera entities in the scene.


Resolution Order

When a transition occurs, the Cam Core asks the profile for the best entry matching the outgoing → incoming pair. Resolution is by specificity:

TierMatch
1 — ExactBoth From and To camera names match.
2 — Any-to-specificFrom is "any" or blank, To matches.
3 — Specific-to-anyFrom matches, To is "any" or blank.
4 — Default fallbackNo entry matched; the Cam Core uses its own default Blend Time, Blend Type, and Blend Shape configured on the component.

This layered resolution lets you author broad defaults (“any” to “any” at 1.0 seconds with Linear shape and Inherit unchecked) while overriding specific transitions (“MenuCam” → “GameplayCam” at 2.5 seconds with Spherical shape, Pivot Source = Destination, and Inherit on).


Blend Shape

How the cam’s position interpolates between source and destination. Rotation always slerps; the shape only affects position.

ShapeWhen to use it
LinearDefault. Straight-line lerp. The cheapest and most predictable. Right for most transitions.
CylindricalThe two cams sit at similar heights around a shared target, and you want the transition to feel like sweeping around the target. Pitch and radius interpolate independently; the yaw arcs around a pivot.
SphericalThe two cams sit at different heights around a shared target, and you want a full 3D orbital arc. The cam traces a great-circle path around the pivot.

Auto-fallback to Linear. When neither cam reports a pivot (e.g. environmental cams without follow / look-at targets), the blend lerps straight regardless of the authored shape. The pivot resolution gracefully drops back to Linear so you don’t accidentally break a transition by picking a shape that can’t be computed.


Pivot Source

When Blend Shape is Cylindrical or Spherical, the cam arcs around a pivot in world space. The Pivot Source selects which cam’s pivot to use:

Pivot SourceMeaning
SourceThe outgoing cam’s pivot. “Leave A elegantly” — the source cam’s framing reference defines the arc.
DestinationThe incoming cam’s pivot. “Arrive at B elegantly” — the destination’s framing defines the arc.
Shared (default)Midpoint of both pivots. Collapses identically when both cams target the same point.

This field is ignored when Blend Shape is Linear.

A cam that doesn’t publish a pivot (typically a body type that has no target — DefaultFollowBody without a follow target) disqualifies itself from being the pivot source. The Cam Core falls back to whichever cam does publish one, or to Linear if neither.


Inherit State

Setting Inherit State opts the matched pair into the cam-to-cam pose handoff protocol. When enabled, the destination cam’s Body stage seeds itself from the outgoing cam’s pose before the blend runs. The blend then reads as a smooth drift instead of a swing.

For when to use it, per-body behavior, and the common authoring gotcha, see the dedicated guide:

State Inheritance API

Default is unchecked so cinematic shots authored at specific poses land at those poses unless you opt in.


Creating a Blend Profile Asset

  1. Open the Asset Editor in O3DE.
  2. Select New → GS_PhantomCamBlendProfile.
  3. Name the asset descriptively — e.g. CombatBlend, CinematicSoftBlend, CharacterSelectBlend.
  4. Add blend entries via the + button on the Blend List array.
  5. For each entry:
    • Set From Camera name (or leave blank for “any”).
    • Set To Camera name (or leave blank for “any”).
    • Set Blend Time in seconds.
    • Choose Blend Type (easing curve).
    • Choose Blend Shape.
    • Choose Pivot Source (only matters if Shape is not Linear).
    • Tick Inherit State if you want a continuation-feel handoff.
  6. Save the asset.
  7. Assign it to a Cam Core component’s Blend Profile slot.

A starter SampleRigBlend.camblendprofile ships in gs_phantomcam/Assets/ for reference.


Quick Reference

NeedHow
Create a blend profileAsset Editor → New → GS_PhantomCamBlendProfile
Assign a profile to the cam systemSet the Blend Profile slot on the Cam Core component
Make a transition instantLeave the entry’s Blend Time at 0, or don’t add an entry at all (falls back to Cam Core defaults — set those to 0 too if you want all transitions instant).
Share one profile across many camsUse the same asset reference across multiple Cam Core components (Tier 3 / multi-rig)
Edit blend timing project-wideEdit the Blend Profile asset — all consumers update automatically.
Continuation-feel handoff between similar camsTick Inherit State on the matching entry
Orbital arc between two cams sharing a targetSet Blend Shape to Spherical and Pivot Source to Shared

Glossary

TermMeaning
Blend ProfileA .camblendprofile data asset defining per-pair transition parameters.
Blend EntryOne row in a Blend Profile — a From / To pair and the parameters for that specific transition.
Blend ShapeThe geometric shape of the cam’s position interpolation — Linear / Cylindrical / Spherical.
Pivot SourceWhich cam’s pivot is used as the orbital center for non-Linear shapes.
Inherit StateThe opt-in toggle that drives the cam-to-cam pose handoff protocol.

For full definitions, see the Glossary.


See Also

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.3 - Phantom Cameras

How to configure a Phantom Camera — priority, target, lens, stage composition, and snap / focus state.

A Phantom Camera is a virtual camera definition placed in your level as a component on an ordinary entity. It does not render anything. Instead, the GS_PhantomCameraComponent holds priority, lens, target routing, and a stage pipeline slot that authors fill in to give the cam its behavior. The Cam Manager arbitrates priority across all registered Phantom Cameras in a channel — whichever active cam holds the highest priority drives the real camera at any given moment via the Cam Core.

Where previous versions of GS_PhantomCam shipped separate components for each behavior, all of those behaviors are now stage variants layered on top of the single base component. To make a cam follow, orbit, sweep along a spline, or track a group of subjects, you slot a Body stage and an Aim stage — and optionally one or more Additive stages — from the editor’s type-picker.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Phantom Camera component in the O3DE Inspector

 

Contents


How Priority Works

Every GS_PhantomCameraComponent has a base priority. The Cam Manager continuously evaluates priority across all active Phantom Cameras in a channel and routes camera control to the highest. Influences (Influence Fields, gameplay code) can add temporary priority modifiers without touching the base value. When the dominant camera changes, the Cam Manager notifies the Cam Core to begin blending toward the new winner.

ConditionResult
Camera A priority 10, Camera B priority 5 — both activeCamera A is dominant.
Camera A is disabled or deactivatedCamera B becomes dominant immediately (or blends if a Blend Profile entry matches the A → B pair).
Camera A and Camera B both priority 10The most recently activated one is dominant.
No Phantom Cameras are active in a channelThe real camera holds its last known position.

In multi-channel projects, each channel arbitrates independently — player 1 dominance changes do not affect player 2’s view.


PhantomCamData Fields

Each Phantom Camera stores a PhantomCamData record with its lens, priority, and follow target. Follow / look-at offsets, damping halflives, and target-mode resolution live on the stages, not on PhantomCamData.

FieldTypePurpose
Prioritys32Base priority used in arbitration. Higher wins.
FOVfloatField of view in degrees. Applied to the real camera when dominant.
NearClipfloatNear clip plane distance.
FarClipfloatFar clip plane distance.
CamTargetEntityIdThe follow target. May be left empty to inherit the channel target from the Cam Manager.

A Phantom Camera with no CamTarget and no inherited channel target sits at its entity’s authored position until a target is bound.


Stage Composition

The cam’s behavior is the composition of one Body stage, one Aim stage, and zero-or-more Additive stages. Each is picked from the editor’s type-picker on the corresponding slot.

SlotCommon picksWhat it controls
BodyDefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBodyHow the cam follows the target.
AimDefaultLookAtAim, ClampedLookAimHow the cam orients toward the target.
Additives (Reposition)CollisionReposition, TugAimListener, TugBodyListenerPose corrections — collision pushback, magnetic spatial pulls.
Additives (Noise)PerlinNoise, ImpulseNoisePose perturbations — handheld sway, event-triggered shake.

A third-person shoulder cam, for example, slots LeadingFollowBody + DefaultLookAtAim + a CollisionReposition additive. An orbital boss cam slots DynamicOrbitBody + DefaultLookAtAim + a PerlinNoise additive for handheld feel.

For full step-by-step composition recipes, see the recipes page.

Stage Composition Recipes API

Retired components

ClampedLook_PhantomCamComponent, StaticOrbit_PhantomCamComponent, and Track_PhantomCamComponent no longer exist as separate components. Their behavior is now provided by stage variants:

Retired componentReplacement
StaticOrbit_PhantomCamComponentOrbitBody Body stage.
ClampedLook_PhantomCamComponentClampedLookAim Aim stage.
Track_PhantomCamComponentTrackBody Body stage.

AlwaysFaceCameraComponent is unchanged — it is still a billboard helper, not a camera type itself.


Channel Scope

Each Phantom Camera authors a Channel Scope that decides how it participates in the channel system. In single-player projects, leave this at the default (Local) and everything routes through channel 0. In multi-channel projects:

ScopeUse for
Local (default)The cam lives in its rig’s channel. Each spawned rig instance has its own copy.
AllChannelsIn-rig per-player broadcast cam — every spawned rig carries a duplicate.
TrueUniqueA single instance bound to a specific channel, or shared across every channel (e.g. cinematic collapse cams paired with Group Targets).

For the full walkthrough, see Channels & Instancing.


Snap and Focus

A Phantom Camera ticks each frame when any of these is true:

  • m_hasFocus — this cam is the channel’s currently-driven cam.
  • m_alwaysUpdate — authored opt-in to always tick (background-tracking cams).
  • m_blendingOut — this cam is the outgoing source of an in-flight blend.

Outside that condition the cam is fully dormant — it does not tick, its stages do not run, and its body does not integrate input.

The cam automatically snaps to its evaluated ideal pose (bypassing damping for one tick) on:

  • Focus gain.
  • A new target binding (SetCameraTarget).
  • A new group-target binding (SetTargetFocusGroup).
  • An explicit QueueSnapCamera() call.

For synchronous snap (typically only needed by framework code at mid-spawn binding), call SnapCameraNow() instead.


Activating and Deactivating Cameras

Phantom Cameras participate in priority arbitration only while their entity is active. Enable or disable the entity to add or remove a camera from the channel. The transition between dominant cameras is animated by whichever Blend Profile entry matches the outgoing → incoming pair (see Blend Profiles).

ScriptCanvas

To raise a camera’s priority and make it dominant:

Because priority is numeric, you can activate multiple cameras simultaneously and let priority values determine dominance without manually disabling others.


Requesting Camera Data

Use PhantomCameraRequestBus (addressed by the Phantom Camera entity’s ID) to read or write its configuration at runtime, query staged poses, or trigger an impulse on all ImpulseNoise additives.

ScriptCanvas


Quick Reference

NeedBusMethod / Event
Read a camera’s lens / priority dataPhantomCameraRequestBus(id)GetCameraData
Change a camera’s follow targetPhantomCameraRequestBus(id)SetCameraTarget(entityId)
Change a camera’s priorityPhantomCameraRequestBus(id)SetCameraPriority(value)
Snap the camera to its ideal next tickPhantomCameraRequestBus(id)QueueSnapCamera
Fire an impulse on all ImpulseNoise additivesPhantomCameraRequestBus(id)TriggerCameraImpulse(strength)
Make a camera dominant(entity activation)Enable the Phantom Camera entity
Remove a camera from arbitration(entity activation)Disable the Phantom Camera entity
Know when the dominant camera changesCamManagerNotificationBusSettingNewCam (channel 0) or SettingNewCamOnChannel (multi-channel)
Read a stable pose for gameplay codePhantomCameraRequestBus(id)GetStablePose — preferred over GetFinalPose for movement input

Glossary

TermMeaning
Phantom CameraA virtual camera definition that publishes a candidate pose every tick.
Stage pipelineThe fixed Body → Aim → Reposition → Noise order that each cam runs each tick.
Body stageThe component that writes state.position. One slot per cam.
Aim stageThe component that writes state.rotation. One slot per cam.
Additive stageA stackable correction or perturbation that runs after Body / Aim.
PriorityA numeric value that determines which Phantom Camera drives the real camera within its channel.
ChannelOne player viewpoint slot. Channel 0 is the implicit default for single-player.

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.3.1 - Stage Composition

Concept primer for composing camera behavior from Body, Aim, and Additive stages on a single Phantom Camera component.

A Phantom Camera’s behavior is the composition of one Body stage, one Aim stage, and zero-or-more Additive stages. The same GS_PhantomCameraComponent becomes a follow cam, an orbital cam, a tracking dolly, or a third-person shoulder cam depending on what is slotted from the editor’s type-picker.

This page is a concept primer for the composition model. For step-by-step recipes (third-person shoulder cam, orbital player cam, static showcase, first-person clamped look, tracking dolly, group-framing cam) see the recipes collection.

PhantomCam Configurations (Recipes) API

For the per-variant fields and kinematic models, see the Stage Pipeline API reference.

A fully-configured Phantom Camera with body, aim, and additive stages slotted

 

Contents


The Pipeline at a Glance

Each tick, the cam runs:

Body → Aim → Reposition additives → Noise additives → Finalize
SlotWhat it doesWhen you change it
BodyWrites the cam’s position.Different follow behavior — orbit vs. spring-damped follow vs. spline-bound.
AimWrites the cam’s rotation.Different look behavior — free look vs. clamped envelope.
Reposition additivesCorrect the smoothed pose (collision pushback, magnetic tug). Run BEFORE noise.When the cam needs to avoid geometry or be pulled by spatial cues.
Noise additivesPerturb the final pose (handheld sway, event-triggered shake). Run AFTER reposition.Game feel — adding life or punch.

Each stage owns its own damping. There is no separate “smoothing” stage.


Where to Go Next

If you want to…Go here
Configure a specific cam type (shoulder, orbital, static showcase, first-person, tracking dolly, group-framing)PhantomCam Configurations
Add camera shake (handheld feel or event-triggered impact)Camera Noise Configurations and The Basics: Noise & Impulse
Pull the cam toward a vista pointCamera Tug Configurations and The Basics: Tug Fields
Frame multiple subjectsGroup Target Configurations and The Basics: Group Targets
See the full per-variant kinematic modelFramework API: Stage Pipeline

See Also

Recipes for stage composition:

For the full per-variant API and kinematic models:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.3.2 - Noise & Impulse

Author guide for camera shake — the two noise additive stages, preset library, gameplay-code impulse triggering, and the Stable-vs-Final pose readback rule.

Camera shake comes in two flavors, both as Noise additives on a Phantom Camera’s stage list:

  • PerlinNoise — continuous Perlin-noise displacement. Use for handheld feel, idle sway, low-frequency wobble. Always sampling.
  • ImpulseNoise — event-triggered burst, gated by an ADSR envelope. Use for explosions, foot-stomps, gun kicks. Stays silent until you fire it.

Both consume the same .camnoiseprofile asset and the same six-axis Perlin layer model. Stack freely on the same cam — a baseline handheld profile plus an event-triggered impact profile is the canonical combination.

For step-by-step composition recipes (handheld baseline, event-triggered impact, stacked handheld + impact), see the recipes collection.

Camera Noise Configurations (Recipes) API

For the asset reference and per-axis layer details, see Noise Profiles (Framework API). For the stage variants’ authored fields and kinematic models, see Additive Stage Variants (Framework API).

 

Contents


Preset Library

A starter library of .camnoiseprofile assets ships in gs_phantomcam/Assets/Noise Profiles/. Drop one onto a PerlinNoise or ImpulseNoise stage’s Profile slot to get an immediate baseline. Match the preset’s lens family to the cam’s lens for natural feel.

Handheld lens family — pair with PerlinNoise

AssetLens character
Normal_Mild.camnoiseprofileNormal-lens baseline. Balanced position and rotation.
Normal_Intense.camnoiseprofileNormal-lens active handheld. Larger amplitudes.
Telephoto_Mild.camnoiseprofileTelephoto baseline. Tighter angular response, low position.
Telephoto_Intense.camnoiseprofileAggressive telephoto handheld.
Wide_Mild.camnoiseprofileWide-lens baseline. Translation dominates; rotation low.
Wide_Intense.camnoiseprofileAggressive wide-lens handheld.

All-axes shake family — pair with ImpulseNoise or aggressive PerlinNoise

AssetCharacter
6D_Shake.camnoiseprofileEarthquake / impact aftermath — all six axes, high-frequency micro-jitter on top of mid-frequency sway.
6D_Wobble.camnoiseprofileFloaty / dreamlike — all six axes, low-frequency long sway.

Tuning intensity without re-authoring

Per-cam intensity is dialed on the additive stage’s AmplitudeGain (and FrequencyGain for speed). A single Normal_Mild profile can drive a subtle cutscene cam (AmplitudeGain = 0.3) and a frantic chase cam (AmplitudeGain = 1.8) without authoring new assets.

For project-specific characters — gunfire-tuned impulse profiles, ultra-mild cinematic sway, etc. — see Authoring a Custom Profile.


Triggering an Impulse from Gameplay Code

Fire TriggerCameraImpulse(strength) on the cam to play one burst of every ImpulseNoise additive on it.

C++

#include <GS_PhantomCam/Cameras/GS_PhantomCameraBus.h>

// Compute distance-based falloff.
const float strength = AZ::GetClamp(1.0f - distance / radius, 0.0f, 1.0f);

GS_PhantomCam::PhantomCameraRequestBus::Event(
    activeCameraEntityId,
    &GS_PhantomCam::PhantomCameraRequests::TriggerCameraImpulse,
    strength);

strength multiplies each stage’s AmplitudeGain for that specific burst. Pass 1.0 for full profile intensity, smaller for distance falloff, larger to boost.

Author pattern: distance-attenuated impact source

A typical “explosion at world position X” pattern:

const float distance     = (explosionPos - cameraPos).GetLength();
const float falloff      = AZ::GetClamp(1.0f - distance / kEffectRadius, 0.0f, 1.0f);
const float baseStrength = 1.5f;       // for the explosion's overall punch

const float impulseStrength = baseStrength * falloff;

GS_PhantomCam::PhantomCameraRequestBus::Event(
    activeCamId,
    &GS_PhantomCam::PhantomCameraRequests::TriggerCameraImpulse,
    impulseStrength);

Cams outside kEffectRadius receive strength = 0 and ignore. Cams at the explosion’s exact position receive full baseStrength.

Querying the active cam

TriggerCameraImpulse is per-cam, so you need a target cam id. The common pattern is to track the channel’s currently-active cam via SettingNewCam / SettingNewCamOnChannel and store it, or query CamManagerRequestBus::GetActiveCamCore() for the engine’s active view.


Stable vs Final Pose for Gameplay Readback

Cam pose snapshots come in three flavors:

SnapshotWhere it landsUse it for…
Desired posePost Body + Aim, pre-Reposition. Clean ideal.Debug visualization.
Stable posePost Reposition, pre-Noise. Collision-corrected but shake-free.Gameplay code that reads camera facing (e.g. movement-input direction).
Final posePost Noise. What gets rendered.Audio listener, screen-space UI.

The pitfall. If your character moves “where the camera is facing,” and you read GetFinalPose (or the entity’s TransformBus), every shake event will momentarily drag the character around. Use GetStablePose instead — the cam’s pose without noise displacement.

AZ::Transform stable;
GS_PhantomCam::PhantomCameraRequestBus::EventResult(
    stable,
    activeCamId,
    &GS_PhantomCam::PhantomCameraRequests::GetStablePose);

const AZ::Vector3 cameraForward = stable.GetBasisY();   // O3DE forward axis

Authoring a Custom Profile

Camera Noise Profile asset in the O3DE Asset Editor

When the shipped presets don’t fit, author your own:

  1. Open the Asset Editor in O3DE.
  2. Select New and choose CameraNoiseProfile.
  3. For each axis where you want noise (typically translation X / Y and rotation pitch / yaw / roll), add 1–3 layers. Three layers per axis is the typical rhythm: one slow / large layer for sway, one mid layer for flutter, one fast / small layer for micro-jitter.
  4. For each layer set:
    • Amplitude — in meters (position) or degrees (rotation).
    • Frequency — in Hz. Handheld typically 0.1 – 2 Hz; shake / impact 3 – 60 Hz.
    • Phase (optional) — decorrelates layers that share frequency.
  5. Set the Description to record intent and tuning history.
  6. Save the asset.
  7. Assign it to a PerlinNoise or ImpulseNoise additive stage’s Profile slot.

See Noise Profiles (Framework API) for the full per-field reference.


See Also

Recipes:

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.4 - Group Targets

Author guide for GroupTargetComponent — weighted-centroid focal entity. Common use cases, centroid modes, runtime subject management, and pitfalls.

A Group Target is an entity whose world transform is the weighted centroid of a runtime-editable subject list. You point a camera at it like any other target, and the cam frames “where the group is” rather than chasing any single subject. The Cam Manager hosts a name-based registry so cameras can reference a group by string name without holding a direct entity reference.

For step-by-step composition recipes (two-player party cam, combat encounter frame, collapse to single view), see the recipes collection.

Group Target Configurations (Recipes) API

For the full per-field reference and the registry bus, see the Framework API reference.

GroupTargetComponent in the O3DE Inspector

 

Contents


Common Use Cases

Use caseWhat you authorRecipe
Party / co-op cam frames all playersGroup Target with one subject per player; cam uses GroupTarget mode.Two-Player Party Cam
Combat encounter cam frames the fightGroup Target whose subjects are dynamically added / removed as combatants enter / leave.Combat Encounter Frame
Cinematic “collapse to single view” trigger in split-screenGroup Target + a shared TrueUnique cam. When all channels’ rigs converge on the shared cam, UI receives a collapse signal.Collapse to Single View
Aim ahead of a moving formationGroup Target on a moving leader’s “look-ahead” point with subjects weighted by formation position.—

Centroid Modes — Which One to Pick

ModeWhen to use it
WeightedMean (default)Most cases. Each subject contributes by weight. Simple, predictable.
BoundingBoxCenterWhen you want the framing point to track the extent of the group, not just the average position. With WeightBias = 0, the cam centers on the bounding-box midpoint; raising the bias blends toward the weighted mean.
BoundingSphereCenterSame intent as bounding-box but spherical — useful when subjects are evenly distributed in all axes.

The WeightBias slider (0..1) on the bbox / sphere modes lets you tune the geometric / weighted mix to taste:

WeightBiasEffect
0Pure geometric center (ignore weights).
0.5Half geometric, half weighted.
1Pure weighted mean (the bbox / sphere effectively ignored).

Subject Management at Runtime

The full subject-management API is exposed on GroupTargetRequestBus (per-entity addressed by the group’s id). Common operations:

OperationUse
AddSubject(entity, weight)Add a subject.
RemoveSubject(entity)Remove a subject.
SetSubjectWeight(entity, weight)Adjust priority without removing.
SetSubjectEnabled(entity, enabled)Temporary include / exclude without removing — cheaper than Remove + Add since cadence rebind is skipped.
ClearSubjects()Remove all.
SetCentroidMode(mode)Swap mode at runtime — e.g. tighten framing when an encounter starts.
SetSmoothingHalflife(halflife)Tighten or loosen centroid response at runtime.

Cadence (PhysX post-sim vs TickBus) is re-evaluated automatically whenever the subject list changes.


Pitfalls

Empty subject list with DeactivateWhenEmpty = false

If you author m_deactivateWhenEmpty = false and the subject list goes empty (all enemies dead, all players despawned), the group target’s centroid sits at world origin — cams pointed at it will swing to (0, 0, 0). Set the flag to its default true to fall back gracefully to hold-last-pose instead.

Group cam targeting the group’s own entity

If you accidentally add the group target entity itself to its own subject list, you create a feedback loop: the centroid moves toward where it already is, which moves it again, etc. Don’t.

Centroid lag on physics subjects

By default the component uses TickBus cadence. If your subjects are physics-driven and you see the cam lagging by a frame, the component should auto-detect this and switch to PhysX post-simulate cadence — but you can verify with IsEvaluatingPostSim() (returns true when post-sim is bound). If it’s stuck on TickBus, file a bug — the auto-detection should have triggered.


See Also

Recipes:

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.5 - Influence Fields

How GlobalCameraInfluenceComponent and CameraInfluenceFieldComponent shift camera priority globally or within spatial trigger volumes — types, priority stacking, channel-aware routing.

Influence Fields modify the effective priority of Phantom Cameras without changing their base priority values. Two types are available: global components that apply for their entire active lifetime, and spatial components that apply only while an entity is inside a PhysX trigger volume.

In multi-player / multi-channel projects, the spatial form is channel-aware — the influence is routed to the channel that owns the entity that triggered it, so player 1 walking into a field does not affect player 2’s cameras.

For step-by-step composition recipes (boost a cam inside a room, always-on cinematic boost), see the recipes collection.

Influence Field Configurations (Recipes) API

For component properties, the bus signature, and C++ extension guide, see the Framework API reference.

Camera Influence Field component in the O3DE Inspector

 

Contents


Types of Influence Fields

ComponentScopeWhen it applies
GlobalCameraInfluenceComponentGlobalActive for the component’s entire lifetime. Typically placed on the StageData entity so the influence is scoped to a level.
CameraInfluenceFieldComponentSpatialActive while an entity (typically a player) is inside the field’s PhysX trigger volume. Channel-aware.

Both use a CamInfluenceData record to describe what they modify (camera name + influence magnitude). Multiple fields can be active simultaneously — their effects stack additively.


Priority Stacking

When multiple influences are active on the same camera within a channel, their values sum with the cam’s base priority. This lets you build layered authoring:

  • A general fallback priority on the cam itself.
  • A stage-wide global influence that boosts a cinematic cam for the level.
  • A spatial field that boosts further when the player enters a specific room.

The same cam gets base + global + spatial = effective priority during arbitration. Exiting the spatial field drops the spatial term; ending the stage drops the global term.


Channel Routing

The influence bus signature carries two entity arguments:

ArgumentMeaning
sourceEntityThe field volume itself (the entity emitting the influence). Used as the per-channel storage key — multiple overlapping fields applied to the same channel don’t collide.
targetEntityThe entity that triggered the influence (typically the player unit that walked into the trigger). Used as the routing key — the Cam Manager looks up which channel owns this entity.
Resolution resultBehavior
Target is bound to a channelInfluence is stored in that channel’s priority table. Only that channel’s arbitration sees the boost.
Target is not bound to any channelInfluence is silently dropped.

This gives clean isolation between players in co-op. A CameraInfluenceFieldComponent triggered by player 1 only affects player 1’s cameras; player 2’s view is unaffected. It also gives natural behavior for non-player triggers — an enemy walking into a player-cam field doesn’t influence anything.

Global influence in multi-channel projects

GlobalCameraInfluenceComponent has no specific target. In multi-channel projects, the global form currently routes through channel 0 as a fallback. For per-channel global influences, author one Global component per channel that you want to affect.


Quick Reference

NeedComponentHow
Always-on priority boost for a levelGlobalCameraInfluenceComponentAdd to the StageData entity. Scopes the influence to the stage lifecycle.
Priority boost inside a spatial zoneCameraInfluenceFieldComponentAdd with a PhysX trigger collider. Entity entering the volume triggers the boost.
Per-player isolation in co-op(automatic)CameraInfluenceFieldComponent routes by the entity that crossed the trigger — only that entity’s channel sees the boost.
Multiple overlapping fields on the same cam(automatic)Each field is keyed by its source entity, so overlaps stack additively without colliding.

Glossary

TermMeaning
Influence FieldA component that adjusts camera priority dynamically without modifying the cam’s base priority.
Priority StackingInfluences sum with the base priority during arbitration — multiple active influences on the same cam add their values.
Global InfluenceAn unconditional priority modifier active for the component’s lifetime.
Spatial InfluenceA volume-based modifier active while an entity is inside the PhysX trigger volume.
Source EntityThe field volume entity, used as the per-channel storage key.
Target EntityThe entity that triggered the influence, used as the routing key (channel lookup).

For full definitions, see the Glossary.


See Also

Recipes:

Framework API:

Related basics pages:

Related utilities:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.10.6 - Tug Fields

Author guide for tug-field cinematic pulls — three-component model, PhysX layer setup, channel filtering, tuning, and pitfalls.

Tug Fields pull the camera’s pose toward a designated point while the player (or any “proxy” entity) is inside a defined PhysX trigger volume. Use them for environmental cinematography — a railing that pulls the cam’s gaze toward a vista, a doorway that draws the cam toward what’s beyond, a hazard that draws attention as the player passes.

The system is built from three decoupled components — a volume, a source, and a proxy — plus one or two listener stages on the cam. Each piece is small; the combination is flexible.

For step-by-step composition recipes (vista pull, doorway glance, decoupled source / destination), see the recipes collection.

Camera Tug Configurations (Recipes) API

For the per-field reference and the per-tick algorithm, see the Framework API reference.

The single biggest authoring trap: Tug Fields require a specific PhysX collision-layer configuration. If you set up volumes and listeners but nothing happens, check the PhysX layer setup first (PhysX Layer Setup).

 

CameraTugVolumeComponent in the O3DE Inspector

 

Contents


The Three Components

CameraTugSourceComponent in the O3DE Inspector

ComponentWhere it livesWhat it does
CameraTugVolumeComponentLevel entity with a PhysX trigger collider on the TugField layerSpatial gate. Fires per-proxy enter / exit broadcasts when something on the TugProxy layer overlaps.
CameraTugSourceComponentEither the volume entity itself, OR a separate entity (for decoupled source / destination)Configures the geometry — proximity center, falloff radii, falloff curve, destination point.
TugFieldProxyComponentOn the player entity (or a child entity, or the cam entity)Lightweight marker. The PhysX trigger collider on this entity is what overlaps tug volumes.

 

CameraTugProxyComponent on Player Unit

 

Plus on the Phantom Camera

Tug Body & Aim Listener in Phantom Camera Additives Stages

Listener stageWhat it does
TugAimListenerSlerps the cam’s rotation toward the source’s destination point.
TugBodyListenerLerps the cam’s position toward the source’s destination point.

A cam can run either listener, both, or neither.


PhysX Layer Setup

The system filters tug-volume contacts at the PhysX layer level — there is no central registry, and no runtime entity-tag check. If the layer setup is wrong, nothing fires.

One-time project setup

  1. Open PhysX Configuration → Collision Layers.
  2. Create two new layers:
    • TugProxy
    • TugField
  3. Open Collision Groups. Create or edit a collision-group preset such that:
    • TugProxy overlaps only TugField (and not Default, Player, Static, etc.).
    • TugField overlaps only TugProxy.

This pair-only configuration ensures tug volumes fire triggers only for registered proxies, with no accidental cross-talk from normal-world physics.

Verify the pairing. It’s easy to leave TugProxy overlapping Default or Player by accident. If your cam jitters whenever the player walks through doorframes, that’s almost certainly the symptom — tug volumes are firing on the player’s main collider instead of the proxy collider.


Channel Filtering

The volume’s Channels field is a list of arbitrary string tags (“Cinematic”, “Combat”, “Environment”). The listener’s Channels field is the same. The listener responds only to volumes whose channels intersect with its own.

Empty channels on the listener = match all volumes.

Use channels when you have multiple kinds of tug fields in the same world — environmental pulls vs scripted-cinematic pulls — and you want some cams to respond only to certain kinds.

Tug-field channels are NOT instancing channels. Tug-field channels are these string tags. Instancing channels (in Channels & Instancing) are integer per-player viewpoint slots. Despite the shared word, the two systems are unrelated.


Tuning the Pull Strength

A few knobs interact:

KnobOnEffect
StrengthListenerOverall multiplier. 0.5 = half the full influence. Default 1.0.
Inner Weight / Outer WeightSourceThe proximity-to-influence falloff curve’s endpoints. Inner weight at the deadzone (full), outer weight at the boundary (typically 0).
Falloff CurveSourceShapes the ramp between outer and inner. Linear is even; EaseInOutQuadratic engages gently and disengages gently.
Blend HalflifeListenerSmoothing on the influence’s engagement and disengagement. Higher = slower ramps.

If the cam feels “jerky” entering or exiting the volume, raise Blend Halflife on the listener (try 0.5 or higher). If the cam pulls too aggressively at the deadzone, lower the listener’s Strength.


Pitfalls

Nothing fires

Almost always the PhysX layer setup. Verify:

  1. TugProxy and TugField layers both exist.
  2. The collision-group preset has them paired (and only paired).
  3. The player’s proxy collider is on TugProxy.
  4. The volume’s trigger collider is on TugField.

The cam yanks the moment the player enters the volume

Blend Halflife is too low (or zero). The listener’s dormant-to-engaged transition seeds at a zero-displacement point, but if the halflife is near zero the ramp is instant. Raise to 0.2–0.4.

The volume is “inert” — warning at startup

The source resolution failed. Either:

  • m_sourceEntity points at an entity that has no CameraTugSourceComponent. Fix the reference.
  • m_sourceEntity is empty AND the volume’s own entity has no CameraTugSourceComponent. Add one or set m_sourceEntity.

Tug volume and instancing channel confusion

If you’re in a multi-channel project and a tug volume’s m_channels says "P1", this does NOT bind the volume to channel 1. The string is just a tag — name it "Player1ExclusivePull" or use proper Channel Scope on the cam if you want per-player exclusivity.


See Also

Recipes:

Framework API:

Related basics pages:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

3.11 - GS_Stats

RPG statistics and status effect systems for GS_Play — timed and permanent modifiers that alter entity stats and drive reactive gameplay behavior.

GS_Stats provides the statistical and modifier infrastructure for RPG-style character systems. It handles the application, tracking, and expiry of status effects — the layer between raw gameplay events and the numerical state of a character.

For architecture details, component properties, and extending the system in C++, see the GS_Stats API.


Quick Navigation

I want to…FeatureAPI
Apply timed or permanent modifiers to entity stats with stacking and expiryStatus EffectsAPI

Installation

GS_Stats requires GS_Core. Add both gems to your project.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Stats gem in your project configuration.
  2. Define status effect data assets for your game’s modifiers.
  3. Place status effect components on entities that need to receive modifiers.

Status Effects

Status Effects are the primary mechanism for applying timed or permanent modifiers to entities. Each effect defines what it changes, how long it lasts, whether it stacks, and what happens on expiry. The system handles the full lifecycle: application, duration tracking, stack management, and cleanup. Effects are data-driven — designers author new types in the Asset Editor without touching code.

Status Effects API


See Also

For the full API, component properties, and C++ extension guide:


Get GS_Stats

GS_Stats — Explore this gem on the product page and add it to your project.

3.11.1 - Status Effects

How to work with GS_Play status effects — timed modifiers that alter entity stats or behavior.

Status Effects are timed or permanent modifiers that alter entity stats or behavior. They support stacking, expiration, and side-effect triggering — enabling standard RPG mechanics like poison damage over time, temporary buffs, and debuff stacking.

For component properties and the status effect data model, see the Framework API reference.

 

Contents


How Status Effects Work

PhaseWhat Happens
ApplicationA status effect is applied to an entity with a duration and configuration.
ActiveThe effect modifies the entity’s stats or triggers periodic side effects.
StackingIf the same effect is applied again, stacking rules determine whether it refreshes, stacks count, or is rejected.
ExpirationWhen the duration expires, the effect is removed and any cleanup side effects fire.

Glossary

TermMeaning
Status EffectA timed or permanent modifier that alters entity stats or behavior
StackingRules governing how repeated applications of the same effect interact
Side EffectAn action triggered when a status effect is applied, ticks, or expires

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:


Get GS_Stats

GS_Stats — Explore this gem on the product page and add it to your project.

3.12 - GS_UI

The complete UI framework for GS_Play — canvas lifecycle management, single-tier page navigation, enhanced buttons, data-driven animation, and input interception.

GS_UI is the complete user interface framework for GS_Play, built on top of O3DE’s LyShine rendering layer. It provides a singleton manager for canvas lifecycle and focus, a single-tier page navigation architecture, motion-based button animations, a LyShine-specific track animation system authored as data assets, and input interception utilities for pause menus and stage transitions.

For architecture details, component properties, and extending the system in C++, see the GS_UI API.


Quick Navigation

I want to…FeatureAPI
Load and unload UI canvases, manage focus stack, or set startup focusUI ManagerAPI
Navigate between pages, handle back navigation, or cross canvas boundariesPage NavigationAPI
Add button animations or intercept input for UI canvasesUI InteractionAPI
Animate UI elements with position, scale, rotation, alpha, and color tracksUI AnimationAPI
Add a load screen or pause menuWidgetsAPI

Installation

GS_UI requires GS_Core and LyShine. Add both gems to your project before placing UI components.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_UI gem in your project configuration.
  2. Create a UI Manager prefab and add it to the Game Manager’s Managers list.
  3. Create LyShine UI canvases with GS_UIPageComponent on root elements.
  4. Create .uiam animation assets for page transitions and button effects.

UI Manager

The UI Manager is the singleton that owns canvas lifecycle for the entire game session. It loads and unloads canvases by name, maintains a global focus stack, and handles startup focus deterministically. All canvas operations go through the Manager so that cross-canvas navigation and focus transitions remain coordinated.

UI Manager API


Page Navigation is the single-tier architecture that structures all navigable screens. Canvases contain root pages that can parent nested child pages recursively. Navigation is driven through the Page component: NavigateTo, NavigateBack, ChangePage, and ChangeUI handle all routing. Pages play UiAnimationMotion assets on show and hide for data-driven transitions.

Page Navigation API


UI Interaction

Enhanced button animations and input interception. GS_ButtonComponent extends LyShine interactables with UiAnimationMotion-based hover and select animations. GS_UIInputInterceptorComponent captures input events while a canvas is focused, preventing them from reaching gameplay. Both systems work together on interactive canvases.

UI Interaction API


UI Animation

UI Animation extends GS_Motion with eight LyShine-specific tracks: position, scale, rotation, element alpha, image alpha, image color, text color, and text size. Tracks are authored together in .uiam assets and played in parallel at runtime. Page components embed motion instances for show/hide transitions, and a standalone component makes animations available on any UI entity.

UI Animation API


Widgets

Standalone UI components for game-event-driven scenarios outside the page navigation model. Load screens display automatically during stage transitions. Pause menus overlay gameplay and suppress gameplay input while active.

Widgets API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.1 - UI Manager

How to work with the GS_Play UI Manager — canvas lifecycle, focus stack, and cross-canvas navigation.

The UI Manager is the singleton controller for all UI canvases in your project. It loads and unloads canvases, maintains a global focus stack that tracks which canvas the player is currently interacting with, and handles cross-canvas navigation so that opening a pause menu over gameplay UI and returning to it works automatically.

For architecture details, component properties, and extension patterns, see the Framework API reference.

UI Manager component in the O3DE Inspector

 

Contents


How It Works

The UI Manager maintains a global focus stack — a history of which UI canvas has focus. When you open a new canvas (like a pause menu), it pushes onto the stack. When you close it, the previous canvas (gameplay HUD) regains focus automatically.

At startup, the UI Manager loads canvases and waits for root pages to register. One canvas is designated as the startup focus UI — it receives focus automatically when the startup sequence completes.


Loading and Unloading Canvases

ScriptCanvas NodeWhat It Does
LoadGSUI(name, path)Loads a UI canvas from an asset path and registers it by name.
UnloadGSUI(name)Unloads a canvas and removes it from the active map.
FocusUI(name)Pushes a canvas onto the global focus stack and focuses its root page.
NavLastUIPops the current canvas off the focus stack and returns focus to the previous one.
ToggleUI(name, on)Shows or hides a canvas without changing the focus stack.

ScriptCanvas

If you wish to change between canvases, leaving the previous inactive, and enabling the next:

Toggle off your current canvas, then focus the next. Focus automatically handles enabling the canvas for use.

Using Page: NavigateBack will naturally hide the new canvas, and reactivate the past one.


Querying UI State

ScriptCanvas NodeWhat It Does
GetFocusedUIReturns the name of the canvas currently on top of the focus stack.
GetUICanvasEntity(name)Returns the canvas entity for a named UI.
GetUIRootPageEntity(name)Returns the root page entity for a named UI.

ScriptCanvas


UI Input Profile

UI Input Profile asset in the O3DE Asset Editor


Quick Reference

NeedBusMethod
Load a canvasUIManagerRequestBusLoadGSUI(name, path)
Unload a canvasUIManagerRequestBusUnloadGSUI(name)
Focus a canvasUIManagerRequestBusFocusUI(name)
Return to previous canvasUIManagerRequestBusNavLastUI
Show/hide a canvasUIManagerRequestBusToggleUI(name, on)
Get focused canvas nameUIManagerRequestBusGetFocusedUI

Glossary

TermMeaning
UI CanvasA LyShine canvas loaded and managed by the UI Manager
Focus StackA global history of focused canvases — opening a new one pushes, closing pops
Root PageA page registered with the UI Manager as a canvas entry point

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.2 - Page Navigation

How to work with GS_Play page navigation — single-tier page hierarchy, focus management, and transitions.

Page Navigation is the core of the GS_UI system. Pages are the fundamental unit of UI organization — each page represents a screen, panel, or section of your interface. Pages can be root pages (registered with the UI Manager as a canvas entry point) or nested child pages (managed by a parent page). Navigation uses a push/pop focus stack so the system always knows where to return when the player backs out.

For architecture details, the NavigateTo algorithm, and component internals, see the Framework API reference.

Root page entity setup in the O3DE Editor

 

Contents


Page Types

TypeHow It’s ConfiguredWhat It Does
Root Pagem_isRoot = trueRegisters with the UI Manager by name. Acts as the canvas entry point.
Child Pagem_isRoot = falseRegisters with its parent page automatically. Managed by parent’s show/hide logic.

A canvas typically has one root page at the top, with child pages nested beneath it for sub-screens (settings tabs, inventory categories, confirmation dialogs).

Nested pages entity hierarchy in the O3DE Editor

Example of pages within pages to determine UI display structure, and handling changing focus between pages. SettingsWindowPage represents the main options windows. The child pages are the actual different windows that display within the space, changing with side menu changes.

For guides on complex page navigation, see Lesson: Create UI.

 

Required Companion Components

Root page entities require two additional components alongside GS_UIPageComponent:

ComponentWhy It’s Needed
FaderComponentDrives alpha-based fade transitions for show/hide animations.
HierarchicalInteractionToggleComponentDisables input on the entire page subtree when the page is hidden, preventing clicks from reaching invisible elements.

Add both to every root page entity in the UI Editor.


These are the primary methods for controlling page flow:

ScriptCanvas NodeWhat It Does
NavigateTo(forced)Focuses this page. Automatically pushes at the correct ancestor level in the hierarchy.
NavigateBackWalks up the page hierarchy to find the first ancestor with a non-empty focus stack and pops it. If no stack is found, calls NavLastUI on the UI Manager (closes the canvas).
ChangePage(target, takeFocus, forced)Swaps the displayed child page without pushing a new focus stack entry.
ToggleShow(on)Shows or hides this page.
ChangeUI(targetUI, takeFocus, hideThis)Cross-canvas navigation — switches to a different UI canvas.
FocusChildPageByName(name, forced)Focuses a child page by its m_pageName string.

Handling Page Components

Navigating Pages

Changing UIs


Return Policies

Each page has a NavigationReturnPolicy that controls which element gets focus when the page is returned to:

PolicyBehavior
RestoreLastReturns focus to the last interactable the player was on (e.g., resume where you left off).
AlwaysDefaultAlways returns to the page’s default interactable (e.g., always land on “New Game” button).

Page Transitions

Pages support three motion slots for animated transitions:

SlotWhen It Plays
onShowWhen the page becomes visible.
onShowLoopLoops continuously while the page is visible.
onHideWhen the page is hidden.

Each slot takes a .uiam (UI Animation Motion) asset. See UI Animation for track types.


Quick Reference

NeedBusMethod
Navigate to a pageUIPageRequestBusNavigateTo(forced)
Go backUIPageRequestBusNavigateBack
Switch child pageUIPageRequestBusChangePage(target, takeFocus, forced)
Show/hide a pageUIPageRequestBusToggleShow(on)
Switch canvasesUIPageRequestBusChangeUI(targetUI, takeFocus, hideThis)
Focus child by nameUIPageRequestBusFocusChildPageByName(name, forced)

Glossary

TermMeaning
PageThe fundamental UI unit — a screen, panel, or section of the interface
Root PageA page registered with the UI Manager as a canvas entry point
Child PageA page managed by a parent page, navigated to via ChangePage or FocusChildPageByName
NavigationReturnPolicyControls which element gets focus when returning to a page (RestoreLast or AlwaysDefault)

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.3 - UI Interaction

How to work with GS_Play UI interaction — motion-based button animations and input interception for focused canvases.

UI Interaction covers two systems that handle player input at the UI layer: enhanced buttons with motion-based hover and select animations, and the input interceptor that captures input events while a canvas is focused.

For component properties and the full API, see the Framework API reference.

 

Contents


Buttons

GS_ButtonComponent in the O3DE Inspector

GS_ButtonComponent extends LyShine’s interactable system with UiAnimationMotion support. When the player hovers over a button (mouse or gamepad navigation), a configurable animation plays. When the button is selected, a separate animation fires. Both animations use .uiam assets, giving full control over position, scale, rotation, alpha, and color transitions.

Setup

  1. Add a LyShine UiButtonComponent (or other interactable) to your entity.
  2. Add GS_ButtonComponent to the same entity.
  3. Author .uiam assets for hover, unhover, and select states.
  4. Assign the assets to the corresponding fields in the Inspector.

Integration with Page Focus

When page navigation moves focus to a button (via keyboard or gamepad), the button treats focus as a hover event and plays its hover animation. This ensures consistent visual feedback regardless of input method.


Input Interception

UI Input Interceptor coponent in the O3DE Asset Editor

GS_UIInputInterceptorComponent prevents input from leaking to gameplay systems while a UI canvas is active. It uses a GS_UIInputProfile asset to define which input channels to intercept. Intercepted events are re-broadcast on UIInputNotificationBus so UI-specific logic can respond to them.

When the canvas loses focus, the interceptor deactivates automatically and input flows normally back to gameplay.

Setup

  1. Add GS_UIInputInterceptorComponent to the root page entity.
  2. Configure a GS_UIInputProfile asset with the channels to intercept.
  3. Connect to UIInputNotificationBus in any component that should respond to intercepted events.

Integration

Buttons and input interception work together on interactive canvases:

  • The input interceptor blocks gameplay input while a menu is open.
  • Buttons provide visual hover/select feedback driven by UIInputNotificationBus events.
  • The page system routes gamepad focus to the correct button automatically.

Glossary

TermMeaning
GS_ButtonComponentAn enhanced button with GS_Motion-based hover and select animations
Hover AnimationA .uiam motion that plays when the button receives focus or mouse hover
Select AnimationA .uiam motion that plays when the button is activated
GS_UIInputInterceptorComponentComponent that captures input events while a canvas is focused
GS_UIInputProfileData asset defining which input channels to intercept

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.3.1 - Buttons

Redirected — button documentation is now part of UI Interaction.

GS_ButtonComponent is an enhanced button that adds GS_Motion-based animations for hover and select states. For the full guide, see The Basics: UI Interaction.

For component properties and API details, see the Framework API reference.

 

GS_ButtonComponent in the O3DE Inspector

Contents


How It Works

The GS_ButtonComponent connects to LyShine’s interactable notification system. When the interactable reports a hover event, the button plays its hover animation. When it reports an unhover event, the animation reverses or stops. Select works the same way.

Animations are defined as .uiam assets and assigned in the Inspector. Each button can have independent hover and select motions.


Integration with Page Focus

When page navigation moves focus to a button (via keyboard or gamepad), the button treats focus as a hover event and plays its hover animation. This ensures consistent visual feedback regardless of input method.


Handling Button Events

Button Events nodes in the Script Canvas


Glossary

TermMeaning
GS_ButtonComponentAn enhanced button with GS_Motion-based hover and select animations
Hover AnimationA .uiam motion that plays when the button receives focus or mouse hover
Select AnimationA .uiam motion that plays when the button is activated

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.4 - UI Animation

How to work with GS_Play UI animations — motion-based LyShine property animation for pages, buttons, and standalone components.

UI Animation extends the GS_Motion system with 8 LyShine-specific animation tracks. Animations are authored as .uiam (UI Animation Motion) assets in the O3DE Asset Editor and can be used for page transitions, button hover effects, and standalone component animation.

For track type details, the domain extension pattern, and component internals, see the Framework API reference.

UI Animation Motion asset in the O3DE Asset Editor

 

Contents


Available Tracks

Each track animates a single LyShine property over time:

TrackTarget ComponentWhat It Animates
PositionAny LyShine elementPosition offset from anchor
ScaleAny LyShine elementElement scale
RotationAny LyShine elementElement rotation
Element AlphaAny LyShine elementWhole-element transparency
Image AlphaUiImageComponentImage-specific transparency
Image ColorUiImageComponentImage color tint
Text ColorUiTextComponentText color
Text SizeUiTextComponentFont size

Multiple tracks can run simultaneously within a single motion — for example, a page show animation might fade in (Element Alpha) while sliding up (Position) and scaling from 90% to 100% (Scale).


Where Animations Are Used

ContextHow It’s Assigned
Page transitionsAssigned to onShow, onShowLoop, and onHide slots on GS_UIPageComponent.
Button statesAssigned to hover and select slots on GS_ButtonComponent.
Standalone playbackAssigned to UiAnimationMotionComponent on any entity.

Authoring Animations

  1. Create a new .uiam asset in the O3DE Asset Editor.
  2. Add tracks for the properties you want to animate.
  3. Configure timing, easing curves, and property values for each track.
  4. Optionally set track identifiers for proxy targeting.
  5. Assign the asset to a component slot in the Inspector.

Proxy Targeting

UiAnimationMotion with proxy bindings configured in the Inspector

When tracks have identifiers (named labels), they appear in the motion’s proxy list. Proxies let you redirect a track to a different entity in the UI hierarchy — for example, animating a background panel separately from a content area within the same page transition.


Quick Reference

NeedHow
Animate a page transitionAssign .uiam assets to onShow/onHide slots on the page
Animate a button hoverAssign .uiam asset to the button’s hover slot
Play an animation on any entityAdd UiAnimationMotionComponent and assign a .uiam asset
Target a child elementUse proxy entries to redirect tracks
Loop an animationEnable loop on the motion asset

Glossary

TermMeaning
UI Animation Motion (.uiam)A data asset containing LyShine-specific animation tracks for UI elements
UiAnimationMotionComponentA standalone component for playing UI animations on any entity
Proxy TargetingRedirecting a track to a different entity in the UI hierarchy via named labels

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.12.5 - Widgets

How to work with GS_Play UI widgets — load screens, pause menus, and other standalone UI components.

Widgets are standalone UI components that handle specific game scenarios outside the page navigation model. They activate and deactivate in response to game events rather than player navigation — a load screen appears during a stage transition, a pause menu overlays gameplay when the player pauses.

For component properties and the full API, see the Framework API reference.

 

Contents


Load Screen

GS_LoadScreenComponent shows a loading canvas during stage transitions and hides it when loading is complete. Place it on the Game Manager entity and configure a LyShine canvas to use as the loading screen.

GS_LoadScreenComponent in the O3DE Inspector

The component listens for StageManagerNotificationBus events and drives the canvas visibility automatically — no ScriptCanvas or manual triggers required.


Pause Menu

PauseMenuComponent handles the pause overlay. It listens for the configured pause input action and toggles a designated pause canvas on and off, broadcasting events so other systems know to yield control.


Glossary

TermMeaning
WidgetA standalone UI component that activates in response to game events rather than player navigation
Load ScreenA canvas displayed during stage transitions, managed by GS_LoadScreenComponent
Pause MenuAn overlay canvas displayed when the game is paused, managed by PauseMenuComponent

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

3.13 - GS_Unit

The character and entity control system — unit registration, player and AI controllers, input processing, and modular movement.

GS_Unit is the complete character control system for GS_Play. It handles how entities are registered as controllable units, which controller possesses each unit, how raw player input is captured and converted into intent, and how that intent is translated into physical movement through a modular stack of movers, grounders, and influence fields.

For architecture details, component properties, and extending the system in C++, see the GS_Unit API.


Quick Navigation

I want to…FeatureAPI
Register units, track active controllers, or spawn units at runtimeUnit ManagerAPI
Possess and release units with player or AI controllersControllersAPI
Capture raw input and convert it into structured movement intentInput DataAPI
Move characters with movers, grounders, and movement influence fieldsMovementAPI

Installation

GS_Unit requires GS_Core. Add both gems to your project before using any Unit components.

For a full guided walkthrough, follow the Simple Project Setup guide.

 

Quick Installation Summary

  1. Enable the GS_Unit gem in your project configuration.
  2. Create a Unit Manager prefab and add it to the Game Manager’s Managers list.
  3. Configure physics collision layers for character movement.
  4. Place a Player Controller and Unit entity in your level.

Unit Manager

The Unit Manager is the singleton coordinator for everything unit-related. It handles registration of new units as they spawn, maintains an indexed list of all active units queryable by name or ID, and provides the spawn interface for runtime unit creation. It also participates in the standby system for clean level transitions.

Unit Manager API


Controllers

Controllers are the possession layer — they determine which entity a given intelligence (player or AI) currently owns and drives. Player and AI controllers share the same possession interface, so swapping control mid-game or having both active simultaneously requires no special-casing.

Controllers API


Input Data

The Input Data feature set converts hardware signals into structured intent your movement code can consume. The PlayerControllerInputReader reads raw input events, InputData components store the current state, and Input Reactors compute movement vectors from keyboard or joystick input with deadzones and scaling.

Input Data API


Units

Unit overview description, leads to movement.

Units API


See Also

For the full API, component properties, and C++ extension guide:

For step-by-step project setup:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.1 - Unit Manager

How to work with the GS_Unit manager — spawning units, registering player controllers, and coordinating standby across all controlled characters.

The Unit Manager is the global coordinator for all units in a GS_Play project. It maintains a registry of every active unit entity, provides the interface for spawning new units at runtime, tracks which player controllers are currently active, and participates in the GS_Core standby system so that all controlled characters pause cleanly during level transitions.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Unit Manager component in the O3DE Inspector Unit Manager entity setup in the O3DE Editor

 

Contents


What the Unit Manager Does

The Unit Manager runs as a singleton that all other unit systems report to. When a unit entity activates, it registers itself so the manager can track it by name and ID. When gameplay code needs a new unit spawned, it asks the manager — not the spawning system directly — so the manager can assign a unique name and fire the appropriate notification.

ResponsibilityDescription
Unit registryEvery active unit registers and deregisters automatically.
SpawningIssues RequestSpawnNewUnit and fires ReturnNewUnit when the entity is ready.
Player controller trackingStores which controllers are registered as player-driven so multi-actor setups stay consistent.
Standby coordinationBroadcasts EnterStandby and ExitStandby to pause and resume all units during transitions.
Unit validationCheckIsUnit lets any system confirm whether an entity is a registered unit.

Spawning Units

How Spawning Works

Spawning a unit is a two-step async operation. Your script calls RequestSpawnNewUnit, the manager creates the entity using the prefab spawner, and when the unit is fully initialized the manager fires ReturnNewUnit on UnitManagerNotificationBus with the new entity ID and its assigned unique name.

You should always listen for ReturnNewUnit rather than attempting to use the entity immediately after the request, because spawning can take one or more frames.

StepWhat Happens
1 — RequestCall RequestSpawnNewUnit with the prefab reference and desired spawn transform.
2 — WaitThe manager spawns the entity and runs its initialization.
3 — ReceiveReturnNewUnit fires on UnitManagerNotificationBus with the entity ID and name.

ScriptCanvas — Spawning a Unit


Registering Player Controllers

Player controllers must register themselves with the Unit Manager on activation so the game always knows which controllers are active. This is handled automatically by GS_PlayerControllerComponent, but if you are building a custom controller, call RegisterPlayerController in your activation logic.

[On Entity Activated (custom controller)]
    └─► [UnitManagerRequestBus → RegisterPlayerController(selfEntityId)]

This registration matters most for split-screen or multi-player setups where the manager needs to route input correctly across multiple simultaneous controllers.


Checking Unit Identity

Any system can confirm whether an arbitrary entity is a registered unit without holding a direct reference to the Unit Manager:

ScriptCanvas NodeReturnsNotes
CheckIsUnit(entityId)booltrue if the entity is an active registered unit.

ScriptCanvas

[UnitManagerRequestBus → CheckIsUnit(targetEntityId)]
    └─► bool
            ├─► true  — proceed with unit-specific logic
            └─► false — entity is not a unit, skip

Standby Mode

The Unit Manager participates in GS_Core’s global standby system. During level transitions or other blocking operations, the Game Manager signals standby and the Unit Manager relays that signal to all units. Units should pause movement, suspend input processing, and halt any tick-driven logic while in standby.

Listen on UnitManagerNotificationBus to coordinate your own logic with unit standby:

EventWhen It FiresWhat to Do
EnterStandbyBefore a level transition or blocking operation begins.Pause all movement, halt ticks, stop animations.
ExitStandbyAfter the blocking operation completes.Resume movement, re-enable ticks.

ScriptCanvas


Quick Reference

NeedBusMethod / Event
Spawn a new unitUnitManagerRequestBusRequestSpawnNewUnit(prefab, transform)
Know when a unit is readyUnitManagerNotificationBusReturnNewUnit(entityId, uniqueName)
Register a player controllerUnitManagerRequestBusRegisterPlayerController(entityId)
Check if entity is a unitUnitManagerRequestBusCheckIsUnit(entityId)
Know when standby beginsUnitManagerNotificationBusEnterStandby
Know when standby endsUnitManagerNotificationBusExitStandby

Glossary

TermMeaning
Unit ManagerThe singleton manager that tracks all active units, handles spawning, and coordinates standby
Unit RegistryThe internal list of every active unit entity, indexed by name and ID
StandbyA pause state broadcast to all units during level transitions or blocking operations

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.2 - Controllers

How to work with GS_Unit controllers — the possession model, player and AI controller setup, and switching control at runtime.

Controllers are the possession layer of GS_Unit. A controller is the intelligence — human input or AI logic — that owns and drives a unit at any given moment. Because both player and AI controllers share the same possession interface, your unit entities do not need to know what is controlling them, and swapping control at runtime requires no special-casing.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Player Controller and Input Reader components in the O3DE Inspector

 

Contents


The Possession Model

Unit Possession Pattern Graph

Breakdown

Every unit has exactly one controller at a time, or no controller at all. Possession is established by calling Possess on the unit and released by calling DePossess. Ownership changes are observed through the GS_Core cross-gem contract PossessionEmissionBus — the controller fires OnPossessedUnit / OnReleasedUnit on its own entity and the unit fires OnPossessedByController / OnReleasedByController on its entity — so other systems can react.

ConceptDescription
PossessionA controller attaches to a unit. The unit accepts input and movement commands from that controller only.
DePossessionThe controller releases the unit. The unit halts input processing and enters a neutral state.
Possession eventsFire on the GS_Core PossessionEmissionBus (addressed by entity ID) whenever a unit’s controller changes.
GetControllerReturns the entity ID of the current controller, or an invalid ID if none.
GetUniqueNameReturns the string name assigned by the Unit Manager when this unit was spawned.

Controller Types

GS_Unit ships three controller components that cover the full range of typical game use:

ComponentPurpose
GS_UnitControllerComponentBase class establishing the possession contract. Extend this for custom controllers.
GS_PlayerControllerComponentHuman-driven controller. Connects into the Input Data pipeline and routes player intent to the possessed unit.
GS_AIControllerComponentNPC controller. Provides the entry point for AI logic to issue movement commands through the same unit interface as player input.

Both GS_PlayerControllerComponent and GS_AIControllerComponent extend the base controller, meaning any code that works with the base controller interface works transparently with either type.


Player Controller

GS_PlayerControllerComponent registers itself with the Unit Manager on activation, making it visible to the game’s controller tracking system. It connects to the Input Data pipeline on the possessed unit so that raw input events are routed correctly as soon as possession is established.

Setup

  1. Add GS_PlayerControllerComponent to a controller entity (not the unit entity itself).
  2. Ensure GS_InputDataComponent and GS_PlayerControllerInputReaderComponent are on the unit entity.
  3. Call Possess to attach the controller to the target unit.

ScriptCanvas — Possessing a Unit


AI Controller

GS_AIControllerComponent gives AI logic the same possession interface as the player controller. An AI behavior system possesses a unit, then issues movement commands through UnitRequestBus or directly drives the unit’s Input Data component to simulate input. The unit processes those commands through the same mover stack it would use for player input.

ScriptCanvas — Handing a Unit to AI

// Called from your behavior tree or AI activation event when the NPC starts acting
[AI behavior activates]
    └─► [UnitRequestBus(unitEntityId) → Possess(aiControllerEntityId)]

[PossessionEmissionBus(unitEntityId) → OnPossessedByController(aiControllerEntityId)]
    └─► [AI logic begins issuing movement / action commands]

Switching Controllers at Runtime

Because possession is a simple attach/detach operation on the unit, switching from player to AI control (or back) is two calls:

[Trigger: cutscene starts, character incapacitated, etc.]
    └─► [UnitRequestBus(unitEntityId) → DePossess]
            └─► [UnitRequestBus(unitEntityId) → Possess(aiControllerEntityId)]

[Trigger: cutscene ends, character recovers, etc.]
    └─► [UnitRequestBus(unitEntityId) → DePossess]
            └─► [UnitRequestBus(unitEntityId) → Possess(playerControllerEntityId)]

The DePossess call ensures the previous controller’s input pipeline is disconnected before the new controller attaches, preventing stale input state from leaking between ownership changes.


Querying Controller State

ScriptCanvas NodeReturnsNotes
GetController (on unit)EntityIdCurrent controller entity. Invalid ID means no controller.
GetUniqueName (on unit)stringThe name assigned to this unit at spawn time.

ScriptCanvas


Standby and Controllers

When the Unit Manager broadcasts EnterStandby, both player and AI controllers should stop issuing commands. Player controllers automatically halt input reading. AI controllers must be suspended by whatever behavior system drives them. On ExitStandby, normal command flow resumes.

EventController Action
UnitEnteringStandbyHalt input reads, suspend AI commands.
UnitExitingStandbyResume input reads, restart AI commands.

Both events fire on UnitNotificationBus addressed by unit entity ID.


Quick Reference

NeedBusMethod / Event
Attach a controller to a unitUnitRequestBus (by ID)Possess(controllerEntityId)
Release a controller from a unitUnitRequestBus (by ID)DePossess
Know when possession changesPossessionEmissionBus (GS_Core, by ID)OnPossessedByController(controllerEntityId) on the unit; OnPossessedUnit on the controller
Get the current controllerUnitRequestBus (by ID)GetController
Get a unit’s nameUnitRequestBus (by ID)GetUniqueName
Know when unit enters standbyUnitNotificationBus (by ID)UnitEnteringStandby
Know when unit exits standbyUnitNotificationBus (by ID)UnitExitingStandby

Glossary

TermMeaning
PossessionThe act of a controller attaching to a unit and becoming its active intelligence
DePossessionReleasing a controller from a unit, halting its input processing
Player ControllerA human-driven controller that routes input from the Input Data pipeline to a unit
AI ControllerAn NPC controller that issues movement commands through the same unit interface as player input

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.3 - Input Data

How to work with the GS_Unit input pipeline — reading player input, converting keyboard and joystick signals into movement vectors, and chaining input reactors.

The Input Data system is the pipeline that converts raw hardware signals into structured intent your movement code can consume. It sits between the active input profile and the mover stack, so controllers, movement components, and action systems all read from a single consistent source rather than polling hardware directly. Swapping input profiles at runtime — for remapping, controller switching, or accessibility options — requires no change to any downstream component.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Input Reactor component in the O3DE Inspector

 

Contents


Pipeline Overview

Unit Input Handling Pattern Graph

Breakdown

The input pipeline has three stages. Each stage is a separate component on the unit entity, and they run in order every frame:

StageComponentWhat It Does
1 — ReadGS_PlayerControllerInputReaderComponentReads raw input events from the active input profile and writes them into GS_InputDataComponent.
2 — StoreGS_InputDataComponentHolds the current frame’s input state — button presses, axis values — as structured data.
3 — ReactThe Input Reactor componentClassifies the event’s phase and dispatches it to the reaction types in its profile, which produce intent.

All reactor components downstream of the store stage read from the same GS_InputDataComponent, so there is no duplicated hardware polling and no risk of two reactors seeing different input states for the same frame.


Input Data Component

GS_InputDataComponent is the shared state store. It does not read hardware — it only holds the values written by the Reader and consumed by Reactors.

Add it to any unit entity that needs to process input. A unit should have exactly one GS_InputDataComponent. All input reactors on the same entity read from it automatically.


Input Reader

GS_PlayerControllerInputReaderComponent connects the unit to the game’s active input profile. It receives input events from the profile and writes the resulting state into the unit’s GS_InputDataComponent each frame.

When the player controller possesses the unit, the reader activates. When the unit is depossessed or control is handed to an AI controller, the reader goes silent and the Input Data component retains its last-written state until overwritten.

Setup

Add GS_PlayerControllerInputReaderComponent to the unit entity alongside GS_InputDataComponent. No additional configuration is required — it picks up the active input profile automatically.

Script Canvas - Enabling and Disabling Input Groups

Input Groups handling nodes in the O3DE Script Canvas


Input Reactors

Reactors turn named input events into movement intent. They are a GS_Core mechanism — your unit carries an Input Reactor component holding a reactor profile asset, and the profile lists the reactions that unit has.

GS_Unit ships three reaction types:

Reactor typeWhat it does
KeyboardMovement_InputReactorFour directional events to movement. Releasing one direction while holding the opposite re-routes rather than stopping.
JoypadMovement_InputReactorTwo analog stick axes to movement, so how far you push is how fast you go.
Jump_InputReactorSets the jump state on press, clears it on release.

Each one has editable event name fields, which must match the event names in your input profile.

A typical player unit has all three in one profile. The keyboard and joypad reactors coexist happily — they listen to different events, so the unit responds to whichever device the player picks up, with no separate prefabs.


Adding Custom Reactors

To handle input the shipped reactors do not cover — sprinting, interaction, ability activation — you have two options.

For one-off behaviour, listen instead of extending. The reactor component emits every event it dispatches on a notification bus, so a ScriptCanvas graph or another component on the unit can respond to "Interact" without any C++ at all.

For a reusable reaction, write a reactor type. Derive from the reactor base, declare which event names you listen to and what to do when they fire, and reflect it. It then appears in the reactor profile’s type dropdown alongside the shipped ones — there is nothing to register.

For the extension guide, see Framework API: Input Reactors.


Quick Reference

NeedComponentNotes
Store input state on a unitGS_InputDataComponentRequired on every unit that processes input.
Read from active input profileGS_PlayerControllerInputReaderComponentActivates automatically when player controller possesses.
Keyboard → movement vectorKeyboardMovement_InputReactorComponentReads WASD / arrow keys from input data.
Joystick → movement vectorJoyAxisMovement_AxisReactorComponentReads axis pair, applies deadzone and scale.
Custom button or axis handlerExtend GS_InputReactorComponent (C++)Reads from GS_InputDataComponent same as built-ins.
Inspect movement vectorGS_InputDataComponent → GetMovementVectorVector3, current frame’s movement intent.

Glossary

TermMeaning
Input Data ComponentThe shared state store that holds the current frame’s input values for a unit
Input ReaderThe component that reads raw input events from the active input profile and writes them into the Input Data Component
Input ReactorA component that reads from the Input Data Component and produces intent values (movement vectors, action triggers)
Movement VectorA normalized 3D direction produced by input reactors and consumed by movers

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.4 - Units

What makes an entity a unit — the GS_UnitComponent, entity configuration, collision setup, possession, standby, and movement.

A “unit” in GS_Play is any entity that can be possessed by a controller and driven through gameplay. The GS_UnitComponent is the marker that transforms an ordinary entity into a unit — it provides the possession interface, unique naming, standby awareness, and automatic registration with the Unit Manager.

Units are the characters, vehicles, creatures, or any other controllable actors in your game. They do not contain decision-making logic themselves — that comes from the controller that possesses them. A unit provides the body: movement, collision, and visuals. The controller provides the brain: player input or AI logic.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

Unit component in the O3DE Inspector

 

Contents


How Units Work

Registration

When a GS_UnitComponent activates, it registers itself with the Unit Manager. The manager tracks all active units and responds to CheckIsUnit queries. When the component deactivates, it unregisters automatically.

Possession

Units are possessed by controllers through the UnitRequestBus:

  1. A controller calls Possess(controllerEntityId) on the unit.
  2. The unit stores the controller reference and fires OnPossessedByController on the GS_Core PossessionEmissionBus (unit entity).
  3. The controller can now drive the unit’s subsystems (movement, actions, etc.).
  4. Calling DePossess() clears the controller reference and releases the unit.

Standby

Units participate in the standby system for clean level transitions. When the Unit Manager signals standby, the unit broadcasts UnitEnteringStandby on its notification bus. Child components — movers, input reactors, and others — listen for this signal and pause their processing. UnitExitingStandby reverses the process.


Entity Configuration

A minimal unit entity requires:

  1. GS_UnitComponent — Registers the entity as a unit and provides the possession interface.
  2. Movement components — At least a mover and optionally a grounder for ground detection.
  3. PhysX collider — For physics interaction and ground detection raycasts.

A fully featured unit entity typically includes:

ComponentRole
GS_UnitComponentMarks the entity as a unit; handles possession and standby.
GS_MoverContextComponentCoordinates movers and grounders; holds the Movement Profile.
A mover (e.g. GS_3DSlideMoverComponent)Defines how the unit moves each frame.
A grounder (e.g. GS_PhysicsRayGrounderComponent)Detects surface contact and reports ground state.
PhysX Rigid Body + ColliderPhysics presence in the world.
Mesh or Actor componentVisual representation.

 

Entity Arrangement

Unit entity setup in the O3DE Editor

Entity arrangement


Collision Setup

Units require properly configured PhysX collision layers to interact with the environment and other units. If you have not set up collision layers yet, refer to the Simple Project Setup guide.

Typical collision layer assignments:

LayerPurpose
Unit layerThe unit’s own collider. Collides with environment geometry and other units.
Ground detection layerUsed by grounder raycasts. Collides with terrain and walkable surfaces only.

Movement

The Movement feature set is a composable stack where each component handles one concern of character locomotion. Movers define core behavior (free, strafe, grid, slide), the MoverContext manages movement state and selects the active mover, Grounders report ground state, and Influence components apply external forces like wind or currents.

Movement API


Unit Action Graph

The Unit Action Graph is a visual HFSM editor for authoring character behavioral logic — separate from and complementary to the Movement components. While Movers handle locomotion physics, the Unit Action Graph defines what the character is doing (Idle, Walk, Attack) and when it transitions between those states. Behavior is organized into parallel layers, each an independent state machine running simultaneously.

Unit Action Graph API


Quick Reference

NeedBusMethod
Assign a controller to this unitUnitRequestBus (ById)Possess(controllerEntityId)
Release the current controllerUnitRequestBus (ById)DePossess()
Get the possessing controllerUnitRequestBus (ById)GetController()
Get the unit’s unique nameUnitRequestBus (ById)GetUniqueName()

 

EventBusFired When
OnPossessedByControllerPossessionEmissionBus (GS_Core)A controller takes possession of this unit. Fires on the unit entity.
UnitEnteringStandbyUnitNotificationBusThe unit is entering standby (level transition).
UnitExitingStandbyUnitNotificationBusThe unit is exiting standby and resuming.

Glossary

TermMeaning
UnitAny entity with a GS_UnitComponent — the controllable body in gameplay
ControllerThe intelligence (player or AI) that possesses and drives a unit
PossessionThe act of a controller claiming ownership of a unit via Possess()
StandbyA paused state units enter during level transitions, coordinated by the Unit Manager
Unique NameAn identifier generated at activation used to look up a unit by name

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.4.1 - Mover Context

Movement state coordinator for GS_Unit — manages movement modes, context states, input axis transformation, and movement profile priority.

GS_MoverContextComponent is the coordinator that sits above the movers and owns the movement state for a unit. It does not move the character itself — it decides which mover runs, transforms input into the correct movement vector, and exposes state to every other system that needs to know what the character is doing.

Add exactly one GS_MoverContextComponent to any unit that uses movers.

For architecture details, component properties, and extending the system in C++, see the Framework API: Mover Context.

Mover Context component in the O3DE Inspector

 

Contents


Movement Modes

The Mover Context uses three independent mode tracks — movement, rotation, and grounding — each holding a named string. Only the mover or grounder whose assigned mode name matches the active mode on that track will run. All others stay dormant.

TrackControlsExample modes
MovementWhich mover applies velocity each frame"Free", "Slide"
RotationWhich component rotates the character"Free", "Locked"
GroundingWhich grounder runs surface detection"Free", "Disabled"

The three tracks are independent — changing the movement mode does not reset rotation or grounding.

Reverting Modes

Every mode change stores the previous value. Calling RevertToLastMovementMode, RevertToLastRotationMode, or RevertToLastGroundingMode restores it. This is how the Slide mover hands control back to Free movement after a slope is cleared — without needing to know what mode was active before.


Context States

Context states are named key/value pairs stored on the Mover Context. Any component can write or read them via MoverContextRequestBus. They provide a lightweight shared blackboard for movement systems to coordinate without direct dependencies.

Built-in StateValuesMeaning
"grounding"0 — FallingNo surface contact.
1 — GroundedStable surface contact.
2 — SlidingSurface too steep to stand. Slide mover will activate.
"StopMovement"1 — ActiveHalts all mover velocity processing for this frame.

The grounder writes "grounding" each frame. The Slide mover writes it back to 0 or 1 when the slope clears. Your own companion components can write custom states in the same way.


Input Axis Transformation

The Mover Context processes raw input through two transformations before handing it to the active mover.

StepMethodWhat It Does
Raw inputSetMoveInputAxisWritten by the input reactor. Screen-space direction.
1 — Camera-relativeModifyInputAxis()Rotates the axis to align with the active camera’s facing.
2 — Ground-projectedGroundInputAxis()Projects onto the contact surface to prevent skating above or below terrain.

Movers read the ground-projected axis. Both intermediate values are available via bus if a component needs an earlier stage.


Movement Profile Priority

The Mover Context resolves which GS_UnitMovementProfile governs the unit’s locomotion parameters at runtime using a priority stack:

  1. Influence fields — Any MovementInfluenceFieldComponent or GlobalMovementInfluenceComponent that has added a profile to the unit. Highest priority. Multiple influences are stacked additively.
  2. Global profile — The scene-level default from GlobalMovementRequestBus. Applied when no influence overrides are active.
  3. Default profile — The profile assigned directly to the GS_MoverContextComponent. Used as the final fallback.

To change a unit’s movement feel in a specific area, place a MovementInfluenceFieldComponent with a volume shape and assign an alternate profile to it. The unit’s own component property does not need to change.


Script Canvas Examples

Changing movement mode:

Script Canvas nodes for changing movement mode

Reverting to the previous movement mode:

Script Canvas nodes for reverting movement mode

Setting a context state flag:

Script Canvas nodes for setting context state


Quick Reference

NeedBusMethod
Change active movement modeMoverContextRequestBusChangeMovementMode(modeName)
Restore previous movement modeMoverContextRequestBusRevertToLastMovementMode()
Read the ground-projected inputMoverContextRequestBusGetGroundMoveInputAxis()
Write a context stateMoverContextRequestBusSetMoverState(name, value)
Read a context stateMoverContextRequestBusGetMoverState(name)
Push a movement profile overrideMoverContextRequestBusAddMovementProfile(influencer, profile)
Remove a profile overrideMoverContextRequestBusRemoveMovementProfile(influencer)
Listen for mode changesMoverContextNotificationBusMovementModeChanged, GroundingModeChanged
Listen for state changesMoverContextNotificationBusMoverStateChanged

Glossary

TermMeaning
Mover ContextThe coordinator component that owns movement state, selects the active mover, and transforms input each frame
Movement ModeA named string on a mode track that determines which mover or grounder is active
Context StateA named key/value pair on the Mover Context used as a shared blackboard between movement components
Input AxisThe movement direction written by input reactors, transformed through camera-relative and ground-projected stages
Movement ProfileA data asset storing locomotion parameters (speed, acceleration, jump force) for a unit

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.4.2 - Movement

How to work with GS_Unit movement — mover types, mover context, grounders, movement profiles, and influence fields.

The Movement system is a composable stack of components that each handle one concern of character locomotion. Movers define the core behavior, grounders report surface contact, the Mover Context manages which mover is active and what movement state the character is in, influence fields apply external forces, and Movement Profiles store the parameters that govern all of it. You assemble what you need — most characters use two or three components — and replace individual pieces without touching anything else.

For architecture details, component properties, and extending the system in C++, see the Framework API reference.

 

Contents


How it Works

Movers & Grounders Pattern Graph

Breakdown

Movers and Grounders are multiple components on a Unit that are constantly changing activation based on the units state. When a mover is active, it freely processes the unit’s movement based on it’s functionality. At any moment, through internal or external forces, the Movement, Rotation, or Grounding state can change, which disables the old movers, and activates the new one to continue controlling the Unit.

Input Reader
    ↓  InputReactorRequestBus::FeedInputEvent
GS_InputReactorComponent
    ↓  dispatches by event name and phase
Reaction types  (KeyboardMovement / JoypadMovement / Jump)
    ↓  MoverContextRequestBus::SetMoveInputAxis
GS_MoverContextComponent
    ↓  ModifyInputAxis() → camera-relative
    ↓  GroundInputAxis() → ground-projected
    ↓  MoverContextNotificationBus::MovementModeChanged("Free")
GS_3DFreeMoverComponent  [active when mode == "Free"]
    ↓  AccelerationSpringDamper → rigidBody->SetLinearVelocity
GS_PhysicsRayGrounderComponent  [active when grounding mode == "Free"]
    ↓  MoverContextRequestBus::SetGroundNormal / SetContextState("grounding", ...)
    ↓  MoverContextRequestBus::ChangeMovementMode("Slide")  ← when slope too steep
GS_3DSlideMoverComponent  [active when mode == "Slide"]

The Slide mover activates when the Unit is walking on too steep an angle. It takes control over the unit, slides down the hill, then restores the previous movement behaviour.


Mover Types

Mover component in the O3DE Inspector

Each mover defines one locomotion behavior. A unit has one active mover at a time, selected by the Mover Context. All movers read from the unit’s movement vector (produced by the input pipeline) and translate it into entity movement each frame.

ComponentBest ForHow It Moves
GS_3DFreeMoverComponentFlying, swimming, zero-gravityUnconstrained motion in all three axes. No ground contact required.
GS_3DSlideMoverComponentGround-bound characters on uneven terrainSlides the character along the contact surface, maintaining ground contact smoothly.
GS_PhysicsMoverComponentCharacters needing full collision responseDrives an O3DE physics rigid body. Forces and impulses apply normally.

Choosing a Mover

  • Use GS_3DFreeMoverComponent for any character that needs to move through air or water without ground contact.
  • Use GS_3DSlideMoverComponent for most ground-bound characters. It handles slopes and steps without requiring physics simulation.
  • Use GS_PhysicsMoverComponent when the character must interact physically with the world — pushing objects, being knocked back by forces, or responding to physics joints.

Mover Context

GS_MoverContextComponent sits above the movers and coordinates everything: it selects which mover runs each frame, owns the movement mode tracks, transforms raw input into camera-relative and ground-projected vectors, and manages the movement profile priority stack.

Add exactly one to any unit that uses movers.

Mover Context →


Grounders

Grounder component in the O3DE Inspector

Grounders detect whether the character has contact with a surface and report that information to the Mover Context. Without a grounder, the Context cannot distinguish ground-bound from airborne states.

ComponentMethodNotes
GS_GrounderComponentBase class — extend for custom detection logic.Not used directly.
GS_PhysicsRayGrounderComponentFires a downward raycast each frame.Suitable for most characters. Configurable ray length and collision layer.

Add one grounder to a unit that uses GS_3DSlideMoverComponent or any mover that needs ground awareness. GS_3DFreeMoverComponent does not require a grounder because it never needs ground contact.


Movement Profiles

Movement Profile asset in the O3DE Asset Editor

GS_UnitMovementProfile is a data asset that stores all locomotion parameters for a unit configuration: walk speed, run speed, acceleration, deceleration, air control, jump force, and any mover-specific values. The Mover Context component holds a reference to a profile asset, so multiple prefabs can share the same profile or each have their own without duplicating inline values.

Creating a Profile

  1. In the O3DE Asset Browser, right-click and select Create Asset → GS_UnitMovementProfile.
  2. Configure the parameters in the asset editor.
  3. Assign the asset to the Movement Profile slot on GS_MoverContextComponent.

Changing a profile asset at runtime allows you to alter all locomotion parameters at once — for example, switching a character between normal and encumbered movement profiles without touching individual component properties.

Reading Movement Profile Data - ScriptCanvas


Influence Fields

Movement Influence component in the O3DE Inspector

Influence components dynamically apply priority that change a units dominant movement profile. Priority values are additive — a unit accumulates priority values from all active sources and evaluate the final priority to determine the acting movement profile.

ComponentScopeTypical Use
GlobalMovementInfluenceComponentAll units globallyStandard movement for the region.
MovementInfluenceFieldComponentUnits inside a spatial volumeOpen fields, narrow channels, indoors and outdoors transitions.

GlobalMovementInfluenceComponent is placed once in the level, usually on the Stage Data entity. MovementInfluenceFieldComponent uses a shape component to define its volume — place one wherever you need to change movement priority.


Assembly Guide

A typical ground-bound player character uses this component combination:

ComponentRole
GS_InputDataComponentHolds input state — movement vector written here each frame.
GS_PlayerControllerInputReaderComponentReads input profile and fills GS_InputDataComponent.
KeyboardMovement_InputReactorComponentConverts keyboard input to movement vector.
JoyAxisMovement_AxisReactorComponentConverts joystick input to movement vector.
GS_PhysicsRayGrounderComponentDetects ground contact below the character.
GS_MoverContextComponentCoordinates grounders and movers, holds the Movement Profile.
GS_3DSlideMoverComponentMoves the character along the contact surface.

A flying or swimming character replaces the grounder and slide mover with GS_3DFreeMoverComponent and removes the grounder entirely.


Quick Reference

NeedComponentNotes
Free 3D movement (fly / swim)GS_3DFreeMoverComponentNo ground contact required.
Surface-hugging ground movementGS_3DSlideMoverComponentSmooth contact on slopes and steps.
Physics-simulated movementGS_PhysicsMoverComponentFull rigid body collision response.
Coordinate movers and stateGS_MoverContextComponentRequired on any unit with movers.
Raycast ground detectionGS_PhysicsRayGrounderComponentUsed with slide and physics movers.
Shared locomotion parametersGS_UnitMovementProfile (asset)Referenced by GS_MoverContextComponent.
Global force on all unitsGlobalMovementInfluenceComponentStandard movement values.
Localized force in a volumeMovementInfluenceFieldComponentNarrow paths, roughage, indoors to outdoors.

Glossary

TermMeaning
MoverA component that defines one locomotion behavior (free flight, surface sliding, physics-driven)
Mover ContextThe coordinator that tracks movement state and selects the active mover each frame
GrounderA component that detects surface contact and reports ground state to the Mover Context
Movement ProfileA data asset storing all locomotion parameters (speed, acceleration, jump force) for a unit
Influence FieldA component that applies additive priority to a units active movement profile

For full definitions, see the Glossary.


See Also

For the full API, component properties, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

3.13.4.3 - Unit Action Graph

How to open the Unit Action Graph editor, author parallel behavior layers, set up states and transitions, and add conditions.

The Unit Action Graph is a visual editor for authoring character behavioral logic as a Hierarchical Finite State Machine (HFSM). Where the Movement system handles how a character moves — velocity, surface contact, physics — the Unit Action Graph defines what the character is doing: idle, walking, attacking, interacting, and when to switch between those states.

Behavior is authored in a .unitaction file as a set of parallel layers, each an independent state machine that evaluates simultaneously. A typical setup has a Movement layer, an Action layer, and a Rotation layer all running at once.

For component properties and C++ extension, see the Framework API reference.

 

Contents


Opening the Editor

From the O3DE Editor menu bar: GS Tools > Unit Action Graph Editor.

  • File > New — Creates a new .unitaction container file
  • File > Open — Opens an existing .unitaction file

All layers for one character type live in a single file. Save with File > Save (Ctrl+S).


The Editor Layout

The editor has two main areas:

Layer Sidebar (left dock) — Lists all layers in the file. Use the toolbar buttons to add, remove, rename, or duplicate layers. Click a layer name to open it as a graph tab.

Graph Canvas (center) — The state machine for the selected layer. States are nodes; transitions are arrows drawn from the edge of one state to the edge of another.

Inspector (right dock) — Shows properties for the selected state or transition.

Variable Panel (dock) — Declare named variables shared across all layers.


Working with Layers

Adding a Layer

  1. Click Add in the Layer Sidebar
  2. Name the layer (e.g., "Movement", "Action", "Rotation")
  3. Set its Priority in the Layer Details panel — higher values override lower ones when resolving conflicts

Each new layer starts with a single Entry node and a blank canvas.

Layer Priority

When layers share variables to communicate, priority determines which layer’s state outputs take precedence. A higher-priority Action layer can suppress a lower-priority Movement layer’s output when a blocking action is playing.


States and Transitions

Adding States

Drag a State node from the Node Palette onto the canvas. Name it to match the behavior it represents: Idle, Walk, Run, Attack.

The Entry Node

Every layer has exactly one Entry node — auto-spawned and non-deletable. Draw a transition from the Entry node to whichever state the machine should start in.

Drawing Transitions

Click and drag from the edge of one state to the edge of another. A transition arrow appears between them.

  • Connections go from the perimeter of one state to the perimeter of the target
  • Multiple transitions from the same source are allowed
  • When several transitions are valid simultaneously, the one with the highest priority fires

Inspecting a Transition

Click a transition arrow to select it. The Inspector shows:

FieldDescription
PriorityInteger. Higher values fire first when multiple transitions are valid.
ConditionsList of conditions. All must pass for the transition to fire. Add via the type picker dropdown.

Inspecting a State

Click a state node to select it. The Inspector shows the node’s properties and a Transition Summary below — a list of every outgoing transition with priority and condition count, and a Select button to jump to that connection’s inspector.

Compound States

A Compound State node contains its own nested state machine. Use them for behaviors with internal sub-states — for example, a Jump state that internally tracks Rising, Apex, and Falling. The nested graph starts from its own Entry node when the compound state is entered.


Transition Conditions

Conditions gate when a transition fires. Add them in the Inspector when a transition is selected — click the type picker dropdown and choose a condition type. All conditions in the list must pass for the transition to be valid.

Built-in Conditions

ConditionWhen It Fires
VariableCompareConditionA named variable satisfies a comparison (==, !=, <, >, <=, >=) against a set value
TimeElapsedConditionThe character has been in the current state for at least N seconds
VariableTrueConditionA named boolean variable is true

Condition-Free Transitions

A transition with no conditions fires as soon as the current state’s tick completes. Use a condition-free transition from an animation state to its follow-up state when you want an unconditional sequence.


Using Variables

Variables are named values declared in the Variable Panel dock. They are shared across all layers in the file — any layer can read or write them.

Common uses:

  • "speed" (Float) — set by the input system each frame, read by movement transitions
  • "isGrounded" (Bool) — set by the grounder, read by jump/fall transitions
  • "isAttacking" (Bool) — set by the Action layer, read by Movement to restrict options
  • "stateTime" (Float) — the auto-variable __stateTime tracks how long the current state has been active

Declaring a Variable

  1. Open the Variable Panel
  2. Click Add
  3. Set the name and type (Bool, Int, Float, String, etc.)
  4. Optionally set a default value

Variables can also be set from gameplay code once runtime integration is available.


Parallel Layers in Practice

All layers evaluate simultaneously on every tick. Because they share variables, they can communicate cleanly:

Example: Attack while running

Movement layer (priority 0):
  Idle → Walk  (condition: speed > 0.1)
  Walk → Run   (condition: speed > 4.0)

Action layer (priority 1):
  None → Attack  (condition: attackInput == true)
  Attack → None  (condition: TimeElapsed >= 0.8)

Both layers run at the same time. The character walks or runs independently of whether an attack is playing. The Action layer sets isAttacking = true on enter and false on exit — the Movement layer can read this variable to restrict sprinting during attacks if needed.

Layer priority determines which layer’s output the movement system uses when both claim the same channel. Higher priority wins.


Quick Reference

TaskHow
Open editorGS Tools > Unit Action Graph Editor
Create a new fileFile > New
Add a layerLayer Sidebar > Add
Add a stateDrag State node from the palette
Draw a transitionDrag from the edge of one state to the edge of another
Set transition prioritySelect the transition arrow → Inspector → Priority field
Add a conditionSelect the transition arrow → Inspector → Conditions → type picker
Declare a variableVariable Panel → Add
Inspect a connectionClick the transition arrow
Jump to a transition from a stateSelect state → Transition Summary → Select button

Glossary

TermMeaning
LayerOne independent state machine within a .unitaction file, evaluated in parallel with all others
StateA node representing one behavior the character can be in
Entry NodeThe auto-spawned marker that designates the initial state of a layer
Compound StateA state that contains its own nested state machine sub-graph
TransitionA directed connection between states, with priority and optional conditions
ConditionA rule that must be satisfied for a transition to fire
PriorityThe tie-breaker when multiple transitions from the same state are simultaneously valid
__stateTimeAuto-variable tracking how long the character has been in the current state (seconds)

For full definitions, see the Glossary.


See Also

For the full API, node types, and C++ extension guide:

For related systems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4 - Framework API

Component references, EBus interfaces, and extension guides for the full GS_Play framework.

This section contains the full technical reference for every GS_Play gem — component properties, EBus request and notification interfaces, extension patterns, internal architecture, and code examples.

If you are looking for a scripting-level introduction or editor setup guides, see The Basics section first.


How This Section Is Organized

Each gem section follows a layered structure:

Gem overview page — Technical summary of the gem’s architecture, a table of feature sets with component listings, and installation notes.

Feature set pages — One page per sub-system (e.g., GS_Save, GS_StageManager). Each covers:

  • Architectural overview and core design pattern.
  • Component and class index with purpose descriptions.
  • Extension and customization guidance.
  • Links to The Basics for editor-level usage.

Class and component pages — One page per major class or component. Each covers:

  • Inspector properties and configuration.
  • EBus request and notification interfaces with method signatures.
  • Virtual methods for subclassing.
  • Code examples for common use cases.

Pages in this section assume familiarity with O3DE concepts (components, EBuses, SerializeContext). If you need a gentler introduction, start with the corresponding page in The Basics — every Framework API page links back to its Basics counterpart.


Sections

4.1 - GS_Core

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

GS_Core is the required foundation for every GS_Play project. All other GS gems depend on it. It provides game lifecycle management (startup, shutdown, standby), persistence (save/load), level loading, input handling, triggerable actions, and a shared utility library.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


GS_Managers

The lifecycle and startup management system. The Game Manager is a singleton placed in every level — it spawns all registered manager prefabs, coordinates their two-stage initialization, and provides global game navigation and standby mode.

All built-in GS_Play managers (Save, Stage, Options, UI, Unit, Camera, Audio) and any custom managers you create extend the Manager base class and plug into this system automatically.

ComponentPurpose
Game ManagerTop-level lifecycle controller. Spawns managers, handles navigation, standby, and debug mode.
Manager (Base)Base class for all managers. Handles two-stage initialization automatically.

GS_Managers API


GS_Save

The persistence system. The Save Manager orchestrates save and load operations across the project. Savers are per-entity components that serialize component state. The Record Keeper stores flat key-value progression records independently of the Saver system.

ComponentPurpose
Save ManagerCoordinates all save and load operations.
SaversPer-entity components that serialize transform and physics state.
Record KeeperKey-value progression store (flags, counters, unlock states).

GS_Save API


GS_StageManager

The level loading and navigation system. The Stage Manager owns the ordered stage list for the project and handles all level transitions. Stage Data components are placed in each level as anchors and spin-up controllers for that level’s systems.

ComponentPurpose
Stage ManagerManages stage list and handles transition requests.
Stage DataPer-level anchor, spawn point, and activation controller.

GS_StageManager API


GS_Options

The configuration system, covering two branches. Settings are the player-facing options a game exposes — declared as data, applied to the engine, persisted between sessions, and applied at boot where they need to be right before the window is created. Input is group-based binding that can be rebound and toggled at runtime.

PagePurpose
Options ManagerMerges option profiles, applies and persists values, holds the active input profile.
SettingsOption profiles, option types, apply targets, presets, persistence and the launch path.
Display & Window ModesWhat the window apply targets do, and the platform limits.
Input ProfilesData asset for input binding mappings.

GS_Options API


Systems

Core framework systems used across multiple gems: the GS_Motion track-based animation engine and the GS_Actions triggerable behavior system.

SystemPurpose
GS_MotionTrack-based animation and tween engine — abstract base classes extended by domain gems.
GS_ActionsTriggerable, composable, data-driven behaviors attachable to any entity.

Systems API


Debug

In-game debugging surfaces. Debug Mode adapts startup so pressing play on a level runs that level instead of booting the whole game. The GS_Play ImGui menu is a content-free anchor that any gem contributes its own menu and windows to.

PagePurpose
Debug ModeWhat the Game Manager’s debug flag changes across the framework, and how to use it in your own systems.
ImGui Debug MenuThe extension pattern, the shipped menus, and how to add one to your gem.

Debug Mode ImGui Debug Menu


Interfaces

The cross-gem contract layer. Feature gems depend on neutral contracts collected in GS_Core instead of on each other’s internals. Contracts come in three kinds, distinguished by their class/bus suffix.

KindPatternExtend by
TypeBaseStrategySubclass a *Type base, reflect it → it appears in the editor’s type-picker.
EmitObservanceHandle the *EmissionBus and override the event you care about.
ExchangeMediatorCall the *Exchange query bus (mostly forward-looking).

Interfaces


Utilities

A general-purpose library of components and math helpers.

AreaContents
PhysicsPhysics Trigger Volume
Easing Curves40+ curve types (Linear, Quad, Cubic, Sine, Expo, Circ, Back, Elastic, Bounce)
Spring Dampers6 spring types, each in float, Vector2 and Vector3 forms plus a quaternion variant, all halflife-parameterised
Orbital SolverPivot-aware position and pose blending — linear, cylindrical and spherical
NoisePerlin 1D/2D/3D, fractal multi-octave, and authorable layered noise stacks
EnvelopeReflected ADSR envelope with evaluation and completion helpers
GradientsColor, float, and Vector2 gradients
Entity HelpersEntity lookup by name
RandomWeighted random selection
SplinesClosest point, fraction, and local/world conversion
Angle HelpersYaw, quaternion from direction, section-by-angle mapping
ComparisonsData-authored boolean comparisons for float and integer values
Debug LoggingPer-component logging depth mix-in

Utilities API


Installation

GS_Core is required by all GS_Play projects. Add it to your project before any other GS gem.

  1. Follow the Simple Project Setup guide for full walkthrough.
  2. Configure collision layers and physics per the Setup Environment guide.
  3. Create a Game Manager prefab and place it in every level.
  4. Add the managers you need to the Game Manager’s Startup Managers list.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.1 - GS_Managers

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

The Managers system is the backbone of every GS_Play project. It provides a controlled startup sequence that initializes game systems in the correct 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.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Architecture

Game Manager Startup Pattern Graph

Breakdown

When the project starts, the Game Manager runs three stages before the game is considered ready:

StageBroadcast EventWhat It Means
1 — Initialize(internal)Each manager is spawned. They activate, then report ready.
2 — SetupOnSetupManagersSetup stage. Now safe to query other managers.
3 — CompleteOnStartupCompleteLast stage. Everything is ready.
Do any last minute things. Now safe to begin gameplay.

For most scripts, you only need OnStartupComplete. Wait for this event before doing anything that depends on managers to be completely setup.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Components

ComponentPurposeReference
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

For step-by-step setup, see Game Manager — Setup and Manager — Setup.

For creating custom managers, see Manager — Extending the Manager Class.


See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.1.1 - Game Manager

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

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. In debug mode it stays in the current level on start; in release mode it navigates to your title stage.

For usage guides and setup examples, see The Basics: GS_Core.

Game Manager component in the O3DE Inspector

The Game Manager component in the Entity Inspector, with added manager prefabs in the Startup Managers list.

Contents


Design Intent

Extend the Game Manager when you need project-wide changes to startup ordering, custom post-startup logic (analytics sessions, online service initialization), or additional global lifecycle events. For most projects, the built-in component is sufficient as-is. Do not move gameplay logic here — the Game Manager orchestrates systems, it does not run game rules.


Dependencies & Interactions

Coordinates withRole
GS_SaveManagerOwns all save/load file operations triggered by NewGame, ContinueGame, LoadGame, SaveAndExitGame
GS_StageManagerOwns level navigation during NewGame, LoadGame, and ReturnToTitle
GS_ManagerComponentBase class all spawned managers extend
All other GS gem managersAny manager in the Startup Managers list registers automatically

Setup

Game Manager Entity

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.


Core Concepts

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. Setup Managers — Once all managers report initialized, the Game Manager broadcasts OnSetupManagers(). Managers now connect to each other and report back via OnRegisterManagerSetup().
  3. Startup Complete — Once all managers report setup, the Game Manager broadcasts OnStartupComplete(). The framework 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.

Shutdown mirrors the same shape: OnShutdownManagers() is broadcast, and each manager reports back via OnRegisterManagerShutdown().

OnBeginGame() sits outside this sequence. Startup completing means the framework is ready; beginning the game is a separate cue your project fires when play actually starts.

A project can also be configured without a Game Manager at all — headless managers drive their own startup, so the Game Manager can be skipped altogether.


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.


Inspector Properties

PropertyTypeDescription
Project PrefixStringSets the project prefix for generated files (e.g. save files). Example: "GS" produces GS_SaveGame.json.
Startup ManagersList of Manager PrefabsThe manager prefab spawnables to instantiate on game start. Order does not matter — the two-stage init handles dependencies. (C++: AZStd::vector<SpawnableAssetRef>)
Debug ModeBoolWhen enabled, the Game Manager stays in the current level instead of navigating to the title stage.

API Reference

Request Bus: GameManagerRequestBus

Commands sent to the Game Manager. Singleton bus — single address, single handler.

MethodParametersReturnsDescription
IsInDebug—boolReturns whether debug mode is active.
IsStarted—boolReturns whether the startup sequence has completed.
GetProjectPrefix—AZStd::stringReturns the configured project prefix string.
EnterStandby—voidBroadcasts standby to all managers, pausing gameplay.
ExitStandby—voidBroadcasts standby exit, resuming gameplay.
NewGame—voidStarts a new game with the default save name.
NewGameconst AZStd::string& saveNamevoidStarts a new game with a specified save file name.
ContinueGame—voidLoads the most recent save file and continues.
LoadGameconst AZStd::string& saveNamevoidLoads a specific save file by name.
ReturnToTitle—voidReturns to the title stage, tearing down the current session.
SaveAndExitGame—voidSaves the current game state and exits the application.
ExitGame—voidExits the application without saving.

Notification Bus: GameManagerNotificationBus

Events broadcast by the Game Manager. Multiple handler bus — any number of components can subscribe.

EventParametersReturnsDescription
OnSetupManagers——Fired when all managers have reported initialized. Managers should now connect to each other.
OnStartupComplete——Fired when the full startup sequence is complete. Safe to begin gameplay.
OnShutdownManagers——Fired when the game is shutting down. Managers should clean up.
OnBeginGame——Fired when a new game or loaded game begins.
OnEnterStandby——Fired when entering standby mode. Pause your systems.
OnExitStandby——Fired when exiting standby mode. Resume your systems.

ScriptCanvas Nodes

These methods and events are available as ScriptCanvas nodes.

Request Nodes

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

Notification Nodes

Listen for these events on GameManagerNotificationBus.

NodeWhen It Fires
OnStartupCompleteThe game is fully initialized and ready to run.
OnBeginGameA new or loaded game session begins.
OnEnterStandbyThe game has entered standby (paused).
OnExitStandbyThe game has exited standby (resumed).

Virtual Methods

Override these when extending the Game Manager. Always call the base implementation.

MethodParametersReturnsDescription
InitializeManagers()—voidSpawns the Startup Managers list. Override to add custom spawn logic.
ProcessFallbackSpawn()—voidHandles fallback when a manager fails to spawn.
StartupManagers()—voidBroadcasts OnSetupManagers. Override to inject logic between init and startup stages.
CompleteStartup()—voidBroadcasts OnStartupComplete. Override to add post-startup logic.
BeginGame()—voidCalled when transitioning into active gameplay.

SC ↔ C++ Quick Reference

ActionScriptCanvas NodeC++ MethodNotes
Start a new gameTriggerNewGameNewGame()Default save name
Start with named saveTriggerNewGameWithName(name)NewGame(saveName)Named save file
Continue from last saveTriggerContinueGameContinueGame()Loads most recent save
Load specific saveTriggerLoadGame(name)LoadGame(saveName)Load by file name
Return to titleTriggerReturnToTitleReturnToTitle()Tears down current session
Save and exitTriggerSaveAndExitGameSaveAndExitGame()—
Exit without savingTriggerExitGameExitGame()—
Check if started—IsStarted()Returns bool

Usage Examples

ScriptCanvas: Title Screen Workflow

Calling new or load game proceedes to start the gameplay and systems. Begin Game event fires to start all systems across the game.

Debug Mode automatically begins the game.


ScriptCanvas: Standby Logic


C++: Reacting to Events

A gameplay component that listens to the full startup and standby lifecycle:

#include <GS_Core/GS_CoreBus.h>

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

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

    void OnStartupComplete() override
    {
        // Safe to access all managers and begin gameplay logic
    }

    void OnEnterStandby() override
    {
        // Pause your gameplay systems
    }

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

C++: Custom Manager Integration

A manager that receives the startup handshake from the Game Manager. Add this class’s prefab to the Game Manager’s Startup Managers list:

#include <GS_Core/GS_CoreBus.h>
#include <Source/Managers/GS_ManagerComponent.h>

class MySystemManager
    : public GS_Core::GS_ManagerComponent
    , protected GS_Core::GameManagerNotificationBus::Handler
{
public:
    AZ_COMPONENT_DECL(MySystemManager);
    static void Reflect(AZ::ReflectContext* context);

protected:
    void Activate() override
    {
        GS_Core::GS_ManagerComponent::Activate();                        // Required: registers with Game Manager
        GS_Core::GameManagerNotificationBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::GameManagerNotificationBus::Handler::BusDisconnect();
        GS_Core::GS_ManagerComponent::Deactivate();                      // Required: deregisters from Game Manager
    }

    // Called during Stage 2 — safe to reference other managers here
    void OnSetupManagers() override
    {
        // Connect to other managers, set up cross-system references
    }

    // Called when the full startup sequence completes — safe to begin gameplay
    void OnStartupComplete() override
    {
        // Initialize systems that depend on all managers being ready
    }

    void OnEnterStandby() override { /* pause your systems */ }
    void OnExitStandby()  override { /* resume your systems */ }
};

Extending the Game Manager

Extend the Game Manager to add custom startup logic, additional lifecycle events, or project-specific behavior. Extension is done in C++.

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();
        // 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();
        // Post-startup logic here (e.g. connect to analytics, initialize online services)
    }

    void MyGameManager::BeginGame()
    {
        GS_GameManagerComponent::BeginGame();
        // Custom game start logic
    }
}

See Also

For scripting patterns and SC-first usage:

For related components:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.1.2 - Manager

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

For usage guides and setup examples, see The Basics: GS_Core.

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

GS_GameManager
  └── (spawns) GS_ManagerComponent ◄ you are here — and all other managers extend this

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.

 

Contents


How It Works

The Game Manager drives startup in stages. At each stage it calls out to every manager over GameManagerNotificationBus, each manager performs its own logic, and each reports back over GameManagerRequestBus when it is done. The Game Manager counts those reports and only advances to the next stage once every manager has answered.

That counting handshake is the point of the system: no manager advances until every manager has registered, which is what makes cross-manager dependencies safe without you having to specify an order.

  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. OnSetupManagers — Called by the Game Manager once every manager has reported initialized. Connect to other managers and set up cross-references here. When done it broadcasts OnRegisterManagerSetup().

  3. OnStartupComplete — Broadcast by the Game Manager once every manager has reported setup. The framework is fully ready. The base implementation is empty — override it to begin runtime operation.

  4. OnBeginGame — Broadcast when the game itself begins. This is a cue, not a state: it is fired when your project decides play has actually started, which is a separate question from whether the managers finished starting up.

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

  6. OnShutdownManagers — Called when the game is shutting down. Clean up resources and disconnect buses. When done it broadcasts OnRegisterManagerShutdown().

The base class handles the registration side of steps 1, 2 and 6 for you. Override the hooks to add your logic, then call the base implementation to complete the handshake.


Headless Managers

Headless is the second way to run a manager, and it is a full alternative to the Game Manager — not a reduced one.

Every manager carries a Headless checkbox. Tick it and that manager takes responsibility for its own startup instead of being driven by a Game Manager:

Activate()
  └── calls OnSetupManagers() itself
        └── calls OnStartupComplete() itself

Deactivate()
  └── calls OnShutdownManagers() itself, then disconnects

The registration calls are skipped entirely — there is no Game Manager to report to, so the manager simply advances itself through each stage.

It reaches startup-complete under its own power, with no Game Manager in the project and no startup sequence to participate in. Everything the manager does after that point behaves exactly as it does under the Game Manager.

Why you would use it

The Game Manager path exists because a GS_Play game wants one coordinated startup across a stack of managers. A project that only wants one feature does not need any of that.

Headless is how a GS feature gem is deployed into a project that is not built on GS_Play:

  • Deploying a single feature manager. A project that wants GS_Audio’s audio system — and nothing else — adds the Audio Manager, ticks Headless, and has a working audio system. No Game Manager, no manager stack, no game-flow layer, no dependency on the GS_Play startup sequence.
  • Adding GS features to an existing project that already has its own game lifecycle, title flow and save system. Those responsibilities stay where they are; the GS manager simply starts itself and does its job.
  • Adopting the framework in stages. Take one manager now, headless. Add a Game Manager later and switch it over when the project is ready for the full lifecycle.
  • Runtimes with no game session at all — servers, tools, editor-side scenes.

Both routes are supported deployments. Use the Game Manager when you want the coordinated startup it provides; use Headless when a manager should simply run.

What you take on

The Game Manager’s counting handshake is what guarantees that no manager reaches setup until every manager has initialized. A headless manager has no such barrier — it drives straight through its own chain the moment it activates, independently of anything else in the level.

For a single deployed manager that is exactly what you want. If you run several headless managers that depend on each other, sequencing between them becomes your project’s responsibility. At that point, consider a Game Manager instead — coordinating a manager stack is the problem it exists to solve.

OnBeginGame is never self-fired. Setup and startup are infrastructure a manager can honestly resolve alone, but “the game has begun” is a cue rather than a state — it stays a signal the host project fires when it decides play has started. A headless manager reaches startup-complete and waits.

A headless manager does not report to the Game Manager at all — it self-drives instead of registering, so it takes no part in the counting handshake and cannot disturb one that is running. A project is free to have a Game Manager coordinating a manager stack and a headless manager running alongside it.

The flag lives on the base class, so it is inherited by every manager — Audio, Options, Cam, Save, Stage and any custom manager you write. One uniform mechanism, no per-gem variation.

A subclass that overrides OnSetupManagers, OnStartupComplete or OnShutdownManagers must still chain to the base implementation exactly as before. Headless changes who calls the hooks, not the contract.


Setup

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 contributes two properties to every manager that inherits it. All other properties are defined by the inheriting component (e.g. the Save Manager exposes a Save System Version Number, the Stage Manager exposes a Stages list).

PropertyTypeDefaultDescription
HeadlessboolfalseThis manager drives its own startup, with no Game Manager in the project. See Headless Managers.
Logging Depthint0Verbosity of this manager’s lifecycle logging. Above 0, the manager prints its entity name as it activates, sets up and shuts down — useful for diagnosing startup order on a single manager without making the whole framework chatty.

API Reference

Lifecycle Events: GameManagerNotificationBus

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

EventWhen It FiresWhat You Do
OnSetupManagersAll managers have reported initializedConnect to other managers, set up cross-references, then call base to report.
OnStartupCompleteAll managers have reported setupBegin runtime operation.
OnBeginGameThe project fires it to signal play has startedStart gameplay-only behaviour. Not fired by startup, and never self-fired in headless mode.
OnEnterStandbyGame is entering standbyPause your subsystem (stop ticks, halt processing).
OnExitStandbyGame is exiting standbyResume your subsystem.
OnShutdownManagersGame is shutting downClean up resources, disconnect buses, then call base to report.

Registration Methods

Called by the Manager base class to report back to the Game Manager over GameManagerRequestBus. Do not call these manually — call the base class method instead and it reports for you.

MethodDescription
OnRegisterManagerInit()Called at the end of Activate(). Reports that this manager has initialized.
OnRegisterManagerSetup()Called at the end of OnSetupManagers(). Reports that this manager has completed setup.
OnRegisterManagerShutdown()Called at the end of OnShutdownManagers(). Reports that this manager has shut down.

Manager Base Buses

Separate from the Game Manager handshake, GS_ManagerBus.h declares a small pair of buses that let non-manager components react to their gem’s manager without participating in the startup count.

ManagerBaseNotifications:

EventMeaning
HandleStartupThis gem’s manager has finished starting up. Components that need the manager ready before they wire themselves in listen here.
HandleShutdownThis gem’s manager is shutting down.
EnterStandby / ExitStandbyStandby propagated to dependent components.

ManagerBaseRequests:

MethodReturnsDescription
ManagerExists()boolWhether this gem’s manager is present. Lets a component degrade gracefully instead of assuming.

Usage Examples

C++ — Checking if the Game Manager Is Ready

#include <GS_Core/GS_CoreBus.h>

bool isStarted = false;
GS_Core::GameManagerRequestBus::BroadcastResult(
    isStarted,
    &GS_Core::GameManagerRequestBus::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. Extension is done in C++.

Header (.h)

#pragma once
#include <GS_Core/GS_Manager/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 OnSetupManagers() 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()
    {
        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::OnSetupManagers()
    {
        // Access other managers here — they are all initialized by now

        // IMPORTANT: Call base last — it reports OnRegisterManagerSetup()
        GS_ManagerComponent::OnSetupManagers();
    }

    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

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.2 - GS_Save

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Architecture

Save System Pattern Graph

Breakdown

When a save or load is triggered, the Save Manager broadcasts to every Saver in the scene. Each Saver independently handles its own entity’s state. The Record Keeper persists flat progression data alongside the save file.

PartBroadcast EventWhat It Means
Save ManagerOnSaveAllBroadcasts to all Savers on save. Each serializes its entity state.
Save ManagerOnLoadAllBroadcasts to all Savers on load. Each restores its entity state.
Record KeeperRecordChangedFires when any progression flag is created, updated, or deleted.

Savers are per-entity components — extend GS_SaverComponent to persist any custom data. The Record Keeper is a global singleton that lives on the Save Manager prefab.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


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

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.2.1 - Save Manager

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

Save Manager component in the O3DE Inspector

 

Contents


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 SaveManagerRequestBus 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 SaveManagerNotificationBus.
  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: SaveManagerRequestBus

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.
GetOrderedSaveList—AZStd::vector<AZStd::pair<AZStd::string, AZ::u64>>Returns all save files ordered by timestamp (newest first). Each entry is a name + epoch timestamp pair.
ConvertEpochToReadableAZ::u64 epochSecondsAZStd::stringConverts an epoch timestamp to a human-readable date string.
GetEpochTimeNow—AZ::u64Returns the current time as an epoch timestamp.
GetAllocator—rapidjson::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.
IsContinuing—boolReturns true if previous save data was found on startup.
RegisterSaving—voidRegisters that a save operation is in progress (used internally by the save counting system).

Notification Bus: SaveManagerNotificationBus

Broadcast to all Saver components. Connect to this bus to participate in global save/load events.

EventParametersDescription
OnSaveAll—Broadcast when a full save is triggered. All savers should gather and submit their data.
OnLoadAll—Broadcast when a save file has been loaded into memory. All savers should retrieve and restore their data.

Local / Virtual Methods

These methods are available when extending the Save Manager. Override them to customize save file handling.

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::SaveManagerRequestBus::BroadcastResult(
    allocator,
    &GS_Core::SaveManagerRequestBus::Events::GetAllocator
);

if (allocator)
{
    rapidjson::Value myData(rapidjson::kObjectType);
    myData.AddMember("health", m_health, *allocator);
    myData.AddMember("level", m_level, *allocator);

    GS_Core::SaveManagerRequestBus::Broadcast(
        &GS_Core::SaveManagerRequestBus::Events::SaveData,
        "MyComponent_PlayerStats",
        myData
    );
}

Loading Data into a Component

#include <GS_Core/GS_CoreBus.h>

rapidjson::Value outData;
bool found = false;
GS_Core::SaveManagerRequestBus::BroadcastResult(
    found,
    &GS_Core::SaveManagerRequestBus::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::SaveManagerRequestBus::BroadcastResult(
    hasSave,
    &GS_Core::SaveManagerRequestBus::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::SaveManagerRequestBus::BroadcastResult(
    saves,
    &GS_Core::SaveManagerRequestBus::Events::GetOrderedSaveList
);

for (const auto& [name, epoch] : saves)
{
    AZStd::string readable;
    GS_Core::SaveManagerRequestBus::BroadcastResult(
        readable,
        &GS_Core::SaveManagerRequestBus::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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.2.2 - Savers

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

For usage guides and setup examples, see The Basics: GS_Core.

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

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.

 

Contents


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 SaveManagerNotificationBus:

#include <GS_Core/GS_CoreBus.h>

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

    void Deactivate() override
    {
        GS_Core::SaveManagerNotificationBus::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

Use the SaverComponent ClassWizard template to generate a new saver with boilerplate already in place — see GS_Core Templates.

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::SaveManagerRequestBus::BroadcastResult(
            allocator,
            &GS_Core::SaveManagerRequestBus::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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.2.2.1 - List of Savers

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

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


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

For component references:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.2.3 - Record Keeper

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

For usage guides and setup examples, see The Basics: GS_Core.

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

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.

 

Contents


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 RecordKeeperRequestBus.
  3. On each call to SetRecord, the Record Keeper broadcasts RecordChanged on the RecordKeeperNotificationBus 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: RecordKeeperRequestBus

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: RecordKeeperNotificationBus

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::RecordKeeperRequestBus::Broadcast(
    &GS_Core::RecordKeeperRequestBus::Events::SetRecord,
    "quest_village_rescue",
    2
);

Reading a Record

#include <GS_Core/GS_CoreBus.h>

AZ::s32 questStage = 0;
GS_Core::RecordKeeperRequestBus::BroadcastResult(
    questStage,
    &GS_Core::RecordKeeperRequestBus::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::RecordKeeperRequestBus::BroadcastResult(
    exists,
    &GS_Core::RecordKeeperRequestBus::Events::HasRecord,
    "switch_bridge_01"
);

if (!exists)
{
    // First time encountering this switch — initialize it
    GS_Core::RecordKeeperRequestBus::Broadcast(
        &GS_Core::RecordKeeperRequestBus::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::RecordKeeperNotificationBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::RecordKeeperNotificationBus::Handler::BusConnect();
    }

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

    // RecordKeeperNotificationBus
    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

For complex data needs beyond simple key-value records, create custom Saver components.


See Also

For component references:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.3 - GS_StageManager

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Architecture

Stage Change Pattern Graph

Breakdown

When a stage change is requested, the system follows a five-step sequence before gameplay resumes in the new level:

StepBroadcast EventWhat It Means
1 — StandbyOnEnterStandbyGame Manager enters standby, halting all gameplay systems.
2 — Unload(internal)The current stage’s entities are torn down.
3 — Spawn(internal)The target stage prefab is instantiated.
4 — Set UpOnBeginSetUpStageStage Data runs its layered startup: SetUpStage → ActivateByPriority → Complete.
5 — CompleteLoadStageCompleteStage Manager broadcasts completion. Standby exits. Gameplay resumes.

The Stage Data startup is layered so heavy levels can initialize incrementally without causing frame-time spikes.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


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

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.3.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.

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.

For usage guides and setup examples, see The Basics: GS_Core.

Stage Manager component in the O3DE Inspector

 

Contents


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

Stage Exit Point component in the O3DE Inspector

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: StageManagerRequestBus

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.
LoadDefaultStage—voidLoads 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: StageManagerNotificationBus

Connect to this bus to react to level loading events.

EventParametersDescription
BeginLoadStage—Broadcast when a stage change has started. Use this to show loading screens, disable input, etc.
LoadStageComplete—Broadcast 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::StageManagerRequestBus::Broadcast(
    &GS_Core::StageManagerRequestBus::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::StageManagerRequestBus::BroadcastResult(
    exitEntity,
    &GS_Core::StageManagerRequestBus::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::StageManagerNotificationBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::StageManagerNotificationBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::StageManagerNotificationBus::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
    }
};

Script Canvas

Changing stages:

Getting an exit point:


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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.3.2 - Stage Data

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

Stage Data component in the O3DE Inspector

 

Contents


How It Works

Connection

When the Stage Manager spawns a stage prefab, it queries the level for the Stage Data component via StageDataRequestBus. 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: StageDataRequestBus

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

MethodParametersReturnsDescription
GetStageData—StageData*Returns a reference to this Stage Data component. Used by the Stage Manager during initial connection.
BeginSetUpStage—voidStarts the stage’s setup sequence. Called by the Stage Manager after connecting.
GetStageName—AZStd::stringReturns the configured stage name.
GetStageNavMesh—AZ::EntityIdReturns the NavMesh entity reference for this stage.

Notification Bus: StageDataNotificationBus

Connect to this bus for level-specific lifecycle events.

EventParametersDescription
OnLoadStageComplete—Broadcast when this stage’s activation sequence has finished.
OnBeginSetUpStage—Broadcast when the stage setup begins.
OnTearDownStage—Broadcast 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::StageDataNotificationBus::Handler
{
protected:
    void Activate() override
    {
        GS_Core::StageDataNotificationBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Core::StageDataNotificationBus::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::StageDataNotificationBus::Handler
{
    int m_myPriority = 2; // Activate during priority layer 2

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

    void Deactivate() override
    {
        GS_Core::StageDataNotificationBus::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.)
        }
    }
};

Script Canvas

Getting stage name and NavMesh reference:


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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4 - GS_Options

The configuration system — player settings with profiles, presets and apply targets, plus input profiles, readers and reactors.

GS_Options is where a project’s configuration lives. It covers two related things:

  • Settings — the player-facing options a game exposes: graphics, audio, display, accessibility. Declared as data, applied to the engine, persisted between sessions.
  • Input — how input reaches gameplay: binding profiles, the readers that consume them, and the reactors that turn input into behaviour.

Both are owned by the Options Manager, which is why they share a home.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Settings

A project declares its options in profile assets. Each option carries a widget kind — toggle, slider, dropdown, colour — and an apply target describing what it does to the engine. The manager merges the profiles into a live value map, applies values, persists them, and restores them next launch.

The part worth knowing early: settings reach the engine by two paths. Anything expressible as a console command is written out and executed at boot, before the window is created, so the game opens at the right resolution rather than switching to it. Everything else applies at runtime.

PageCovers
SettingsProfiles, the four option types, apply targets, presets, persistence and the launch path.
Display & Window ModesWhat the window targets do, and the platform limits.

Settings API Display & Window


Input

Input binding is data. An Input Profile defines groups, event mappings and key bindings, replacing raw engine binding files with something that can be rebound at runtime and toggled per group. Input Readers consume the active profile and fire gameplay events; Input Reactors turn those events into behaviour.

PageCovers
Input OptionsReaders, profiles and the input pipeline.
Input ProfilesThe profile asset — groups, events and bindings.
Input ReactorsTurning named input events into gameplay, and writing your own reaction types.

Input Options API


Components

ComponentPurposeDocumentation
GS_OptionsManagerComponentSingleton manager. Merges option profiles, applies and persists values, and holds the active Input Profile.Options Manager
GS_InputProfileData asset defining input groups, event mappings and key bindings.Input Profiles
GS_InputReaderComponentReads input through the active profile and fires gameplay events.Input Options

Quick Start

  1. Create an Options Manager prefab with the GS_OptionsManagerComponent.
  2. Create an option profile asset and add the options your game exposes, giving each one an apply target.
  3. Create a GS_InputProfile asset with your input groups and event mappings.
  4. Assign both to the Options Manager.
  5. Add the Options Manager .spawnable to the Game Manager’s Startup Managers list.
  6. Attach Input Readers to entities that process input.

The fastest way to check an option works end to end is the ImGui debug menu — it renders the whole catalog live and writes through the same bus your menus will use.


See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.1 - Options Manager

The singleton options controller — merges option profiles into a live value map, applies values through their targets, persists them, and serves the active input profile.

The Options Manager is the runtime of the GS_Options system. It loads the project’s option profiles and presets, merges them into a live value map, pushes values through their apply targets, persists them, and notifies anything listening when a value changes.

It also holds the active Input Profile and serves it to Input Readers.

As a Manager, it is spawned by the Game Manager and participates in the standard startup sequence.

For usage guides and setup examples, see The Basics: GS_Core.

Options Manager component in the O3DE Inspector

 

Contents


Setup

  1. Create an entity. Attach the GS_OptionsManagerComponent.
  2. Assign your option profiles and any presets.
  3. Assign your Input Profile.
  4. Turn the entity into a prefab. Enter prefab edit mode, set the wrapper entity to Editor Only, and 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

PropertyTypeDescription
Option ProfilesassetsThe option catalogs to merge. Multiple profiles stack and merge by key.
Option PresetsassetsPreset value sets, surfaced by GetPresetNames and ApplyPreset.
Input ProfileassetThe active input binding profile, served to Input Readers.

Plus Headless and Logging Depth, inherited from the manager base.


Lifecycle

StageWhat happens
ActivateBegins loading the profile and preset assets.
OnSetupManagersWaits for those loads, builds the option map, seeds defaults, then overlays persisted values.
OnStartupCompleteSchedules ApplyAll — see below.
OnShutdownManagersSaves options.

Why apply is deferred

Applying everything at OnStartupComplete is too early. Managers finish starting up before the render pipeline and window are ready, so display and render-scale settings applied at that moment silently do not take effect.

The manager therefore applies after a short tick delay rather than immediately. Any apply target that touches the renderer or the window inherits this constraint — if you write one, it will be applied on that same delayed pass, not during startup.

This is also why the launch-time path exists: the settings that most need to be right before the window exists are handled by console commands at boot, and the deferred apply covers everything else.


API Reference

Request Bus: OptionsManagerRequestBus

Singleton bus — call via Broadcast.

Generic value access

MethodParametersReturnsDescription
SetOptionkey, OptionValuevoidApplies, persists and notifies. The single write path.
GetOptionkeyOptionValueCurrent value.
HasOptionkeyboolWhether the key exists in the merged catalog.
ResetToDefaultkeyvoidRestores the option’s authored default.
ResetAllToDefault—voidRestores every option.

Typed convenience

Script-friendly — no OptionValue object required.

MethodParametersReturns
SetOptionBoolkey, boolvoid
SetOptionFloatkey, doublevoid
SetOptionIntkey, s64void
SetOptionStringkey, stringvoid
GetOptionBoolkeybool
GetOptionFloatkeydouble
GetOptionIntkeys64
GetOptionStringkeystring

Catalog

MethodParametersReturnsDescription
GetAllOptionKeys—vector<string>Every key in the merged catalog.
GetOptionsByCategorycategoryvector<string>Keys in one category — how a menu builds its pages.
GetOptionDefinitionkeyOptionType*The option’s definition. Non-owning, C++ only.

Lifecycle and presets

MethodParametersReturnsDescription
ApplyAll—voidPushes every current value through its apply target.
SaveOptions—voidFlushes current values to the user registry.
GetPresetNames—vector<string>Loaded preset names.
ApplyPresetpresetNamevoidOverwrites matching options, then applies and persists.
GetActivePresetName—stringThe preset whose values all match, otherwise "Custom".

Input

MethodParametersReturnsDescription
GetActiveInputProfileinputNameinput profile assetThe active profile, requested by Input Readers on activation.

Notification Bus: OptionsManagerNotificationBus

EventParametersDescription
OnOptionChangedkey, OptionValueFired on every value change. Script-bindable, so UI and gameplay can react without polling.

Usage Examples

C++ — reading a setting

#include <GS_Core/GS_Options/GS_OptionsManagerBus.h>

float masterVolume = 1.0f;
GS_Core::OptionsManagerRequestBus::BroadcastResult(
    masterVolume,
    &GS_Core::OptionsManagerRequests::GetOptionFloat,
    "audio.master"
);

C++ — building a settings page

AZStd::vector<AZStd::string> keys;
GS_Core::OptionsManagerRequestBus::BroadcastResult(
    keys,
    &GS_Core::OptionsManagerRequests::GetOptionsByCategory,
    "Graphics"
);

// GetOptionDefinition gives you the display name, description and widget
// hint for each key, so a menu can build itself from the catalog.

Writing a value goes through SetOption or its typed forms, which apply, persist and notify in one call — a menu never has to apply or save separately.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.2 - GS_InputOptions

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

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.).

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


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

Input Profiles

Data assets that map key bindings to named events, organized into toggleable groups. They replace O3DE’s raw input binding files with a more flexible system that supports runtime rebinding and per-group enable/disable toggling.

Asset / TypePurpose
GS_InputProfileData asset defining input groups, event mappings, and key bindings.
InputBindingGroupNamed group of event mappings — can be toggled at runtime.
EventInputMappingMaps a named event to one or more raw O3DE input bindings.

Input Profiles API


Input Reader

Components that read input through the active profile and fire named gameplay events. Supports runtime group toggling to enable or disable input categories on the fly, and a claim flag to absorb matched events from other readers.

ComponentPurpose
GS_InputReaderComponentPer-entity input processor. Queries the active profile, matches raw input, and fires named gameplay events.

Input Reader API


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.

Input Reactors

Readers resolve raw input into named events. Reactors decide what those events do. A reactor component carries a profile asset listing authored reaction types, and dispatches each event to whichever reactors subscribed to it — with the phase (pressed, held, released, analog) already classified.

Input Reactors API


See Also

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.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.

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.

For usage guides and setup examples, see The Basics: GS_Core.

Input Profile asset in the O3DE Asset Editor

 

Contents


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}

Reflection: GS_InputProfile extends AZ::Data::AssetData. Its Reflect() function requires GS_AssetReflectionIncludes.h — see Serialization Helpers.

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::OptionsManagerRequestBus::BroadcastResult(
    profile,
    &GS_Core::OptionsManagerRequestBus::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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.2.2 - Input Reader

Component that reads input through the active profile and fires named gameplay events, with runtime group toggling and input claiming.

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.

For usage guides and setup examples, see The Basics: GS_Core.

Input Reader component in the O3DE Inspector

 

Contents


How It Works

On activation the Input Reader queries the Options Manager for the active Input Profile and subscribes to raw O3DE input events. Each frame it matches incoming raw input against the profile bindings. When a match is found it fires the associated named event into the gameplay event system. Input groups can be enabled or disabled at runtime to control which events are active at any moment.


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 Reference

GS_InputReaderComponent

FieldValue
HeaderGS_Core/GS_CoreBus.h

Request Bus: InputReaderRequestBus

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 Examples

Toggling Input Groups

#include <GS_Core/GS_CoreBus.h>

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

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

Script Canvas

Input Groups handling nodes in the O3DE Script Canvas


See Also

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.2.3 - Input Reactors

The reactor mechanism — a component driven by a reactor profile asset that dispatches named input events to authored reaction types, with a notification bus for scripts.

An Input Reactor turns a named input event into gameplay. Input Readers resolve raw device input into named events like "Jump" or "MoveForward"; reactors decide what those events do.

GS_Core owns the mechanism and the extension base. The reactions themselves are authored data, and the concrete reaction types are supplied by whichever gem owns that gameplay domain — GS_Unit ships movement and jump reactors, and your own gem can ship its own.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


How It Fits Together

raw device input
      │
      ▼
Input Reader          resolves bindings through the active input profile
      │                   fires a named event, e.g. "Jump"
      ▼
Input Reactor         classifies the event's phase and dispatches it
      │                   to every reactor subscribed to that name
      ▼
reactor types         do the gameplay thing

The split matters: the reader knows about devices and bindings, the reactor knows about gameplay, and neither knows about the other’s concerns. Rebinding a key changes nothing about the reaction, and changing a reaction changes nothing about the binding.


Reactor Profiles

InputReactorProfileAsset

A reactor profile is an asset holding AZStd::vector<InputReactorType*> — the set of reactions an entity has.

This is the framework’s polymorphic array shape: the serializer supplies an Add dropdown listing every reactor type reflected anywhere in the project, so a profile can mix reactors from GS_Unit, from another gem, and from your own game.

Because the reaction set is an asset, different entity kinds share or diverge by swapping profiles rather than by rebuilding component stacks.


The Component

GS_InputReactorComponent goes on the entity that should react. It carries one Reactor Profile.

At activation it clones the profile’s reactors so each component owns its own instances, then builds an event-name-to-reactor dispatch map from what each reactor declares it listens to. Cloning is what lets a reactor hold per-entity runtime state — two characters using the same profile do not share it.

It also tracks the current value of every event it has seen, which is what lets it classify phases and answer state queries.


Event Phases

The component classifies each fed event before handing it on, so a reactor is told what kind of input this is rather than having to work it out from value changes.

PhaseMeaning
PressedThe value went from zero to non-zero on this feed.
HeldA repeat feed while the value stays non-zero.
ReleasedThe value went from non-zero back to zero.
AnalogA continuous update, carrying the raw value.

Discrete inputs — buttons and keys — produce Pressed, Held and Released. Axis inputs produce Analog with the value.

This is why a jump reactor can be three lines: set the state on Pressed, clear it on Released, and ignore everything else.


API Reference

Request Bus: InputReactorRequestBus

Entity-addressed, single handler. This is how an Input Reader feeds a resolved event to a target entity.

MethodParametersReturnsDescription
FeedInputEventeventName, valuevoidFeed a resolved event. The component classifies its phase and dispatches.
ClearInput—voidClear all tracked event state — used when input should stop mattering, such as entering a menu.
GetEventStateeventNamefloatThe current value of a named event.

Notification Bus: InputReactorNotificationBus

Entity-addressed, multiple handlers. Emitted in parallel with internal reactor dispatch, so ScriptCanvas graphs and other components can react to the same events without being reactor types.

EventParametersDescription
OnInputEventeventName, value, phaseAn input event fired on this entity. Phase is 0 Pressed, 1 Held, 2 Released, 3 Analog.
OnInputCleared—Input state was cleared.

This bus is the answer to “I just want a graph to run when the player presses a key” — you do not need to write a reactor type for one-off behaviour.


Writing a Reactor Type

Derive InputReactorType and implement two methods:

MethodRequiredPurpose
GetSubscribedEventsYesThe event names this reactor listens to. The component builds its dispatch map from these.
OnInputEventYesCalled with the owning entity, the event name, its value and its phase.
OnActivateNoConnect to downstream systems for this owner.
OnDeactivateNoDisconnect.

Reflect it against the base and it appears in the reactor profile’s Add dropdown. There is no registry to add it to and no discovery step.

Two design habits worth adopting from the shipped reactors:

  • Author event names as fields. The shipped reactors expose their event names as editable strings rather than hard-coding them, so one reactor type serves projects with different naming conventions.
  • Keep per-owner state on the reactor. Because each component clones its reactors, instance state is safe — the keyboard movement reactor uses this to handle a player releasing one direction key while still holding the opposite one.

See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.3 - Settings

The GS_Options settings framework — option profiles, the four option types, apply targets, presets, persistence, and the launch-time apply path.

The settings framework is how a project declares its player-facing options — graphics, audio, display, accessibility, controls — and gets them applied, persisted and restored without writing that plumbing itself.

The model in one line: a profile asset declares options; each option carries a widget type and an apply target; the manager merges profiles into a live value map, persists it, and pushes values through their apply targets.

.optionsprofile ──┐
.optionsprofile ──┼──▶ Options Manager ──▶ apply target  (console var / window / audio / gamma / flag)
.optionspreset  ──┘          │
                             ├──▶ SettingsRegistry
                             └──▶ user registry file

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Option Profiles

OptionsProfileAsset

A .optionsprofile holds the option catalog — AZStd::vector<OptionType*>, authored in the Asset Editor.

This is the framework’s polymorphic array shape: the serializer supplies an Add dropdown listing every registered option subtype, and the container owns what it holds.

Profiles are assigned to the Options Manager, and multiple profiles stack and merge by key. That is what lets a project layer its own profile over a framework-supplied one without editing the original — same key, later profile wins.


Option Types

OptionType is the abstract base. Every option, whatever its kind, carries the same identity:

FieldPurpose
KeyThe stable persistence key, e.g. audio.master. This is what code and presets refer to.
Display NameThe UI label.
CategoryGroups the option in menus — Graphics, Audio, Display, Gameplay, Accessibility, Controls.
DescriptionExplanatory text for tooltips.
ApplyThe apply target — what this option actually does when its value changes.

Four concrete types ship:

TypeWidgetAuthored fields
BoolOptionToggleDefault
SliderOptionSliderMin, Max, Step (0 for continuous), Default
EnumOptionDropdownA list of entries, each with a Label and a Value
ColorOptionColour pickerDefault

OptionValue

A tagged union carrying one of five kinds: Bool, Float, Int, String, Color. Construct with MakeBool, MakeFloat, MakeInt, MakeString, MakeColor; read with AsBool, AsFloat, AsInt, AsString, AsColor.

It is reflected to the behaviour context, so scripts can construct and read values directly.

Most script work does not need OptionValue at all. The typed convenience methods take and return plain bools, floats, ints and strings.


Apply Targets

An apply target is what an option does to the engine. OptionApplyTarget is an abstract base with one job — Apply(value) — plus an optional second: contributing console commands to the launch-time path.

Keeping this separate from the option type is the design’s load-bearing decision. An option’s widget and an option’s effect vary independently: a slider might drive a console variable, an audio channel or nothing at all. Composing the two means neither axis has to enumerate the other.

Shipped in GS_Core

TargetWhat it does
DataFlagApplyNothing. The value is stored and read by game code — for options your own systems consume.
ConsoleVarApplyRuns a console command with the value. Bools emit 1 / 0. Carries the variable name.
WindowModeApplySets the window mode. Carries the width and height used when returning to windowed.
WindowResolutionApplySets resolution from a WxH string, or native.
WindowFullscreenApplyDirect fullscreen toggle.
WindowVsyncApplySets the sync interval.

An Atom-dependent gamma target ships in the gem’s private module layer rather than its public API — see why.

Shipped in GS_Audio

TargetWhat it does
AudioMasterApplyDrives master volume.
AudioChannelApplyDrives one named volume channel. Carries the channel name.

These are the reference example for the extension pattern: a gem defines apply targets for its own systems, and they appear in the profile’s dropdown automatically.


Presets

OptionPresetAsset

A .optionspreset is values only — a preset name plus a list of { key, value } entries. It does not redefine widgets or apply targets, because those already live on the option.

Applying a preset overwrites every matching option, then applies and persists in one action.

How “Custom” works

A preset is considered active only if every one of its entries matches the current value. Otherwise the active preset name is "Custom".

There is no extra state behind this — no “dirty” flag, no explicit mode. Editing any value while a preset is selected simply means the preset no longer matches, so the name resolves to Custom. That gives the familiar Low / Medium / High / Custom behaviour for free.


Persistence and Launch

There are two paths by which a setting reaches the engine, and knowing which is which explains the whole design.

The runtime path

SetOption applies, persists and notifies in one call. It is the single write path — the debug UI, your menus and your scripts all go through it, so there is no way to change a value without its apply target running and its notification firing.

Values live in the SettingsRegistry under a per-key path, and are flushed to a user registry file on SaveOptions and at shutdown — not on every set, so dragging a slider does not thrash the disk.

The launch path

Applying everything at runtime means the game opens at defaults and then visibly switches — a flash of the wrong resolution before yours takes effect.

To avoid that, saving also writes a second registry file containing console commands. The engine merges the user registry and executes those commands during application init — before system components and renderer bootstrap. So render scale, anti-aliasing, vsync and the window’s width, height and fullscreen state are already correct when the window is created.

An apply target contributes to this path by implementing AppendStartupConsoleCommands, and one option may emit several commands.

PathCarriesWhen
LaunchAnything expressible as a console commandBefore the window exists
RuntimeLive changes, and everything non-cvar — window mode, audio, data flagsOn SetOption / ApplyAll

Two things to know: the boot-time user registry merge is profile-build behaviour, and on a genuinely first launch no command file exists yet, so the first boot uses engine defaults and the manager’s deferred apply corrects them.


API Reference

Everything is on OptionsManagerRequestBus — see the Options Manager for the full table. In brief:

  • Generic: SetOption, GetOption, HasOption, ResetToDefault, ResetAllToDefault
  • Typed convenience: SetOptionBool / Float / Int / String and their getters
  • Catalog: GetAllOptionKeys, GetOptionsByCategory, GetOptionDefinition (C++ only, non-owning)
  • Lifecycle: ApplyAll, SaveOptions
  • Presets: GetPresetNames, ApplyPreset, GetActivePresetName
  • Notification: OnOptionChanged(key, value), script-bindable

Adding Your Own Setting Kind

Apply targets are the cross-gem extension point. Derive OptionApplyTarget, implement Apply, reflect it — and it appears in the apply-target dropdown on every .optionsprofile in the project, with no registration step. If your setting should also survive into the launch path, implement AppendStartupConsoleCommands as well.

GS_Audio’s targets are the reference implementation.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.4.3.1 - Display & Window Modes

The window modes GS_Options drives, how resolution relates to render resolution, and the platform limits worth stating plainly.

Display settings are ordinary options — they are only separated here because what they do to the window is worth describing precisely.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Window Modes

Four canonical PC window modes:

ModeBehaviour
WindowedA framed window at the chosen resolution.
BorderlessA frameless window at the chosen resolution.
Fullscreen WindowedFrameless, filling the monitor at its native size.
FullscreenCurrently identical to Fullscreen Windowed.

The window-mode apply target carries the width and height to restore when returning to windowed mode, so switching out of fullscreen does not leave the window at monitor size.


Resolution and Render Resolution

Resolution is authored as a WxH string, or native.

For the two fill modes, the chosen resolution drives the internal render resolution and the result is upscaled to the monitor. “Fullscreen at 1024x768” renders at 1024x768 and scales up to fill your display — it does not switch your monitor to 1024x768.

In practice this is what you want from a resolution setting today: it is a performance control, and it behaves identically on every monitor without touching the desktop.


Platform Support

The four-mode behaviour is Windows-only, implemented against the platform’s window API because the engine’s portable interface exposes only a binary fullscreen toggle.

Other platforms fall back to that binary toggle, which on Linux desktops is borderless fullscreen. Fuller support belongs in the engine’s window layer rather than here.

Multi-monitor: display changes act on the monitor the window is currently on.


See Also

For related systems:

  • Settings — profiles, apply targets and the launch path
  • Options Manager — including why display settings apply on a delay

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5 - Systems

Core framework systems — the GS_Motion track-based animation engine and the GS_Actions triggerable behavior system.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


GS_Motion

The track-based animation and tween engine. GS_Motion defines abstract base classes for tracks, assets, composites, and proxies. Domain gems (GS_UI, GS_Juice) extend it with concrete track types and custom asset formats. The system handles playback timing, per-track easing, proxy-based entity targeting, and deep-copy runtime instancing.

ClassPurpose
GS_MotionTrackAbstract base class for all animation tracks.
GS_MotionPlayback engine — ticks tracks and manages lifecycle.
GS_MotionCompositeRuntime deep-copy instance with proxy overrides.
GS_MotionAssetAbstract data asset base for motion definitions.
GS_MotionProxySerialized struct for track-to-entity redirection.

GS_Motion API


See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1 - GS_Motion

Track-based animation and tween system — abstract base classes for motions, tracks, composites, assets, and proxies with a domain extension pattern.

GS_Motion is the framework’s central track-based animation system. It defines abstract base classes that domain gems extend with concrete track types — GS_UI extends it with 8 UI animation tracks (.uiam assets), and GS_Juice extends it with 2 feedback tracks (.feedbackmotion assets). The system handles playback timing, per-track easing, proxy-based entity targeting, and deep-copy runtime instancing from data assets.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Architecture

GS_MotionTrack (abstract base)
    ├── UiMotionTrack (GS_UI domain base)
    │       ├── UiPositionTrack
    │       ├── UiScaleTrack
    │       ├── UiRotationTrack
    │       ├── UiElementAlphaTrack
    │       ├── UiImageAlphaTrack
    │       ├── UiImageColorTrack
    │       ├── UiTextColorTrack
    │       └── UiTextSizeTrack
    └── FeedbackMotionTrack (GS_Juice domain base)
            ├── FeedbackTransformTrack
            └── FeedbackMaterialTrack

GS_MotionAsset (abstract base)
    ├── UiAnimationMotionAsset (.uiam)
    └── FeedbackMotionAsset (.feedbackmotion)

GS_MotionComposite (runtime instance)
    └── Created by asset's CreateRuntimeComposite()

Core Classes

ClassDescriptionPage
GS_MotionTrackAbstract base for all animation tracks — fields, lifecycle, virtual methods, extension guide.MotionTrack
GS_MotionPlayback engine — ticks tracks, computes per-track progress windows, applies easing, manages lifecycle.Motion Engine
GS_MotionCompositeRuntime deep-copy instance with proxy entity overrides — created from GS_MotionAsset.MotionComposite
GS_MotionAssetAbstract base for motion data assets — holds track definitions and creates runtime composites.MotionAsset
GS_MotionProxySerialized struct for track-to-entity redirection — allows designers to target named tracks at different entities.MotionProxy

Domain Extensions

GS_Motion is designed to be extended by domain gems. Each domain creates its own track hierarchy, asset type, and file extension.

DomainGemBase TrackConcrete TracksAsset Extension
UI AnimationGS_UIUiMotionTrackPosition, Scale, Rotation, ElementAlpha, ImageAlpha, ImageColor, TextColor, TextSize (8).uiam
FeedbackGS_JuiceFeedbackMotionTrackFeedbackTransformTrack, FeedbackMaterialTrack (2).feedbackmotion

Extension Pattern

To create a new motion domain:

  1. Create a domain base track — class MyTrack : public GS_Core::GS_MotionTrack
  2. Create concrete tracks extending the domain base — each overrides Update(float easedProgress) and GetTypeName()
  3. Create a domain asset — class MyAsset : public GS_Core::GS_MotionAsset with vector<MyTrack*> m_tracks
  4. Create an instance wrapper struct (not a component) — holds Asset<MyAsset> + vector<GS_MotionProxy>, manages Initialize / Unload / Play / Stop
  5. Embed the wrapper — components embed the instance wrapper struct as a serialized field

Critical: All track vectors must use raw pointers: vector<MyTrack*>. Never use unique_ptr — O3DE SerializeContext requires raw pointers for polymorphic enumeration in the asset editor.


See Also

For conceptual overviews and usage guides:

For class references:

For domain extensions:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1.1 - GS_MotionTrack

Abstract base class for all animation tracks — fields, lifecycle, and virtual methods for domain extension.

GS_MotionTrack is the abstract base class for all animation tracks in the GS_Motion system. Each track animates one aspect of an entity over a time window within a motion. Domain gems extend this with concrete track types — GS_UI provides 8 LyShine tracks, GS_Juice provides 2 feedback tracks.

For usage guides and setup examples, see The Basics: GS_Core.


Fields

FieldTypeDescription
m_idAZ::UuidUnique track identifier (auto-generated).
m_identifierAZStd::stringProxy label — if set, the track appears in the proxy list for entity override targeting.
curveTypeCurveTypeEasing curve applied to track progress (from the Curves utility library).
startTimefloatTime offset before the track begins playing (seconds).
durationfloatTrack playback duration (seconds).
startVarianceMinfloatMinimum random variance added to start time.
startVarianceMaxfloatMaximum random variance added to start time.

Lifecycle

Each track goes through a fixed lifecycle managed by the parent GS_Motion:

PhaseMethodDescription
InitializeInit(AZ::EntityId owner)Stores the owner entity. Called once when the motion is initialized.
StartStart()Called when the track’s time window begins.
UpdateUpdate(float easedProgress)Called each frame with eased progress (0 → 1). Override this in concrete tracks.
EndEnd()Called when the track’s time window completes.
UnloadUnload()Cleanup. Called when the motion is unloaded.

Virtual Methods

These methods must be overridden in concrete track implementations:

MethodReturnsDescription
Update(float easedProgress)voidApply the animation at the given eased progress value (0 → 1).
GetTypeName()AZStd::stringReturn the track’s display name for proxy labels in the editor.

Extension Guide

To create a new domain of animation tracks:

  1. Create a domain base track: class MyTrack : public GS_Core::GS_MotionTrack — this serves as the common base for all tracks in your domain.
  2. Create concrete tracks extending the domain base — each overrides Update(float easedProgress) to animate a specific property.
  3. Reflect the track class using O3DE’s SerializeContext and EditContext. The system discovers the new type automatically.

Critical: Track vectors must use raw pointers (vector<MyTrack*>). Never use unique_ptr — O3DE SerializeContext requires raw pointers for polymorphic enumeration in the asset editor.


See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1.2 - GS_Motion

The playback engine — ticks through tracks, computes per-track progress windows, and manages motion lifecycle.

GS_Motion is the playback engine that drives animation tracks. It ticks through all tracks each frame, computes per-track progress windows based on start time and duration, applies easing curves, and calls Update(easedProgress) on each active track. It handles motion lifecycle from initialization through completion.

For usage guides and setup examples, see The Basics: GS_Core.


API Reference

MethodParametersReturnsDescription
Play—voidBegin playback from the start.
PlayWithCallbackAZStd::function<void()> cbvoidPlay and invoke callback on completion.
Stop—voidStop playback immediately.
InitializeAZ::EntityId ownervoidInitialize all tracks with the owner entity.
Unload—voidUnload all tracks and clean up.

Fields

FieldTypeDescription
motionNameAZStd::stringDisplay name for the motion.
tracksvector<GS_MotionTrack*>The tracks in this motion.
loopboolWhether playback loops.
onCompletefunction<void()>Completion callback (set via PlayWithCallback).

How It Works

Each frame during playback:

  1. The motion calculates the global elapsed time.
  2. For each track, it computes the track-local progress based on startTime and duration.
  3. If the track is within its active window, the progress is eased using the track’s curveType.
  4. Update(easedProgress) is called on the track with the eased value (0 → 1).
  5. When all tracks complete, OnMotionComplete() is called.

Virtual Methods

MethodDescription
OnMotionCompleteCalled when playback finishes. Override in subclasses for custom teardown.

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1.3 - GS_MotionComposite

Runtime deep-copy motion instance with proxy entity overrides — created from GS_MotionAsset for per-entity playback.

GS_MotionComposite is the runtime instance of a motion, created from a GS_MotionAsset. It deep-copies all tracks so each entity gets independent playback state, and applies proxy overrides to redirect specific tracks to different entities in the hierarchy.

For usage guides and setup examples, see The Basics: GS_Core.


GS_MotionComposite extends GS_Motion. When an asset’s CreateRuntimeComposite() is called, all tracks are SC-cloned (serialization-context cloned) into a new composite instance. This ensures each entity playing the same motion has its own independent track state — no shared mutation.


API Reference

MethodParametersReturnsDescription
ApplyProxiesAZ::EntityId owner, vector<GS_MotionProxy> proxiesvoidMatches proxy trackIds to tracks and overrides the target entity for each matched track.

How It Works

  1. Creation: GS_MotionAsset::CreateRuntimeComposite() deep-copies all asset tracks into a new composite.
  2. Proxy Application: ApplyProxies() walks the proxy list, matching each proxy’s m_trackId to a track’s m_id. Matched tracks redirect their owner entity to the proxy’s m_proxyEntity.
  3. Playback: The composite plays exactly like a regular GS_Motion — it ticks tracks, applies easing, and calls Update().
  4. Cleanup: On Unload(), the composite cleans up all deep-copied tracks.

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1.4 - GS_MotionAsset

Abstract base for motion data assets — holds track definitions and creates runtime composites.

GS_MotionAsset is the abstract base class for motion data assets. Domain gems extend this with their own asset type and file extension — GS_UI creates UiAnimationMotionAsset (.uiam), GS_Juice creates FeedbackMotionAsset (.feedbackmotion). Assets are created and edited in the O3DE Asset Editor.

Extends AZ::Data::AssetData. All subclasses require GS_AssetReflectionIncludes.h in their header — see Serialization Helpers.

For usage guides and setup examples, see The Basics: GS_Core.


API Reference

MethodReturnsDescription
GetTrackInfosvector<GS_TrackInfo>Returns track UUID + label pairs for proxy list synchronization in the editor.
CreateRuntimeCompositeGS_MotionComposite*Factory — deep-copies all asset tracks into a new runtime GS_MotionComposite instance.

GS_TrackInfo

Lightweight struct used for proxy synchronization between asset tracks and instance proxy lists.

FieldTypeDescription
idAZ::UuidTrack identifier (matches the track’s m_id).
labelAZStd::stringTrack display name (from GetTypeName()).

Domain Extensions

DomainGemAsset ClassExtensionTracks
UI AnimationGS_UIUiAnimationMotionAsset.uiam8 LyShine-specific tracks
FeedbackGS_JuiceFeedbackMotionAsset.feedbackmotion2 feedback tracks

Extension Guide

To create a new domain asset type:

  1. Create class MyAsset : public GS_Core::GS_MotionAsset with vector<MyDomainTrack*> m_tracks. Include GS_AssetReflectionIncludes.h in your asset’s header — see Serialization Helpers.
  2. Implement GetTrackInfos() — iterate tracks and return UUID + label pairs.
  3. Implement CreateRuntimeComposite() — deep-copy tracks into a new GS_MotionComposite.
  4. Register the asset type in your gem’s DataAssetsSystemComponent.
  5. Add a .setreg entry for the asset processor to recognize your file extension.

Critical: Track vectors must use raw pointers (vector<MyTrack*>). O3DE SerializeContext requires raw pointers for polymorphic enumeration.


See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.1.5 - GS_MotionProxy

Serialized struct for track-to-entity redirection — allows designers to target named tracks at different entities.

GS_MotionProxy is a serialized struct that allows designers to redirect a named track to a different entity in the hierarchy. This enables a single motion asset to animate multiple entities — for example, a UI animation that moves one element while fading another.

For usage guides and setup examples, see The Basics: GS_Core.


Fields

FieldTypeDescription
m_trackIdAZ::UuidReferences the track’s m_id. Matched during ApplyProxies().
m_labelAZStd::stringRead-only display label (synced from track info at edit time via GetTrackInfos()).
m_proxyEntityAZ::EntityIdThe entity this track should target instead of the motion’s owner entity.

How It Works

  1. Edit time: The proxy list syncs from the asset’s track info. Each track with an m_identifier (non-empty label) appears as a proxy slot in the inspector.
  2. Designer assignment: The designer drags an entity reference into the proxy’s m_proxyEntity field.
  3. Runtime: When GS_MotionComposite::ApplyProxies() runs, it matches each proxy’s m_trackId to a track’s m_id and overrides that track’s target entity.
  4. Playback: The track now animates the proxy entity instead of the motion’s owner.

Usage

Proxies are embedded in instance wrapper structs (e.g., UiAnimationMotion, FeedbackMotion) alongside the asset reference. Components serialize the proxy list, and the wrapper handles synchronization with the asset.

Only tracks with a non-empty m_identifier appear in the proxy list. Tracks without an identifier always animate the owner entity.


See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.2 - GS_Actions

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

Architecture

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

Any system calls:
  ActionRequestBus::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 ActionRequestBus to trigger matching actions.
  4. Optionally enable Broadcast On Complete and set an On Complete Channel Chain for action sequencing.

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.2.1 - Action

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


How It Works

Firing an Action

  1. A system calls DoAction(targetChannel) on an entity’s ActionRequestBus.
  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 ActionNotificationBus.
    • 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 ActionNotificationBus 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: ActionRequestBus

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

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

Notification Bus: ActionNotificationBus

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

EventParametersDescription
OnActionComplete—Broadcast 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::ActionRequestBus::Event(
    targetEntityId,
    &GS_Core::ActionRequestBus::Events::DoAction,
    AZStd::string("open_door")
);

Listening for Action Completion

#include <GS_Core/GS_CoreBus.h>

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

    void Deactivate() override
    {
        GS_Core::ActionNotificationBus::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

For component references:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.5.2.1.1 - Core Actions

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

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.

For usage guides and setup examples, see The Basics: GS_Core.


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::ActionRequestBus::Event(
    uiEntityId,
    &GS_Core::ActionRequestBus::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::ActionRequestBus::Event(
    debugEntityId,
    &GS_Core::ActionRequestBus::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


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.6 - Interfaces

The cross-gem contract layer — neutral agreements collected in GS_Core so feature gems depend on contracts instead of on each other’s internals. Covers the three contract kinds (TypeBase, Emit, Exchange).

GS_Core/Interfaces/ is the framework’s cross-gem contract layer. Feature gems no longer depend on each other’s internals — they depend on neutral contracts collected here in GS_Core. A contract lives in GS_Core; the feature gem that produces it owns and operates it. GS_Core holds the agreement, the gem holds the behavior.

This is the layer that lets GS_Interaction emit a “pulse received” signal that GS_Juice can react to without either gem including the other.

 

Contents


Why This Layer Exists

Before this layer, cross-gem features were glued together by bridge components — a component in GS_Complete whose only job was to listen to one gem and drive another (e.g. listen to GS_Unit possession, drive a GS_PhantomCam target). Every such bridge carried a hard dependency on both gems’ internals.

Once both sides speak a Core contract, those bridges collapse into their natural gem and shed the cross-gem-internals dependency. Feature glue now lives in the gem that owns the feature, not in a catch-all integration gem.


The Three Contract Kinds

The layer collects exactly three kinds of contract. The kind tells you how to extend it.

KindPatternOne-lineExtend by
TypeBaseStrategy“Inherit this and you’re plugged in.”Subclass a *Type base, reflect it → it appears in the editor’s type-picker.
EmitObservance“Wait for this signal, then act.”Handle the *EmissionBus (C++ or ScriptCanvas) and override the event you care about.
ExchangeMediator“Ask, a provider answers.”Call the *Exchange query bus. (Rare today — mostly forward-looking.)

Full mechanics, editor/script appearance, and an authoring how-to for each are on the Classifications page.


The Glance Test

The class/bus suffix encodes scope and kind, so you can classify any bus at a glance:

SuffixMeaning
*Requests / *NotificationsIn-gem bus — internal to one gem, not a cross-gem contract.
*Type (a base class)TypeBase contract — a pickable Strategy base.
*Emissions / *EmissionBusEmit contract — a one-way observation bus.
*Exchange / *ExchangeBusExchange contract — a two-way request/fulfill bus.

Status at a Glance

KindShippedNotes
TypeBase9 Strategy bases — built & verifiedThe compiled, reflected contracts.
Emit13 observation buses across 8 gemsAll built, except GS_Cinematics’ three (Typewriter / Cinematic / DialogueSequence) which are code-complete but pending a build — treat their detail as provisional.
Exchange1 (CamCoreExchange)The rest (Play/Stop family, graph parameters, surface query, CamManager queries) are planned and not yet shipped.

The full per-contract breakdown is in the Contract Reference.


Where Contracts Live

Contracts are collected under GS_Core/Interfaces/<Capability>/ by capability (Camera, Pulse, Trigger, Dialogue, Motion, Targeting, UI, Possession, Cinematics, Time) — not by gem. One capability may be produced by one gem and consumed by several.

  • Emit / Exchange buses are header-only in Core (an EBus is runtime, not serialized). The producing gem reflects them to ScriptCanvas.
  • TypeBase classes are compiled (.h + .cpp) and registered in the Core reflection aggregator, so a base is reflected ahead of any gem’s subtypes by module load order.

See the Contract Reference for the full capability-folder map and which gem produces each.


See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.6.1 - Classifications

The three GS_Core contract kinds in depth — TypeBase (Strategy), Emit (Observance), and Exchange (Mediator) — each with its authoring how-to, plus the naming guard and the hoisting mechanics authors need.

The interfaces layer collects exactly three kinds of cross-gem contract. Each kind has a single, recognisable extension story — the suffix tells you which one you are looking at (see the glance test). This page covers each kind in depth and shows how to author against it.

For the contract concept and why the layer exists, start at the Interfaces overview.

 

Contents


TypeBase — Strategy

“Inherit this and you’re plugged in.”

A pickable polymorphic base class. An author writes a subclass, reflects it, and it auto-appears in the editor’s type-picker for any component that holds a list of that base. There is no registration call and no manifest — the O3DE SerializeContext drives the picker directly from the reflected inheritance.

TypeBase contracts are the only compiled contracts: they carry reflection (.h + .cpp) and are registered in the Core reflection aggregator so the base is reflected ahead of any gem’s subtypes.

“TypeBase” is the category name we use to talk about them — the classes themselves keep their domain name (PulseType, DialogueCondition, UiMotionTrack, …). That saved name string is the on-disk identity and is frozen: renaming it breaks existing assets.

Examples: PulseType / ReactorType (interaction pulses), WorldTriggerType / TriggerSensorType (world triggers), DialogueCondition / DialoguePerformance / DialogueEffect (dialogue graph nodes), UiMotionTrack / FeedbackMotionTrack (UI & FX motion tracks).

How to: add a new type

  1. Derive from the Strategy base — class MyReactor : public GS_Core::ReactorType.
  2. Reflect it to SerializeContext (and EditContext for inspector fields), exactly like any O3DE class.
  3. Done — it appears in the type-picker dropdown of every component that holds a vector of that base. No registration call.

Emit — Observance

“Wait for this signal, then act.”

A one-way observation bus. A producer emits a signal; any number of consumers listen. If the producer is not present at runtime, the signal simply never fires — consumers degrade to silence rather than erroring.

Code shape: an EBus interface named *Emissions, aliased *EmissionBus. Each event has an empty default body, so a handler only implements the events it cares about.

Examples: PossessionEmissions (a controller possessed a unit / a unit was possessed), PulseReactorEmissions (a reactor received a pulse), TimeEmissions (world tick, day/night changed), UIPageEmissions (a page shown/hidden). The full list is in the Contract Reference.

Addressing: per-entity vs global

  • By EntityId — listen to a specific entity (interaction, UI interactable, motion, possession buses). Connect at the address of the entity you care about.
  • Global broadcast — listen for any occurrence of a state change (TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, UIPageEmissions). Connect to the broadcast.

The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.

Fire on resolved state, not on request

Emits fire at the moment the observable state actually changed, not when a command was issued. You can trust their timing:

  • “Active camera changed” fires at blend-complete, not when a new camera was requested.
  • “Page shown / hidden” fires after the show/hide animation resolves, not on the call.
  • “Unit released” carries the released entity (captured before the reference clears), so the payload is meaningful rather than already-nulled.

How to: react to an event

  1. Handle the bus — class MyThing : public GS_Core::PulseReactorEmissionBus::Handler (C++), or drop the bus’s event-handler node into a ScriptCanvas graph.
  2. Connect at the right address — BusConnect(entityId) for per-entity buses, BusConnect() for global broadcasts.
  3. Override only the events you need. Disconnect when done.

Exchange — Mediator

“Ask, a provider answers.” — mostly forward-looking

A two-way request/fulfill (query) bus: a consumer asks, a single provider answers. An Exchange is created only when a bidirectional cross-gem need is proven — a pull, not a push. Until then a feature’s command surface stays an in-gem *Requests bus.

Code shape: *Exchange / *ExchangeBus.

Shipped today: only CamCoreExchange (GetCamCore — “which entity is the active camera core?”). It pairs with the CamCore lifecycle Emit and data Emit to form an observable-service trio: learn it exists (Emit) → query it (Exchange) → observe its updates (Emit).

Planned (not yet shipped): Play/Stop a sequence / UI motion / feedback emit / soundtrack / audio event; Get/Set a graph parameter; a terrain surface-info query for footstep sounds; CamManager queries. Each planned Play/Stop Exchange will pair with a completion Emit — command via Exchange, observe via Emit.

How to: query a provider

  1. Call the Exchange bus with a result — GS_PhantomCam::CamCoreExchangeBus::BroadcastResult(out, &GS_PhantomCam::CamCoreExchange::GetCamCore).
  2. A single provider answers. If no provider is present, the result is the bus’s default.

Hoisting Mechanics

The defining idea: a contract lives in GS_Core, but the feature gem owns and operates it. Only the neutral C++ interface moves to Core; everything that needs the producer present stays home.

PieceLives in
The EBus interface (*Emissions / *Exchange) or the Strategy base (*Type)GS_Core
The emit / broadcast call sites (where the signal fires)producing gem
The ScriptCanvas binder + its BehaviorContext reflectionproducing gem
The command/query half (*Requests) until proven cross-gemproducing gem

An observation handler is inert without its emitter, so binding the signal into ScriptCanvas only makes sense where the emitter exists — the producing gem reflects it. GS_Core just publishes the shape.

Transparent hoist vs deliberate redesign

  • Transparent hoist (same events, new home) — ScriptCanvas name, handler UUID, and translation assets are all preserved. No graph breakage. This is the norm (Time, Interaction, UI, Cinematics emits all did this).
  • Deliberate redesign (events split or renamed) — the ScriptCanvas name intentionally changes and old graphs break by design. The one case is Possession: the single “possessed” event (which fired on both possess and depossess) was split into explicit possess + release, across controller and unit vantages. It is now PossessionEmissionBus.

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.6.2 - Contract Reference

Every GS_Core contract by capability folder and producing gem — the 9 TypeBase Strategy bases, the 13 Emit observation buses, and the Exchange query buses — with shipped-versus-pending status and a per-gem feature index.

Every contract collected under GS_Core/Interfaces/<Capability>/, the gem that produces it, and its status. For what each kind is and how to author against it, see Classifications.

✦ marks an event or contract created during the interfacing effort (a gap-fill, not a pre-existing notification).

 

Contents


TypeBase — Strategy Bases

Compiled, editor-pickable. Subclass + reflect → the subtype appears in the type-picker. All nine are shipped and build-verified.

ContractCapability folderProducing gem
PulseTypePulseGS_Interaction
ReactorTypePulseGS_Interaction
WorldTriggerTypeTriggerGS_Interaction
TriggerSensorTypeTriggerGS_Interaction
DialogueConditionDialogueGS_Cinematics
DialoguePerformanceDialogueGS_Cinematics
DialogueEffectDialogueGS_Cinematics
UiMotionTrackMotionGS_UI
FeedbackMotionTrackMotionGS_Juice

DialogueEffect is passive — its reversal logic lives in the dialogue node’s teardown rather than a bus handler. An authoring detail, not a contract change.


Emit — Observance Buses

Header-only. Handle the *EmissionBus and override the events you care about.

ContractEventsCapabilityProducerStatus
CamCoreEmissionsUpdateCameraPosition, OnCamCoreRegistered, OnCamCoreUnregisteredCameraGS_PhantomCam✅ Built
TimeEmissionsWorldTick, DayNightChangedTimeGS_Environment✅ Built
PulseReactorEmissionsOnPulseReceivedPulseGS_Interaction✅ Built
TriggerSensorEmissionsOnTriggered, OnResetTriggerGS_Interaction✅ Built
TargetingEmissionsOnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteract✦TargetingGS_Interaction✅ Built
FeedbackMotionEmissionsOnFeedbackMotionComplete, OnFeedbackMotionLoopedMotionGS_Juice✅ Built
UIInteractableEmissionsOnClickedUIGS_UI✅ Built
UiMotionEmissionsOnUiMotionComplete(entity, name), OnUiMotionLooped(entity, name)MotionGS_UI✅ Built
UIPageEmissionsOnPageShown(page, name), OnPageHidden(page, name)UIGS_UI✅ Built
PossessionEmissionsOnPossessedUnit✦, OnReleasedUnit✦, OnPossessedByController, OnReleasedByController✦PossessionGS_Unit✅ Built
TypewriterEmissionsOnTypeFired, OnTypewriterCompleteDialogueGS_Cinematics⚠️ Pending build
CinematicEmissionsEnterCinematic, ExitCinematicCinematicsGS_Cinematics⚠️ Pending build
DialogueSequenceEmissionsOnDialogueSequenceStarted✦, OnDialogueTextBegin, OnDialogueSequenceCompleteDialogueGS_Cinematics⚠️ Pending build

Addressing. CamCoreEmissions (data event) is global. The Interaction / UI-interactable / motion / possession buses are addressed by EntityId — listen to a specific entity. TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, and UIPageEmissions are global broadcasts — listen for any occurrence. The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.


Exchange — Mediator Buses

Two-way query buses. Only CamCoreExchange ships today; the rest are planned.

ContractMethodsCapabilityProducerStatus
CamCoreExchangeGetCamCoreCameraGS_PhantomCam✅ Built
Play/Stop — Sequence · UIMotion · FeedbackEmit · soundtrack · audio event—(multi)Cinematics / UI / Juice / Audio⏳ Planned
Get/Set Graph Parameter—(graphcanvas graphs)multi⏳ Planned
Terrain surface-info query (footstep sounds)—EnvironmentGS_Environment⏳ Planned
CamManager queries—CameraGS_PhantomCam⏳ Planned

Each planned Play/Stop Exchange pairs with a completion Emit — command via Exchange, observe via Emit. GS_Audio’s emits are greenfield and will land with its Play/Stop Exchange, not before.


Per-Gem Feature Index

What each gem contributed to the layer.

GemContribution
GS_CoreThe Interfaces/ tree, the ReflectAllInterfaces aggregator, and a generic motion-loop hook (OnMotionLoop) added to the shared motion runtime to back the UI & Juice motion-loop emits.
GS_PhantomCamCamCore (Exchange + Emit); CamManager rig-lifetime emits; an always-face-camera helper repointed. The internal “set this camera” command stayed an in-gem bus — it is a manager→core pipe, not a cross-gem contract.
GS_EnvironmentTimeManager day/night and world-tick emits; time control stays in-gem.
GS_InteractionPulse-reactor, trigger-sensor, and targeting emits; a new OnInteract✦ emit; a dead handler base removed.
GS_JuiceFeedback-motion complete/loop emits, wired through the emitter.
GS_UIButton “clicked”, UI-motion complete/loop (with a motion name), page shown/hidden.
GS_UnitPossession (controller & unit vantages, possess & release); in-gem consumers repointed.
GS_CinematicsTypewriter, cinematic enter/exit, dialogue-sequence lifecycle (+ a new sequence-started event). Code-complete, pending a build.
GS_CompleteBridge components collapsed: a paper-facing handler moved to GS_Performer; a dialogue-select button, a possession→cam-target utility, and a cinematic controller all repointed to Core contracts and dropped their producing-gem includes.
GS_PerformerReceived the paper-facing handler from GS_Complete.

Deferred / Not Yet in This Layer

  • GS_Audio emits — greenfield; ship with the planned Play/Stop audio Exchange.
  • CamState hoist — unblocks when the neutral EntityId-addressed Camera Exchange replaces its owner-pointer coupling.
  • Possession ScriptCanvas translation assets — regeneration pending.
  • A legacy UI hub bus header — stale; cleanup pending (GS_Page is the live page system).

See Also


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.7 - Debug

In-game debugging surfaces provided by GS_Core — the extensible GS_Play ImGui menu.

Debug surfaces GS_Core provides for inspecting and driving a running game.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents

PagePurpose
Debug ModeThe Game Manager flag that turns “boot the game” into “run this level” — what it changes, and how to branch your own systems on it.
ImGui Debug MenuThe in-game GS_Play menu — a content-free anchor that any gem contributes its own menu and windows to.

See Also

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.7.1 - Debug Mode

The Game Manager’s Debug Mode flag — what it changes about startup, which managers respond to it, and how to branch your own systems on it.

Debug Mode is a checkbox on the Game Manager. It tells the framework that you are running a level directly out of the Editor rather than booting the game properly, and systems across the framework adjust accordingly.

The problem it solves: a GS_Play project normally boots to a title stage, waits for the player to start a game, then loads the first level and places the player. Pressing play on the level you are currently editing should not do any of that — it should just run this level, now.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


What Changes

Debug Mode is a single flag, but three systems consult it, and together they turn “boot the game” into “run this level”.

Game Manager — the game begins on its own

Normally startup completing means the framework is ready, and the game itself begins when your project decides it does — a title screen, a New Game button, a cutscene ending.

In Debug Mode, the Game Manager calls begin-game itself as soon as startup completes. OnBeginGame fires with no title flow, so gameplay systems come up immediately.

Stage Manager — you stay in the level you are editing

Normally the Stage Manager loads the project’s configured starting stage once startup completes, unloading whatever level you happened to be in.

In Debug Mode, if the current level has a Stage Data component, the Stage Manager adopts that level as the current stage instead of loading the default one. You keep working in the level you pressed play on.

If the level has no Stage Data, there is nothing to adopt and the default stage loads as usual — which is a useful thing to know when a level unexpectedly navigates away from itself.

Unit Manager — the player is placed for you

Normally a player unit waits for the correct level to load and for the game flow to position it.

In Debug Mode, the Unit Manager places the player unit at the debug exit point as soon as it registers, so you get a controllable character in the level without a stage transition having happened.


Reading the Flag

IsInDebug on the Game Manager request bus returns the flag, and it is exposed to scripts as In Debug.

Every manager already reads it. GS_ManagerComponent queries it during activation and caches it, so any manager subclass has the answer available without asking again.


Using It in Your Own Manager

The pattern the built-in managers follow is worth copying: branch on the flag anywhere your system’s behaviour depends on how the game was started rather than on the game’s state.

Typical uses:

  • Skip a wait. If your manager normally waits for a game-flow cue that debug startup never sends, act immediately instead.
  • Substitute a stand-in. Provide a debug default where a real session would have supplied something — a placeholder save, a default loadout, a starting position.
  • Say more. The framework’s managers print what they are doing in debug. Combine with Logging Depth on the individual manager you are diagnosing.

A custom manager reads the cached flag it inherits from the manager base, so there is nothing to wire up — the value is populated before your setup hook runs.

The manager component template already includes the query, so a scaffolded manager has the flag available from its first build.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.7.2 - ImGui Debug Menu

The in-game GS_Play debug menu — a content-free anchor in GS_Core that any gem contributes its own menu to, with ordered placement and per-gem ownership.

GS_Play adds a GS_Play entry to the in-game ImGui menu bar that any gem can contribute to. Each gem owns its own sub-menu, its own contents, and its own imgui dependency.

The core invariant: the anchor is content-free. GS_Core provides a component that broadcasts while the menu bar is being drawn, and gems draw their own entries in response. GS_Core never knows what other gems put there.

That makes the debug surface decentralised — a gem’s debug UI ships and versions with that gem — and it is a small, clear example of how the framework prefers extension to work.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Shipped Menus

MenuGemContents
CoreGS_CoreA live Options editor, and a Game Manager window.
PhantomCamGS_PhantomCamCamera Management — live camera selection, priority override and lens editing.
AudioGS_AudioAudio Mixer — a master slider plus one per volume channel.

Core ▸ Options

The Options window reads the entire option catalog, groups it by category, and renders the right widget for each option — checkbox, slider, dropdown or colour picker. It offers Apply All, Save, Reset All and per-option reset, with tooltips showing each option’s key, description and apply-target type. A preset dropdown lists Custom plus every loaded preset, keeping a snapshot of hand-tuned values so you can try a preset and return to your own.

Every edit goes through OptionsManagerRequestBus, so it exercises apply, persist and notify exactly as your own menus will. This is the fastest way to verify a new option end to end — author it, run, and drive it live before building any UI.

Audio ▸ Audio Mixer

Enumerates the configured volume channels and gives each a slider, plus master. Useful for balancing a mix without rebuilding.


How It Works

GS_ImGuiMenuComponent in GS_Core draws the top-level entry and broadcasts on a notification bus while the menu bar is open. Handlers draw their own sub-menu in response.

The bus is ordered. Each handler returns a sort key, so menu order is deterministic rather than dependent on which gem happened to register first. Core returns the lowest key and is always first; the default places everything else after it.

A handler that only wants a menu entry implements the menu notification alone. A handler that also wants windows additionally implements the imgui update listener — one component can own both its menu entry and the windows it opens. Core and PhantomCam both do this.


Adding a Menu to Your Gem

Create a component in your gem that handles the menu notification, returns a sort key, and draws its own sub-menu. Add the imgui update listener too if it opens windows.

Three things bite in practice, and all three are worth checking before concluding your menu is broken:

CMake. ImGui dependencies go PUBLIC on your gem’s private object target so the enable define propagates to the editor target through its public dependency, which keeps it ODR-safe. The editor target then needs no imgui configuration of its own. Add the imgui gem as a runtime dependency of the client module.

Guard each window separately. If your update handler returns early when one window is closed, every window below that point in the same function silently stops drawing. Check each window’s open flag around its own draw rather than returning.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8 - Utilities

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

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.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Physics Trigger Volume

PhysicsTriggeringVolume is a non-component base class that manages the full lifecycle of a physics trigger or collision volume — entity tracking, enter/exit/hold callbacks, and collision contact events. Inherit from it to create interactive volumes without boilerplate physics code. PhysicsTriggerComponent is the concrete O3DE component that wraps this base and adds game-lifecycle awareness (standby handling).

Physics Trigger Volume API


Easing Curves

The Curves namespace provides 40+ easing functions and a CurveType enum for data-driven curve selection. All functions take a normalized t (0 → 1) input. The dispatch function EvaluateCurve(CurveType, t) routes to the correct function at runtime. Curve families: Linear, Quadratic, Cubic, Quartic, Quintic, Sine, Exponential, Circular, Back, Elastic, and Bounce.

Easing Curves API


Spring Dampers

The Springs namespace provides 6 spring-damper types for physics-based animation and smoothing — each available in float, Vector2, and Vector3 overloads (plus a Quaternion variant). All springs use a halflife parameter (seconds to reach 50% of goal) rather than raw stiffness/damping constants.

Spring Dampers API


Orbital Solver

The OrbitalSolver namespace (GS_Core::Math) provides a pivot-aware position and pose blend primitive — Linear / Cylindrical / Spherical shapes around a configurable pivot. Used by PhantomCam’s cross-cam blend execution, DynamicOrbitBody per-tick damping, and LeadingFollowBody band response. The pose form returns a “look at pivot” rotation by default, making it a one-call replacement for spring damping when motion naturally arranges around a pivot.

Orbital Solver API


Noise

The Noise namespace (GS_Core::Noise) provides a project-owned, license-clean Perlin noise toolkit — signed Perlin 1D / 2D / 3D, fractal multi-octave, and the LayeredPerlin1D (amp, freq, phase) stack with its NoiseLayer struct. Used by PhantomCam camera shake (PerlinNoise and ImpulseNoise stages) and open for adoption by any UI / VFX / unit modulation consumer that needs time-varying smooth values. Stateless, deterministic across runs, thread-safe.

Noise API


Envelope

The Envelope namespace (GS_Core::Envelope) provides a reflected ADSR envelope — attack, decay, sustain and release — with Evaluate, TotalDuration and IsFinished. General-purpose rather than audio-specific: any value that should rise, hold and fall over time can be described as an authored envelope instead of hand-written timing code. Because the struct is reflected, the shape is tuned in the Inspector rather than in source.

Envelope API


Gradients

Multi-stop gradient types (FloatGradient, Vector2Gradient, ColorGradient) for sampling values over a normalized [0,1] range. Used throughout the motion system and feedback effects for curve-based value animation. ColorGradient exposes independent EvaluateColor(t) and EvaluateAlpha(t) channels.

Gradients API


Entity Utilities

The EntityUtility namespace provides helper functions for entity lookup by name — returning either an AZ::Entity* or AZ::EntityId.

Entity Utilities API


Weighted Random

RandomUtils::GetRandomWeighted<T> selects a key from an AZStd::unordered_map<T, float> with probability proportional to each entry’s float weight. Requires a caller-provided AZ::SimpleLcgRandom instance.

Weighted Random API


Angle Helpers

The Orientation namespace maps angles to discrete sector indices for directional gameplay — facing directions, animation sectors, compass queries. Includes the SectionConfig enum (x4 through x24 presets), RotationDirection, hysteresis support, and yaw/quaternion helpers.

Angle Helpers API


Spline Utilities

The SplineUtility namespace provides helper functions for O3DE spline queries — closest world point, closest local point, and normalized fraction along a spline — taking an entity ID and world/local position.

Spline Utilities API


Serialization Helpers

Three helpers that reduce reflection boilerplate: GS_ReflectionIncludes.h (single-include for all reflection headers), and GS_AssetReflectionIncludes.h (extends with asset serialization).

Serialization Helpers API


Common Enums

Shared enum types used across the framework: CurveType (maps to all easing functions for serialized curve selection) and BooleanConditions (condition evaluation for dialogue and record-keeping systems).

Common Enums API


Comparisons

ComparisonUtility.h supplies BooleanComparison(value, compareValue, condition) for float, AZ::u32 and AZ::s32, evaluating one of the BooleanConditions — equal, greater, less and so on — against a pair of values.

This is what lets a condition be authored as data: a component exposes a comparison enum and a threshold in the Inspector, and the code evaluates whichever comparison the designer picked without a branch per operator.

#include <GS_Core/Utility/ComparisonUtility.h>

if (GS_Core::BooleanComparison(currentHealth, m_threshold, m_condition))
{
    // the authored condition passed
}

Debug Logging

DebugLoggingHelper is a small mix-in that gives a component a Logging Depth property and gated log output. Above zero, the component reports what it is doing; at zero it stays silent.

GS_ManagerComponent inherits it, which is why every manager can be made verbose individually from the Inspector. Inherit it on your own components when you want the same per-instance diagnostic control — it is more useful than a global verbosity flag precisely because you can turn up one system while the rest stays quiet.


See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.1 - Common Enums

Shared enumeration types used across the GS_Play framework — CurveType for easing curve selection and BooleanConditions for condition evaluation.

Common Enums provides shared enumeration types registered with O3DE’s SerializeContext. They can be used in component properties, asset fields, and ScriptCanvas nodes across any gem in the framework. The two primary enums are CurveType (curve selection for motion and gradient systems) and BooleanConditions (condition evaluation for dialogue and record-keeping systems).

Reflect functions: GS_Core::ReflectCommonEnums(context), GS_Core::ReflectCurveType(context)

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


BooleanConditions

Used by condition-evaluation logic in the dialogue and record-keeping systems to compare numeric or string values.

Used by: DialogueCondition, Record_DialogueCondition, RecordKeeperComponent

ValueDescription
EqualsExact equality check
NotEqualsInequality check
GreaterThanStrict greater-than
GreaterOrEqualsGreater-than or equal
LessThanStrict less-than
LessOrEqualsLess-than or equal

CurveType

Maps to all easing curve functions in the Curves utility. Used by GS_Motion tracks, blend profiles, gradient markers, and any system that needs designer-selectable easing.

Used by: GS_MotionTrack, UiMotionTrack, FeedbackMotionTrack, gradient markers, GS_PhantomCamBlendProfile

FamilyValues
LinearLinear
QuadraticEaseInQuadratic, EaseOutQuadratic, EaseInOutQuadratic
CubicEaseInCubic, EaseOutCubic, EaseInOutCubic
QuarticEaseInQuartic, EaseOutQuartic, EaseInOutQuartic
QuinticEaseInQuintic, EaseOutQuintic, EaseInOutQuintic
SineEaseInSine, EaseOutSine, EaseInOutSine
ExponentialEaseInExpo, EaseOutExpo, EaseInOutExpo
CircularEaseInCirc, EaseOutCirc, EaseInOutCirc
BackEaseInBack, EaseOutBack, EaseInOutBack
ElasticEaseInElastic, EaseOutElastic, EaseInOutElastic
BounceEaseInBounce, EaseOutBounce, EaseInOutBounce

Evaluating a CurveType in C++

#include <GS_Core/Utility/Math/CurvesUtility.h>

// Dispatch to the correct curve function via enum
float result = GS_Core::Curves::EvaluateCurve(GS_Core::CurveType::EaseInOutCubic, t);

Using Enums in Components

Use EnumAttribute on a DataElement with UIHandlers::ComboBox to expose enum selection as an Inspector dropdown.

#include <GS_Core/Utility/Math/CurvesUtility.h>
#include <GS_Core/Utility/CommonEnums.h>

// In your component's Reflect() method:
editContext->Class<MyComponent>("My Component", "Description")
    ->DataElement(AZ::Edit::UIHandlers::ComboBox,
        &MyComponent::m_curveType, "Curve Type",
        "The easing curve applied to this animation.")
        ->EnumAttribute(GS_Core::CurveType::Linear,             "Linear")
        ->EnumAttribute(GS_Core::CurveType::EaseInQuadratic,    "Ease In Quadratic")
        ->EnumAttribute(GS_Core::CurveType::EaseOutQuadratic,   "Ease Out Quadratic")
        ->EnumAttribute(GS_Core::CurveType::EaseInOutQuadratic, "Ease InOut Quadratic")
        ->EnumAttribute(GS_Core::CurveType::EaseInCubic,        "Ease In Cubic")
        ->EnumAttribute(GS_Core::CurveType::EaseOutCubic,       "Ease Out Cubic")
        ->EnumAttribute(GS_Core::CurveType::EaseInOutCubic,     "Ease InOut Cubic")
        // ... continue for all desired variants
    ;

This creates an Inspector dropdown where designers select a curve by name without touching code.


See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.2 - Serialization Helpers

Three reflection helpers — single-include headers for common and asset serialization, and a generic asset handler template for custom asset types.

Three helpers that eliminate serialization boilerplate: a single-include header for common reflection, an extension for asset fields, and a ready-made O3DE asset handler template for any custom AssetData subclass.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


GS_ReflectionIncludes.h

A single-include header that brings in all O3DE reflection headers needed for any class with an inline Reflect() method.

Includes:

  • AzCore/RTTI/RTTI.h
  • AzCore/Memory/SystemAllocator.h
  • AzCore/Serialization/SerializeContext.h
  • AzCore/Serialization/EditContext.h
  • AzCore/std/string/string.h

Use this in any header declaring a reflected struct, class, or enum. Does not handle AZ::Data::Asset<T> fields.

#include <GS_Core/Utility/Reflection/GS_ReflectionIncludes.h>

namespace MyProject
{
    struct MyData
    {
        AZ_TYPE_INFO(MyData, "{YOUR-UUID-HERE}");
        static void Reflect(AZ::ReflectContext* context);

        float m_value = 0.0f;
    };
}

GS_AssetReflectionIncludes.h

Extends GS_ReflectionIncludes.h with asset serialization headers.

Adds:

  • AzCore/Asset/AssetCommon.h
  • AzCore/Asset/AssetSerializer.h

Use this in any header declaring a class with AZ::Data::Asset<T> fields. Ensures SerializeGenericTypeInfo<Asset<T>> is visible and prevents silent failures in Unity builds.

#include <GS_Core/Utility/Reflection/GS_AssetReflectionIncludes.h>

namespace MyProject
{
    struct MyComponent : public AZ::Component
    {
        AZ_COMPONENT_DECL(MyComponent);
        static void Reflect(AZ::ReflectContext* context);

        AZ::Data::Asset<MyAssetType> m_asset;
    };
}

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.3 - Curves

40+ easing curve functions for smooth animation and interpolation — organized by family with a CurveType enum for data-driven selection.

The Curves namespace (GS_Core::Curves) provides 40+ easing functions for smooth animation and interpolation. All functions take a normalized t value in [0,1] and return a remapped [0,1] value. The dispatch function EvaluateCurve routes to the correct function by CurveType enum value, making curve selection fully data-driven from the Inspector or asset editor.

Used by every GS_Motion track, all gradient evaluate calls, and the blend profile system.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Curve Families

FamilyFunctionsCharacter
LinearLinearConstant speed
QuadraticEaseInQuadratic, EaseOutQuadratic, EaseInOutQuadraticGentle acceleration
CubicEaseInCubic, EaseOutCubic, EaseInOutCubicModerate acceleration
QuarticEaseInQuartic, EaseOutQuartic, EaseInOutQuarticStrong acceleration
QuinticEaseInQuintic, EaseOutQuintic, EaseInOutQuinticVery strong acceleration
SineEaseInSine, EaseOutSine, EaseInOutSineGentle, natural feel
ExponentialEaseInExpo, EaseOutExpo, EaseInOutExpoDramatic speed change
CircularEaseInCirc, EaseOutCirc, EaseInOutCircQuarter-circle shape
BackEaseInBack, EaseOutBack, EaseInOutBackOvershoot and return
ElasticEaseInElastic, EaseOutElastic, EaseInOutElasticSpring-like oscillation
BounceEaseInBounce, EaseOutBounce, EaseInOutBounceBouncing ball effect

Variant naming: EaseIn = slow start, EaseOut = slow end, EaseInOut = slow start and end.


API Reference

Dispatch Function

float EvaluateCurve(CurveType curveType, float t);

Dispatches to the correct curve function based on the CurveType enum value. This is the primary call site for all motion and gradient systems. t must be in [0,1].

Individual Functions

One free function per CurveType value, all sharing the signature float <Name>(float t). Examples:

FunctionDescription
GS_Core::Curves::Linear(t)Linear — no easing
GS_Core::Curves::EaseInQuadratic(t)Quadratic ease-in
GS_Core::Curves::EaseOutBounce(t)Bounce ease-out
GS_Core::Curves::EaseInOutBack(t)Back ease-in-out (overshoot both ends)

All functions follow the same naming pattern as the CurveType enum values.


Usage Examples

Dispatch via enum (data-driven)

#include <GS_Core/Utility/Math/CurvesUtility.h>

// Evaluate at normalized progress t (0 → 1)
float eased = GS_Core::Curves::EvaluateCurve(GS_Core::CurveType::EaseInOutCubic, t);

// Interpolate between two values
float result = start + (end - start) * GS_Core::Curves::EvaluateCurve(curveType, t);

Direct function call

// Call the function directly for a known curve
float eased = GS_Core::Curves::EaseOutBack(t);

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.4 - Springs

Spring-damper functions for physically-grounded value interpolation — smooth following, overshoot, and settling for floats, vectors, and quaternions.

The Springs namespace (GS_Core::Springs) provides six spring-damper types for physics-based animation and smoothing. Springs produce natural-feeling motion that reacts to velocity — ideal for camera follow, UI motion, and any value that should settle rather than snap.

All springs use a halflife parameter: the number of seconds to cover 50% of the remaining distance to the goal. This is more intuitive than raw stiffness/damping constants. Each spring type is available in float, AZ::Vector2, and AZ::Vector3 overloads where applicable.

Used by: gs_phantomcam (camera smoothing), gs_unit (character movement), gs_juice (feedback motions)

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


Spring Types

TypeCharacterBest For
SimpleSpringDamperExactFast start, ease-out arrivalSimple follow, snap behaviors
AccelerationSpringDamperTracks target velocity, smoother on input changesCharacter movement, camera motion
DoubleSpringDamperS-curve — slow start AND slow arrivalUI transitions, polished cuts
TimedSpringDamperReaches target by a specified timeChoreographed, loosely timed movements
VelocitySpringDamperPredictive — leads the target’s directionCamera following a moving target
QuaternionSpringDamperRotational spring (angular velocity)Smooth orientation changes

SimpleSpringDamperExact

A critically-damped spring that moves toward a target position. Fast start, ease-out arrival. Computed exactly (no approximation). The most common spring for follow and snap behaviors.

ParameterTypeDescription
positionT (in/out)Current position, updated in-place each frame
velocityT (in/out)Current velocity — must be cached between frames
targetPositionTGoal position to move toward
halflifefloatSeconds to cover 50% of remaining distance
deltaTimefloatFrame delta time

AccelerationSpringDamper

Tracks a target velocity rather than a position. Adds an acceleration memory term for smoother response to sudden direction changes (e.g. thumbstick flicks). Suited for character movement and camera motion where input can change abruptly.

ParameterTypeDescription
positionT (in/out)Accumulated position
velocityT (in/out)Current velocity — must be cached between frames
accelerationT (in/out)Acceleration memory — must be cached between frames
targetVelocityTThe desired velocity to spring toward
halflifefloatSettling time
deltaTimefloatFrame delta time

DoubleSpringDamper

Two springs chained together, producing an S-curve (ease-in AND ease-out). Slower to start and slower to arrive than the simple spring. Gives a more polished feel for UI transitions or cinematic camera cuts.

ParameterTypeDescription
positionT (in/out)Current position
velocityT (in/out)Current velocity — must be cached between frames
previousPositionT (in/out)Internal state — must be cached between frames
previousVelocityT (in/out)Internal state — must be cached between frames
targetPositionTGoal position
halflifefloatSettling time
deltaTimefloatFrame delta time

TimedSpringDamper

Attempts to reach the target by a specific time. Adjusts halflife internally so the spring arrives near the target time rather than asymptotically. Useful for choreographed movements with loose time targets.

ParameterTypeDescription
positionT (in/out)Current position
velocityT (in/out)Current velocity — must be cached between frames
previousTargetT (in/out)Last known target — must be cached between frames
targetPositionTDestination
targetTimefloatTime (in seconds) at which the spring should arrive
halflifefloatBase settling time
deltaTimefloatFrame delta time

VelocitySpringDamper

Tracks a moving target by incorporating the target’s own velocity for predictive lead. The follower anticipates the direction of movement, reducing lag on fast-moving targets. Suited for camera following a character at speed.

ParameterTypeDescription
positionT (in/out)Follower position
velocityT (in/out)Follower velocity — must be cached between frames
previousPositionT (in/out)Internal state — must be cached between frames
targetPositionTCurrent target position
targetVelocityTTarget’s velocity (used for predictive lead)
halflifefloatSettling time
deltaTimefloatFrame delta time

QuaternionSpringDamper

Rotation spring that operates on angular velocity. Includes a flip option to reverse the rotation direction.

Note: The rotation output jumps to the goal each frame. Extract angularVelocity and integrate externally if you need a continuously smooth rotation output.

ParameterTypeDescription
rotationAZ::Quaternion (in/out)Quaternion moved toward targetRotation
angularVelocityAZ::Vector3 (in/out)Angular velocity — must be cached between frames
targetRotationAZ::QuaternionGoal orientation
halflifefloatSettling time
deltaTimefloatFrame delta time
flipboolReverses rotation direction (default: false)

Usage Example

#include <GS_Core/Utility/Math/SpringsUtility.h>

// Member fields — must persist between frames
AZ::Vector3 m_followVelocity = AZ::Vector3::CreateZero();

// In your tick function:
AZ::Vector3 currentPos = GetEntityTranslation();
AZ::Vector3 targetPos  = GetTargetTranslation();

GS_Core::Springs::SimpleSpringDamperExact(
    currentPos,       // in/out: current position (updated in place)
    m_followVelocity, // in/out: velocity (cached between frames)
    targetPos,        // target position
    0.1f,             // halflife: 0.1 seconds to cover half the distance
    deltaTime
);

SetEntityTranslation(currentPos);

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.5 - Noise

Project-owned Perlin noise toolkit — signed Perlin 1D/2D/3D, fractal multi-octave, layered (amp, freq, phase) stacks with the NoiseLayer struct, unit-range wrappers, and decorrelation patterns. Backs PhantomCam noise stages and any time-varying value modulation consumer.

The Noise namespace (GS_Core::Noise) provides a project-owned, stateless, deterministic, thread-safe Perlin noise toolkit. It backs every continuous-noise subsystem in the framework — PhantomCam Noise Profiles and the PerlinNoise / ImpulseNoise additive stages — plus any future UI wobble, unit idle-jitter, or VFX parameter modulation consumer that needs smooth time-varying values.

The same NoiseLayer struct documented below is what CameraNoiseProfile stores in its six per-axis vectors — no duplication. The utility is intentionally outside PhantomCam so non-camera consumers can adopt it without pulling a camera dependency.

Used by: gs_phantomcam (camera noise and impulse stages). Open for adoption by any consumer needing time-varying smooth modulation.

 

Contents


Signed Perlin

Classic Perlin noise. Output range approximately [-1, 1], statistically zero-biased — typical samples cluster near zero, not the extremes. Amplitude tuning at the consumer side accounts for this.

float Perlin1D(float x);
float Perlin2D(float x, float y);
float Perlin2D(const AZ::Vector2& p);
float Perlin3D(float x, float y, float z);
float Perlin3D(const AZ::Vector3& p);

Normalization details:

  • 1D scaled by * 0.125 (gradient outputs land in ~[-8, 8]).
  • 2D scaled by * 1/3 (gradient set in ~[-3, 3]).
  • 3D natively in ~[-1, 1] with a GetClamp safety bound.

Fractal Perlin

Multi-octave Perlin renormalized so output stays ~[-1, 1] regardless of octave count. Useful for environmental / spatial noise (terrain undulations, ambient flicker, foliage rustle).

float FractalPerlin1D(float x,
                      int   octaves     = 4,
                      float persistence = 0.5f,
                      float lacunarity  = 2.0f);

float FractalPerlin2D(const AZ::Vector2& p,
                      int   octaves     = 4,
                      float persistence = 0.5f,
                      float lacunarity  = 2.0f);

float FractalPerlin3D(const AZ::Vector3& p,
                      int   octaves     = 4,
                      float persistence = 0.5f,
                      float lacunarity  = 2.0f);

Each octave doubles frequency (lacunarity) and halves amplitude (persistence) by default. The accumulated total is divided by the amplitude sum so the output remains ~[-1, 1] regardless of octave count.

ParameterDefaultClamping
octaves4Clamped to [1, 16].
persistence0.5Non-positive values bumped to a safe minimum.
lacunarity2.0Non-positive values bumped to a safe minimum.

Camera noise does NOT use Fractal. PhantomCam’s noise stages use LayeredPerlin1D below, which gives authors explicit per-layer control over the (amp, freq, phase) stack rather than the geometric octave progression Fractal enforces.


NoiseLayer Struct

A single layer in a layered Perlin stack — amplitude, frequency, optional phase.

struct NoiseLayer
{
    AZ_TYPE_INFO(NoiseLayer, "{2D5E9B7A-4C03-4518-8A1C-6F7B1E3D9C40}");
    AZ_CLASS_ALLOCATOR(NoiseLayer, AZ::SystemAllocator);

    float m_amplitude = 0.0f;   // consumer units (m, deg, unitless)
    float m_frequency = 1.0f;   // Hz of input `t`
    float m_phase     = 0.0f;   // per-layer time offset

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

Reflection

NoiseLayer::Reflect registers the type once, guarded with FindClassData (required because multiple owners may reflect it). On-disk field strings are Amplitude, Frequency, Phase — capitalized, no m_ prefix. The EditContext UI handler uses AutoExpand: true so layers display inline in the Asset Editor without further user action.

This is the same struct CameraNoiseProfile stores in its six per-axis vectors. Other future consumers reuse the same type.


LayeredPerlin1D

Sum of per-layer Perlin1D samples at time t.

float LayeredPerlin1D(float                            t,
                      const AZStd::vector<NoiseLayer>& layers,
                      float                            decorrelate = 0.0f);

Evaluation:

result = Σ Perlin1D(t * layer.frequency + layer.phase + decorrelate) * layer.amplitude

Key behaviors

  • Layers with m_amplitude == 0 or m_frequency == 0 are skipped by the inner loop. Authors can leave dead rows in a profile without paying for them.
  • Output is NOT renormalized. Unlike Fractal, amplitudes sum directly. Authors choose amplitudes that add up to their intended envelope.
  • Layer ordering does not matter — the sum is commutative.
  • The decorrelate parameter is added to every layer’s input — see Decorrelation Pattern.

Camera-shake authoring conventions

QuantityTypical range
Position layer amplitude0.002 – 0.4 m
Rotation layer amplitude0.1 – 17 deg
Frequency0.1 – 60 Hz

Concrete preset values live in the shipped .camnoiseprofile assets — see Noise Profiles for the asset library.


Unit-Range Wrappers

For consumers that want a 0..1 modulator (UI alpha pulses, scalar VFX drivers) rather than a signed delta.

inline float Perlin1DUnit(float x)              { return Perlin1D(x) * 0.5f + 0.5f; }
inline float Perlin2DUnit(const AZ::Vector2& p) { return Perlin2D(p) * 0.5f + 0.5f; }
inline float Perlin3DUnit(const AZ::Vector3& p) { return Perlin3D(p) * 0.5f + 0.5f; }

A linear * 0.5 + 0.5 remap of the signed signal.


Decorrelation Pattern

For multi-channel consumers that share one stream’s shape but need independent values (camera 6-DoF noise is the canonical case), offset the sample point rather than reseeding:

const float chX = Perlin1D(t * freqX + 1000.0f);
const float chY = Perlin1D(t * freqY + 2000.0f);
const float chZ = Perlin1D(t * freqZ + 3000.0f);

Offsets of a few hundred units in the sample space give effectively independent samples from the shared gradient table. PhantomCam’s PerlinNoise stage applies a per-axis decorrelation offset to LayeredPerlin1D so the same profile drives all six axes without phase-locking them.


Determinism

  • The permutation table is built once at static init from a fixed seed. No runtime randomness.
  • Perlin1D(t) returns the same value every call across runs, platforms, and threads — no shared mutable state in the hot path.
  • Camera noise stages own m_time per-instance — replays at the same m_time reproduce the same sway frame-for-frame.

Performance

OperationCost
Perlin1D hot path2 table lookups, 2 gradient evals, 1 fade, 1 lerp. Effectively free at gameplay rates.
LayeredPerlin1DLinear in the number of non-skipped layers. Typical 1–3 layers per axis → ~18 Perlin1D evaluations per cam tick across all six axes.
FractalPerlin*Linear in octaves. Default 4 octaves is comparable to a 4-layer LayeredPerlin1D.

Licensing Posture

The toolkit is license-clean for commercial distribution. The header carries a longform license note; the summary:

  • Ken Perlin’s noise algorithm (1983, fade refinement 2002) is not patented and never was.
  • The expired Simplex patent (US 6,867,776) does not apply — this is classic Perlin, and even Simplex would now be free.
  • The permutation table is not transcribed from any reference implementation. It is generated at static init from a deterministic LCG (Numerical Recipes constants) seeded with 0x4E435347 (“GSCN” = GS Core Noise). The 256-byte table is duplicated to 512 so the hot path wraps with & 511 instead of branching.
  • Gradient sets and the quintic fade (6t⁵ − 15t⁴ + 10t³) are public-domain math.

Consumers

SubsystemFunction usedTime source
PhantomCam PerlinNoise additive stageLayeredPerlin1D × 6 axesm_time += deltaTime since activation.
PhantomCam ImpulseNoise additive stageLayeredPerlin1D × 6 axes, ADSR-envelopedm_elapsed since Trigger(strength).

Future consumers will live in this table as they ship — UI wobble, unit idle-jitter, VFX scalar drivers, etc. The utility is namespaced under GS_Core::Noise precisely so other gems can adopt it without pulling PhantomCam.


See Also

Related GS_Core utilities:

  • Springs — spring-damper smoothing. Use Springs for one-shot smoothing toward a goal; use Noise for sustained time-varying modulation around a baseline.
  • Curves — CurveType enum for easing curves. Often paired with Noise — an ADSR envelope (curve-shaped) gates a Layered Perlin burst, for example.
  • Orbital Solver — pivot-aware blend primitive. Companion utility consumed by the same PhantomCam systems.

PhantomCam consumer pages:


Get GS_Core

Explore other GS_Play gems on the product page.

4.1.8.6 - Orbital Solver

Pivot-aware position and pose blend primitive — Linear / Cylindrical / Spherical shapes around a configurable pivot. Powers PhantomCam blend shapes, DynamicOrbitBody damping, and LeadingFollowBody band response.

The OrbitalSolver namespace (GS_Core::Math) provides a stateless, deterministic, pure position / pose blend primitive. Given two positions (or transforms) and a pivot point, it blends them by a fraction t along an arc that preserves the geometric relationship to the pivot — rather than the straight-line lerp that cuts through the pivot when the start and end sit on opposite sides.

The primitive originated as a fix for PhantomCam cross-cam blends (two orbit cams at ~180° around a shared target produced visible “cuts through the target” mid-blend). It has since been reframed as the standard movement primitive for any motion naturally arranged around a pivot — orbit cams, leading-follow band response, post-config stage transitions. Linear lerp becomes the special case used only when no meaningful pivot exists.

Used by: gs_phantomcam (cross-cam blend execution, DynamicOrbitBody damping, LeadingFollowBody band response).

For usage guides and PhantomCam-specific consumption patterns, see the PhantomCam consumer pages linked under Consumers.

 

Contents


BlendShape

The path shape. The same enum is reflected for authoring on consumers like Blend Profiles and DynamicOrbitBody.

enum class BlendShape : AZ::u8 {
    Linear,
    Cylindrical,
    Spherical,
};
ShapeBehaviorRight for
LinearStraight-line lerp in world space. Used as the automatic fallback when the pivot or either input is degenerate.Cams with no meaningful pivot. Short displacements where arcing would be over-engineered.
CylindricalInterpolate in a plane perpendicular to cylinderAxis (default world +Z). Radius and angle lerp in the plane; height along the axis lerps linearly.Ground cams sweeping horizontally without dipping / rising mid-sweep. PhantomCam LeadingFollowBody band response default.
SphericalFull 3D arc on the surface of a sphere centered on the pivot. Radius lerps along the arc.Cams that change height during the motion (over-shoulder ↔ overhead). Free pitch-aware orbits. PhantomCam DynamicOrbitBody default.

PivotSource

For two-cam blends, which cam’s pivot drives the arc. Single-pivot consumers don’t need this — they pass the one pivot they already have. The enum exists for cross-cam blend authoring on PhantomBlend.

enum class PivotSource : AZ::u8 {
    Source,
    Destination,
    Shared,
};
ValueBehavior
SourceSweep around the outgoing cam’s pivot. “Leave cam A elegantly, then settle into wherever cam B is.”
DestinationSweep around the incoming cam’s pivot. “Arrive at cam B elegantly, regardless of where cam A was.”
SharedMidpoint of both pivots. Collapses identically to either single-pivot mode when the pivots coincide. Recommended default — handles same-target and split-target cases gracefully.

BlendPositionAroundPivot

Position-only blend.

AZ::Vector3 BlendPositionAroundPivot(
    const AZ::Vector3& a,
    const AZ::Vector3& b,
    const AZ::Vector3& pivot,
    float              t,
    BlendShape         shape,
    const AZ::Vector3& cylinderAxis = AZ::Vector3::CreateAxisZ());

Blend position a toward b around pivot by fraction t along the chosen shape.

Parameters

ParameterPurpose
aStart position.
bEnd position.
pivotWorld-space pivot point. The geometric center of the arc.
tBlend fraction. NOT pre-clamped — values outside [0, 1] extrapolate.
shapeLinear / Cylindrical / Spherical.
cylinderAxisUp axis for cylindrical mode. Default world +Z. Ignored by Linear and Spherical.

Degenerate cases (automatic Linear fallback)

The function falls back to linear lerp when:

  • Either input position is at or very-near the pivot (degenerate radius makes arc direction undefined).
  • shape == Linear explicitly.

Extrapolation

Occasionally useful — e.g. t = -0.1 to anti-extrapolate slightly past the source for an overshoot effect.

Cylindrical math sketch

height_a = (a - pivot) ⋅ cylinderAxis
height_b = (b - pivot) ⋅ cylinderAxis
plane_a  = a - pivot - height_a * cylinderAxis
plane_b  = b - pivot - height_b * cylinderAxis

radius_a = plane_a.length
radius_b = plane_b.length
angle_a  = atan2(plane_a.y, plane_a.x)
angle_b  = atan2(plane_b.y, plane_b.x)
shortestAngle_b = ShortestArc(angle_a, angle_b)

radius_t = lerp(radius_a, radius_b, t)
angle_t  = lerp(angle_a, shortestAngle_b, t)
height_t = lerp(height_a, height_b, t)

result = pivot + height_t * cylinderAxis +
         radius_t * (cos(angle_t) * basisX + sin(angle_t) * basisY)

Spherical math sketch

dir_a    = (a - pivot).normalized
dir_b    = (b - pivot).normalized
radius_a = (a - pivot).length
radius_b = (b - pivot).length

// Slerp the direction on the unit sphere.
angle = acos(clamp(dir_a · dir_b, -1, 1))
if angle ≈ 0:  dir_t = dir_a
else:
    dir_t = (dir_a * sin((1 - t) * angle) + dir_b * sin(t * angle)) / sin(angle)

radius_t = lerp(radius_a, radius_b, t)

result = pivot + dir_t * radius_t

BlendPoseAroundPivot

Pose-level blend. Position uses BlendPositionAroundPivot. Rotation defaults to “look at pivot from the blended position.”

AZ::Transform BlendPoseAroundPivot(
    const AZ::Transform& a,
    const AZ::Transform& b,
    const AZ::Vector3&   pivot,
    float                t,
    BlendShape           shape,
    const AZ::Vector3&   cylinderAxis    = AZ::Vector3::CreateAxisZ(),
    bool                 slerpRotations  = false);

Rotation modes

slerpRotationsRotation behavior
false (default)Fresh “look at pivot from the blended position” quaternion. The most-often-correct cam semantic — orientation always faces the pivot regardless of either input cam’s authored aim quirks. Uses +Y forward / +Z up (matches AZ::Transform::CreateLookAt).
trueSlerp a.GetRotation() → b.GetRotation() directly. For UI / VFX consumers that want raw input-rotation interpolation.

ResolveCrossCamPivot

For consumers that need to pick between two cams’ pivots based on a PivotSource enum.

struct ResolvedPivot {
    AZ::Vector3 pivot;
    bool        valid;   // false → no meaningful pivot; use Linear
};

ResolvedPivot ResolveCrossCamPivot(
    bool               sourceHasPivot,
    const AZ::Vector3& sourcePivot,
    bool               destHasPivot,
    const AZ::Vector3& destPivot,
    PivotSource        source);

Resolution table

sourceBoth have pivotsOnly source hasOnly dest hasNeither
SourcesourcePivotsourcePivotdestPivot (fallback)invalid
DestinationdestPivotsourcePivot (fallback)destPivotinvalid
Sharedmidpoint(s, d)sourcePivotdestPivotinvalid

valid = false signals the consumer should fall back to BlendShape::Linear.


Reflection

void ReflectOrbitalSolverEnums(AZ::ReflectContext* context);

Reflects BlendShape and PivotSource into the SerializeContext so authoring sites (PhantomBlend, body stages) can expose them as ComboBox EnumAttributes.

Guarded internally — safe to call multiple times from independent reflect chains. PhantomBlend and DynamicOrbitBody both call it at the top of their own Reflect.


Per-Tick Damping Pattern

The primitive doubles as a per-tick damping operator. Spring damping toward a pivot-arranged destination is exactly this primitive:

m_idealPosition = GS_Core::Math::BlendPositionAroundPivot(
    m_idealPosition,
    desiredPos,
    pivot,
    HalflifeAlpha(halflife, deltaTime),
    shape);

No separate spring needed. HalflifeAlpha comes from the standard halflife → per-frame alpha conversion 1 − exp2(-deltaTime / halflife).

This is how DynamicOrbitBody damps the cam’s position toward whatever its target angles resolve to on the orbit surface — the solver call IS the smoothing. Authors tune one halflife and get spring-like response shaped around the pivot.


Consumers

PhantomCam consumers

ConsumerUse
Cam Core — cross-cam blend executionBlendPositionAroundPivot per-tick during cross-cam blends. Pivot resolved by the matched blend entry’s PivotSource choice (Source / Destination / Shared).
Blend Profiles — authoringEach PhantomBlend entry authors BlendShape and PivotSource.
DynamicOrbitBody — per-tick dampingSolver IS the damping primitive — yaw / pitch target produces desiredPos on the orbit surface; solver damps m_idealPosition toward it.
LeadingFollowBody — band responseCylindrical default. Cam arcs around the target during band response rather than cutting through.

Non-PhantomCam consumers

The same primitive serves any system that needs pivot-aware blending — UI elements orbiting a focal point, world-space VFX paths, unit motion patterns. The primitive is namespaced under GS_Core::Math precisely so other gems can adopt it.


See Also

Related GS_Core utilities:

  • Springs — spring-damper primitives. Use Springs for non-pivot smoothing; use Orbital Solver when motion naturally arranges around a pivot.
  • Curves — CurveType enum used for blend easing curves.
  • Angles Helper — yaw wrap / shortest-arc helpers used by orbit body stages.

PhantomCam consumer pages:


Get GS_Core

Explore other GS_Play gems on the product page.

4.1.8.7 - Gradients

Multi-stop gradient types for sampling color, float, and vector values over a normalized range — used by motion tracks and feedback effects.

The Gradients utility provides three parallel gradient types for interpolating values over a normalized [0,1] range using a sorted list of marker points. All types are fully reflected (SerializeContext + EditContext) and editable in the Inspector with visual marker placement. Gradients are lazily sorted before evaluation.

Used by: FeedbackMaterialTrack (color/float animation), UiImageColorTrack, procedural visual effects, any system needing editable color or value ramps.

For usage guides and setup examples, see The Basics: GS_Core.

Gradient Slider in the O3DE Inspector

 

Contents


Gradient Types

TypeValue TypeDescription
FloatGradientfloatSingle float value ramp
Vector2GradientAZ::Vector22D vector ramp
ColorGradientAZ::ColorRGBA color ramp with separate color and alpha channels

Marker Structs

Each gradient type has a corresponding marker struct that defines a single stop on the gradient.

FloatGradientMarker

FieldTypeDescription
markerValuefloatThe float value at this stop
markerPositionfloatPosition in [0, 1] along the gradient

Vector2GradientMarker

FieldTypeDescription
markerValueAZ::Vector2The 2D vector value at this stop
markerPositionfloatPosition in [0, 1] along the gradient

ColorGradientMarker

FieldTypeDescription
markerColorAZ::ColorThe color (RGB + A) at this stop
markerPositionfloatPosition in [0, 1] along the gradient

Gradient Classes

All three gradient types share the same structure and interface:

Field / MethodDescription
sliderAZStd::vector<Marker> — the sorted list of gradient stops (field name for Float/Vector2)
sortedInternal dirty flag; gradient is lazily sorted before Evaluate is called
SortGradient()Sorts markers by position. Called automatically before evaluation when markers change.
Evaluate(float t)Returns the interpolated value at normalized position t in [0, 1]

ColorGradient

ColorGradient maintains two separate marker lists for independent RGB and alpha control:

FieldDescription
colorSliderAZStd::vector<ColorGradientMarker> — RGB stops
alphaSliderAZStd::vector<ColorGradientMarker> — alpha stops

ColorGradient Channels

ColorGradient exposes three evaluate methods for flexible sampling:

MethodReturnsDescription
Evaluate(float t)AZ::ColorFull RGBA color — samples both colorSlider and alphaSlider
EvaluateColor(float t)AZ::ColorRGB only — alpha is always 1.0
EvaluateAlpha(float t)floatAlpha channel only

Usage Example

#include <GS_Core/Utility/Gradients/FloatGradientUtility.h>
#include <GS_Core/Utility/Gradients/ColorGradientUtility.h>

// Sample a float gradient at normalized progress (0 → 1)
float value = myFloatGradient.Evaluate(normalizedProgress);

// Sample full RGBA color
AZ::Color color = myColorGradient.Evaluate(normalizedProgress);

// Sample RGB and alpha independently
AZ::Color rgb   = myColorGradient.EvaluateColor(normalizedProgress);
float alpha     = myColorGradient.EvaluateAlpha(normalizedProgress);

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.8 - Entity Helpers

Utility functions for finding entities in the scene by name — runtime entity lookup without maintaining manual references.

The EntityUtility namespace provides helper functions for finding entities in the active scene by name. Useful for runtime entity lookup without maintaining manual entity references.

For usage guides and setup examples, see The Basics: GS_Core.


API Reference

FunctionReturnsDescription
GetEntityByName(name)AZ::Entity*Finds an active entity with the given name. Returns nullptr if not found.
GetEntityIdByName(name)AZ::EntityIdFinds an active entity’s ID by name. Returns an invalid EntityId if not found.

Usage Example

#include <GS_Core/Utilities/EntityUtility.h>

// Find an entity by name
AZ::EntityId playerId = GS_Core::EntityUtility::GetEntityIdByName("Player");
if (playerId.IsValid())
{
    // Use the entity
}

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.9 - Angle Helpers

Angle and orientation math — sector mapping, yaw extraction, quaternion conversion, and hysteresis for directional classification.

The Orientation namespace (GS_Core::Orientation) provides angle-to-sector mapping for directional gameplay — animation direction selection, facing classification, and compass-style sector queries. It splits a full circle into N equal angular sectors and determines which sector a given angle falls into, with hysteresis to prevent rapid sector-switching at boundaries.

Used by: Paper-facing systems (gs_performer), directional input reactors (gs_unit), targeting systems (gs_interaction)

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


SectionConfig Enum

A reflected, editor-friendly enum selecting common sector counts and alignment modes. Two alignment modes exist per count:

  • cardinal / sideAligned — sector boundaries fall on the cardinal directions; sectors straddle diagonals
  • quarters / forwardAligned — sector boundaries fall on diagonals; sectors straddle cardinals
FamilyValues
x4x4_cardinal, x4_quarters
x6x6_sideAligned, x6_forwardAligned
x8x8_cardinal, x8_quarters
x10x10_sideAligned, x10_forwardAligned
x12x12_sideAligned, x12_forwardAligned
x14x14_sideAligned, x14_forwardAligned
x16x16_sideAligned, x16_forwardAligned
x18x18_sideAligned, x18_forwardAligned
x20x20_sideAligned, x20_forwardAligned
x22x22_sideAligned, x22_forwardAligned
x24x24_sideAligned, x24_forwardAligned

Register with GS_Core::Orientation::ReflectOrientationEnums(context) to expose these in the Inspector.


RotationDirection Enum

Controls the winding convention used when computing sector angles.

ValueNumericDescription
CCW1Counter-clockwise winding
CW-1Clockwise winding

Pick Struct

Returned by all PickByAngle overloads. Contains full sector geometry for the selected sector.

FieldTypeDescription
indexintWhich sector was selected [0, N)
countintTotal number of sectors
anglefloatThe input angle as provided
widthfloatAngular width of each sector (2π / N)
centerfloatCenter angle of the selected sector
startfloatStart angle of the selected sector
endfloatEnd angle of the selected sector

Use GS_Core::Orientation::Changed(pick, prevIndex) to detect when the sector index changes between frames.


API Reference

Sector Mapping

FunctionDescription
PickByAngle(angle, count, halfAligned, prevIndex, hysteresisDeg, startAngle, dir)Primary overload — full parameter control
PickByAngle(angle, SectionConfig, prevIndex, hysteresisDeg, startAngle, dir)Convenience overload using SectionConfig enum
PickByAngle(angle, count, offsetRad, prevIndex, hysteresisDeg, startAngle, dir)Low-level overload with explicit alignment offset
ConfigToParams(cfg)Maps a SectionConfig value to its (count, halfAligned) pair
Changed(pick, prevIndex)Returns true if the sector index changed from prevIndex

Angle Math

FunctionDescription
WrapToTwoPi(x)Wraps any angle to [0, 2π)
WrapToPi(x)Wraps any angle to (-π, π]
AlignmentOffsetRad(N, halfAligned)Returns the alignment shift in radians for a given count and mode

Yaw and Quaternion

FunctionDescription
YawFromDir(dir, rotDir)Flat yaw from a direction vector (Z-up world)
YawFromDir(dir, upAxis, forwardHint, rotDir)General yaw with custom up and forward axes
FlatSignedYaw_ToCam(camFwd, rootFwd, up, dir)Signed yaw from camera-forward to entity-forward, projected flat
QuatFromYaw(yawRad, upAxis)Builds a rotation quaternion from a yaw angle and up axis

Reflection

FunctionDescription
ReflectOrientationEnums(context)Registers SectionConfig and RotationDirection with SerializeContext

Usage Example

#include <GS_Core/Utility/Math/SectionByAngle.h>

// Member field — track previous sector to enable hysteresis
int m_prevSectorIndex = -1;

// In your tick / update function:
AZ::Vector3 moveDir = GetMovementDirection();

// Get the yaw from the movement direction (Z-up world)
float yaw = GS_Core::Orientation::YawFromDir(moveDir, GS_Core::Orientation::RotationDirection::CCW);

// Map to an 8-way sector with 5-degree hysteresis
GS_Core::Orientation::Pick pick = GS_Core::Orientation::PickByAngle(
    yaw,
    GS_Core::Orientation::SectionConfig::x8_cardinal,
    m_prevSectorIndex,  // previous index for hysteresis
    5.0f                // hysteresis in degrees
);

if (GS_Core::Orientation::Changed(pick, m_prevSectorIndex))
{
    m_prevSectorIndex = pick.index;
    // React to direction change: play animation, update facing, etc.
}

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.10 - Envelope

The reflected ADSR envelope — an authorable attack/decay/sustain/release shape with evaluation, duration and completion helpers.

GS_Core::Envelope provides an ADSR envelope — the attack / decay / sustain / release shape borrowed from synthesis, generalised for any value that needs to rise, hold and fall over time.

It is a general-purpose utility, not an audio one. Anywhere a value should ramp in, hold, and ramp out — a shake intensity, a feedback effect’s strength, a UI element’s emphasis — an envelope describes that shape as data instead of as hand-written timing code.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


ADSREnvelope

A reflected struct, so it can be authored directly on a component or asset and tuned in the Inspector.

FieldDefaultDescription
Attack0.05Seconds to rise from zero to full.
Decay0.10Seconds to fall from full to the sustain level.
Sustain0.00Seconds to hold at the sustain level.
Release0.30Seconds to fall from the sustain level to zero.
Sustain Level1.0The level held during the sustain phase.

The defaults describe an attack-release shape — a quick rise and a slower fall with no hold, which is what most one-shot effects want. Give Sustain a non-zero duration when the value should stay up for a while before releasing.

Being reflected is the point: a designer authors the envelope on the component, and the code just evaluates it.


Functions

FunctionReturnsDescription
Evaluate(elapsed, envelope)floatThe envelope’s value at a given elapsed time.
TotalDuration(envelope)floatAttack + decay + sustain + release.
IsFinished(elapsed, envelope)boolWhether the envelope has completed.

IsFinished is worth using rather than comparing against TotalDuration yourself — it is the signal to stop evaluating and shut the effect down, which is the cheaper path when many effects are running.


Usage

The pattern is always the same: accumulate elapsed time, evaluate, and stop when finished.

#include <GS_Core/Utility/Math/EnvelopeUtility.h>

// each tick, while active
m_elapsed += deltaTime;

const float gain = GS_Core::Envelope::Evaluate(m_elapsed, m_envelope);
ApplyGain(gain);

if (GS_Core::Envelope::IsFinished(m_elapsed, m_envelope))
{
    m_active = false;
}

Reset m_elapsed to zero to retrigger. Because the envelope is data, retriggering with a different shape is a matter of swapping the struct rather than changing any of this code.


See Also

For related utilities:

  • Easing Curves — shaping a single transition rather than a full envelope
  • Noise — continuous variation rather than a one-shot shape
  • Gradients — value over a normalized range rather than over time

For conceptual overviews and usage guides:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.11 - Spline Helpers

Utility functions for O3DE spline queries — closest point, fraction, and local/world space conversion by entity ID.

The SplineUtility namespace (GS_Core::SplineUtility) provides free functions for querying the closest point on an O3DE spline component attached to an entity. All functions take an entity ID and a position in world or local space.

Used by: PathTo_DialoguePerformance (gs_cinematics), any system that guides an entity along a spline path.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


API Reference

FunctionParametersDescription
FindClosestWorldPointAZ::EntityId entityId, AZ::Vector3 worldPosReturns the closest world-space point on the spline attached to entityId
FindClosestLocalPointAZ::EntityId entityId, AZ::Vector3 localPosReturns the closest point in local spline space
FindClosestFractionAZ::EntityId entityId, AZ::Vector3 worldPosReturns the normalized [0, 1] fraction along the spline of the closest point to worldPos

Usage Example

#include <GS_Core/Utility/SplineUtility.h>

// Find how far along a path the player is (0 = start, 1 = end)
AZ::Vector3 playerPos = GetPlayerWorldPosition();
float fraction = GS_Core::SplineUtility::FindClosestFraction(splineEntityId, playerPos);

// Get the actual closest world position on the path
AZ::Vector3 closestPoint = GS_Core::SplineUtility::FindClosestWorldPoint(splineEntityId, playerPos);

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.12 - Physics Trigger Volume

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

The physics trigger system is two classes: PhysicsTriggeringVolume (the non-component base class with all trigger logic) and PhysicsTriggerComponent (the concrete O3DE component that wraps it with game-lifecycle awareness). Inherit from either to create interactive volumes — damage zones, pickup areas, dialogue triggers, environmental hazards — without writing boilerplate physics code.

The base class handles entity tracking (one enter/exit per entity), supports both trigger overlaps and collision contacts, and provides optional hold/persist callbacks for continuous processing.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


PhysicsTriggeringVolume

File: Physics/PhysicsTriggeringVolume.h Namespace: GS_Core Base classes: Physics::RigidBodyNotificationBus::Handler, DebugLoggingHelper

A non-component base class that manages the full lifecycle of a physics trigger or collision volume. Add it alongside your own AZ::Component via multiple inheritance.

Configuration Fields

FieldTypeDefaultDescription
EnableCollisionAsTriggerboolfalseTreat collision begin/persist/end events as trigger-style callbacks
EnableTriggerHoldUpdateboolfalseEnable per-physics-tick TriggerHold callback while entities are inside

Lifecycle Methods

MethodDescription
ConnectTriggering(AZ::EntityId entityId)Subscribes to physics events on the given entity. Call from your component’s Activate().
DisconnectTriggering()Unsubscribes and clears all handlers. Call from your component’s Deactivate().
OnPhysicsEnabled(AZ::EntityId entityId)Called when the physics body becomes active. Internally calls InitPhysicsTriggerHandler.
OnPhysicsDisabled(AZ::EntityId entityId)Called when the physics body is destroyed. Internally calls DisconnectTriggering.

Virtual Callbacks

Override these in your subclass to react to trigger and collision events.

MethodParametersReturnsDescription
TriggerEnterAZ::EntityId entityboolCalled when an entity enters the volume. Return false to reject the entity.
TriggerHoldfloat fixedDeltaTimevoidCalled each physics tick while entities are inside. Requires EnableTriggerHoldUpdate = true.
CollisionHoldAZ::EntityId entityvoidCalled per-entity each physics tick for collision events. Requires EnableCollisionAsTrigger = true.
TriggerExitAZ::EntityId entityboolCalled when an entity exits the volume. Return false to reject.

Internal State

FieldDescription
m_entitiesAZStd::unordered_set<AZ::EntityId> — entities currently inside the volume
m_triggerEntityThe entity whose physics body is being monitored

PhysicsTriggerComponent

File: Physics/PhysicsTriggerComponent.h Namespace: GS_Core Base classes: PhysicsTriggeringVolume, GameManagerNotificationBus::Handler

A concrete O3DE component that wraps PhysicsTriggeringVolume and adds game-lifecycle awareness. Automatically disables the trigger during game standby and re-enables it on exit.

Used by: WorldTriggerComponent (gs_interaction), ColliderTriggerSensorComponent — these subclass PhysicsTriggerComponent to implement game-specific trigger behaviors.

Data Fields

FieldTypeDefaultDescription
isActiveboolfalseWhether the trigger is currently armed
triggerEntityAZ::EntityIdinvalidThe entity providing the physics body to monitor

Virtual Methods

Override these to implement game-specific trigger behavior.

MethodDescription
ActivatePhysicsTrigger(AZ::EntityId entity)Called when triggered. Subclasses perform the response here.
DeactivatePhysicsTrigger()Called on exit. Subclasses clean up here.

Standby Handling

MethodDescription
OnEnterStandby()Disables the trigger when the game enters standby
OnExitStandby()Re-enables the trigger when the game exits standby

Extending Physics Trigger Volume

Use the PhysicsTriggerComponent ClassWizard template to generate a new trigger component with boilerplate already in place — see GS_Core Templates.

Inherit directly from PhysicsTriggeringVolume alongside AZ::Component for a lightweight custom trigger. Use PhysicsTriggerComponent as your base if you need the built-in standby handling.

Header (.h)

#pragma once
#include <AzCore/Component/Component.h>
#include <GS_Core/Utility/Physics/PhysicsTriggeringVolume.h>

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

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

    protected:
        void Activate() override;
        void Deactivate() override;

        // Trigger overrides
        bool TriggerEnter(AZ::EntityId entity) override;
        void TriggerHold(float fixedDeltaTime) override;
        bool TriggerExit(AZ::EntityId entity) 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 sc = azrtti_cast<AZ::SerializeContext*>(context))
        {
            sc->Class<DamageZoneComponent, AZ::Component, GS_Core::PhysicsTriggeringVolume>()
                ->Version(0)
                ->Field("DamagePerSecond", &DamageZoneComponent::m_damagePerSecond);

            if (AZ::EditContext* ec = sc->GetEditContext())
            {
                ec->Class<DamageZoneComponent>("Damage Zone", "Deals damage 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", "Damage dealt per second while inside");
            }
        }
    }

    void DamageZoneComponent::Activate()
    {
        EnableTriggerHoldUpdate = true;  // Enable hold callbacks for continuous damage
        GS_Core::PhysicsTriggeringVolume::ConnectTriggering(GetEntityId());
    }

    void DamageZoneComponent::Deactivate()
    {
        GS_Core::PhysicsTriggeringVolume::DisconnectTriggering();
    }

    bool DamageZoneComponent::TriggerEnter(AZ::EntityId entity)
    {
        AZ_TracePrintf("DamageZone", "Entity entered damage zone");
        return true; // Accept the entity
    }

    void DamageZoneComponent::TriggerHold(float fixedDeltaTime)
    {
        // Apply damage to all entities currently inside
        for (const AZ::EntityId& entity : m_entities)
        {
            // Apply m_damagePerSecond * fixedDeltaTime damage to entity...
        }
    }

    bool DamageZoneComponent::TriggerExit(AZ::EntityId entity)
    {
        AZ_TracePrintf("DamageZone", "Entity exited damage zone");
        return true;
    }
}

Setup

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

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.8.13 - Weighted Random

Weighted random selection — pick a key from a weighted map with probability proportional to each entry’s float weight.

RandomUtils (GS_Core::RandomUtils) provides a single templated static method for weighted random selection from a map. The caller provides their own AZ::SimpleLcgRandom instance for deterministic control over the random sequence.

Used by: RandomNodeData (dialogue random node), KlattVoiceComponent (phoneme selection), any system needing weighted random draws.

For usage guides and setup examples, see The Basics: GS_Core.

 

Contents


API Reference

template<typename T>
static T GetRandomWeighted(
    AZ::SimpleLcgRandom* rand,
    const AZStd::unordered_map<T, float>& weightedTable
);

Selects one key from weightedTable with probability proportional to its float weight value. Higher weights mean higher probability of selection.

ParameterDescription
randCaller-owned AZ::SimpleLcgRandom instance. The caller controls seeding and lifetime.
weightedTableMap of candidates to weights. Keys are the items to select from; values are their relative weights.

Returns: The selected key of type T.

Edge cases:

  • Falls back to the last entry on floating-point precision edge cases.
  • Asserts if weightedTable is empty.

Usage Example

#include <GS_Core/Utility/Random/RandomUtils.h>
#include <AzCore/Math/Random.h>

// Member field — keep the random instance alive across calls
AZ::SimpleLcgRandom m_random;

// Build a weighted table: key = item, value = relative weight
AZStd::unordered_map<AZStd::string, float> lootTable;
lootTable["Common Sword"]    = 50.0f;
lootTable["Rare Shield"]     = 30.0f;
lootTable["Epic Helmet"]     = 15.0f;
lootTable["Legendary Ring"]  =  5.0f;

// Select an item — "Common Sword" is 10x more likely than "Legendary Ring"
AZStd::string selected = GS_Core::RandomUtils::GetRandomWeighted(&m_random, lootTable);

See Also

For related resources:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.9 - Templates

The GS_Play project templates, the ClassWizard component scaffolds for extending GS_Core, and the Editor-Only Environment prefab.

GS_Play ships two kinds of template. Project templates give you a working project with the managers already wired up. Component templates scaffold the classes you write when extending the framework.

For usage guides and setup examples, see The Basics: GS_Core.

 

Project Templates

Both appear in o3de create-project and in O3DE Project Manager once the gem is registered, and both ship inside the gem archives.

TemplateGemWhat you get
GS_Core ProjectGS_CoreGS_Core plus the standard GS_Play level, with four managers already configured — Game, Options, Save and Stage.
GS_Complete ProjectGS_CompleteEvery GS featureset at pinned versions, with twelve managers — the Core four plus Audio, Cam, Cinematics, Dialogue, Environment, Performer, UI and Unit.

Both ship the same starter level, GS_Play_Basic.

Why the manager collection matters

The single most useful thing a project template does is ship a level that already aggregates the manager prefabs into a Managers_Collection.

GS managers only run if they are present in the loaded level. A blank level means no managers, and nothing tells you why. The templates remove that problem by handing you a level where the wiring is done.

That gives you two ways in, depending on what you want:

  • A project template — the batteries-included path, with the whole lifecycle already assembled.
  • Headless managers — for deploying one feature into a project that is not built on GS_Play.

Editor-Only Environment

GS_Core ships an ==_EDITOR_ONLY_ENVIRONMENT_== prefab whose contents exist while you work but never at runtime — lighting, sky and reference setup you want while authoring a level and do not want to ship in it.

The leading == sorts it to the top of the entity outliner, so it stays out of the way of your actual level contents.


Component Templates

The classes you write when extending GS_Core are generated through the ClassWizard CLI. The wizard handles UUID generation, cmake file-list registration, and module descriptor injection automatically. Never create these files from scratch.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

<GemPath> is the full path to your gem root. <SymbolName> becomes ${Name} — the prefix on all generated filenames and class names.

 

Contents


Manager Component

Template: GS_ManagerComponent

Creates a standard game component with an optional EBus interface header. This is the baseline component pattern for all GS_Play manager-style objects — systems that own state and expose it to other components via a request bus.

Generated files:

  • Source/${Name}ManagerComponent.h/.cpp
  • Include/${GemName}/${Name}Bus.h (optional — the EBus interface)

CLI:

python ClassWizard.py --template GS_ManagerComponent --gem <GemPath> --name <Name>

# Skip the EBus header if no bus is needed:
python ClassWizard.py --template GS_ManagerComponent --gem <GemPath> --name <Name> \
    --input-var skip_interface=true

Input vars:

VarTypeDefaultDescription
skip_interfacetogglefalseOmit the ${Name}Bus.h EBus interface header

Post-generation: None — cmake registration and module descriptor are fully automatic.

See also: GS_Managers — the built-in manager system this component integrates with.


Saver Component

Template: SaverComponent

Creates a component that participates in the save system. Handles serializing and restoring a specific block of game state when the save system broadcasts its save/load events.

Generated files:

  • Source/${Name}SaverComponent.h/.cpp

CLI:

python ClassWizard.py --template SaverComponent --gem <GemPath> --name <Name>

Post-generation: Implement BuildSaveData() and ProcessLoad() bodies with save record reads/writes via GS_Core::SaveSystemRequestBus. Set GetSubComponentName() to a unique string so save keys do not collide with other savers.

See also: GS_Save / Savers — full extension guide with header and implementation examples.


InputReader Component

Template: GS_InputReaderComponent

Sits on the Controller Entity. Reads raw hardware input, resolves it through the active input profile, and feeds the resulting named events to the target entity’s Input Reactor.

Generated files:

  • Source/${Name}InputReaderComponent.h/.cpp

CLI:

python ClassWizard.py --template GS_InputReaderComponent --gem <GemPath> --name <Name>

Post-generation: Bind the hardware input channels you care about in Activate(), and feed resolved events to the target entity through InputReactorRequestBus.

See also: Input Options — the full input pipeline.


InputReactor Component

Template: InputReactor

Scaffolds a new reaction type — a class that declares which named input events it listens to and what to do when they fire. Reflect it and it appears in the reactor profile’s type dropdown alongside the shipped reactors.

CLI:

python ClassWizard.py --template InputReactor --gem <GemPath> --name <Name>

Post-generation: Return your event names from GetSubscribedEvents(), and handle them in OnInputEvent() — branching on the event phase where press and release mean different things.

See also: Input Reactors — the mechanism, and Unit Input Reactors for worked examples.


Physics Trigger Component

Template: PhysicsTriggerComponent

Creates a component that wraps a PhysX trigger volume. Responds to TriggerEnter / TriggerExit events to fire game logic when entities enter or leave a physics shape.

Generated files:

  • Source/${Name}PhysicsTriggerComponent.h/.cpp

CLI:

python ClassWizard.py --template PhysicsTriggerComponent --gem <GemPath> --name <Name>

Post-generation: Implement TriggerEnter / TriggerExit / TriggerHold bodies. Requires a PhysX Shape component on the same entity with Trigger mode enabled. Stack multiple trigger components on one entity for compound logic.

See also: Physics Trigger Volume — full extension guide with header and implementation examples.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:

For getting a project running:


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.1.10 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Core.


Get GS_Core

GS_Core — Explore this gem on the product page and add it to your project.

4.2 - GS_AI

Foundation scaffold for AI controller integration in GS_Play projects. Provides the system component and extension point for custom AI controller gems.

GS_AI is the AI foundation gem for GS_Play. It installs the system-level scaffold that custom AI controller gems and in-project AI logic hook into. The gem itself ships a single system component — it does not provide ready-made AI behaviours. Instead it defines the integration pattern: your AI controllers extend the provided base classes and register against the system component at activation. The gem depends on GS_Core.

For usage guides and setup examples, see The Basics: GS_AI.

 

Contents


AI System

The AI system component is the runtime anchor for the gem. It initializes the AI subsystem, manages the controller registry, and provides the bus interface that AI controllers and external systems use to query active agents. All custom AI controllers in a project register through this component.

ComponentPurpose
GS_AISystemComponentRuntime system component. Initializes the AI subsystem and hosts the controller registry. Extension point for custom AI controller gems and in-project AI logic.

Extension pattern:

Custom AI controllers are O3DE components that extend the GS_AI base controller interface and activate/deactivate against GS_AISystemComponent. No gem source modification is required — new controllers self-register on Activate() via the system bus and deregister on Deactivate().

AI System API


Installation

GS_AI requires GS_Core and is a prerequisite for any project that uses custom AI controller gems.

  1. Enable GS_AI and GS_Core in your O3DE project’s gem list.
  2. The GS_AISystemComponent activates automatically as a system component — no manual placement in the level is required.
  3. Implement your AI controller as an O3DE component that connects to GS_AIRequestBus on Activate() and disconnects on Deactivate().
  4. Add your custom AI controller component to agent entities in the level.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_AI

GS_AI — Explore this gem on the product page and add it to your project.

4.2.1 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_AI.


Get GS_AI

GS_AI — Explore this gem on the product page and add it to your project.

4.3 - GS_Audio

The GS audio framework — event-based playback, mixing and the full DSP effect palette, HRTF spatialization, and the premium Audio Pro tier adding visual graph authoring, geometric acoustics, scoring and voice synthesis.

Audio in GS_Play is two gems, and the line between them is the free/premium product line.

  • GS_Audio — the base gem. Free. The engine, audio events, pooling, mixing, the full DSP effect palette, the listener, and HRTF spatialization with distance attenuation.
  • GS_AudioPro — the premium gem. Depends on the base. Adds the visual Audio Event Graph, procedural scoring, Klatt voice synthesis, and geometric acoustics.

Every page in this section marks features Base or Pro, because that tag tells you which gem a feature ships in.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


Start Here: the Signal Model

Before the feature pages, read the Audio Signal Model. It covers the two-plane architecture, what miniaudio, Steam Audio and SoLoud each actually do, the canonical order stages run in, and the vocabulary the rest of these pages use.

It also explains the one thing most likely to trip you up: spatialization is a property of a source, not a mix effect.

Audio Signal Model


What Lives Where

CapabilityGS_Audio (Base, free)GS_AudioPro (Premium)
Audio engine, node graph and outputYesuses the base engine
Audio events, libraries, pooling and instancingYes—
Mixing buses, volume channels, influence systemYes—
DSP effectsall 22 effect types, authored on componentsthe same processors, authored as graph nodes
SpatializationHRTF binaural + distance attenuationadds occlusion and reflections
The listenerYes, including split spatial/orientation targets—
Visual Audio Event Graph—Yes
Procedural scoring—Yes
Klatt voice synthesis—Yes
Geometric acoustics—Yes

One processor, two authoring skins: each DSP effect exists once and is surfaced twice — as a component-authored mix effect in the base gem, and as a graph node in Pro. They are the same processor wearing different authoring surfaces.


Audio Management

Base. The Audio Manager is the singleton that owns the audio engine, the mixing channels, the loaded event libraries and the listener. It extends GS_ManagerComponent and participates in the standard manager lifecycle — including headless deployment, if you want audio in a project with no Game Manager.

ComponentPurpose
Audio ManagerMaster audio controller — engine lifecycle, project settings, the listener, mixing channels, event libraries.

Audio Manager API


Audio Events

Base. A named sound the game can fire, authored in a .aev library. An event declares only what plays — pooling, concurrency, distance, tracking and spatialization are shared machinery every event type inherits.

PagePurpose
Audio EventsEvent libraries, the three event types, pooling and concurrency, and how to write a new event type.
SFXR ProfilesProcedural chiptone assets, sfxr.me import, and randomized profile pools.

Three kinds of event ship: file pools (a set of clips, one picked at random per fire), procedural noise, and sfxr chiptones. All three are pooled the same way, so variation costs nothing at the call site.

Audio Events API SFXR Profiles


Mixing & Effects

Base. Named volume channels built on custom miniaudio nodes, each with its own level and an optional effects chain, plus environmental influence that lets world volumes push effects onto channels with priority stacking.

This is the free tier’s authoring surface for the DSP palette — all 22 effect types, configured on components and routed by name rather than wired on a canvas.

PagePurpose
Mixing & EffectsVolume channels, environmental influence, and the mixing request bus.
Mix EffectsThe 22 effect types, named chains, order-independent wiring, runtime control.

Mixing & Effects API Mix Effects


Spatialization

Base. HRTF binaural placement and distance attenuation, applied automatically to every 3D source. There is no spatializer to author — place a source and it is spatialized.

PagePurpose
SpatializationHow a sound is placed, the bounded distance curve, HRTF settings, and the limits worth knowing.

Spatialization API


Audio Pro

Premium. GS_AudioPro extends every base feature and adds four of its own. It subclasses the base Audio Manager, so a project moves onto Pro by swapping one component rather than rebuilding its audio setup.

FeaturePurpose
Audio Event GraphVisual node editor for authoring complex sound events — sources, filters, effects, routing and phase lifecycle.
Score ArrangementMulti-layer procedural music — time signature, tempo, fade control and runtime layer selection.
Klatt Voice SynthesisFormant text-to-speech with 3D spatial audio and configurable voice profiles.
Geometric acousticsReal occlusion and reflections computed from world geometry and acoustic materials.

Pro’s buses are separate from the base gem’s. Scoring is on AudioScoreRequestBus, graphs on AudioGraphRequestBus, voice on the Klatt buses — none of them on AudioManagerRequestBus.


Dependencies

GS_Audio (Base)

  • GS_Core (required)
  • MiniAudio gem (required)
  • Steam Audio — vendored, loaded at runtime, for HRTF spatialization and distance attenuation
  • SoLoud — vendored. The gem’s DSP effect and source node implementations derive from SoLoud, and its generators feed the effect nodes.

GS_AudioPro (Premium)

  • GS_Audio (required)
  • SoLoud — used further as the synthesis engine behind Klatt voice

Installation

  1. Enable the GS_Audio gem in your project configuration.
  2. Ensure GS_Core and the MiniAudio gem are also enabled.
  3. Create an Audio Manager prefab and add it to the Game Manager’s Startup Managers list — or tick Headless on the manager if your project has no Game Manager.

For the premium tier, enable GS_AudioPro as well and use the Pro Audio Manager component on your manager prefab in place of the base one.


See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.1 - Audio Signal Model

How GS audio is put together — the two-plane architecture, the roles of miniaudio, SoLoud and Steam Audio, the canonical signal chain, and the vocabulary the rest of the audio docs use.

This page is the model every other audio page assumes. It describes how sound is put together in GS_Play — what processes audio, what merely computes numbers, the order stages run in, and the words used for each part.

Read it before the feature pages. Several parts of the audio API look arbitrary until you know the model, and one of them (spatialization) is deliberately not where most engines put it.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


Two Planes

Audio is two planes that meet at nodes.

PlaneRuns onCarriesOwned by
Audio planethe audio threadactual soundminiaudio, always
Control planethe game thread, per framenumbersthe framework and your game code

The audio plane is the ground floor and the only floor that reaches the speakers. Everything audible is a node in one miniaudio graph. Fan-in sums automatically; you route by attaching one node’s output to another’s input.

The control plane never carries audio. It writes numbers that the audio plane reads — the listener transform, a source’s position, an occlusion factor, reverb decay times, a binaural direction.

Control plane writes; audio plane reads. That decoupling is what keeps the system tractable, and it is the one sentence to leave this page with.


What Each Library Does

Three libraries are involved, and they are not peers. Confusing their roles is the most common way to misread the system.

LibraryOn the audio planeOn the control planeTier
miniaudioThe substrate, mixer and output device. Everything is one of its nodes.Built-in listener, per-sound position and panner.Base
Steam AudioDSP nodes — binaural, direct, reflection — that pass audio through like any other effect. Not the ground floor, and not a source.An entire scoped simulation, analogous to a physics world. You register geometry, sources and a listener; it computes acoustic parameters; those parameters drive the DSP nodes.Base (binaural + distance) / Pro (geometry)
SoLoudThe lineage of the DSP layer. The base gem’s effect and source node implementations derive from SoLoud’s, and its generators and synthesis feed the effect nodes. In Pro it also acts as a source, rendering speech to PCM and relaying it in through a custom node.Emission position pushed to the voice node each frame.Base (DSP layer) / Pro (voice synthesis)

The rule: anything that makes sound becomes, or feeds, a miniaudio node. SoLoud and Steam Audio both relay into miniaudio; neither bypasses it.

SoLoud ships in the base gem. The framework’s own effect and source nodes were extracted into a header-only layer so they can be wired directly into the miniaudio graph, but they remain derivative of SoLoud and are sourced from it — the attribution belongs to the free tier, not only to Pro. Pro then uses SoLoud further as the synthesis engine behind Klatt voice.

Steam Audio is worth stating twice, because it is the one people misplace. Its simulation is a parallel world you connect into. It produces numbers. Nodes consume those numbers. miniaudio moves the audio.


The Canonical Chain

Every sound travels the same path from emission to speakers:

Emission          Source + local tone shaping (EQ, filters, per-emitter mix nodes)
    |
    v
Environment       Manual Reverb  XOR  Steam reflections/occlusion   <-- SAME layer
    |
    v
Binaural / HRTF   the "ears" stage — ALWAYS LAST before volume, if spatial
    |
    v
Volume Channel    grouped, options-driven gain
    |
    v
Master  ->  speakers

Two rules hold everywhere:

Manual reverb and Steam reflections are the same conceptual layer — the environment. Manual reverb fakes the room. Steam reflections are the room, derived from real geometry. Use one or the other. Stacking them double-reverbs the sound.

Binaural/HRTF is always the last stage before the volume channel. It is the ears. Nothing spatial happens after it.

Worked cases:

CaseChain
Non-spatial (2D) soundEmission -> Reverb -> Volume Channel — no binaural stage
Full geometric acoustics (Pro)Emission -> Steam {distance + occlusion + reflections + binaural} -> Volume Channel — manual reverb is dropped
MixedEmission -> Reverb -> Binaural -> Volume Channel — a faked room, a real direction

Vocabulary

The audio docs use these four words precisely. Source → Spatialization → Volume Channel → Master.

TermMeaning
SourceAn emission — a position plus its effects channel — or a selected destination. “2D” means pure stereo, with no spatial consideration at all.
SpatializationEverything that happens to a sound as it travels from its emission point to the listener: direction, distance, occlusion, environment. It is a property of the source, not a mix effect.
Volume ChannelFinal amplification of grouped sound types — voice, environment, combat SFX — into Master. Driven by the options system.
MasterThe single output everything sums into before the device.

Spatialization and volume channels are orthogonal. A voice line, a footstep and a magic blast all get position-based spatialization, and all three route to different volume channels. One is about where the sound is; the other is about what kind of sound it is.


Spatialization

Most audio middleware lets you drop a spatializer into an effects chain. GS does not. Spatialization is a property of the source, and the framework applies it for you.

Where it happens. Each 3D sound instance gets its own spatializer node, placed between the source and its volume channel — the Binaural stage in the chain above. It is always the last thing before volume.

One spatializer, never two. A 3D source is spatialized by either miniaudio’s built-in spatializer or Steam Audio’s binaural path, decided by the project’s spatialization mode on the Audio Manager. The two are alternatives. They are never stacked, because a fixed HRTF layered over a positional pan cancels the spatial presence the pan was already producing.

It is applied procedurally. You do not author it per sound. The framework inserts the node, recomputes direction each frame from the source-to-listener vector on the control plane, and handles distance attenuation alongside it. Every 3D source therefore behaves the same way, whether it came from an event, a graph or a voice — consistent and reliable, with nothing to wire up and nothing to get wrong.

Channel discipline is why the position is fixed. Binaural is mono- or stereo-in and always stereo-out, so it has to sit at a defined point in the chain rather than anywhere a designer drops it.


Frame Size — the Audio Timestep

Steam Audio effects want exactly one block of samples per call. A mismatch is audible as crunching.

The framework therefore treats frame size as a fixed audio timestep. By default it queries the miniaudio device period and matches it, rather than adapting with a ring buffer. This is exposed on the Audio Manager as Auto (recommended) versus Fixed.

Think of it the way you think of a physics timestep: a rate the whole simulation agrees on, not a per-effect setting.


See Also

For the systems this model describes:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.2 - Audio Manager

The base audio controller — engine lifecycle, project settings, the authoritative listener, mixing channels, event libraries, and the accessors GS_AudioPro builds on.

Base — ships in GS_Audio.

The Audio Manager is the singleton that owns the audio system. It initializes the engine, builds the mixing channel graph, loads your event libraries, and carries the listener that all spatialization is measured against.

It extends GS_ManagerComponent, so it participates in the standard manager lifecycle — including headless deployment if your project has no Game Manager. Like every manager it responds to standby, pausing audio while a blocking operation such as a stage change completes.

FieldValue
ComponentGS_AudioManagerComponent
TypeId{F28721FD-B9FD-4C04-8CD1-6344BD8A3B78}
ExtendsGS_Core::GS_ManagerComponent
Request BusAudioManagerRequestBus (Single / Single)

For usage guides and setup examples, see The Basics: GS_Audio.

Audio Manager component in the O3DE Inspector

 

Contents


What It Owns

The base manager does not tick. Per-frame work belongs to the systems that need it.


The Listener

There is no separate listener component. The manager carries the listener, and you point it at an entity. One listener, unambiguous.

MethodEffect
SetListenerBoth position and facing follow this entity.
SetListenerSpatialTargetPosition only — proximity and distance are computed from here.
SetListenerOrientationTargetFacing only — direction and HRTF are computed from here.
GetListenerReturns the current listener entity.
GetListenerSpatialTargetReturns the current spatial target.
GetListenerOrientationTargetReturns the current orientation target.
GetListenerTransformReturns the resolved listener transform.

Splitting the listener for third-person

The split targets exist for third-person games, and they are the recommended setup for one.

In third person, the camera and the character are in different places, and the two halves of “where is the listener” want different answers. Proximity should follow the character — how close a sound is to you is about your body, not your camera. Direction should follow the camera — what is on your left is about where you are looking.

Set them separately and the manager switches to computing both, which is what produces the result the framework was tuned for. Set only SetListener and both follow one entity, which is correct for first-person.


Project Settings

GS_AudioProjectSettings is a reflected block on the manager, so the component panel is the configuration UI. TypeId {2B7F4C19-8E63-4A05-9D2C-6F1A8B3E7D40}.

SettingDefaultDescription
SpatializationBinauralStereo (HRTF off) or Binaural (Steam Audio HRTF).
HRTF InterpolationNearestNearest is cheap and suits point sources. Bilinear costs more and suits wide-band material.
HRTF Volume1.0Level of the binaural path.
Frame Size ModeAutoAuto matches the audio device period. Recommended — a mismatched frame size is audible as crunching.
Fixed Frame Size512Used only in Fixed mode.
Default Min Distance1.0Metres.
Default Max Distance100.0Metres.

HRTF settings are hidden unless spatialization is Binaural; the fixed frame size is hidden unless the mode is Fixed.

Geometry settings are not here — they are additive on the Pro manager, and the base gem never reflects them.


API Reference

Request Bus: AudioManagerRequestBus

Singleton bus — single address, single handler. Call via Broadcast.

Libraries and playback

MethodParametersDescription
AddAudioLibraryAZ::Data::Asset<AudioEventLibrary>Load a library and register its events.
RemoveAudioLibraryconst AZStd::string& nameUnregister a library’s events.
PlayEvent2Dconst AZStd::string& eventNameFire an event with no spatialization.
PlayEventAtLocationconst AZStd::string& eventName, const AZ::Vector3& positionFire at a world position.
PlayEventOnEntityconst AZStd::string& eventName, AZ::EntityId entityFire on an entity and track it.

Volume and channels

MethodParametersDescription
ChangeMasterVolumefloat volumeSet the master output level.
ChangeChannelVolumeconst AZStd::string& channel, float volumeSet a named volume channel’s level.
GetMixingChannelNames—Enumerate the configured channel names.
GetMixingBusconst AZStd::string& channelRetrieve a channel’s mixing bus.

These are the natural attachment point for the options system’s audio sliders.

Environmental influence

MethodParametersDescription
ApplyAudioEffectInfluenceinfluencer, effectsImpose an effect set on channels while the influencer applies.
RemoveAudioEffectInfluenceinfluencerRemove that influencer’s contribution.

See Mixing & Effects for how influences stack.

Mix effect routing

Runtime control of named mix effect chains — RerouteMixEffect, SetMixEffectParam, SetMixEffectParamOn and the registration and wiring calls. Documented in full on the Mix Effects page.

Listener

The seven listener methods are tabled under The Listener.


Extending with GS_AudioPro

Worth understanding even if you never write a gem against it, because it is a clean example of how GS gems layer.

GS_AudioPro does not subclass the base manager’s internals or reach into its private state. It fetches primitives across a small accessor surface and attaches its own nodes to them:

AccessorReturnsUsed for
GetEnginethe miniaudio enginecreating sounds
GetGraphthe node graphallocating nodes
GetEndpointthe terminal nodeattaching output
GetMixingBusa volume channelrouting into a channel
GetSteamAudioContextthe shared Steam Audio contextreusing one context
GetSteamAudioHrtfthe shared HRTFreusing one HRTF
GetSteamAudioFrameSizethe audio timestepmatching block size
GetAudioProjectSettingsthe settings blockreading base configuration

Two accessors run the other way and are null in a base-only install — this is how the base gem stays standalone while Pro enriches it:

AccessorPurpose
RegisterSpatialSim / GetSpatialSimPro registers its geometric simulator; the base per-voice spatializer consumes occlusion values from it if one is present.
GetReverbSendNodeThe tap base voices use to feed Pro’s geometry-driven reverb.

The interface those two are typed against is declared in the base gem and implemented in Pro. That is the entire enrichment mechanism: base defines the socket, Pro supplies the plug, and nothing breaks when the plug is absent.

The Pro manager subclasses the base one and declares itself incompatible with it, so the two cannot coexist on an entity. A project running Pro puts the Pro manager component on its manager prefab in place of the base one, and authors everything — channels, libraries, project settings — on that single component.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.3 - Audio Events

The .aev event library and the polymorphic audio event types — file pools, procedural noise, and sfxr chiptones — with shared pooling, concurrency, spatial tracking and extension hooks.

Base — ships in GS_Audio.

An audio event is a named sound the game can fire. Events live in .aev event libraries, which are loaded onto the Audio Manager; firing one is a matter of naming it.

The important structural fact: an event declares only what plays. Everything else — pooling, concurrency limits, distance falloff, 3D tracking, spatialization, re-fire suppression — is shared machinery that every event type inherits. That is what makes adding a new kind of sound source cheap.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


Event Libraries

AudioEventLibrary

A loadable collection of events, authored in the Asset Editor.

FieldValue
Extension.aev
TypeId{04218A1E-4399-4A7F-9649-ED468B5EF76B}
ExtendsAZ::Data::AssetData

Libraries are assigned to the Audio Manager and can also be added or removed at runtime:

MethodParametersDescription
AddAudioLibraryAZ::Data::Asset<AudioEventLibrary>Loads a library and registers its events by name.
RemoveAudioLibraryconst AZStd::string& nameUnregisters a library’s events.

Both are on AudioManagerRequestBus.


Event Types

GS_AudioEvent is an abstract base, and a library holds AZStd::vector<GS_AudioEvent*>. When you add an event in the Asset Editor, the type dropdown offers every registered subtype.

This is the framework’s standard polymorphic array authoring shape: a raw-pointer vector of a reflected abstract base, where the serializer supplies the type picker and the owning container owns its elements.

TypeSource of soundAuthored fields
GS_AudioEvent_PoolSound file assetsSound Pool — a pool of MiniAudio::SoundAsset. One is picked at random per fire. A single file is simply a pool of one.
GS_AudioEvent_NoiseProcedural noiseNoise Type (White / Pink / Brownish / Blueish) and Duration Seconds. Setting the event to loop overrides duration and runs continuously.
GS_AudioEvent_SfxrProcedural chiptoneProfiles — a pool of .sfxrprofile assets, randomized per fire. The profile’s envelope defines the length.

The pooling model is the reason to prefer pools over single assets. Five mutated explosion profiles in one Sfxr event, or five recorded footstep variants in one Pool event, means the sound never repeats identically — with no logic on the calling side.


Shared Configuration

Every event type carries these, regardless of what produces its sound.

FieldDefaultDescription
Event NameThe name PlayEvent* resolves.
Mixing ChannelWhere this event routes — a volume channel or a named mix effect chain. Both live in the same namespace.
Base Volume100Event volume before channel gain.
Distance Min/Max(1, 20)Metres. Full volume within min, attenuating out to max.
Outer Volume0Level beyond the max distance.
Is 2DfalsePure stereo, no spatial consideration.
LoopfalseRepeat until stopped.
Concurrent Event Limit5Maximum simultaneous voices for this event.
Instance Pad Size3Spare voices kept in the pool.
Event Repeat Hold0.05Seconds of re-fire suppression, so a rapid trigger cannot machine-gun the sound.
Pitch ModulationzeroPer-fire pitch jitter. The Pitch mix effect gives finer control over the same idea.

Firing Events

All three are on AudioManagerRequestBus, and all three resolve the event by name across every loaded library.

MethodParametersDescription
PlayEvent2Dconst AZStd::string& eventNameFires with no spatialization.
PlayEventAtLocationconst AZStd::string& eventName, const AZ::Vector3& positionFires at a fixed world position.
PlayEventOnEntityconst AZStd::string& eventName, AZ::EntityId entityFires on an entity and tracks it — the voice follows the entity as it moves.

Instancing and Pooling

Each event owns a flat pool of voices, and the model is identical across every event type.

  • A fire takes an idle voice from the pool, or produces one if none is free.
  • Active, paused and tracking sets are maintained per event. The tracking set maps a voice to the entity it follows for 3D updates.
  • The pool is trimmed on an interval — by default every 10 seconds — so a burst of sound does not permanently inflate memory.
  • The concurrency limit is enforced per event, so one over-eager emitter cannot flood the mixer.

Routing survives late changes. When a mix effect registers after an event has already preloaded, the manager re-points every pooled and future instance at the new target. In practice this means you can add a MixEffectComponent at runtime and voices that were already pooled will follow it — you are not required to have the routing in place before sounds exist.


Spatialization

Resolved once at preload; the project’s spatialization mode does not change at runtime.

When binaural is active, a 3D voice routes through its own Steam Audio binaural node instead of miniaudio’s spatializer, and the event pushes the listener transform as it ticks. The shared Steam Audio context and HRTF are borrowed from the Audio Manager rather than created per event.

See Spatialization for the behaviour, and the Audio Signal Model for why this is a property of the source rather than an effect.


Writing a New Event Type

Subclass GS_AudioEvent and implement a small set of virtuals. Everything in Shared Configuration, Instancing and Pooling and Spatialization is inherited.

HookRequiredPurpose
CreateInstance(engine, targetNode)YesProduce one fresh playable voice attached to the target node.
IsSourceReady()YesWhether the source can currently produce instances.
HasContent()YesWhether the event has authored content. An event with none is a silent no-op.
AcquireAssets()NoRe-acquire referenced assets with PreLoad behaviour.
QueueLoad()NoKick off asynchronous asset loads.
PreloadSource(engine)NoRegister or prepare source data against the engine.
UnloadSource()NoFree source data.

The four optional hooks exist for asset-backed sources. A generator — noise, sfxr, or anything you synthesise — leaves them as no-ops and is simply ready once the engine exists. The asset machinery is optional, not assumed.

Reflect your subclass and it appears in the event type dropdown automatically. There is no registry to add it to.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.4 - SFXR Profiles

The .sfxrprofile asset — procedural chiptone sound effects as first-class assets, with sfxr.me import and randomized profile pools.

Base — ships in GS_Audio.

.sfxrprofile assets are procedural sound effects built on the DrPetter sfxr model — the classic generator behind coin pickups, laser shots, jumps and explosions. Each profile is a complete sound described entirely by parameters, so it takes no audio files and no disk space worth measuring.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


The Model

One .sfxrprofile is one sound, stored as an explicit parameter set.

There is deliberately no preset enum and no preset-plus-seed mode. The canonical sfxr presets were baked out into asset files instead of kept as a runtime option, so every sound in a project is inspectable and editable the same way — including the ones that came with the gem.

SfxrParamSet carries 24 parameters: the wave type plus 23 floats covering envelope, frequency, vibrato, arpeggio, duty cycle, retrigger, phaser, filters and volume.

Wave typeValue
Square0
Saw1
Sine2
Noise3

The field layout matches the sfxr.me / jsfxr Serialize JSON field for field, which is what makes the import below a straight paste.

Three sfxr parameters are not stored — master volume, vibrato delay, and the filter enable flag — because jsfxr does not export them. The runtime supplies sfxr’s defaults for all three.


Importing from sfxr.me

The asset carries an Import from sfxr.me group with a multi-line paste field.

  1. Design your sound at sfxr.me or any jsfxr-based tool.
  2. Press Serialize and copy the JSON.
  3. Paste it into the import field on the profile asset.
  4. The parameters populate on change. Save the asset.

This is the recommended authoring loop: the browser tool has instant preview and randomize/mutate buttons, and the result lands in your project in one paste.

Three things to know:

  • If the pasted JSON reports oldParams: false, a warning is emitted. Sfxr.me emits true, which is what the framework’s parameter model matches.
  • The inspector may need a reselect to repaint after a paste. The values are imported correctly — this is a display refresh issue, not an import failure.
  • Wave Type is authored as a spin box over 0-3 rather than a named dropdown, because the underlying field is a plain integer.

Profile Pools

Profiles are consumed as pools, not one at a time. Both consumers hold a vector of profile assets and pick one at random per fire:

ConsumerGemNotes
GS_AudioEvent_SfxrBaseFires through a pooled voice with a per-voice random number generator.
Aud_SfxrNodeProThe Audio Event Graph node form, using the node’s own generator.

An empty pool is silent — the event reports that it has no content. A single profile is just a pool of one.

The authoring idiom this enables is the point of the whole system: generate five mutated explosions and drop them all in one pool, and the explosion never sounds identical twice. Mutation is a single button in sfxr.me, so building the pool costs about a minute.


Shipped Profiles

GS_Audio ships 27 profiles in Assets/SFXR Profiles/:

  • 7 canonical presets — Coin, Laser, Explosion, Powerup, Hurt, Jump, Blip.
  • 20 named custom profiles.

These are free-tier gem content. A new project has working sound effects before it has any audio assets of its own.


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.5 - Mixing & Effects

Named volume channels built on custom miniaudio nodes, environmental influence with priority stacking, and the component-authored mix effect layer.

Base — ships in GS_Audio.

Mixing has two parts, and it is worth separating them from the start.

Volume channels are the grouping layer — voice, environment, combat SFX — each with its own level, feeding Master. This is the layer your options menu drives.

Mix effects are the processing layer — filters, EQ, delay, reverb, character effects — authored as named chains on components and routed by name. This is the free tier’s authoring surface for the DSP palette, and it is not a reduced one: it exposes the same 22 processors that Audio Pro exposes as graph nodes.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


Volume Channels

GS_MixingBus

A custom miniaudio node that mixes its inputs, applies a level, and optionally runs an effects chain.

FieldValue
TypeId{26E5BA8D-33E0-42E4-BBC0-6A3B2C46F52E}

The Audio Manager owns a named set of these. GetMixingChannelNames enumerates them and ChangeChannelVolume sets a level.

This is the Volume Channel stage of the canonical signal chain: grouped sound types feeding Master. It is orthogonal to spatialization — a voice line, a footstep and a spell all get positional audio, and all three route to different channels.

Channels are the natural attachment point for the options system’s audio sliders, which is exactly what the shipped audio apply targets do.


Environmental Influence

An entity in the world can temporarily impose an effect set on channels — walk into a cave and everything gains reverb; walk out and it lifts.

MethodParametersDescription
ApplyAudioEffectInfluenceinfluencer, effectsPush an effect set onto channels on behalf of an influencing entity.
RemoveAudioEffectInfluenceinfluencerRemove that entity’s contribution.

Influences stack by priority and are keyed by the entity that applied them, so removal is unambiguous even when several influences overlap. Two overlapping zones resolve by priority rather than by whichever fired last.


Mix Effects

The processing layer. A MixEffectComponent holds an ordered chain of effects, registers it under a name, and feeds its output somewhere — another chain, or a volume channel. Sources route in by naming that chain as their mixing channel.

Twenty-two effect types are available, in four families: biquad filters, time and space, character and modulation, and utility.

Mix Effects API


API Reference

Request Bus: GS_MixingRequestBus

Channel control. Singleton bus — single address, single handler.

MethodParametersReturnsDescription
SetBusVolumeconst AZStd::string& busName, float volumevoidSets the level of a named channel (0.0 to 1.0).
GetBusVolumeconst AZStd::string& busNamefloatReturns the current level of a named channel.
MuteBusconst AZStd::string& busName, bool mutevoidMutes or unmutes a named channel.
IsBusMutedconst AZStd::string& busNameboolReturns whether a named channel is muted.
ApplyBusEffectsconst AZStd::string& busName, const AudioBusEffects& effectsvoidApplies an effects chain to a channel, replacing any existing chain.
ClearBusEffectsconst AZStd::string& busNamevoidRemoves all effects from a channel.
PushInfluenceEffectsconst AZStd::string& busName, const AudioBusInfluenceEffects& effectsvoidPushes environmental influence effects onto a channel with priority stacking.
PopInfluenceEffectsconst AZStd::string& busName, int priorityvoidRemoves influence effects at the given priority level.

Master and per-channel volume are also reachable from AudioManagerRequestBus via ChangeMasterVolume and ChangeChannelVolume.


Data Structures

TypePurpose
BusEffectsPair {#bus-effects-pair}Maps a channel name to an effects chain. This is how channels are authored on the Audio Manager.
AudioBusEffects {#audio-bus-effects}An effects chain applied to a channel.
AudioBusInfluenceEffects {#audio-bus-influence-effects}An effect set contributed by a world influencer, carrying its priority.

See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.5.1 - Mix Effects

The MixEffectComponent and all 22 mix effect types — named effect chains, order-independent wiring, local and global routing, and the runtime control surface.

Base — ships in GS_Audio.

Mix effects are the DSP processing layer of the free tier. You build a chain of effects on a component, give it a name, and point sources at that name.

Twenty-two effects are available. They are the same processors Audio Pro exposes as graph nodes — one processor, two authoring surfaces. Choosing the free tier does not reduce what you can do to a signal; it changes how you author it.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


MixEffectComponent

Drop it on an entity and it holds an ordered chain of effects — a polymorphic list where each entry’s concrete type is chosen from a dropdown.

FieldValue
TypeId{9F2A6C13-4D85-4E70-B1C6-3A8E5D7F2049}
Authored fieldMeaning
NameThe routing name registered for the head of the chain. Sources and other chains target this.
EffectsThe ordered chain. Index 0 receives the input; each effect feeds the next.
Output TargetWhere the tail of the chain feeds — another chain’s Name, or a volume channel.

The chain runs top to bottom. Index 0 is what gets registered under Name; the last entry feeds Output Target.

Setup is idempotent, and teardown unwires and destroys the nodes while keeping the authored list — so the component re-activates cleanly.


How Chains Route

Sources route in by name. An audio event’s Mixing Channel field can name either a volume channel or a mix effect chain. They share one namespace, so a source does not need to know which kind of thing it is pointing at.

Any number of chains can live on one entity, each with its own Name. Point one chain’s Output Target at another chain’s Name to compose them.

Wiring is order-independent. If a chain’s target does not exist yet, the connection is deferred and resolves when the target registers. You do not have to think about component activation order, and a missing target is not an error — it is a connection waiting to be made.

Routing catches up with sounds that already exist. When a chain registers after events have preloaded, already-pooled voices are re-pointed at the new target. Adding a MixEffectComponent at runtime works, and the sounds that were already playing through that channel follow it.

Local and global routing

Both patterns are supported and they coexist:

  • Global named channels — everything funnels through shared, named mix targets. Best for project-wide treatments.
  • Local chains on the emitting entity — the component lives on the emitter, scoping its effects to that one emission. Best for per-character or per-object colour.

The 22 Effects

Biquad filters

EffectDescription
Low PassAttenuates frequencies above the cutoff. Muffling, distance, underwater.
High PassAttenuates frequencies below the cutoff. Thinning, radio and telephone effects.
Band PassPasses only a band, attenuating everything outside it.
NotchAttenuates a narrow band while passing everything else. The inverse of band pass.
Peaking EQBoosts or cuts around a centre frequency with configurable bandwidth.
Low ShelfBoosts or cuts everything below a threshold by a fixed amount. Bass adjustment.
High ShelfBoosts or cuts everything above a threshold by a fixed amount. Treble adjustment.

Time and space

EffectDescription
EchoFeedback delay — repeats that decay over time.
ReverbSimulated room ambience.
BinauralFixed-direction HRTF placement. See the caution below.

Character and modulation

EffectDescription
Wave ShaperNon-linear distortion and saturation.
LofiBit depth and sample rate reduction.
RobotizeVocoder-style robotic character.
FlangerSweeping comb filtering from a modulated short delay.
ChorusThickening from multiple detuned delayed copies.
Ring ModRing modulation — metallic, inharmonic tones.
TremoloRhythmic amplitude modulation.
PitchPitch shifting that preserves tempo.

Utility

EffectDescription
PannerStereo placement.
CompressorDynamic range compression.
DC RemovalRemoves DC offset from a signal.
AmplifyStraight gain.

Runtime Control

Chains are drivable at runtime through AudioManagerRequestBus, so ScriptCanvas can change a treatment while it is playing.

MethodEffect
RerouteMixEffectRe-point a chain’s tail at a new output target.
SetMixEffectParamSet a parameter on the chain head.
SetMixEffectParamOnSet a parameter on a named effect inside the chain.
ResolveMixTargetResolve a name to its node.
RegisterMixEffect / UnregisterMixEffectManual node registration. Advanced.
WireMixSource / UnwireMixSourceManual wiring. Advanced.
RegisterMixEffectControl / UnregisterMixEffectControlControl-surface registry.

Environmental treatment is per-emission and rapidly modifiable by design. A character moving through a space changes its effects continuously, and that change isolates to the environmental portion of the signal regardless of which volume channel the sound belongs to.


Two Cautions


See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.6 - Spatialization

HRTF binaural spatialization and distance attenuation in the base gem — how a 3D sound is placed, the distance curve, HRTF interpolation modes, and the limits worth knowing.

Base — ships in GS_Audio.

Spatialization is what makes a sound feel like it is coming from somewhere. In GS_Audio it is powered by Steam Audio and it is automatic: place a source in the world and it is spatialized, with no per-sound setup and nothing to wire into an effects chain.

This page covers what the base gem does. Occlusion and geometry-driven reflections — sound genuinely blocked and reverberated by world geometry — are Audio Pro features.

For usage guides and setup examples, see The Basics: GS_Audio.

 

Contents


How a Sound Is Placed

Each 3D sound instance gets its own spatializer node, inserted between the source and its volume channel. Direction is recomputed every frame from the source-to-listener vector.

A source uses either miniaudio’s built-in spatializer or Steam Audio’s binaural path, depending on the project’s spatialization mode. Never both — a fixed HRTF layered over a positional pan cancels the spatial presence the pan was already producing.

The listener that all of this measures against lives on the Audio Manager, including the split spatial/orientation setup that lets proximity follow your character while direction follows your camera.

See the Audio Signal Model for where this sits in the chain and why it is a property of the source rather than an effect.


Distance

Distance is handled alongside direction: attenuation plus air absorption, both derived from the source-to-listener distance and the event’s authored Distance Min/Max.

The framework uses a bounded distance curve rather than the raw inverse-distance model:

SignalBehaviour
Direct (the dry sound)Full within min distance, attenuating to true silence at and beyond max distance.
Reverb sendFlat until 75% of max distance, then eased smoothly to zero at max.

The reason to state this plainly: a pure inverse-distance model never reaches zero and ignores a maximum distance, which leaves a faint version of every sound audible forever. The bounded curve trades physical exactness for a max distance that actually means something. An event’s Distance Min/Max therefore controls both how far the dry sound carries and how far it feeds the environment.

It is a chosen curve, not a physical simulation.


Configuration

All of it lives on the Audio Manager’s project settings, so the manager’s component panel is the configuration surface.

SettingDefaultNotes
SpatializationBinauralStereo turns HRTF off and uses miniaudio’s spatializer. Binaural uses Steam Audio HRTF.
HRTF InterpolationNearestNearest is cheap and good for point sources. Bilinear costs more and is better for wide-band material such as noise.
HRTF Volume1.0Level of the binaural path.
Default Min / Max Distance1.0 / 100.0Metres. Per-event distances override these.

HRTF settings are hidden in the editor unless spatialization is set to Binaural.

The spatialization back-end is resolved when sounds preload and does not change at runtime.


Limits Worth Knowing

These are expected behaviour for the technique, not defects — worth knowing before you evaluate the result.

  • Front/back and elevation cues are subtle. The HRTF is generic, with no head tracking and no per-listener personalization. Left/right positioning is clear and reliable; “above and behind me” is not something a generic HRTF conveys strongly.
  • Distance attenuation is a chosen curve, as described above, rather than physically exact falloff.
  • Nothing blocks sound in the base gem. A sound behind a wall is quieter with distance but is not occluded — geometry is not consulted. Occlusion and geometry-driven reverb are Audio Pro features.

See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.7 - Audio Pro

GS_AudioPro — the premium audio gem. Visual graph authoring, procedural scoring, Klatt voice synthesis and geometric acoustics, layered onto everything the free gem already does.

GS_AudioPro is the premium audio gem. It requires GS_Audio and extends it — every base feature keeps working exactly as documented, and Pro adds four capabilities of its own.

Added by ProWhat it gives you
Audio Event GraphAuthor a sound as a visual node graph instead of a component chain.
Score ArrangementMulti-layer adaptive music that responds to game state.
Klatt Voice SynthesisGenerated speech — hundreds of distinct voices, no recordings.
Geometric AcousticsOcclusion and reverb derived from real world geometry.

 

Contents


How Pro Attaches to the Base Gem

GS_ProAudioManagerComponent subclasses the base Audio Manager, which in turn subclasses GS_ManagerComponent. It declares itself incompatible with the base manager, so the two cannot coexist on one entity — a project runs one or the other.

Pro never reaches into the base gem’s internals. It fetches primitives across the accessor seam — the engine, the node graph, the output endpoint, volume channels, the shared Steam Audio context — and attaches its own nodes to them.

Two accessors run the other way, and they are the whole enrichment mechanism:

  • Pro registers its spatial simulator, and the base gem’s per-voice spatializer starts consuming occlusion values from it.
  • Pro exposes a reverb send, and base voices tap into it to feed geometry-driven reverb.

Both are null in a base-only install. The interface they are typed against is declared in the base gem and implemented in Pro — base defines the socket, Pro supplies the plug, and nothing breaks when the plug is absent.

Configuration follows the same shape. The base project settings stay where they are; Pro adds its geometry settings additively on the Pro manager. The base gem never reflects Pro’s knobs.


Enabling Audio Pro

  1. Enable the GS_AudioPro gem alongside GS_Audio.
  2. Put the Pro Audio Manager component on your manager prefab in place of the base one.
  3. Assign your graph libraries and score tracks on that component.

Everything the base manager configures — volume channels, event libraries, project settings — is authored on the Pro component too, since it inherits them. Pro’s geometry settings appear alongside them.

A project runs one manager or the other; the two cannot coexist on an entity.


Bus Map

BusGemCarries
AudioManagerRequestBusBaseListener, event libraries, PlayEvent*, volume, mixing, mix effect routing
AudioGraphRequestBusProEverything to do with .audiograph documents
AudioGraphPlayerRequestBusProPer-entity graph playback, addressed by entity
AudioScoreRequestBusProEverything to do with score arrangements
KlattVoiceSystemRequestBus / KlattVoiceRequestBusProVoice synthesis, system-wide and per-entity

In ScriptCanvas, Pro’s nodes sit under the GS_Play/GS_AudioPro category, with each bus grouped under its own feature name. Base nodes remain under GS_Play/GS_Audio.


Features

Audio Event Graph

Build a sound as a node graph — sources, filters, effects and conditional routing on a canvas, with a Start / Loop / Finish phase lifecycle and runtime variables that reshape the sound while it plays. The same DSP processors the base gem exposes as mix effects appear here as nodes.

Audio Event Graph

Score Arrangement

Multi-layer music assets. An arrangement defines time signature, tempo and fade behaviour and holds layers that can be brought in and out independently, so the score follows game state instead of looping past it.

Score Arrangement

Klatt Voice Synthesis

Formant text-to-speech with 3D spatial audio. It generates voice from parameters rather than playing recordings, so a project can give hundreds of characters distinct voices with no audio assets and no recording sessions.

Klatt Voice

Geometric Acoustics

Sound occluded by the geometry between it and the listener, and reverb derived from the room the listener is actually standing in — driven by authored acoustic volumes and O3DE shapes with material presets.

Geometric Acoustics


See Also

For the base gem this builds on:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.7.1 - Audio Event Graph

Visual graph editor for authoring complex sound events with filters, effects, routing, and phase lifecycle.

The Audio Event Graph is an FMOD-style visual editor for authoring complex sound events. Each .audiograph file defines one sound event as a node graph — sources, filters, effects, and routing all connected visually. At runtime, the graph maps directly to a miniaudio processing pipeline.

The Audio Event Graph uses the gs_graphcanvas framework with a DataFlowGraph topology. Nodes evaluate in topological order with dirty-propagation — only nodes affected by a change are re-evaluated.

Audio Event Graph Editor in O3DE Editor

 

Contents


Opening the Editor

Open the Audio Event Graph editor from the O3DE Editor menu: GS Tools > Audio Event Graph Editor.

Each graph is a standalone .audiograph file — use File > New to create one, or File > Open to load an existing graph. Multiple graphs can be open simultaneously in separate tabs.


How It Works

Audio Event Graphs use a data-flow model. Connections carry AudioRoute values — handles that represent a point in the miniaudio processing chain. The signal flows left to right:

  1. Entry nodes gate which phase is active (Start, Loop, or Finish)
  2. Source nodes create sound instances and output an AudioRoute
  3. Filter and effect nodes process the audio and pass it along
  4. Routing nodes (If/Switch) direct the signal based on conditions or variables
  5. The Output node wires the final AudioRoute to a mixing bus

Variables can be set from gameplay code at any time, causing affected nodes to re-evaluate without restarting the entire graph.


Node Catalog

Entry Nodes (Phase Gates)

NodePurpose
Aud_StartNodeActive during the Start phase
Aud_LoopNodeActive during the Loop phase
Aud_FinishNodeActive during the Finish phase

Entry nodes output a gate signal. Downstream nodes only produce audio when their entry gate is active.

Source Nodes

Source Nodes in Audio Event Editor

NodePurposeKey Properties
Aud_SoundNodeSingle audio asset playbackVolume, pitch, delay, looping, 3D spatialization
Aud_AudioPoolNodeRandom selection from a pool of audio assetsSame as SoundNode, plus asset pool list
Aud_NoiseNodeProcedural noise generationNoise colour, duration
Aud_SfxrNodeProcedural chiptone from a pool of SFXR profilesProfile pool, picked at random per fire

Filter Nodes

Filter Nodes in Audio Event Editor

NodeFilter Type
Aud_LowPassFilterNodeLow-pass filter
Aud_HighPassFilterNodeHigh-pass filter
Aud_BandPassFilterNodeBand-pass filter
Aud_NotchFilterNodeNotch (band-reject) filter
Aud_PeakingEQNodePeaking EQ
Aud_LowShelfFilterNodeLow shelf filter
Aud_HighShelfFilterNodeHigh shelf filter

All filter nodes take an audio_in input and produce an audio_out output. Filter parameters (cutoff frequency, Q factor, gain) are configurable per-node or bindable to variables.

Time & Space Nodes

Effect Nodes in Audio Event Editor

NodePurpose
Aud_EchoNodeFeedback delay — repeats that decay over time
Aud_ReverbNodeSimulated room ambience

Character & Modulation Nodes

NodePurpose
Aud_WaveShaperNodeNon-linear distortion and saturation
Aud_LofiNodeBit depth and sample rate reduction
Aud_RobotizeNodeVocoder-style robotic character
Aud_FlangerNodeSweeping comb filtering from a modulated short delay
Aud_ChorusNodeThickening from multiple detuned delayed copies
Aud_RingModNodeRing modulation — metallic, inharmonic tones
Aud_TremoloNodeRhythmic amplitude modulation

Utility Nodes

NodePurpose
Aud_PannerNodeStereo placement
Aud_CompressorNodeDynamic range compression
Aud_DCRemovalNodeRemoves DC offset from a signal
Aud_AmplifyNodeStraight gain

These are the same processors the base gem exposes as mix effects — one DSP, two authoring surfaces. Two mix effects have no node form: Binaural, because spatialization is automatic and per-source, and Pitch.

Structure Nodes

NodePurpose
Aud_MixNodeSums several audio routes into one
Aud_SplitNodeFans one audio route out to several destinations

Routing Nodes

The built-in gs_graphcanvas IfNode and SwitchNode work with AudioRoute values. Use them for conditional audio paths based on game state variables (e.g., play different sounds based on surface type or weather).

Terminal Node

NodePurpose
Aud_OutputNodeWires the final audio chain to the mixing bus

Every graph needs exactly one Output node.


Phase Lifecycle

Audio Event Graph Lifecycle Nodes

Audio Event Graphs support a three-phase lifecycle for structured sound playback:

  1. Start — Initial playback. The Aud_StartNode gate is active. One-shot intro sounds play here.
  2. Loop — Repeating section. The Aud_LoopNode gate is active. Ambient loops and sustained sounds live here.
  3. Finish — Outro/tail. The Aud_FinishNode gate is active. Fade-outs and release tails play here.

Phases transition automatically: Start plays once, then Loop repeats (if looping is enabled), then Finish plays when FinishAudioGraph() is called. Not all phases are required — a simple one-shot sound only needs a Start node.


Using Variables

Variables let gameplay code control sound behavior at runtime. Common uses:

  • Filter parameters — Bind a filter’s cutoff frequency to a variable, then adjust it from code (e.g., underwater muffling)
  • Routing conditions — Use a variable as the condition for an If/Switch node to select different sound paths
  • Source selection — Gate different source nodes based on game state

Declare variables in the Variable Panel, then either bind them to input slots (right-click > Convert to Reference) or use Get/Set variable nodes.

At runtime, set variables via the Audio Manager API:

AudioManagerRequestBus::Broadcast(&AudioManagerRequests::SetAudioGraphVariable, instanceId, "underwater", 0.8f);

Setting a variable marks dependent nodes dirty and re-evaluates only the affected portion of the graph.


Extending with Custom Nodes

To add a custom audio node:

  1. Create a class inheriting from BaseNode and IDataFlowNode
  2. Register it with GS_AUTO_REGISTER_NODE_FOR(MyAudioNode, "audiograph")
  3. Implement Process(GraphExecutionContext& context)
  4. Follow the empty any pattern for inactive outputs:
void MyAudioNode::Process(GraphExecutionContext& context)
{
    auto inputAny = context.GetInputValueAny(this, "audio_in");
    if (inputAny.empty())
    {
        context.SetOutputValueAny(this, "audio_out", AZStd::any{});  // CRITICAL: truly empty
        return;
    }
    // Process audio...
    context.SetOutputValue(this, "audio_out", outputRoute);
}

The empty any pattern ensures that inactive paths don’t mask valid audio from other connections in multi-input slots.

For full details on node creation, see gs_graphcanvas Nodes.


Playing a Graph

There are three ways to reach a graph. They form an escalation ladder — most projects should use the last one.

ApproachSurfaceUse when
Player componentAudioGraphPlayerComponent on an entityAlmost always. No handles to manage.
LibraryPlayGraph(name) / AcquireGraph(name)You want named access across a project.
Raw pathPlayAudioGraph(assetPath)Low-level. You manage the path yourself.

AudioGraphPlayerComponent

The recommended surface. Drop it on an entity, point it at a graph, and it handles the rest.

Authored fieldMeaning
GraphThe .audiograph to play.
Auto PlayStart on activation.
SpatializeTrack this entity’s own transform.
LoopingRun the Loop phase.

It acquires lazily on the first Play or variable set, applies spatialization and looping, and holds its own handle — there is no acquire/release to write. Its per-entity bus offers Play, Finish, Stop, and SetVariable in float, int, bool and string forms.

With no Pro manager present it is inert and warns rather than failing.

Graph libraries

An AudioGraphLibrary (.agl) maps names to graphs, so gameplay code refers to "door_creak" rather than a path. Libraries are assigned to the Pro manager and can be added or removed at runtime.

Because .audiograph is a source-only file — no builder, no product asset — the library uses a custom picker that browses source files directly rather than the product catalog. This is the same approach ScriptCanvas uses for .scriptcanvas, and it is worth copying if you build an asset that references source files.

Current limitation: the picker stores an absolute source path.


Instance Lifecycle

This is the behaviour most likely to surprise you, so it is worth stating plainly.

There are two kinds of instance:

  • Fire-and-forget — created by PlayGraph / PlayAudioGraph, destroyed when it stops.
  • Owned — created by AcquireGraph / AcquireAudioGraph, and kept until you explicitly release it.

For an owned instance, Stop, Finish, a fade-out or a natural end all quiesce it to idle rather than destroying it. Sounds stop, pooled resources return, entry gates deactivate and the phase resets — but the instance, its variables and its spatial and loop configuration are all retained. Only ReleaseAudioGraph or manager shutdown actually destroys it.

The reason: a play → loop → end → replay cycle should keep its variables across calls. What you hear is identical to destruction; the difference is that replaying does not mean reconfiguring.

GetGraph(name) returns the live instance for a name, acquiring on first call. Repeat calls return the same id, and because owned instances persist, a handle normally stays valid for the lifetime of whatever is holding it.

All instance ids are 64-bit unsigned integers, and 0 means failure.


Runtime API

Everything below is on AudioGraphRequestBus — a GS_AudioPro bus, not the Audio Manager’s.

Named access: AddGraphLibrary, RemoveGraphLibrary, PlayGraph(name), AcquireGraph(name), GetGraph(name)

Raw path: PlayAudioGraph(assetPath), AcquireAudioGraph(assetPath)

Transport: FireAudioGraph(id), StopAudioGraph(id), StopAllAudioGraphs(), FinishAudioGraph(id), StopAudioGraphFade(id, fadeTime), ReleaseAudioGraph(id)

Variables: SetAudioGraphVariable (float), SetAudioGraphVariableInt, SetAudioGraphVariableBool, SetAudioGraphVariableString — each taking (instanceId, name, value)

Spatialization: SetAudioGraphEntity(id, entity), SetAudioGraphPosition(id, position), ClearAudioGraphSpatialization(id)

State: SetAudioGraphLooping(id, bool), IsAudioGraphActive(id)

Per-entity playback goes through AudioGraphPlayerRequestBus instead, addressed by entity id.


See Also

4.3.7.2 - Score Arrangement

Multi-layer musical score system for dynamic music – tempo, time signatures, fade control, and layer selection.

The Score Arrangement system provides multi-layer dynamic music for GS_Play projects. A ScoreArrangementTrack asset defines a musical score with configurable tempo, time signature, fade behavior, and multiple layers that can be enabled or disabled at runtime. This allows game music to adapt to gameplay state – adding or removing instrumental layers, changing intensity, or crossfading between sections.

Score tracks are assigned to the Pro Audio Manager and controlled through AudioScoreRequestBus. They are not on the base audio manager’s bus.

For usage guides and setup examples, see The Basics: GS_Audio.

Score Arrangement asset in the O3DE Asset Editor

 

Contents


Data Model

ScoreArrangementTrack

A multi-layer musical score asset. Each track defines the musical structure and contains one or more layers that play simultaneously.

FieldValue
TypeId{DBB48082-1834-4DFF-BAD2-6EA8D83F1AD0}
ExtendsAZ::Data::AssetData
ReflectionRequires GS_AssetReflectionIncludes.h — see Serialization Helpers
FieldTypeDescription
Track NameAZStd::stringIdentifier for this score track.
Time SignatureTimeSignaturesThe time signature for this score (e.g. 4/4, 3/4, 6/8).
BPMfloatTempo in beats per minute.
Fade In TimefloatDuration in seconds for the score to fade in when playback begins.
Fade Out TimefloatDuration in seconds for the score to fade out when playback stops.
LoopboolWhether the score loops back to the beginning when it reaches the end.
LayersAZStd::vector<ScoreLayer>The musical layers that compose this score.
Active LayersAZStd::vector<int>Indices of layers that are active (audible) at the start of playback.

ScoreLayer

A single musical layer within a score arrangement. Each layer represents one track of audio (e.g. drums, bass, melody) that can be independently enabled or disabled.

FieldValue
TypeId{C8B2669A-FAEA-4910-9218-6FE50D2E588E}
FieldTypeDescription
Layer NameAZStd::stringIdentifier for this layer within the score.
Audio AssetAZ::Data::Asset<AudioClipAsset>The audio clip for this layer.
VolumefloatBase volume level for this layer (0.0 to 1.0).
Fade TimefloatDuration in seconds for this layer to fade in or out when toggled.

TimeSignatures (Enum)

Supported musical time signatures for score arrangement tracks.

FieldValue
TypeId{6D6B5657-746C-4FCA-A0AC-671C0F064570}
ValueBeats per MeasureBeat UnitDescription
FourFour4Quarter note4/4 – Common time. The most widely used time signature.
FourTwo4Half note4/2 – Four half-note beats per measure.
TwelveEight12Eighth note12/8 – Compound quadruple meter. Four groups of three eighth notes.
TwoTwo2Half note2/2 – Cut time (alla breve). Two half-note beats per measure.
TwoFour2Quarter note2/4 – Two quarter-note beats per measure. March time.
SixEight6Eighth note6/8 – Compound duple meter. Two groups of three eighth notes.
ThreeFour3Quarter note3/4 – Waltz time. Three quarter-note beats per measure.
ThreeTwo3Half note3/2 – Three half-note beats per measure.
NineEight9Eighth note9/8 – Compound triple meter. Three groups of three eighth notes.

API Reference

Request Bus: AudioScoreRequestBus

Singleton bus – single address, single handler. Implemented by the Pro Audio Manager.

MethodParametersDescription
AddScoreArrangementTrackAZ::Data::Asset<ScoreArrangementTrack>Register a score track.
RemoveScoreArrangementTrackconst AZStd::string& scoreNameUnregister a score track.
PlayScorescoreName, fadeTime = 0Start a score with its authored active layers.
PlayScoreAndTracksscoreName, vector<trackName>, fadeTime = 0Start a score with a specific layer set.
PlayTracktrackName, fadeTime = 0Bring one layer in.
StopTracktrackName, fadeTime = 0Take one layer out.
StopScorefadeTime = 0Stop the whole score.

The adaptive-music loop is PlayScore once, then PlayTrack and StopTrack as game state changes. Layers stay in time with each other because they share the arrangement’s tempo and time signature – bringing a percussion layer in mid-bar does not knock it out of sync.


See Also

For conceptual overviews and usage guides:

For component references:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.7.3 - Geometric Acoustics

Occlusion and reflections derived from world geometry — acoustic volumes and shapes, 15 material presets, listener-centric reverb, and an honest account of what is and is not simulated.

The base gem places sounds by direction and distance. Geometric acoustics adds the room: sound muffled by the geometry between it and the listener, and reverb derived from the space the listener is actually standing in rather than an effect an author dialled in.

Geometry is authored, not inferred. You mark up the acoustically significant parts of a level with volumes and shapes carrying material presets — you do not hand the simulation your render meshes.

 

Contents


Turning It On

Geometric acoustics is off by default. It is enabled on the Pro manager’s settings, which are additive to the base project settings.

SettingDefaultMeaning
Geometry EnabledfalseMaster switch for the whole system.
Occlusion EnabledtrueMuffle sounds behind geometry.
Occlusion Type—Raycast or Volumetric.
Occlusion Radius1.0Source sphere radius for volumetric occlusion, in metres.
Max Occlusion Samples16Rays used when sampling an occlusion volume.
Reflections EnabledtrueDerive reverb from geometry.
Reflection ModeListenerSee reflections.
Reflection Wet1.0How much reverb is added to the dry signal.
Reflection Smooth Time0.75Seconds to ease toward a new room. 0 snaps.
Reflection Order1Recursion depth, 0–4.
Simulation Update Rate30.0 HzHow often the simulator recomputes.

Authoring Geometry

Two components mark up a level. Both carry a material preset, plus a custom material for the Custom case.

ComponentWhat it contributes
Audio GeometryAn authored box volume.
Audio Shape GeometryReads an existing O3DE Shape component. A Quad becomes a single-layer plane, a Box becomes a box, and anything else falls back to its bounding box.

The Shape variant is usually the faster path — a wall you have already blocked out with a Shape becomes acoustically real by adding one component.

Registration goes through a request bus that takes raw vertices rather than entities. That is deliberate: it is what will let streaming systems fold geometry in and out of the simulation without the acoustics layer knowing anything about entity lifetimes.


Acoustic Materials

Fifteen presets, plus Custom.

GroupPresetsCharacter
Hard / reflectiveGeneric, Brick, Concrete, Ceramic, Glass, Plaster, Wood, Metal, RockBounce sound. Long, bright reverb.
AbsorptiveGravel, CarpetSoak sound up. Short, dull reverb.
Soft barriersCurtain, Blanket, Door, DrapeTransmit meaningfully — they muffle rather than silence.
Custom—Dial all seven coefficients by hand: absorption low/mid/high, scattering, and transmission low/mid/high.

The soft-barrier group is the interesting one for designers. A curtain over a doorway does not block the sound behind it, it dulls it — which is the effect people usually reach for a low-pass filter to fake.


Occlusion

Each voice is simulated against the geometry between it and the listener, and muffled accordingly.

ModeBehaviourCost
RaycastA single ray. A source is occluded or it is not.Cheap.
VolumetricSamples a sphere of the configured radius.Higher, scaling with sample count.

Volumetric is what gives partial occlusion at edges — a sound leaking around a doorframe rather than switching off as it passes behind the jamb. If occlusion feels binary and abrupt, this is the setting to change.

Occlusion ducks the direct signal.


Reflections

Reverb is derived from the geometry around the listener — one shared room, recomputed as the listener moves through the level. Reflection Mode has a PerSource value reserved for future work; it currently falls back to Listener.

Two behaviours are worth understanding because they are deliberate design choices, not side effects:

Reverb eases between rooms. Crossing a geometry boundary blends the reverb toward the new room over the Reflection Smooth Time rather than snapping. Set it to 0 if you want an instant change.

Reverb does not fade with distance. Each voice taps its raw source into a wet-only reverb, so the room stays constant across its whole volume while only the dry signal falls off with distance. This is what makes a large room sound like the same room from either end of it. The reverb send does taper at the far edge of a sound’s range — flat until three quarters of max distance, then eased to zero — so an event’s distance settings still bound how far it feeds the room.


How It Stays Safe

Worth knowing if you are budgeting for this system.

Reflections use a parametric reverb model — a handful of decay-time floats — rather than convolution. No impulse response ever crosses to the audio thread, and there is no ambisonic decode in the path.

The reflection simulation runs on a worker thread at up to 15 Hz, committing its results under a mutex. Occlusion and direct-path values are lock-free single-writer floats. Everything else stays on the main thread.

The practical consequence: enabling geometric acoustics adds a bounded, predictable cost rather than a per-sound one that scales alarmingly.


What This Does Not Do

State these to yourself before evaluating the result — each is a deliberate v1 boundary, not a defect.

  • Reverb cannot tell your room from the one next door. It is listener-centric, so a source sealed in an adjacent room still contributes to the same shared reverb. Distinguishing them is what per-source reflections would do, and that is not built.
  • An occluded source still feeds the room. Occlusion ducks the direct signal only. Within max distance, a muffled source still excites the reverb.
  • Geometry is what you author. Render meshes and PhysX colliders are not consulted — v1 is authored volumes and O3DE Shapes.
  • No offline baking. There are no precomputed reverb probes.
  • Reverb is the tail, not early reflections. The parametric model gives you the decay, not discrete early reflections.
  • CPU only. There is no GPU acceleration path.

See Also

For related systems:

For conceptual overviews and usage guides:


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.7.4 - Klatt Voice Synthesis

Custom text-to-speech via Klatt formant synthesis with 3D spatial audio, phoneme mapping, and voice profiling.

The Klatt Voice Synthesis system provides custom text-to-speech using Klatt formant synthesis with full 3D spatial audio. Speech is generated by SoLoud and relayed into the miniaudio graph through a custom node, so it travels the same signal chain as every other sound – see the Audio Signal Model.

All types on this page are in the GS_AudioPro namespace.

The system has two layers:

  • KlattVoiceSystemComponent – A singleton that manages the shared SoLoud engine instance and tracks the 3D audio listener position.
  • KlattVoiceComponent – A per-entity component that generates speech, queues segments, applies voice profiles, and emits spatialized audio from the entity’s position.

Voice characteristics are defined through KlattVoiceProfile assets containing frequency, speed, waveform, formant, and phoneme mapping configuration. Phoneme maps convert input text to ARPABET phonemes for the Klatt synthesizer, with support for custom pronunciation overrides.

For usage guides and setup examples, see The Basics: GS_Audio.

Klatt Voice Profile asset in the O3DE Asset Editor

 

Contents


Components

KlattVoiceSystemComponent

Singleton component that manages the shared SoLoud engine and 3D listener tracking.

FieldValue
TypeId{F4A5D6E7-8B9C-4D5E-A1F2-3B4C5D6E7F8A}
ExtendsAZ::Component, AZ::TickBus::Handler
BusKlattVoiceSystemRequestBus (Single/Single)

KlattVoiceComponent

Per-entity voice component with spatial audio, phoneme mapping, and segment queue.

FieldValue
TypeId{4A8B9C7D-6E5F-4D3C-2B1A-0F9E8D7C6B5A}
ExtendsAZ::Component, AZ::TickBus::Handler
Request BusKlattVoiceRequestBus (Single/ById, entity-addressed)
Notification BusKlattVoiceNotificationBus (Multiple/Multiple)

API Reference

Request Bus: KlattVoiceSystemRequestBus

System-level voice management. Singleton bus – Single address, single handler.

MethodParametersReturnsDescription
GetSoLoudEngine–SoLoud::Soloud*Returns a pointer to the shared SoLoud engine instance.
SetListenerPositionconst AZ::Vector3& positionvoidUpdates the 3D audio listener position for spatial voice playback.
SetListenerOrientationconst AZ::Vector3& forward, const AZ::Vector3& upvoidUpdates the 3D audio listener orientation.
GetListenerPosition–AZ::Vector3Returns the current listener position.
IsEngineReady–boolReturns whether the SoLoud engine has been initialized and is ready.

Request Bus: KlattVoiceRequestBus

Per-entity voice synthesis controls. Entity-addressed bus – Single handler per entity ID.

MethodParametersReturnsDescription
Speakconst AZStd::string& textvoidConverts text to speech and plays it. Uses the component’s configured voice profile.
SpeakWithParamsconst AZStd::string& text, const KlattVoiceParams& paramsvoidConverts text to speech using the specified voice parameters instead of the profile defaults.
StopSpeaking–voidImmediately stops any speech in progress and clears the segment queue.
IsSpeaking–boolReturns whether this entity’s voice is currently producing speech.
QueueSegmentconst AZStd::string& textvoidAdds a speech segment to the queue. Queued segments play in order after the current segment finishes.
ClearQueue–voidClears all queued speech segments without stopping current playback.
SetVoiceProfileconst AZ::Data::Asset<KlattVoiceProfile>& profilevoidChanges the voice profile used by this component.
GetVoiceProfile–AZ::Data::Asset<KlattVoiceProfile>Returns the currently assigned voice profile asset.
SetSpatialConfigconst KlattSpatialConfig& configvoidUpdates the 3D spatial audio configuration for this voice.
GetSpatialConfig–KlattSpatialConfigReturns the current spatial audio configuration.
SetVolumefloat volumevoidSets the output volume for this voice (0.0 to 1.0).
GetVolume–floatReturns the current output volume.

Notification Bus: KlattVoiceNotificationBus

Events broadcast by voice components. Multiple handler bus – any number of components can subscribe.

EventParametersDescription
OnSpeechStartedconst AZ::EntityId& entityIdFired when an entity begins speaking.
OnSpeechFinishedconst AZ::EntityId& entityIdFired when an entity finishes speaking (including all queued segments).
OnSegmentStartedconst AZ::EntityId& entityId, int segmentIndexFired when a new speech segment begins playing.
OnSegmentFinishedconst AZ::EntityId& entityId, int segmentIndexFired when a speech segment finishes playing.

Data Types

KlattVoiceParams

Core voice synthesis parameters controlling the Klatt formant synthesizer output.

FieldValue
TypeId{8A9C7F3B-4E2D-4C1A-9B5E-6D8F9A2C1B4E}
FieldTypeDescription
Base FrequencyfloatFundamental frequency (F0) in Hz. Controls the base pitch of the voice.
SpeedfloatSpeech rate multiplier. 1.0 is normal speed.
DeclinationfloatPitch declination rate. Controls how pitch drops over the course of an utterance.
WaveformKlattWaveformGlottal waveform type used by the synthesizer.
Formant ShiftfloatShifts all formant frequencies up or down. Positive values raise pitch character, negative values lower it.
Pitch VariancefloatAmount of random pitch variation applied during speech for natural-sounding intonation.

KlattVoiceProfile

A voice profile asset combining synthesis parameters with a phoneme mapping.

FieldValue
TypeId{2CEB777E-DAA7-40B1-BFF4-0F772ADE86CF}
ReflectionRequires GS_AssetReflectionIncludes.h — see Serialization Helpers
FieldTypeDescription
Voice ParamsKlattVoiceParamsThe synthesis parameters for this voice profile.
Phoneme MapAZ::Data::Asset<KlattPhonemeMap>The phoneme mapping asset used for text-to-phoneme conversion.

KlattVoicePreset

A preset configuration for quick voice setup.

FieldValue
TypeId{2B8D9E4F-7C6A-4D3B-8E9F-1A2B3C4D5E6F}
FieldTypeDescription
Preset NameAZStd::stringDisplay name for this preset.
ProfileKlattVoiceProfileThe voice profile configuration stored in this preset.

KlattSpatialConfig

3D spatial audio configuration for voice positioning.

FieldValue
TypeId{7C9F8E2D-3A4B-5F6C-1E0D-9A8B7C6D5E4F}
FieldTypeDescription
Enable 3DboolWhether this voice uses 3D spatialization. When false, audio plays as 2D.
Min DistancefloatDistance at which attenuation begins. Below this distance the voice plays at full volume.
Max DistancefloatDistance at which the voice reaches minimum volume.
Attenuation ModelintThe distance attenuation curve type (linear, inverse, exponential).
Doppler FactorfloatIntensity of the Doppler effect applied to this voice. 0.0 disables Doppler.

KlattPhonemeMap

Phoneme mapping asset for text-to-ARPABET conversion with custom overrides.

FieldValue
TypeId{F3E9D7C1-2A4B-5E8F-9C3D-6A1B4E7F2D5C}
ReflectionRequires GS_AssetReflectionIncludes.h — see Serialization Helpers
FieldTypeDescription
Base MapBasePhonemeMapThe base phoneme dictionary to use as the foundation for conversion.
OverridesAZStd::vector<PhonemeOverride>Custom pronunciation overrides for specific words or patterns.

PhonemeOverride

A custom pronunciation rule that overrides the base phoneme map for a specific word or pattern.

FieldValue
TypeId{A2B5C8D1-4E7F-3A9C-6B2D-1F5E8A3C7D9B}
FieldTypeDescription
WordAZStd::stringThe word or pattern to match.
PhonemesAZStd::stringThe ARPABET phoneme sequence to use for this word.

Enumerations

KlattWaveform

Glottal waveform types available for the Klatt synthesizer.

FieldValue
TypeId{8ED1DABE-3347-44A5-B43A-C171D36AE780}
ValueDescription
SawSawtooth waveform. Bright, buzzy character.
TriangleTriangle waveform. Softer than sawtooth, slightly hollow.
SinSine waveform. Pure tone, smooth and clean.
SquareSquare waveform. Hollow, reed-like character.
PulsePulse waveform. Variable duty cycle for varied timbres.
NoiseNoise waveform. Breathy, whisper-like quality.
WarbleWarble waveform. Modulated tone with vibrato-like character.

BasePhonemeMap

Available base phoneme dictionaries for text-to-ARPABET conversion.

FieldValue
TypeId{D8F2A3C5-1B4E-7A9F-6D2C-5E8A1B3F4C7D}
ValueDescription
SoLoud_DefaultThe default letter-to-phoneme mapping built into SoLoud. Covers standard English pronunciation.
CMU_FullThe full CMU Pronouncing Dictionary — over 130,000 entries. Not yet functional.

KTT Voice Tags

KTT (Klatt Text Tags) are inline commands embedded in strings passed to KlattVoiceComponent::SpeakText. They are parsed by KlattCommandParser::Parse and stripped from the spoken text before synthesis begins — they are never heard.

Format: <ktt attr1=value1 attr2=value2>

Multiple attributes can be combined in a single tag. Attribute names are case-insensitive. String values may optionally be wrapped in quotes. An empty value (e.g. speed=) resets that parameter to the voice profile default.


speed=X

Override the speech speed multiplier from this point forward.

Range0.1 – 5.0
Default resetspeed= (restores profile default)
1.0Normal speed
Normal speech <ktt speed=2.0> fast bit <ktt speed=> back to default.

decl=X / declination=X

Pitch declination — how much pitch falls over the course of the utterance. Both decl and declination are accepted.

Range0.0 – 1.0
0.0Steady pitch (no fall)
0.8Strong downward drift
Rising <ktt decl=0.0> steady <ktt decl=0.8> falling voice.

waveform="TYPE"

Change the glottal waveform used by the synthesizer, setting the overall character of the voice.

ValueCharacter
sawDefault, neutral voice
triangleSofter, smoother
sin / sinePure tone, robotic
squareHarsh, mechanical
pulseRaspy, textured
noiseWhispered, breathy
warbleWobbly, character voice
<ktt waveform="noise"> whispered section <ktt waveform="saw"> normal voice.

vowel=X

First formant (F1) frequency multiplier. Shifts the quality of synthesised vowel sounds.

1.0Normal
> 1.0More open vowel quality
< 1.0More closed vowel quality
<ktt vowel=1.4> different vowel colour here.

accent=X

Second formant (F2) frequency multiplier. Shifts accent or dialect colouration.

1.0Normal
< 1.0Shifted accent colouring
<ktt accent=0.8> shifted accent here.

pitch=X

F0 pitch variance amount. Controls how much pitch varies during synthesis.

1.0Normal variance
> 1.0More expressive intonation
< 1.0Flatter, more monotone
<ktt pitch=2.0> very expressive speech <ktt pitch=0.1> flat monotone.

pause=X

Insert a pause of X seconds at this position in the voice playback. Value is required — there is no default.

Hello.<ktt pause=0.8> How are you?

Combined Example

Dialogue string using typewriter text commands and KTT voice tags together:

[b]Warning:[/b] [color=#FF0000]do not[/color] proceed.[pause=1]
<ktt waveform="square" pitch=1.8>This is a mechanical override.<ktt pause=0.5><ktt waveform="saw" pitch=1.0>
[speed=3]Resuming normal protocol.[/speed]

See Also

For conceptual overviews and usage guides:

For component references:

  • Audio Manager – Manager lifecycle that the voice system participates in

Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.3.8 - Third Party Implementations

Integration guides for third-party audio systems with GS_Audio.

This section will contain integration guides for connecting third-party audio middleware and tools with the GS_Audio system.

For usage guides and setup examples, see The Basics: GS_Audio.


Get GS_Audio

GS_Audio — Explore this gem on the product page and add it to your project.

4.4 - GS_Cinematics

Dialogue graphs, cinematic staging, UI display, localization, and extensible condition/effect/performance systems for authored narrative experiences.

GS_Cinematics is the narrative and staging gem for GS_Play. It provides a node-based dialogue graph system backed by .dialoguedb assets, extensible polymorphic conditions, effects, and performances, world-space and screen-space dialogue UI with typewriter effects, audio babble, cinematic stage marker management, and a full localization pipeline. The gem depends on GS_Core, LyShine, and RecastNavigation.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


Cinematics Manager

The top-level cinematic lifecycle controller. The Cinematics Manager is a GS_Play manager that enters and exits cinematic mode for the current level. Stage Marker components are placed in the scene as named anchor points that performances and sequences can reference at runtime.

ComponentPurpose
GS_CinematicsManagerComponentBegins and ends cinematic mode. Registers and retrieves stage markers. Broadcasts EnterCinematic / ExitCinematic notifications.
CinematicStageMarkerComponentNamed world-space anchor placed in the level. Retrieved by the manager and referenced by sequences and performances.

Dialogue System API


Dialogue System

The core playback engine. The Dialogue Manager owns the active .dialoguedb asset and performer registry. The Dialogue Sequencer executes sequences node-by-node, issuing performances, conditions, and effects as it traverses the graph. The system is fully extensible — custom conditions, effects, and performances are discovered automatically through O3DE serialization at startup.

Component / AssetPurpose
GS_DialogueManagerComponentGS_Play manager. Loads and swaps dialogue databases. Registers performer markers by name.
DialogueSequencerComponentExecutes a DialogueSequence node graph. Manages runtime tokens and signals sequence completion.
DialogueDatabase (.dialoguedb)Persistent asset. Contains actor definitions, sequences, and all node data.
Node TypesTextNodeData, SelectionNodeData, RandomNodeData, EffectsNodeData, PerformanceNodeData.
ConditionsPolymorphic DialogueCondition hierarchy. Built-in: Boolean_DialogueCondition, Record_DialogueCondition.
EffectsPolymorphic DialogueEffect hierarchy. Built-in: SetRecords_DialogueEffect, ToggleEntitiesActive_DialogueEffect.
LocalizedStringIdReference to a localizable string with a key and default fallback text. Resolved at runtime.
LocalizedStringTableRuntime string lookup table loaded alongside the active database.

Dialogue System API


Dialogue UI

Screen-space and world-space presentation layer. The UI Bridge routes active dialogue to whichever UI component is registered — swapping from screen-space to world-space at runtime requires no sequencer changes. The Typewriter and Babble components provide character-by-character text reveal and procedural audio babble respectively.

ComponentPurpose
DialogueUIComponentScreen-space dialogue text display. Shows speaker name, portrait, and text lines.
WorldDialogueUIComponentWorld-space dialogue display (speech bubbles). Extends DialogueUIComponent.
DialogueUISelectionComponentScreen-space player choice display. Builds selection buttons from SelectionOption data.
WorldDialogueUISelectionComponentWorld-space selection display. Extends DialogueUISelectionComponent.
DialogueUIBridgeComponentRoutes active dialogue to whichever UI is registered. Decouples sequencer from presentation.
TypewriterComponentCharacter-by-character text reveal with configurable speed. Fires OnTypeFired and OnTypewriterComplete notifications.
BabbleComponentProcedural audio babble synchronized to typewriter output. Driven by BabbleToneEvent / SpeakerBabbleEvents data.

Dialogue UI API


Performances

Polymorphic, async performance types executed from PerformanceNodeData within a sequence. Each performance drives a performer entity in the world and signals completion back to the sequencer. External gems register additional performance types automatically by extending the base class.

ComponentPurpose
MoveTo_PerformanceComponentMoves a performer entity to a named stage marker over time.
PathTo_PerformanceComponentNavigates a performer to a marker along a RecastNavigation navmesh path.

Dialogue System API


Installation

GS_Cinematics requires GS_Core, LyShine, and RecastNavigation to be enabled in your project first.

  1. Enable GS_Cinematics, GS_Core, LyShine, and RecastNavigation in your O3DE project’s gem list.
  2. Add a GS_CinematicsManagerComponent to your Game Manager prefab and register it in the Startup Managers list.
  3. Create a .dialoguedb asset using the Dialogue Editor and assign it to the Dialogue Manager component.
  4. Place CinematicStageMarkerComponent entities in your level and register them by name.
  5. Add a DialogueSequencerComponent to the entity that will drive dialogue playback.
  6. Add a DialogueUIBridgeComponent and connect it to your chosen DialogueUIComponent variant.

See Also

For conceptual overviews and usage guides:

For sub-system references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.1 - Cinematics Manager

Full API reference for GS_CinematicsManagerComponent — cinematic lifecycle control, stage marker registration, and the CinematicStageMarkerComponent.

GS_CinematicsManagerComponent is the top-level cinematic lifecycle controller for GS_Cinematics. It extends GS_Core::GS_ManagerComponent and participates in the standard Game Manager startup sequence.

For usage guides and setup examples, see The Basics: Cinematics Manager.

Cinematics Manager component in the O3DE Inspector

 

Contents


GS_CinematicsManagerComponent

Singleton GS_Play manager that controls cinematic mode for the current level. Registered with the Game Manager startup sequence via GS_Core::GS_ManagerComponent.

PropertyValue
ExtendsGS_Core::GS_ManagerComponent
BusCinematicsManagerRequestBus (Single address, single handler)
NotificationsCinematicsManagerNotificationBus

Request Bus: CinematicsManagerRequestBus

Singleton bus — Single address, single handler.

MethodParametersReturnsDescription
BeginCinematic–voidEnters cinematic mode for the current level. Broadcasts EnterCinematic to all listeners.
EndCinematic–voidExits cinematic mode. Broadcasts ExitCinematic to all listeners.
RegisterStageMarkerconst AZStd::string& name, AZ::EntityId entityvoidRegisters a CinematicStageMarkerComponent entity under the given name.
GetStageMarkerconst AZStd::string& nameAZ::EntityIdReturns the entity registered under the given stage marker name.

Notification Bus: CinematicsManagerNotificationBus

Multiple handler bus — any number of components can subscribe.

EventDescription
EnterCinematicFired when cinematic mode begins. Listeners should suppress gameplay systems.
ExitCinematicFired when cinematic mode ends. Listeners should resume gameplay systems.

CinematicStageMarkerComponent

A simple world-space anchor component placed on entities in the level. Stage markers are registered by name with the Cinematics Manager during activation. Performances and sequences reference markers by name to position performers in the world.

Cinematic Stage Marker component in the O3DE Inspector

Attach a CinematicStageMarkerComponent to any entity, give it a unique name, and it self-registers with the CinematicsManagerRequestBus on Activate().


Script Canvas Examples

Entering and exiting cinematic mode:

Getting a stage marker entity by name:


See Also

For usage guides:

For related references:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2 - Dialogue System

Architecture overview of the GS_Cinematics dialogue system — the Dialogue Editor, sequencing, UI display, actors, and extensible conditions, effects, and performances.

The Dialogue System is the runtime engine inside GS_Cinematics that drives all authored narrative experiences. Dialogue is authored visually in the Dialogue Editor — a graph-based tool built on gs_graphcanvas — and executed at runtime by a node-graph sequencer. World-space and screen-space UI display, and an extensible set of polymorphic conditions, effects, and performances connect dialogue to gameplay.

Every piece communicates through EBus interfaces, so any layer can be replaced or extended without modifying the gem itself.

The system is managed by the GS_DialogueManagerComponent, which participates in the standard Game Manager startup sequence.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


Dialogue Editor

The in-engine visual graph editor for authoring dialogue databases, sequences, and node graphs. Built on gs_graphcanvas with a FlowGraph topology.

Dialogue Editor in the O3DE Editor

The editor manages a .dialoguedatabase container file holding all sequences for a dialogue set, along with performer definitions and database-level settings. Each sequence is an independent graph opened as a tab.

Dialogue Editor


Dialogue Manager

GS_DialogueManagerComponent owns the active dialogue database and performer registry. Extends GS_Core::GS_ManagerComponent.

Dialogue Manager API


Dialogue Sequencer

DialogueSequencerComponent traverses a node graph at runtime. The DialogueUIBridgeComponent routes dialogue and selection events to whichever UI is registered.

Dialogue Sequencer API


Effects

Polymorphic DialogueEffect types executed synchronously from Effects nodes. Built-in: SetRecords_DialogueEffect, ToggleEntitiesActive_DialogueEffect. Fully extensible.

Effects API


Performances

Polymorphic DialoguePerformance types executed asynchronously from Performance nodes. Built-in: MoveTo, PathTo, RepositionPerformer. Fully extensible.

Performances API


Dialogue UI

Screen-space and world-space dialogue display, selection menus, the typewriter text-reveal system, and the babble audio system.

Dialogue UI API


Actors

DialoguePerformerMarkerComponent places named performer anchors in the world, plus the ActorDefinition data model.

Dialogue Actors API


See Also

For conceptual overviews and usage guides:

For related references:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.1 - Dialogue Manager

Full API reference for GS_DialogueManagerComponent — dialogue database management, performer marker registration, and the DialoguePerformerMarkerComponent.

GS_DialogueManagerComponent owns the active dialogue database and performer registry. It extends GS_Core::GS_ManagerComponent and participates in the standard Game Manager startup sequence.

For usage guides and setup examples, see The Basics: GS_Cinematics.

Dialogue Manager component in the O3DE Inspector

 

Contents


GS_DialogueManagerComponent

Singleton GS_Play manager that owns the active .dialoguedb asset and the performer registry. Registered with the Game Manager startup sequence via GS_Core::GS_ManagerComponent.

PropertyValue
ExtendsGS_Core::GS_ManagerComponent
BusDialogueManagerRequestBus (Single address, single handler)
NotificationsDialogueManagerNotificationBus (inherited from ManagerBaseOutgoingEvents)

Request Bus: DialogueManagerRequestBus

Singleton bus — Single address, single handler.

MethodParametersReturnsDescription
StartDialogueSequenceByNameconst AZStd::string& sequenceNamevoidLooks up a sequence by name in the active database and starts playback through the sequencer.
ChangeDialogueDatabaseAZ::Data::Asset<DialogueDatabase> databasevoidSwaps the active .dialoguedb asset at runtime.
RegisterPerformerMarkerconst AZStd::string& name, AZ::EntityId entityvoidRegisters a DialoguePerformerMarkerComponent entity under the given performer name.
GetPerformerconst AZStd::string& nameAZ::EntityIdReturns the entity registered under the given performer name.

Notification Bus: DialogueManagerNotificationBus

Dialogue Manager notifications are inherited from ManagerBaseOutgoingEvents. See Manager for the full notification interface.


DialoguePerformerMarkerComponent

A world-space anchor component placed on NPC entities in the level. Performer markers register by name with the Dialogue Manager during activation. The dialogue system resolves actor names from the database to these world-space entities.

Request Bus: PerformerMarkerRequestBus

MethodParametersReturnsDescription
GetPerformerName–AZStd::stringReturns the registered name for this performer.
GetPosEntity–AZ::EntityIdReturns the entity used as the position reference for this performer.
GetPosPoint–AZ::Vector3Returns the world-space position of the performer anchor.
ShouldTrackTarget–boolReturns whether this performer should track a target entity.

Script Canvas Examples

Starting a dialogue sequence by name:


See Also

For usage guides:

For component references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.2 - Dialogue Editor

Visual graph editor for authoring branching dialogue sequences — database model, node catalog, and extensibility.

The Dialogue Editor is a visual graph editor for authoring branching dialogue sequences. It uses the gs_graphcanvas framework with a FlowGraph topology — nodes execute sequentially following FlowIn/FlowOut connections.

The key architectural feature is the container model: multiple dialogue sequences live inside a single .dialoguedatabase file, managed through a sequence sidebar.

For usage guides and setup examples, see The Basics: GS_Cinematics.

Dialogue Editor in O3DE Editor

 

Contents


Opening the Editor

Open the Dialogue Editor from the O3DE Editor menu: Tools > Dialogue Editor, or select the Speech Bubble Icon Dialogue Editor Launch Icon in the Editor Tool Bar.

Use File > New to create a new .dialoguedatabase file, or File > Open to load an existing one. The editor saves and loads the entire database as a single file — individual sequences are not separate files.


Editor Layout

The Dialogue Editor has three pages, selectable via the tab bar at the top:

Sequences Page (Graph Editor)

The main workspace. The graph canvas occupies the center, with docks for:

  • Sequence Sidebar — Lists all sequences in the database. Add, remove, rename, or duplicate sequences.
  • Node Palette — Drag-drop available node types onto the canvas.
  • Inspector — Properties for the selected node.
  • Variable Panel — Declare and manage graph variables.

Performers Page

Define actors and characters for the dialogue system. Each performer has a name, voice configuration, portrait, and other display properties. Performers are referenced by name in Text nodes.

System Page

System Page in Dialogue Editor

Database-level settings and configuration.


Database Model

A .dialoguedatabase file is a container holding:

  • Actors — Character definitions (name, voice, portrait)
  • Default settings — Database-wide defaults
  • Sequences — Each sequence is an independent dialogue graph

Opening a sequence from the sidebar opens it as a graph tab. Multiple sequences can be open simultaneously. Saving writes all open sequences back to the database file.


Working with Sequences

Sequence Pane in O3DE Editor

Use the Sequence Sidebar to manage sequences:

  • Add — Create a new blank sequence with a Start node
  • Remove — Delete a sequence from the database
  • Rename — Double-click to rename
  • Duplicate — Copy an existing sequence as a starting point

Click a sequence name to open it in a graph tab. Each tab tracks its own dirty state — the asterisk * appears when a sequence has unsaved changes.


Performers

The Performers page defines the cast of characters available in the dialogue. Each performer has properties used by the dialogue UI at runtime (name display, portrait image, voice settings). Text nodes reference performers by name to identify the speaker.


Nodes

Dialogue Nodes in O3DE Editor

All dialogue nodes use FlowIn/FlowOut slots for sequential execution. The evaluator steps through nodes one at a time, waiting when a node needs player input or time to display.

NodePurposeKey Properties
StartEntry point for the sequenceAuto-spawned, one per sequence, cannot be deleted
TextDisplay dialogue from a speakerSpeaker (LocalizedStringId), text (LocalizedStringId), close-on-end
SelectionPresent player with choicesQuestion text, dynamic output slots (one per choice option)
RandomRandom branch selectionOptional weighted randomization
EffectsTrigger game effects during dialoguePolymorphic list of DialogueEffect objects
PerformanceCharacter actions (move, animate)Polymorphic list of DialoguePerformance objects, wait-to-continue flag
EndTerminate the sequenceNo properties

Text Node

The most common node. Set the Speaker to a performer name and the Text to the dialogue line. Uses LocalizedStringId for both fields — a localization key plus fallback text. The node footer previews the text content.

Selection Node

Presents the player with choices. Each SelectionOption creates a dynamic FlowOut slot. Add or remove options in the inspector. The evaluator waits for the player to pick an option, then follows the corresponding output.

Effects and Performance Nodes

Both use polymorphic lists — the inspector shows a type picker dropdown to add new entries. This is the primary extension point (see Extending below).


Conditions

Efect Node with Condition in O3DE Editor

Any dialogue node can have conditions that gate whether the node is entered. Conditions are evaluated before the node executes — if conditions fail, the evaluator skips to the next option.

Conditions use the same polymorphic extension pattern as effects. Built-in conditions:

ConditionDescription
Boolean_DialogueConditionChecks a boolean variable
Record_DialogueConditionChecks a record/flag state

Conditions are not limited to player choice branches — they can gate any node in the sequence for conditional dialogue flow.


Extending

The Dialogue Editor uses O3DE’s polymorphic type discovery. Adding new conditions, effects, or performances requires only:

  1. Create a subclass of DialogueCondition, DialogueEffect, or DialoguePerformance
  2. Add AZ_RTTI and implement Reflect() with SerializeContext and EditContext
  3. Add #include and ::Reflect(context) call to DialogueSequencerComponent::Reflect()

The inspector’s type picker dropdown automatically discovers all registered subtypes via SerializeContext::EnumerateDerived(). No registry code or manual wiring needed.

Custom Nodes

To add entirely new node types to the dialogue palette:

  1. Inherit from Dlg_BaseNode (which extends BaseNode + IExecutableNode)
  2. Register with GS_AUTO_REGISTER_NODE_FOR(MyDialogueNode, "dialogue")
  3. Implement Execute() returning FlowResult::Continue, Wait, or Stop

Variables

The Dialogue Editor supports graph variables. Common uses:

  • Dialogue flags — Track conversation state (has the player seen this line before?)
  • Counters — Count visits or choices
  • Condition inputs — Variable values drive condition checks on nodes

Variables can be read and set by Effects nodes or from external game systems.


See Also


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.3 - Dialogue Sequencer

Full API reference for the DialogueSequencerComponent and DialogueUIBridgeComponent — runtime dialogue graph traversal, node processing, and UI routing.

The Dialogue Sequencer is the runtime engine that executes dialogue sequences node-by-node. It receives a DialogueSequence from the Dialogue Manager, traverses the node graph, evaluates conditions, fires effects and performances, and routes text and selection events to the UI layer through the DialogueUIBridgeComponent.

The two components on this page work together:

ComponentRole
DialogueSequencerComponentTraverses the node graph. Processes each DialogueNodeData in order, manages runtime tokens, and signals sequence completion.
DialogueUIBridgeComponentDecouples the sequencer from presentation. Routes dialogue text and selection events to whichever DialogueUIComponent or DialogueUISelectionComponent is currently registered.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


DialogueSequencerComponent

The sequencer is the core playback controller. When a sequence starts, the sequencer reads the startNodeId, resolves the first node, and begins processing. Each node type has its own processing logic:

  • TextNodeData – Sends text, speaker, and portrait data to the UI bridge for display.
  • SelectionNodeData – Sends options to the UI bridge. Waits for a player selection before continuing.
  • RandomNodeData – Picks a random outgoing connection (pure random or weighted) and continues.
  • EffectsNodeData – Executes all attached DialogueEffect instances, then continues.
  • PerformanceNodeData – Starts a DialoguePerformance. Waits for the performance to signal completion before continuing.

At each node, outgoing connections are evaluated. Conditions on connections are tested in priority order; the first connection whose conditions all pass is followed. When no outgoing connections remain, the sequence ends.


Request Bus: DialogueSequencerRequestBus

Singleton bus – Single address, single handler.

MethodParametersReturnsDescription
StartDialogueBySequenceconst DialogueSequence& sequencevoidBegins playback of the given sequence from its startNodeId.
OnPerformanceComplete–voidCalled by a running DialoguePerformance when it finishes. The sequencer advances to the next node.

Notification Bus: DialogueSequencerNotificationBus

Multiple handler bus – any number of components can subscribe.

EventParametersDescription
OnDialogueTextBeginconst DialogueTextPayload& payloadFired when the sequencer begins processing a text node. Contains speaker name, text, portrait, and display settings.
OnDialogueSequenceComplete–Fired when the sequencer reaches the end of a sequence (no further outgoing connections).

Runtime Token Management

The sequencer maintains a set of runtime tokens that track state within a single sequence execution. Tokens are used internally to prevent infinite loops, track visited nodes, and manage branching state. They are cleared when a sequence completes or is interrupted.


DialogueUIBridgeComponent

The UI Bridge sits between the sequencer and the presentation layer. It holds a reference to the currently registered DialogueUIComponent and DialogueUISelectionComponent. When the sequencer needs to display text or a selection menu, it calls the bridge, which forwards the request to the active UI. This allows you to swap between screen-space, world-space, or custom UI implementations at runtime without changing the sequencer or the dialogue data.


Request Bus: DialogueUIBridgeRequestBus

Singleton bus.

MethodParametersReturnsDescription
RunDialogueconst DialogueTextPayload& payloadvoidForwards a dialogue text event to the registered DialogueUIComponent.
RunSelectionconst DialogueSelectionPayload& payloadvoidForwards a selection event to the registered DialogueUISelectionComponent.
RegisterDialogueUIAZ::EntityId uiEntityvoidRegisters the entity whose DialogueUIComponent and/or DialogueUISelectionComponent will receive events.
CloseDialogue–voidTells the registered UI to close and clean up.

Notification Bus: DialogueUIBridgeNotificationBus

Multiple handler bus.

EventParametersDescription
OnDialogueComplete–Fired when the registered UI finishes displaying a dialogue line (after typewriter completes or the player advances). The sequencer listens for this to advance to the next node.
OnSelectionCompleteint selectedIndexFired when the player selects an option. The sequencer uses the index to follow the corresponding connection.

Execution Flow

A typical dialogue playback follows this path:

  1. Start – DialogueManagerRequestBus::StartDialogueSequenceByName("MySequence") looks up the sequence in the active database and calls DialogueSequencerRequestBus::StartDialogueBySequence(sequence).
  2. Node Processing – The sequencer reads the start node and begins processing. For each node:
    • Text – The sequencer calls DialogueUIBridgeRequestBus::RunDialogue(payload). The bridge forwards to the registered UI. The UI displays text (via TypewriterComponent), then fires OnDialogueComplete. The sequencer advances.
    • Selection – The sequencer calls DialogueUIBridgeRequestBus::RunSelection(payload). The bridge forwards to the selection UI. The player picks an option, the UI fires OnSelectionComplete(index). The sequencer follows the indexed connection.
    • Effects – The sequencer executes each DialogueEffect on the node, then advances immediately.
    • Performance – The sequencer starts the DialoguePerformance. When the performance calls OnPerformanceComplete, the sequencer advances.
    • Random – The sequencer picks a connection and advances immediately.
  3. End – When no outgoing connections remain, the sequencer fires OnDialogueSequenceComplete and calls DialogueUIBridgeRequestBus::CloseDialogue().

C++ Usage

Starting a Sequence

#include <GS_Cinematics/GS_CinematicsBus.h>

// Start by name through the Dialogue Manager
GS_Cinematics::DialogueManagerRequestBus::Broadcast(
    &GS_Cinematics::DialogueManagerRequestBus::Events::StartDialogueSequenceByName,
    AZStd::string("PerryConfrontation")
);

Listening for Sequence Completion

#include <GS_Cinematics/GS_CinematicsBus.h>

class MyListener
    : public AZ::Component
    , protected GS_Cinematics::DialogueSequencerNotificationBus::Handler
{
protected:
    void Activate() override
    {
        GS_Cinematics::DialogueSequencerNotificationBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Cinematics::DialogueSequencerNotificationBus::Handler::BusDisconnect();
    }

    void OnDialogueSequenceComplete() override
    {
        // Sequence finished — resume gameplay, unlock camera, etc.
    }
};

See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.4 - Dialogue UI

Full API reference for all dialogue UI components — screen-space and world-space dialogue display, selection menus, typewriter text reveal, and babble audio.

The Dialogue UI layer handles all player-facing presentation of dialogue content. It is completely decoupled from the sequencer through the DialogueUIBridgeComponent – the sequencer never talks to a UI component directly. This page documents the seven components that make up the built-in UI system.

ComponentPurpose
DialogueUIComponentScreen-space dialogue text display.
WorldDialogueUIComponentWorld-space speech bubble display.
DialogueUISelectionComponentScreen-space player choice display.
WorldDialogueUISelectionComponentWorld-space player choice display.
DialogueSelectButtonComponentIndividual selection button within a choice menu.
TypewriterComponentCharacter-by-character text reveal.
BabbleComponentProcedural audio babble synchronized to text reveal.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


DialogueUIComponent

The standard screen-space dialogue display. Receives dialogue payloads from the UI Bridge and renders speaker name, portrait, and text lines using LyShine UI canvases.

Request Bus: DialogueUIRequestBus

MethodParametersReturnsDescription
DoDialogueconst DialogueTextPayload& payloadvoidProcesses a full dialogue payload: sets speaker name, portrait, and begins text display through the typewriter.
ShowDialogue–voidMakes the dialogue UI canvas visible.
HideDialogue–voidHides the dialogue UI canvas without destroying it.
CloseUI–voidFully closes and cleans up the dialogue UI.
GetTypewriter–AZ::EntityIdReturns the entity that holds the TypewriterComponent used by this UI.

DialogueUIComponent canvas in the O3DE UI Editor


WorldDialogueUIComponent

Extends DialogueUIComponent for world-space rendering. Displays speech bubbles attached to performer entities in 3D space. Shares the same DialogueUIRequestBus interface as the base class. The world-space variant tracks the performer entity’s position and orients the UI element toward the camera.

Use WorldDialogueUIComponent when you want dialogue to appear as in-world speech bubbles rather than as a screen overlay.

WorldDialogueUIComponent canvas in the O3DE UI Editor


DialogueUISelectionComponent

Screen-space player choice display. Builds a list of selectable options from a DialogueSelectionPayload, spawns DialogueSelectButtonComponent instances for each valid option, and waits for the player to pick one.

Request Bus: DialogueUISelectionRequestBus

MethodParametersReturnsDescription
DoSelectionconst DialogueSelectionPayload& payloadvoidBuilds and displays the selection menu from the provided options.
OnSelectionint selectedIndexvoidCalled when a button is pressed. Processes the selection and notifies the bridge.
ShowSelection–voidMakes the selection UI canvas visible.
ClearSelection–voidRemoves all spawned option buttons and resets the selection state.
CloseUI–voidFully closes and cleans up the selection UI.

DialogueUISelectionComponent canvas in the O3DE UI Editor


WorldDialogueUISelectionComponent

Extends DialogueUISelectionComponent for world-space rendering. Displays selection options as in-world UI elements attached to performer entities. Shares the same DialogueUISelectionRequestBus interface as the base class.

WorldDialogueUISelectionComponent canvas in the O3DE UI Editor


DialogueSelectButtonComponent

An individual button within a selection menu. One instance is spawned per valid option. The button displays option text and forwards press events to the parent selection component.

Event Bus: DialogueUISelectionEventBus

MethodParametersReturnsDescription
SetupOptionconst SelectionOption& option, int indexvoidConfigures the button with display text and its index in the selection list.
GetInteractableEntity–AZ::EntityIdReturns the LyShine interactable entity used for input detection.

DialogueSelectButtonComponent in the O3DE Inspector


TypewriterComponent

Character-by-character text reveal system. The typewriter receives a full text string and reveals it one character at a time at a configurable speed. It fires events on each character reveal and when the full text has been displayed. The typewriter is used internally by DialogueUIComponent but can also be used independently for any text reveal effect.

For detailed usage and standalone setup, see the Typewriter sub-page.

Request Bus: TypewriterRequestBus

MethodParametersReturnsDescription
StartTypewriterconst AZStd::string& text, float speedvoidBegins revealing the given text at the specified characters-per-second speed.
ForceComplete–voidImmediately reveals all remaining text. Fires OnTypewriterComplete.
ClearTypewriter–voidClears all displayed text and resets the typewriter state.

Notification Bus: TypewriterNotificationBus

Multiple handler bus.

EventParametersDescription
OnTypeFiredchar characterFired each time a character is revealed. Used by the BabbleComponent to synchronize audio.
OnTypewriterComplete–Fired when all characters have been revealed, either naturally or via ForceComplete.

BabbleComponent

Procedural audio babble that plays in sync with the typewriter. Each character reveal from OnTypeFired can trigger a short audio event, creating a character-by-character speech sound. Different speakers can have different babble tones.

Request Bus: BabbleRequestBus

MethodParametersReturnsDescription
GetBabbleEventconst AZStd::string& speakerNameBabbleToneEventReturns the babble audio event associated with the given speaker.

Data Types

TypeDescription
BabbleToneEventA single audio event reference (FMOD or Wwise event) that represents one character’s babble sound.
SpeakerBabbleEventsA mapping of speaker names to BabbleToneEvent instances. Stored on the BabbleComponent and queried at runtime.

Setup Guide

Screen-Space Dialogue

  1. Create a UI canvas entity with your dialogue layout (speaker name text, portrait image, dialogue text area).
  2. Attach a DialogueUIComponent to the entity.
  3. Attach a TypewriterComponent to the text entity (or the same entity).
  4. Optionally attach a BabbleComponent and configure speaker babble events.
  5. Attach a DialogueUISelectionComponent to a separate entity (or the same canvas) for player choices.
  6. Register the UI entity with DialogueUIBridgeRequestBus::RegisterDialogueUI(uiEntityId).

World-Space Dialogue

  1. Follow the same steps as screen-space, but use WorldDialogueUIComponent and WorldDialogueUISelectionComponent instead.
  2. The world-space components will track the performer entity’s position automatically.
  3. Register the UI entity with the bridge the same way.

Swapping between screen-space and world-space at runtime requires only changing which UI entity is registered with the bridge. No sequencer or dialogue data changes are needed.


See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.4.1 - Typewriter

Full API reference for the TypewriterComponent — character-by-character text reveal with configurable speed, force-complete, and notification events.

The TypewriterComponent provides character-by-character text reveal for dialogue display. It receives a string and a speed value, then reveals one character at a time on tick. It fires a notification on every character reveal and again when the full text is complete. The component is used internally by the DialogueUIComponent but can also be attached to any text entity independently for non-dialogue text effects.

For usage guides and setup examples, see The Basics: GS_Cinematics.

TypewriterComponent in the O3DE Inspector

 

Contents


Request Bus: TypewriterRequestBus

Entity-addressed bus.

MethodParametersReturnsDescription
StartTypewriterconst AZStd::string& text, float speedvoidBegins revealing the given text. speed is in characters per second. The component connects to TickBus and reveals characters on each tick.
ForceComplete–voidImmediately reveals all remaining text. Fires OnTypewriterComplete. Useful when the player presses a button to skip the reveal.
ClearTypewriter–voidClears all displayed text and resets the internal state. Disconnects from TickBus.

Notification Bus: TypewriterNotificationBus

Multiple handler bus – any number of listeners can connect.

EventParametersDescription
OnTypeFiredchar characterFired each time a single character is revealed. The BabbleComponent listens to this event to trigger per-character audio. Custom effects (particles, screen shake, emphasis) can also listen here.
OnTypewriterComplete–Fired when all characters have been revealed, either through normal tick-based reveal or through ForceComplete. The dialogue UI listens for this to know when the line is fully displayed.

How It Works

  1. A caller (typically DialogueUIComponent) calls StartTypewriter(text, speed).
  2. The component runs ParseFormattedText on the input string, stripping inline [command] tags and building an internal command schedule — pauses, speed changes, and style runs are all resolved before the first tick.
  3. The component stores the parsed text, resets its character index to zero, and connects to TickBus.
  4. On each tick, the component calculates how many characters should be visible based on elapsed time and the current speed. For each newly revealed character it fires OnTypeFired(character). Scheduled commands (pauses, speed changes, style tags) execute when their character position is reached.
  5. The component updates the associated LyShine text element — style tags ([color], [b], etc.) are output as LyShine <font> / <b> / <i> markup inside the visible string.
  6. When the last character is revealed, the component fires OnTypewriterComplete and disconnects from TickBus.
  7. If ForceComplete is called mid-reveal, all remaining characters are revealed at once, OnTypewriterComplete fires, and the tick handler disconnects.

Text Command Reference

Inline commands are embedded directly in dialogue text strings and parsed by ParseFormattedText before reveal begins. Commands do not appear in the displayed text.

Format: [command=value] to open, [/command] to close where applicable.


[pause=X]

Pause the reveal for X seconds before continuing. If no value is given, defaults to 1.0 second. No closing tag.

Hello[pause=1.5] world.

[speed=X] / [/speed]

Change the typing speed to X characters per second from this point forward. [/speed] resets to the component’s configured default speed. Setting speed=0 causes all following text to appear instantly until the next speed tag.

Normal text [speed=5]fast text[/speed] normal again.

[color=VALUE] / [/color]

Change text colour. VALUE is any valid LyShine <font color> value — hex (#FF0000) or a named colour. Outputs <font color="VALUE">…</font> to the text element. Styles are tracked on a stack so nested formatting closes correctly.

Regular [color=#FF4444]danger text[/color] back to normal.

[size=VALUE] / [/size]

Change font size. VALUE is a numeric point size (e.g. 32). Outputs <font size="VALUE">…</font> to the text element.

Normal [size=48]big text[/size] normal.

[bold] or [b] / [/bold] or [/b]

Apply bold formatting. Both bold and b are valid keywords. Outputs <b>…</b>.

This is [b]bold[/b] text.

[italic] or [i] / [/italic] or [/i]

Apply italic formatting. Both italic and i are valid keywords. Outputs <i>…</i>.

This is [i]italic[/i] text.

[n] or [new]

Insert a newline character. No closing tag. Both n and new are valid.

First line[n]Second line.

Standalone Usage

The typewriter is not limited to dialogue. You can attach it to any entity with a LyShine text element for effects like:

  • Tutorial text that types out instructions
  • Item descriptions that reveal on hover
  • Narrative text during loading screens
  • Any text that benefits from a gradual reveal

Setup

  1. Create an entity with a LyShine text component.
  2. Attach a TypewriterComponent to the same entity.
  3. Call TypewriterRequestBus::Event(entityId, &TypewriterRequestBus::Events::StartTypewriter, text, speed) from C++ or ScriptCanvas.
  4. Optionally listen to TypewriterNotificationBus for per-character or completion events.

C++ Usage

#include <GS_Cinematics/GS_CinematicsBus.h>

// Start typewriter on a specific entity
AZ::EntityId textEntity = /* your text entity */;
GS_Cinematics::TypewriterRequestBus::Event(
    textEntity,
    &GS_Cinematics::TypewriterRequestBus::Events::StartTypewriter,
    AZStd::string("Hello, traveler. Welcome to the village."),
    12.0f  // 12 characters per second
);

// Force-complete if the player presses a button
GS_Cinematics::TypewriterRequestBus::Event(
    textEntity,
    &GS_Cinematics::TypewriterRequestBus::Events::ForceComplete
);

Script Canvas

Typewriter requests and notification events:


See Also

For conceptual overviews and usage guides:

For component references:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.5 - Dialogue Actors

Full API reference for DialoguePerformerMarkerComponent, ActorDefinition data model, and the performer registration and lookup system.

Dialogue Actors connect authored character data in the .dialoguedb asset to world-space entities in the level. The system has two halves:

  • ActorDefinition – The data side. Defined in the DialogueDatabase, it holds the actor’s name, portrait, and metadata.
  • DialoguePerformerMarkerComponent – The world side. Attached to an entity in the level, it registers a named performer with the Dialogue Manager so the sequencer can find it at runtime.

When a dialogue sequence references an actor by name, the sequencer looks up the matching performer entity through DialogueManagerRequestBus::GetPerformer(name). This links the authored text, portraits, and performance instructions to a specific entity in the world.

For usage guides and setup examples, see The Basics: GS_Cinematics.


DialoguePerformerMarkerComponent

Dialogue Performer Marker component in the O3DE Inspector

A component placed on any entity that represents a dialogue performer in the level. On Activate(), the marker registers itself with the Dialogue Manager by name. Multiple performers with the same name can exist in different levels, but only one should be active at a time within a single level.

Request Bus: PerformerMarkerRequestBus

Entity-addressed bus – each marker entity responds on its own address.

MethodParametersReturnsDescription
GetPerformerName–AZStd::stringReturns the performer’s registered name. This name must match the actor name in the dialogue database.
GetPosEntity–AZ::EntityIdReturns the entity to use as the performer’s position source. Typically the marker entity itself, but can point to a child entity for offset control.
GetPosPoint–AZ::Vector3Returns the performer’s current world-space position.
ShouldTrackTarget–boolReturns whether the performer entity should continuously track and face its dialogue target during a sequence.

ActorDefinition Data Model

The ActorDefinition is the authored data for a single character, stored inside the DialogueDatabase.

PropertyTypeDescription
TypeId{1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D}
Actor NameAZStd::stringInternal lookup name. Must match the performer marker name in the level.
Display NameAZStd::stringThe name shown to the player in dialogue UI. Can differ from the internal name.
PortraitAsset referenceDefault portrait image. Overridden per-node in TextNodeData when needed.
Emotion CategoriesAZStd::vectorGroupings of emotional states (e.g. happy, angry, sad). Used to organize profile images and poses.
Profile Image SetsAZStd::vectorSets of portrait images keyed by emotion or attitude. The sequencer can switch profiles during dialogue.
Pose SetsAZStd::vectorSets of body poses and idle animations keyed by attitude. Referenced by performance nodes.

Performer Registration and Lookup

Registration Flow

  1. A DialoguePerformerMarkerComponent activates on an entity in the level.
  2. During Activate(), the component calls DialogueManagerRequestBus::RegisterPerformerMarker(name, entityId).
  3. The Dialogue Manager stores the mapping from name to entity ID.
  4. When the component deactivates, it unregisters itself.

Lookup Flow

  1. The sequencer processes a node that references an actor name (e.g. a TextNodeData with speaker “perry” or a PerformanceNodeData targeting “perry”).
  2. The sequencer calls DialogueManagerRequestBus::GetPerformer("perry").
  3. The Dialogue Manager returns the registered entity ID.
  4. The sequencer (or performance) can now query the performer’s position, facing, and tracking state through PerformerMarkerRequestBus.

Multiple Performers

The actorTargetId field on node data allows a sequence to target a specific instance of a performer when multiple entities share the same actor name. This supports scenarios like having the same NPC appear in multiple locations across different levels while keeping the same actor definition in the database.


Setup

  1. In your .dialoguedb asset, define an actor with a unique name (e.g. “perry”) using the Dialogue Editor.
  2. In your level, create an entity and attach a DialoguePerformerMarkerComponent.
  3. Set the marker’s performer name to match the actor name in the database (e.g. “perry”).
  4. Position the entity where the performer should appear in the world.
  5. Optionally, add child entities for position offsets or attach visual representation (mesh, animation) to the same entity.

The marker entity will self-register with the Dialogue Manager on activation. The sequencer can now find this performer when processing sequences that reference the matching actor name.


Script Canvas Examples

Getting a performer’s name and world position:


See Also


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.6 - Effects

Full API reference for the DialogueEffect base class and built-in effect types — SetRecords and ToggleEntitiesActive.

Effects are synchronous actions executed from EffectsNodeData nodes within a dialogue sequence. When the Dialogue Sequencer encounters an Effects node, it calls DoEffect() on each DialogueEffect in the node’s list in order, then immediately advances to the next node.

All effects are polymorphic — extending the base class automatically populates the effects list in the dialogue editor at startup.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


DialogueEffect (Abstract Base)

The base class for all dialogue effects. Effects execute synchronously and can be reversed for rollback scenarios.

PropertyTypeDescription
TypeId{71E2E05E-A7A3-4EB3-8954-2F75B26D5E3A}

Virtual Methods

MethodDescription
DoEffect()Executes the effect. Called by the sequencer when the EffectsNodeData is processed.
ReverseEffect()Undoes the effect. Used for rollback or undo scenarios.

Lifecycle

  1. The sequencer encounters an EffectsNodeData node.
  2. The sequencer calls DoEffect() on each effect in the node’s list in order.
  3. All effects complete synchronously within the same frame.
  4. The sequencer immediately advances to the next node.

SetRecords_DialogueEffect

Sets one or more game records in the GS_Save RecordKeeper system. Commonly used to flag quest progress, NPC disposition, or world state changes triggered by dialogue.

PropertyTypeDescription
TypeId{FFCACF63-0625-4EE5-A74B-86C809B7BF80}
RecordsAZStd::vector<RecordEntry>List of record name/value pairs to set on execution.

ToggleEntitiesActive_DialogueEffect

Activates or deactivates a list of entities in the level. Used to show or hide objects, enable or disable triggers, or change world state during dialogue.

PropertyTypeDescription
TypeId{F97E1B87-EE7A-4D26-814C-D2F9FA810B28}
EntitiesAZStd::vector<AZ::EntityId>The entities to activate or deactivate.
ActiveboolTarget active state: true to activate, false to deactivate.

Complete Effect Type Reference

TypeTypeIdDescription
DialogueEffect{71E2E05E-A7A3-4EB3-8954-2F75B26D5E3A}Abstract base
SetRecords_DialogueEffect{FFCACF63-0625-4EE5-A74B-86C809B7BF80}Sets game records
ToggleEntitiesActive_DialogueEffect{F97E1B87-EE7A-4D26-814C-D2F9FA810B28}Activates/deactivates entities

Creating a Custom Effect

To add a custom effect type from an external gem:

1. Define the class

#pragma once
#include <GS_Cinematics/Dialogue/Effects/DialogueEffect.h>

namespace MyGem
{
    class PlaySound_DialogueEffect : public GS_Cinematics::DialogueEffect
    {
    public:
        AZ_RTTI(PlaySound_DialogueEffect, "{YOUR-UUID-HERE}", GS_Cinematics::DialogueEffect);
        AZ_CLASS_ALLOCATOR(PlaySound_DialogueEffect, AZ::SystemAllocator);

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

        void DoEffect() override;
        void ReverseEffect() override;

    private:
        AZStd::string m_soundEvent;
    };
}

2. Implement and reflect

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

namespace MyGem
{
    void PlaySound_DialogueEffect::Reflect(AZ::ReflectContext* context)
    {
        if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
        {
            sc->Class<PlaySound_DialogueEffect, GS_Cinematics::DialogueEffect>()
                ->Version(1)
                ->Field("SoundEvent", &PlaySound_DialogueEffect::m_soundEvent);

            if (auto ec = sc->GetEditContext())
            {
                ec->Class<PlaySound_DialogueEffect>("Play Sound", "Fires a sound event during dialogue.")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyGem/Effects")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &PlaySound_DialogueEffect::m_soundEvent, "Sound Event", "");
            }
        }
    }

    void PlaySound_DialogueEffect::DoEffect()
    {
        // Fire the sound event
    }

    void PlaySound_DialogueEffect::ReverseEffect()
    {
        // Stop the sound if needed
    }
}

Custom effects are discovered automatically at startup through O3DE serialization — no manual registration step is required.


See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.2.7 - Performances

Full API reference for the DialoguePerformance base class and built-in performance types — MoveTo, PathTo, and RepositionPerformer.

Performances are asynchronous actions executed from PerformanceNodeData nodes within a dialogue sequence. When the Dialogue Sequencer encounters a performance node, it instantiates the appropriate DialoguePerformance subclass, starts it, and waits for it to signal completion before advancing to the next node.

All performances are polymorphic by extending the class they automatically populate the performances list when adding performances to a node.

For usage guides and setup examples, see The Basics: GS_Cinematics.

 

Contents


DialoguePerformance (Abstract Base)

The base class for all dialogue performances. Extends AZ::TickBus::Handler so that performances can drive time-based behavior across multiple frames.

PropertyTypeDescription
TypeId{BCCF5C52-42C3-49D4-8202-958120EA8743}
Tick HandlerAZ::TickBus::HandlerPerformances connect to TickBus during execution for frame-by-frame updates.

Virtual Methods

MethodDescription
DoPerformance()Entry point called by the sequencer. Sets up the performance and calls ExecutePerformance().
ExecutePerformance()Core execution logic. Override this in subclasses to implement the actual behavior (movement, animation, etc.).
FinishPerformance()Called when the performance is complete. Disconnects from TickBus and signals the sequencer via DialogueSequencerRequestBus::OnPerformanceComplete().

Lifecycle

  1. The sequencer encounters a PerformanceNodeData and instantiates the corresponding DialoguePerformance subclass.
  2. The sequencer calls DoPerformance().
  3. DoPerformance() connects to TickBus and calls ExecutePerformance().
  4. The performance runs across multiple ticks until its completion condition is met.
  5. The performance calls FinishPerformance(), which disconnects from TickBus and notifies the sequencer.
  6. The sequencer advances to the next node.

MoveTo_DialoguePerformance

Moves a performer entity to a named stage marker position over time using direct interpolation (no navmesh). Suitable for short-distance movements within a scene where pathfinding is unnecessary.

PropertyTypeDescription
TypeId{6033A69D-F46F-40DF-A4A0-A64C4E28D6D5}
TargetStage marker nameThe destination position, resolved through CinematicsManagerRequestBus::GetStageMarker().
Speed / DurationfloatControls how quickly the performer reaches the target.

Request Bus: MoveTo_PerformanceRequestBus

MethodParametersReturnsDescription
StartMoveToAZ::EntityId performer, AZ::Vector3 target, float speedvoidBegins moving the performer entity toward the target position.

Notification Bus: MoveTo_PerformanceNotificationBus

EventDescription
OnMoveToCompleteFired when the performer reaches the target position. The performance calls FinishPerformance() in response.

PathTo_DialoguePerformance

Navigates a performer entity to a named stage marker along a RecastNavigation navmesh path. Suitable for longer-distance movements where the performer must navigate around obstacles. Requires RecastNavigation to be enabled in the project.

PropertyTypeDescription
TypeId{C0DF4B0E-924D-4C38-BD26-5A286161D95C}
TargetStage marker nameThe destination position, resolved through CinematicsManagerRequestBus::GetStageMarker().
NavigationRecastNavigationUses the navmesh to compute a valid path from the performer’s current position to the target.

Request Bus: PathTo_PerformanceRequestBus

MethodParametersReturnsDescription
StartPathToAZ::EntityId performer, AZ::Vector3 targetvoidComputes a navmesh path and begins navigating the performer toward the target.

Notification Bus: PathTo_PerformanceNotificationBus

EventDescription
OnPathToCompleteFired when the performer reaches the end of the computed path. The performance calls FinishPerformance() in response.

RepositionPerformer_DialoguePerformance

Instantly teleports a performer entity to a named stage marker position. No interpolation, no pathfinding – the entity is moved in a single frame. Useful for off-screen repositioning between scenes or for snapping a performer to a mark before a sequence begins.

PropertyTypeDescription
TypeId{DE1E0930-F0A4-4F60-A741-4FB530610AEE}
TargetStage marker nameThe destination position, resolved through CinematicsManagerRequestBus::GetStageMarker().

Notification Bus: RepositionPerformer_PerformanceNotificationBus

EventDescription
OnRepositionCompleteFired immediately after the performer is teleported. The performance calls FinishPerformance() in the same frame.

Complete Performance Type Reference

TypeTypeIdMovementNavigation
DialoguePerformance{BCCF5C52-42C3-49D4-8202-958120EA8743}Abstract base–
MoveTo_DialoguePerformance{6033A69D-F46F-40DF-A4A0-A64C4E28D6D5}InterpolatedNone
PathTo_DialoguePerformance{C0DF4B0E-924D-4C38-BD26-5A286161D95C}Navmesh pathRecastNavigation
RepositionPerformer_DialoguePerformance{DE1E0930-F0A4-4F60-A741-4FB530610AEE}Instant teleportNone

Creating a Custom Performance

To add a custom performance type from an external gem:

1. Define the class

#pragma once
#include <GS_Cinematics/Dialogue/Performances/DialoguePerformance.h>

namespace MyGem
{
    class PlayAnimation_DialoguePerformance : public GS_Cinematics::DialoguePerformance
    {
    public:
        AZ_RTTI(PlayAnimation_DialoguePerformance, "{YOUR-UUID-HERE}", GS_Cinematics::DialoguePerformance);
        AZ_CLASS_ALLOCATOR(PlayAnimation_DialoguePerformance, AZ::SystemAllocator);

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

    protected:
        void ExecutePerformance() override;
        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;

    private:
        AZStd::string m_animationName;
        bool m_animationComplete = false;
    };
}

2. Implement and register

void PlayAnimation_DialoguePerformance::ExecutePerformance()
{
    // Start the animation on the performer entity
    // Listen for animation completion
}

void PlayAnimation_DialoguePerformance::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
    if (m_animationComplete)
    {
        FinishPerformance();
    }
}

See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.3 - Timeline Expansion

For usage guides and setup examples, see The Basics: GS_Cinematics.


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.4 - Templates

ClassWizard templates for GS_Cinematics — dialogue conditions, effects, and performance objects for the dialogue sequencer.

All GS_Cinematics extension types are generated through the ClassWizard CLI. The wizard handles UUID generation and cmake file-list registration automatically.

For usage guides and setup examples, see The Basics: GS_Cinematics.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Dialogue Condition

Template: DialogueCondition

Creates a condition object that gates a dialogue node connection at runtime. Assigned to a node’s conditions list in the DialogueDatabase asset. EvaluateCondition() is called before the connected node is offered to the player — return true to allow it, false to hide it.

Two types available via --input-var:

OptionDescription
Basic Condition (default)Free-form stub — implement EvaluateCondition() however the design requires
Boolean ConditionPre-wired to read a save record key and compare it with GS_Core::BooleanConditions

Generated files (Basic):

  • Source/${Name}_DialogueCondition.h/.cpp

Generated files (Boolean):

  • Source/${Name}_BooleanDialogueCondition.h/.cpp

CLI:

# Basic Condition:
python ClassWizard.py --template DialogueCondition --gem <GemPath> --name <Name> \
    --input-var condition_type="Basic Condition"

# Boolean Condition:
python ClassWizard.py --template DialogueCondition --gem <GemPath> --name <Name> \
    --input-var condition_type="Boolean Condition"

Post-generation — manual registration required:

In DialogueSequencerComponent.cpp, add:

#include <path/to/${Name}_DialogueCondition.h>
// inside Reflect(context):
${Name}_DialogueCondition::Reflect(context);

Extensibility: Fully polymorphic. The conditions list on each DialogueNodeData holds DialogueCondition* raw pointers — O3DE’s property editor type picker discovers all registered subtypes via EnumerateDerived. Add as many condition types as needed; they appear in the picker automatically once reflected.

See also: Dialogue Data Structure — full extension walkthrough with header and implementation examples.


Dialogue Effect

Template: DialogueEffect

Creates an effect object that fires world events when the sequencer reaches an Effects node. If the node’s temporary flag is set, ReverseEffect() is called at sequence end to undo the change. Used for enabling/disabling lights, playing sounds, showing UI, or changing world state during dialogue.

Generated files:

  • Source/${Name}_DialogueEffect.h/.cpp

CLI:

python ClassWizard.py --template DialogueEffect --gem <GemPath> --name <Name>

Post-generation — manual registration required:

In DialogueSequencerComponent.cpp, add:

#include <path/to/${Name}_DialogueEffect.h>
// inside Reflect(context):
${Name}_DialogueEffect::Reflect(context);

Key overrides:

MethodWhen called
DoEffect()When the Effects node is reached in the sequence
ReverseEffect()At sequence end, only if the node’s temporary flag is true

Extensibility: Same polymorphic pattern as DialogueCondition. The effects list holds DialogueEffect* pointers. All reflected subtypes appear in the editor type picker automatically.

See also: Dialogue Data Structure — full extension walkthrough.


Dialogue Performance

Template: DialoguePerformance

Creates a performance object that drives world-space NPC actions from a Performance node in the dialogue sequence. The sequencer waits at the node until all performances signal completion (unless waitToContinue is false). Used for NPC movement, animation, repositioning, or any async world action that must complete before the dialogue continues.

Generated files:

  • Source/${Name}_DialoguePerformance.h/.cpp

CLI:

python ClassWizard.py --template DialoguePerformance --gem <GemPath> --name <Name>

Post-generation — manual registration required:

In DialogueSequencerComponent.cpp, add:

#include <path/to/${Name}_DialoguePerformance.h>
// inside Reflect(context):
${Name}_DialoguePerformance::Reflect(context);

Lifecycle:

MethodRole
ExecutePerformance()Entry point — start the world action here
FinishPerformance()Call when the action is done (may be async); clean up state, then call base
PerformanceComplete()Called by base after FinishPerformance() — signals the sequencer to advance

Inherited fields: delayStartTime (seconds before ExecutePerformance is called), delayEndTime (seconds before PerformanceComplete fires after finish).

For instant actions, call FinishPerformance() at the end of ExecutePerformance(). For async actions, store a callback or subscribe to a bus and call FinishPerformance() from the completion handler.

Extensibility: Same polymorphic pattern as the other dialogue types. The performances list on a Performance node holds DialoguePerformance* pointers. All reflected subtypes appear in the editor picker automatically.

See also: Dialogue Data Structure — full extension walkthrough.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.4.5 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Cinematics.


Get GS_Cinematics

GS_Cinematics — Explore this gem on the product page and add it to your project.

4.5 - GS_Environment

Time of day progression, day/night cycle management, and sky colour configuration for GS_Play projects.

GS_Environment is the time and world atmosphere gem for GS_Play. It drives a configurable time-of-day clock, broadcasts day/night transition events, exposes per-tick world time notifications for dependent systems, and provides a sky colour configuration asset for Atom renderer integration. The gem depends on GS_Core.

For usage guides and setup examples, see The Basics: GS_Environment.

 

Contents


Time Manager

The Time Manager singleton owns the world clock. It advances time at a configurable speed, determines whether the current time of day is day or night, and registers the active main camera for sky calculations. Listeners subscribe via the GS_Core TimeEmissionBus to react to time changes or the day/night boundary crossing.

Component / AssetPurpose
GS_TimeManagerComponentGS_Play manager. Owns the world clock. Controls time passage speed and exposes day/night state queries.
SkyColourConfigurationAsset class for sky colour settings used by Atom renderer integration.

Time Manager API


Environment System

The system component manages gem-level initialization and global environment state. It runs on AZ::TickBus to advance the world clock each frame and coordinates environment requests across the level.

ComponentPurpose
GS_EnvironmentSystemComponentRuntime system component. Advances the world clock on tick. Handles GS_EnvironmentRequestBus queries.

Environment System API


Dependencies

  • GS_Core (required)

Installation

  1. Enable GS_Environment and GS_Core in your O3DE project’s gem list.
  2. Add GS_TimeManagerComponent to your Game Manager prefab and include it in the Startup Managers list.
  3. Configure the day window (start and end time values) on the Time Manager component.
  4. Set time passage speed to 0 if you want a static time of day, or to a positive value for a live clock.
  5. Call SetMainCam via TimeManagerRequestBus once your active camera entity is known (typically from a stage startup sequence).
  6. Subscribe to the GS_Core TimeEmissionBus (WorldTick / DayNightChanged) on any entities that respond to time changes.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

4.5.1 - Time Manager

World clock management, time-of-day control, day/night cycle detection, and per-tick time notifications.

The Time Manager is the singleton world clock for every GS_Play project. It extends GS_ManagerComponent and participates in the standard two-stage initialization managed by the Game Manager. On activation it begins advancing a time-of-day value each frame, determines whether the current time falls within the configured day window, and registers the active main camera for sky positioning calculations.

Listeners subscribe to the GS_Core TimeEmissionBus to react to per-frame ticks or the day/night boundary crossing.

For usage guides and setup examples, see The Basics: Time Manager.

Time Manager component in the O3DE Inspector

 

Contents


How It Works

Time Progression

Each frame the Time Manager advances the current time-of-day value by deltaTime × timePassageSpeed. A speed of 0 freezes the clock at the configured initial time; positive values run the clock forward in real time.

Day/Night State

The Time Manager compares the current time against a configured day window (start and end values). When the time crosses either threshold it fires DayNightChanged once. IsDay() returns the cached state and is safe to call every tick.

Sky Integration

Call SetMainCam with the active camera entity after stage startup. The Time Manager uses this reference to pass sky positioning data to the SkyColourConfiguration asset used by the Atom renderer.


Inspector Properties

PropertyTypeDescription
Day StartfloatNormalized time value (0.0–1.0) at which day begins.
Day EndfloatNormalized time value (0.0–1.0) at which night begins.
Initial TimefloatStarting time-of-day value when the component activates.
Time Passage SpeedfloatMultiplier controlling how fast time advances each frame. Set to 0 for a static clock.
Sky Colour ConfigAZ::Data::Asset<SkyColourConfiguration>Sky colour configuration asset used by the Atom renderer integration.

API Reference

GS_TimeManagerComponent

FieldValue
ExtendsGS_Core::GS_ManagerComponent
HeaderGS_Environment/GS_TimeManagerBus.h

Request Bus: TimeManagerRequestBus

Commands sent to the Time Manager. Singleton bus – Single address, single handler.

MethodParametersReturnsDescription
SetTimeOfDayfloat timevoidSets the current time of day (0.0–1.0 normalized range).
GetTimeOfDay–floatReturns the current time of day value.
GetWorldTime–floatReturns the total elapsed world time since startup.
SetTimePassageSpeedfloat speedvoidSets the multiplier controlling how fast time advances each frame.
IsDay–boolReturns whether the current time falls within the configured day window.
SetMainCamAZ::EntityId entityIdvoidRegisters the active main camera entity for sky positioning calculations.

Cross-Gem Contract: GS_Core::TimeEmissionBus

World-time signals are observed through the GS_Core TimeEmissionBus — the in-gem TimeManagerNotificationBus was hoisted to GS_Core in the interfaces migration (the ScriptCanvas-facing name is preserved). Global broadcast, multiple handlers. Time control (SetTimeOfDay, SetTimePassageSpeed, IsDay) stays on the in-gem TimeManagerRequestBus.

EventParametersDescription
WorldTickfloat deltaTimeFired every frame while the Time Manager is active. Use for time-dependent per-frame updates.
DayNightChangedbool isDayFired once when the time-of-day crosses the day/night boundary in either direction.

SkyColourConfiguration

Asset class for sky colour settings. Referenced by GS_TimeManagerComponent and consumed by the Atom renderer integration to drive sun, moon, and ambient colour values across the day/night cycle.


See Also

For conceptual overviews and usage guides:

For component references:


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

4.5.2 - Environment System

Runtime system component that drives the world clock tick and handles GS_EnvironmentRequestBus queries.

The Environment System Component is the gem-level runtime system for GS_Environment. It extends AZ::Component and implements AZ::TickBus::Handler to advance the world clock each frame. It handles GS_EnvironmentRequestBus queries and coordinates global environment state across the level.

This component activates automatically as part of the GS_Environment gem – it does not need to be added manually to any entity.

For usage guides and setup examples, see The Basics: GS_Environment.

Sky Colour Configuration asset in the O3DE Asset Editor

 

Contents


How It Works

Tick Integration

The Environment System Component connects to AZ::TickBus on activation. Each tick it drives the Time Manager’s clock advancement and ensures all environment-dependent systems receive their frame update in the correct order.

Environment Requests

GS_EnvironmentRequestBus provides a global access point for querying environment state. The system component is the sole handler; any gem or component can broadcast requests to read current environment conditions.


API Reference

GS_EnvironmentSystemComponent

FieldValue
TypeId{57B91AE7-B0EC-467E-A359-150B5FB993F9}
ExtendsAZ::Component, AZ::TickBus::Handler
HeaderGS_Environment/GS_EnvironmentBus.h

Request Bus: GS_EnvironmentRequestBus

Commands sent to the Environment System. Singleton bus – Single address, single handler.

FieldValue
Interface TypeId{39599FDC-B6DC-4143-A474-9B525599C919}
MethodParametersReturnsDescription
(base interface)––Extended by project-level environment systems as needed.

See Also

For conceptual overviews and usage guides:

For component references:

  • Time Manager – World clock, day/night cycle, and per-tick notifications

Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

4.5.3 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Environment.


Get GS_Environment

GS_Environment — Explore this gem on the product page and add it to your project.

4.6 - GS_GraphCanvas

A framework for building custom visual graph editors in O3DE — descriptors, nodes, variables, execution engines, and editor infrastructure.

GS_GraphCanvas is a higher-level framework built on top of O3DE’s GraphCanvas and GraphModel gems. It lets you create fully-featured visual graph editors — dialogue trees, audio event graphs, state machines, or any domain-specific tool — without touching the complex boilerplate of the underlying engine systems. You provide a descriptor, a context, and your nodes; the framework gives you a complete editor.

 

Contents


Architecture

GS_GraphCanvas sits between your gem and the engine’s graph infrastructure. The layer model:

[Your Gem]              (gs_cinematics, gs_audio, gs_unit, or your own)
   Provides: GraphSystemDescriptor, GraphContext subclass, Node definitions
        |
[gs_graphcanvas]        (STATIC LIB — framework layer)
   Provides: MainWindow, save/load, undo/redo, variables, inspector,
             node registry, palette, execution engines, multi-doc, pages
        |
[O3DE GraphModel]       (engine — data model)
   Provides: Graph, Node, Slot, Connection, DataType, GraphContext
        |
[O3DE GraphCanvas]      (engine — visual rendering)
   Provides: QGraphicsScene rendering, node visuals, slot widgets,
             connection rendering, selection, copy/paste, bookmarks

Downstream gems inherit from the framework’s MainWindow and extend it without fighting the base infrastructure. The base MainWindow is a fully functional single-graph editor. Downstream editors add domain-specific behavior (database containers, sequence sidebars, layer systems) on top.

Build Model

gs_graphcanvas is a static library. Each downstream gem links against it and gets its own compiled copy. This means reflected types can be registered multiple times from different DLLs — all Reflect() methods must include FindClassData guards to prevent duplicate registration crashes.


Descriptor & Topology

The GraphSystemDescriptor is the identity card for a graph editor type. It tells the framework what kind of editor to build — its name, file extension, topology, and enabled features.

The topology is the most important decision. It determines connection style, execution model, and slot types:

TopologyUse CaseConnectionsExecution Model
FlowGraphSequential processes, dialogue treesDirectional FlowIn/FlowOutStep/wait/resume
DataFlowGraphSignal processing, audio, shadersDirectional data slotsTopological dirty-propagation
StateMachineGraphState machines, HFSM, behavior treesMultidirectional perimeter arrowsTick-based state transitions

See Descriptor & Topology for full field reference and the GraphContext data type system.


Nodes

All gs_graphcanvas nodes inherit from BaseNode. The framework provides macros for slot registration, auto-registration into the node palette, and rapid node creation for simple cases.

Three tiers of complexity:

  • Macro node (~5 lines) — DEFINE_SIMPLE_NODE_WITH_SLOTS generates the entire class
  • Helper macro node (~15 lines) — Use slot macros with a custom class
  • Full custom node (30+ lines) — Full control over slots, properties, and behavior

See Nodes for the node system, slot macros, and auto-registration.


Editor Window

The MainWindow class provides a complete editor out of the box:

  • Multi-document tabs with dirty tracking and close-with-save prompts
  • File menu (New, Open, Save, Save As, Open Recent)
  • Edit menu (Undo/Redo, Cut/Copy/Paste)
  • Node palette with drag-drop creation
  • Inspector panel for node and connection properties
  • Variable panel (when enabled)
  • Full-window page system for non-graph pages (e.g., Performers, Settings)
  • In-memory graph operations for container/database editors

Downstream editors subclass MainWindow and override virtual hooks to add domain-specific behavior.

See Editor Window for virtual hooks, page system, and container editor patterns.


Variables

When variablesEnabled = true in the descriptor, the framework provides a complete variable system:

  • Variable Panel dock widget for declaring typed variables
  • Get/Set Variable nodes automatically included in the palette
  • Convert to Reference — right-click any input slot to bind it to a variable
  • Drag to Canvas — drag a variable from the panel onto the canvas to create a Get or Set node
  • Rename propagation — renaming a variable updates all referencing nodes

See Variables for the variable system and runtime bindings.


Execution Engines

gs_graphcanvas includes three execution engines matching the three topologies:

EngineTopologyPattern
FlowGraphEvaluatorFlowGraphStep/wait/resume along FlowIn/FlowOut connections
DataFlowGraphEvaluatorDataFlowGraphTopological ordering, dirty-propagation, re-evaluate only changed nodes
StateMachineEvaluatorStateMachineGraphTick-based with OnEnter/OnTick/OnExit lifecycle and transition conditions

All engines share GraphExecutionContext for variable storage and value resolution. GraphInstance provides independent runtime copies of a graph for safe concurrent execution.

See Execution Engines for engine details and the runtime pipeline.


Inspector & Save/Load

Inspector Panel

The inspector auto-generates UI from node EditContext reflection. Select a node to edit its properties; in state machine graphs, click a transition line to edit its priority and conditions.

Save/Load

Graphs are saved as GraphDocumentAsset — a plain struct (not AZ::Data::AssetData) containing the graph as a binary byte buffer wrapped in XML. This avoids ObjectStream and AssetManager interaction issues.

Undo/Redo

Snapshot-based: each undo point captures the entire graph state as a byte buffer. Per-graph undo stacks. Ctrl+Z / Ctrl+Y.


Installation

In your gem’s editor module CMakeLists.txt, add gs_graphcanvas as a build dependency:

ly_add_target(
    NAME GS_MyTool.Editor.Static STATIC
    ...
    BUILD_DEPENDENCIES
        PRIVATE
            Gem::GraphCanvas.Editor.Static
            Gem::GraphModel.Editor.Static
            Gem::GS_GraphCanvas.Editor.Static
            ...
)

See Also

4.6.1 - Descriptor & Topology

GraphSystemDescriptor fields, topology selection, and GraphContext data type registration.

The GraphSystemDescriptor tells gs_graphcanvas what kind of editor to build. It is a plain struct that you fill out and pass to the MainWindow constructor.

 

GraphSystemDescriptor Fields

Identity

FieldTypeDescription
systemIdconst char*Unique ID string (e.g., "dialogue", "audiograph", "unitaction"). Used for node filtering.
systemNameconst char*Display name shown in the editor title bar.
fileExtensionconst char*File extension for graph files (e.g., ".dialogue", ".audiograph").
mimeTypeconst char*MIME type for drag-drop from the node palette.
saveIdentifierconst char*QSettings key for persisting window layout state.
editorIdGraphCanvas::EditorIdUnique editor ID for the GraphCanvas system.

Topology

FieldTypeDefaultDescription
topologyGraphTopologyFlowGraphDetermines connection style, slot types, and execution model.

Topology options:

  • FlowGraph — Directional flow slots (FlowIn/FlowOut), sequential step/wait/resume execution. Best for: dialogue trees, scripting, sequential processes.
  • DataFlowGraph — No flow slots, data-only connections with dirty-propagation evaluation. Best for: audio, shaders, procedural generation, signal processing.
  • StateMachineGraph — Multidirectional perimeter connections with tick-based state transitions. Best for: HFSM, behavior trees, character controllers.

See Execution Engines for detailed Topology Flow reference and internal API for runtime execution.

 

Feature Flags

FieldTypeDefaultDescription
variablesEnabledboolfalseEnables the variable panel, Get/Set variable nodes, and reference bindings.
allowConnectionLoopbackboolfalseWhether cycles are allowed in the graph.

 

State Machine Features

These fields are only relevant when topology == StateMachineGraph:

FieldTypeDefaultDescription
hierarchicalStatesboolfalseEnable compound/nested states.
parallelLayersboolfalseEnable parallel layer evaluation.
transitionConditionsboolfalseEnable polymorphic transition conditions on connections.

 

UI

FieldTypeDescription
autoSpawnNodeTypesAZStd::vector<AZ::TypeId>Node types automatically created in every new graph (e.g., entry nodes).
stylesheetPathconst char*Optional path to a custom QSS stylesheet for node styling.
windowTitleconst char*Window title text.
menuCategoryconst char*Menu path in the O3DE editor (e.g., "GS Tools").

GraphContext

Each graph system needs a GraphContext subclass that registers the data types available in that system. The framework provides a set of built-in CommonDataTypes available to all systems:

TypeEnumC++ Type
BoolGS_Boolbool
IntGS_Intint
FloatGS_Floatfloat
StringGS_TextAZStd::string
Vector2GS_Vec2AZ::Vector2
Vector3GS_Vec3AZ::Vector3
ColorGS_ColorAZ::Color
EntityIdGS_EntityIdAZ::EntityId

If your tool only uses these built-in types, your GraphContext subclass may be trivial. Domain-specific types (e.g., AudioRoute for audio graphs) are registered by calling context->RegisterDataType(...) in your subclass.


Topology Decision Matrix

ConsiderationFlowGraphDataFlowGraphStateMachineGraph
Flow slots (FlowIn/FlowOut)YesNoNo
Connection directionLeft-to-rightLeft-to-rightMultidirectional (perimeter)
Execution modelStep/wait/resumeTopological dirty-propagationTick-based state transitions
Node interfaceIExecutableNodeIDataFlowNodeIStateMachineNode
EvaluatorFlowGraphEvaluatorDataFlowGraphEvaluatorStateMachineEvaluator
Cycles allowedOptionalNo (DAG required)Yes (inherent)
Transition conditionsN/AN/ATransitionCondition base class
Example downstreamDialogue EditorAudio Event GraphUnit Action Graph

4.6.2 - Nodes

BaseNode, slot macros, auto-registration, rapid node creation, and built-in node types.

All gs_graphcanvas nodes inherit from BaseNode, which extends GraphModel::Node. BaseNode provides slot registration helpers, flow slot support, inspector property reflection, and transition descriptor management for state machines.

 

Slot Registration

Slots are registered in the RegisterSlots() override using helper macros:

GS_INPUT_SLOT_TYPED("slot_name", "Display Name", DataTypeEnum, DefaultValue)
GS_OUTPUT_SLOT_TYPED("slot_name", "Display Name", DataTypeEnum)
GS_INPUT_SLOT_CONNECTION("slot_name", "Display Name")        // Connection-only, no inline editor
GS_MULTI_INPUT_SLOT_CONNECTION("slot_name", "Display Name")  // Multiple connections allowed

All input slots use editableOnNode=true by default, providing inline value editing directly on the node in the graph canvas.

For flow-based graphs, call RegisterFlowSlots() to add FlowIn and FlowOut slots. Override HasFlowIn() or HasFlowOut() to control which flow slots appear (e.g., a Start node has no FlowIn).

For state machine graphs, call RegisterTransitionSlots() to add transition_in and transition_out perimeter slots. Override HasTransitionIn() / HasTransitionOut() to control slot presence.


Auto-Registration

Nodes self-register into the node palette via macros:

// Register for ALL graph systems:
GS_AUTO_REGISTER_NODE(MyNode)

// Register for a specific system only:
GS_AUTO_REGISTER_NODE_FOR(MyNode, "dialogue")

Each registered node includes a category string (from CATEGORY constant), display name (from TITLE), and a creation lambda. The NodeRegistry singleton manages all registrations and handles MIME event reflection for drag-drop.


Rapid Node Creation

For simple nodes with no custom logic, a single macro generates the entire class:

DEFINE_SIMPLE_NODE_WITH_SLOTS(
    MyNode,
    "{UNIQUE-UUID}",
    "My Node",
    "My Category",
    {INPUT_SLOT("input", GS_Text, "Input text")},
    {OUTPUT_SLOT("output", GS_Text, "Output text")}
)

This generates the class with TITLE, CATEGORY, Reflect(), RegisterSlots(), and the constructor.

Three Tiers of Node Complexity

TierLinesWhen to Use
Macro node~5Simple pass-through or data-holding nodes
Helper macro node~15Nodes with custom properties but standard slot patterns
Full custom node30+Nodes with custom execution logic, dynamic slots, or complex behavior

Reflect Pattern

Every node’s Reflect() method must include a FindClassData guard because gs_graphcanvas is a static library and types can be registered from multiple DLLs:

void MyNode::Reflect(AZ::ReflectContext* context)
{
    if (auto* sc = azrtti_cast<AZ::SerializeContext*>(context))
    {
        if (sc->FindClassData(azrtti_typeid<MyNode>())) { return; }  // MANDATORY guard
        sc->Class<MyNode, GS_GraphCanvas::BaseNode>()
            ->Version(1)
            ->Field("MyProperty", &MyNode::m_myProperty)
            ;
    }
    if (auto* ec = azrtti_cast<AZ::EditContext*>(context))
    {
        ec->Class<MyNode>("My Node", "Description")
            ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
            ->DataElement(AZ::Edit::UIHandlers::Default, &MyNode::m_myProperty,
                "My Property", "Tooltip")
            ;
    }
}

Built-in Nodes

gs_graphcanvas includes several utility nodes available to all graph systems:

NodeCategoryPurpose
IfNodeRoutingRoutes data based on a boolean condition
SwitchNodeRoutingRoutes data based on a selector value
GetVariableNodeVariablesOutputs the value of a declared variable
SetVariableNodeVariablesWrites a value to a declared variable

Variable nodes are automatically included in the palette when variablesEnabled = true in the descriptor.

4.6.3 - Editor Window

MainWindow features, virtual hooks, page system, and container editor patterns.

The MainWindow class provides a complete graph editor out of the box. Downstream editors subclass it and override virtual hooks to add domain-specific behavior.

 

Out-of-the-Box Features

Every MainWindow instance includes:

  • Multi-document tabs — Each graph opens in its own tab with dirty tracking (asterisk * in tab title) and close-with-save prompts
  • File menu — New, Open, Save, Save As, Open Recent (tracks last 10 files)
  • Edit menu — Undo/Redo (Ctrl+Z / Ctrl+Y), Cut/Copy/Paste
  • Node palette — Searchable list of available nodes with drag-drop creation
  • Inspector panel — Auto-generated property editor from node EditContext reflection
  • Variable panel — Variable declarations, type selection, and default value editing (when enabled)

Virtual Hooks

MainWindow provides virtual methods for downstream customization:

File Menu Overrides

virtual void AddFileNewAction();
virtual void AddFileOpenAction();
virtual void AddFileSaveAction();
virtual void AddFileSaveAsAction();

Override these to replace per-file behavior with container-level behavior (e.g., the Dialogue Editor overrides these to save the entire database instead of individual graphs).

Lifecycle Hooks

HookCalled When
OnEditorOpened()A graph tab is opened
OnEditorClosing()A graph tab is being closed
OnActiveGraphChanged()The user switches between graph tabs
OnNewGraphRequested(QString& outTitle)Before a new graph tab is created. Return false to cancel.
OnNodeDoubleClicked(BaseNode* node)A node is double-clicked. Used to open sub-graph editors.

Full-Window Page System

The page system lets you add non-graph pages alongside the graph editor in the same window:

AddFullWindowPage("Performers", m_performersWidget);
AddFullWindowPage("System", m_systemWidget);
SetGraphPageTitle("Sequences");  // Rename the default graph tab
SetActivePage(index);            // Switch between pages

The base class creates a tab bar on the first call. When switching to a non-graph page, the graph canvas and all docks are hidden; the page widget fills the entire window. Dock layout is preserved between switches.

Examples in practice:

  • Dialogue Editor: Sequences (graph) + Performers + System
  • Unit Action Editor: Layers (graph) + Layer Details

Container Editor Pattern

For editors where multiple graphs live inside a single file (like dialogue sequences in a database, or state machine layers in a unit action asset), MainWindow provides in-memory graph operations:

Opening Sub-Graphs

OpenGraphFromAsset(GraphDocumentAsset& asset, const QString& title, AZ::SerializeContext* sc);

Deserializes a graph from an asset’s byte buffer and opens it as a new tab.

Capturing Sub-Graphs

CaptureGraphToAsset(GraphModel::GraphId graphId, GraphDocumentAsset& outAsset, AZ::SerializeContext* sc);

Serializes an open graph back into the asset’s byte buffer. Used before saving the container.

Creating Empty Graphs

OpenNewGraphTab(const QString& title);

Opens a blank graph tab for a new sub-object.

Clearing Dirty State

ClearAllDocumentDirty();

Clears dirty markers on all open tabs. Called after a container-level save so that individual tab dirty states don’t linger.

Typical Container Save Flow

  1. Iterate all open graph tabs
  2. Call CaptureGraphToAsset() for each to write graphs back to the container
  3. Serialize the container to disk
  4. Call ClearAllDocumentDirty() to clear all tab markers

4.6.4 - Variables

Variable declarations, Get/Set nodes, reference bindings, and drag-to-canvas interactions.

When variablesEnabled = true in the GraphSystemDescriptor, the framework provides a complete variable system for storing and reading named values within a graph.

 

Variable Panel

The Variable Panel is a dock widget with a table listing all declared variables. Each variable has:

  • Name — User-defined identifier
  • Data Type — Selected from a dropdown of registered types (Bool, Int, Float, String, etc.)
  • Default Value — Type-specific editor (checkbox for Bool, spinner for numeric, text field for String, multi-field for vectors, color picker for Color)

Variables are per-graph. Add or remove variables using the panel controls. Undo/redo integration is automatic.


Get / Set Variable Nodes

When variables are enabled, GetVariableNode and SetVariableNode are automatically included in the node palette under the “Variables” category.

  • GetVariableNode — Outputs the current value of a named variable
  • SetVariableNode — Writes a value to a named variable

Both nodes reference a variable by name. If the variable is renamed, all referencing Get/Set nodes update automatically.


Convert to Reference

Right-click any typed input slot on a node to convert it to a variable reference. A dropdown appears listing all declared variables that match the slot’s data type. Once bound:

  • The slot displays the variable name instead of an inline value
  • The slot reads the variable’s value at evaluation time
  • Right-click again and select “Convert to Value” to unbind

Reference bindings are persisted as VariableBinding structs (NodeId + SlotName + VariableId) and restored automatically on load.


Drag to Canvas

Drag a variable from the Variable Panel directly onto the graph canvas. A popup menu asks whether to create a Get or Set node for that variable. The node is automatically configured with the variable name and data type.


Runtime Variable Access

At runtime, variables live in the GraphExecutionContext blackboard. The execution context provides:

  • Read/write access by variable name
  • InitializeBindings() resolves VariableId references to names
  • Variable changes can trigger re-evaluation (in DataFlowGraph, MarkDirty(variableName) propagates to dependent nodes)

Downstream gems expose variable control through their own APIs. For example, the Audio Event Graph provides SetAudioGraphVariable(instanceId, name, value) to control graph variables from gameplay code.

4.6.5 - Execution Engines

FlowGraphEvaluator, DataFlowGraphEvaluator, StateMachineEvaluator, GraphExecutionContext, and GraphInstance.

gs_graphcanvas provides three execution engines — one for each topology. All share a common GraphExecutionContext for variable storage and value resolution, and GraphInstance for creating independent runtime copies.

 

FlowGraphEvaluator

Flow Evaluator in Graph Tool

Topology: FlowGraph

Step/wait/resume execution model for sequential graphs. The evaluator follows FlowIn/FlowOut connections from node to node.

Nodes implement the IExecutableNode interface:

FlowResult Execute(GraphExecutionContext& context);

FlowResult controls what happens after a node executes:

ResultBehavior
ContinueImmediately proceed to the next connected node
WaitPause execution until Resume() is called externally
StopEnd graph execution

Used by: Dialogue Editor — text nodes return Wait while dialogue is displayed, then Resume() advances to the next node.


DataFlowGraphEvaluator

Data Flow Evaluator in Graph Tool

Topology: DataFlowGraph

Dirty-propagation evaluator for continuously-evaluated graphs. On initialization, builds a topological ordering of all nodes via Kahn’s algorithm.

Nodes implement the IDataFlowNode interface:

void Process(GraphExecutionContext& context);

Evaluation Pipeline

  1. Initialize — Build topological order from the graph’s connection structure
  2. Track dependencies — Map which nodes depend on which variables
  3. Mark dirty — MarkDirty(variableName) marks dependent nodes dirty, then transitively marks all downstream nodes
  4. Evaluate — Iterate nodes in topological order, re-running only dirty nodes

The explicit dirty marking (not automatic) gives downstream code batching control — set multiple variables, then call Evaluate() once.

Empty Any Pattern

When a data-flow node has no valid output (gated off, no valid input), it must output AZStd::any{} (truly empty), never a default-constructed value:

if (validInput)
    context.SetOutputValue(this, "result", computedValue);
else
    context.SetOutputValueAny(this, "result", AZStd::any{});

This is critical because multi-input resolution returns the first non-empty AZStd::any. A default-constructed struct inside an any is non-empty and will mask valid values from other connections.

Used by: Audio Event Graph — entry gate nodes, filter nodes, and effect nodes all follow this pattern.


StateMachineEvaluator

State Transition Flow Evaluator in Graph Tool

Topology: StateMachineGraph

Tick-based evaluator for hierarchical finite state machines. Maintains the current active state and evaluates transitions each tick.

Nodes implement the IStateMachineNode interface:

void OnEnter(GraphExecutionContext& context);
void OnTick(GraphExecutionContext& context, float deltaTime);
void OnExit(GraphExecutionContext& context);

Tick Cycle

  1. Increment __stateTime auto-variable
  2. Gather all outgoing transitions from the current state
  3. Evaluate conditions on each transition
  4. Fire the highest-priority valid transition (OnExit on source, OnEnter on destination)
  5. Call OnTick on the active state

Transition Conditions

Each connection carries a TransitionDescriptor with a priority and a list of polymorphic TransitionCondition objects. Built-in conditions:

ConditionDescription
VariableCompareConditionCompares a variable to a value using a comparison operator
TimeElapsedConditionFires after N seconds in the current state (reads __stateTime)
VariableTrueConditionFires when a boolean variable is true

Custom conditions are created by subclassing TransitionCondition, reflecting it, and it auto-discovers via SerializeContext::EnumerateDerived().

ParallelLayerEvaluator

Wraps multiple StateMachineEvaluator instances (one per layer) with shared context synchronization. Each layer evaluates independently but shares the same variable context for cross-layer reads.

Used by: Unit Action Graph — parallel Movement, Action, and Rotation layers.


GraphExecutionContext

Shared context for all evaluator types. Provides:

  • Input value resolution — GetInputValue<T>() and GetInputValueAny() with multi-input resolution (returns first non-empty value)
  • Output values — SetOutputValue() and SetOutputValueAny()
  • Blackboard — Variable storage by name
  • Binding initialization — InitializeBindings() resolves VariableId references to variable names

GraphInstance

An independent runtime copy of a graph. GraphInstance::CreateFromAsset() deserializes a fresh graph from a GraphDocumentAsset’s byte buffer, initializes variables and bindings. Each instance is fully independent — downstream gems can pool instances for high-frequency execution paths.

4.7 - GS_Interaction

Entity interaction systems — physics-based pulse events, targeting and cursor management, and extensible world trigger actions.

GS_Interaction is the gem that connects entities to each other and to the player. It provides three distinct systems: a physics-based pulse emitter/reactor pair for broadcasting typed events through trigger volumes, a targeting subsystem that tracks and selects interact candidates relative to a cursor, and a world trigger framework that composes trigger conditions with discrete world-state actions. All three systems are designed to be extended — custom pulse types, reactor types, trigger actions, and world trigger behaviors can be registered without modifying the gem.

For usage guides and setup examples, see The Basics: GS_Interaction.

 

Contents


Pulsors

A physics-based event broadcast system. A PulsorComponent emits a typed pulse event when its physics trigger volume fires. PulseReactorComponents on nearby entities receive the pulse and execute their registered reactor logic. Pulse and reactor types are extensible, so any gem can contribute new pulse/reactor pairs.

ComponentPurpose
PulsorComponentEmits a typed pulse when a physics trigger volume fires.
PulseReactorComponentReceives typed pulses and routes them to registered reactor logic.

Pulsors API


Targeting

A targeting and cursor management system. The Targeting Handler selects the best available interact target from registered candidates detected through proximity trigger volumes. The cursor components manage visual cursor representation on a LyShine canvas. An input reader component bridges raw player input into interaction events.

ComponentPurpose
GS_TargetingHandlerComponentCore targeting logic — selects the closest/best registered interact target.
GS_TargetComponentBase target component with visual properties (size, offset, color, sprite).
GS_CursorComponentManages the active cursor: registration, visibility, position, and visuals.
GS_CursorCanvasComponentPer-canvas cursor sprite component, registered with GS_CursorComponent.
GS_TargetingInteractionFieldComponentPhysics trigger volume used for proximity-based target detection.
InteractInputReaderComponentMaps raw player input to interaction events.

Targeting API


World Triggers

A composable trigger-action framework for world-state changes. Trigger Sensor components define the condition that fires the trigger (player interact, physics collision, save record state). World Trigger components define the effect (log a message, set a record, toggle entity activation, change stage). Any number of trigger actions can drive any number of world triggers on the same entity.

ComponentPurpose
TriggerSensorComponentBase class for all trigger condition components.
InteractTriggerSensorComponentFires on interact input.
PlayerInteractTriggerSensorComponentFires on interact input from the player only.
ColliderTriggerSensorComponentFires on physics collision with the trigger volume.
RecordTriggerSensorComponentFires when a save record matches a configured condition.
WorldTriggerComponentBase class for all world trigger effect components.
PrintLog_WTComponentLogs a message when triggered.
SetRecord_WorldTriggerComponentSets a key-value save record when triggered.
ToggleEntityActivate_WorldTriggerComponentToggles entity activation state when triggered.
ChangeStage_WorldTriggerComponentRequests a stage/level transition when triggered.

World Triggers API


Cross-Gem Contracts

GS_Interaction’s extensible types and observation signals are collected in the GS_Core Interfaces layer, so other gems depend on the contracts rather than on GS_Interaction:

ContractKindUsed for
PulseType · ReactorTypeTypeBaseAuthor new pulse / reactor types — they appear in the editor picker.
WorldTriggerType · TriggerSensorTypeTypeBaseAuthor new trigger conditions and effects.
PulseReactorEmissionBusEmitOnPulseReceived — a reactor processed a pulse.
TriggerSensorEmissionBusEmitOnTriggered / OnReset.
TargetingEmissionBusEmitOnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteract.

See the Contract Reference for the full table and addressing details.


Installation

GS_Interaction requires GS_Core, LmbrCentral, LyShine, and CommonFeaturesAtom to be active in your project.

  1. Enable GS_Interaction in Project Manager or project.json.
  2. Add a GS_TargetingHandlerComponent to the player entity and configure its proximity trigger.
  3. Add GS_CursorComponent and GS_CursorCanvasComponent to the cursor entity and link them together.
  4. Place TriggerSensorComponent and WorldTriggerComponent variants on any entity that should react to the world.
  5. Refer to the Interaction Set Up Guide for a full walkthrough.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.1 - Pulsors

Physics-based pulse emitter and reactor system — extensible typed interactions via polymorphic PulseType and ReactorType classes.

The Pulsor system is a physics-driven interaction layer. A PulsorComponent emits typed pulses when its physics trigger fires. PulseReactorComponents on other entities receive and process those pulses based on their type. The system is fully extensible — new pulse and reactor types are discovered automatically through O3DE serialization, so new interaction types can be added from any gem without modifying GS_Interaction.

For usage guides and setup examples, see The Basics: GS_Interaction.

 

Contents


Architecture

Pulse Pattern Graph

Breakdown

When an entity enters a Pulsor’s trigger volume, the Pulsor emits its configured pulse type to all Reactors on the entering entity:

StepWhat It Means
1 — Collider overlapPhysics detects an entity entering the Pulsor’s trigger volume.
2 — Pulse emitPulsorComponent reads its configured PulseType and prepares the event.
3 — Reactor queryEach PulseReactorComponent on the entering entity is checked with IsReactor().
4 — ReactionReactors returning true have ReceivePulses() called and execute their response.

Pulse types are polymorphic — new types are discovered automatically at startup via EnumerateDerived. Any gem can define custom interaction semantics without modifying GS_Interaction.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Components

PulsorComponent

Pulsor component in the O3DE Inspector

Emits typed pulses when its physics trigger fires. Extends AZ::Component and PhysicsTriggeringVolume.

PropertyTypeDescription
Pulse Typesvector<PulseType*>The pulse types this pulsor emits on trigger.

The pulsor fires all configured pulse types simultaneously when an entity enters its trigger volume.


PulseReactorComponent

Pulse Reactor component in the O3DE Inspector

Receives and processes typed pulses from pulsors.

Bus: PulseReactorRequestBus (ById, Multiple)

MethodParametersReturnsDescription
ReceivePulses—voidProcess incoming pulse events.
IsReactor—boolReturns whether this component is an active reactor.

Pulse Types

Pulse types define what kind of interaction a pulsor emits. All types extend the abstract PulseType base class.

TypeTypeIdDescription
PulseType (base){8A1B2C3D-4E5F-6A7B-8C9D-0E1F2A3B4C5D}Abstract base class for all pulse types.
Debug_Pulse{123D83FD-027C-4DA4-B44B-3E0520420E44}Test and debug pulse for development.
Destruct_Pulse{98EC44DA-C838-4A44-A37A-FA1A502A506B}Destruction pulse — triggers destructible reactions.

Pulse Types API


Reactor Types

Reactor types define how an entity responds to incoming pulses. All types extend the abstract ReactorType base class.

TypeTypeIdDescription
ReactorType (base){9B2C3D4E-5F6A-7B8C-9D0E-1F2A3B4C5D6E}Abstract base class for all reactor types.
Debug_Reactor{38CC0EA0-0975-497A-B2E5-299F5B4222F7}Test and debug reactor for development.
Destructable_Reactor{47C9B959-2A9F-4E06-8187-E32DDA3449EC}Handles destruction responses to Destruct_Pulse.

Reactor Types API


Extension Guide

Use the ClassWizard templates to generate new pulse and reactor classes with boilerplate already in place — see GS_Interaction Templates:

  • PulsorPulse — generates a new PulseType subclass with a named channel and payload stub. Supply type_display_name and type_category input vars to control how it appears in the editor dropdown.
  • PulsorReactor — generates a new ReactorType subclass. Supply the required pulse_channel var — the channel string is baked into the header at generation time.

To create a custom pulse or reactor type manually:

  1. Create a class extending PulseType (or ReactorType) with a unique RTTI TypeId.
  2. Reflect the class using O3DE’s SerializeContext and EditContext. The system discovers the new type automatically.

Channels are string-matched at runtime — keep the channel string consistent between the Pulse and the Reactor that receives it.


See Also

For related resources:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.1.1 - Pulse Types

Built-in pulse types for the Pulsor interaction system.

Pulse types define what kind of interaction a PulsorComponent emits. Each pulse type is a data class extending the abstract PulseType base. The PulsorComponent holds an array of pulse types and emits all of them simultaneously when its physics trigger fires.

For usage guides and setup examples, see The Basics: GS_Interaction.


Component

PulsorComponent

Pulsor component in the O3DE Inspector

Emits configured pulse types when its physics trigger fires. Extends AZ::Component and PhysicsTriggeringVolume.

FieldTypeDescription
pulseTypesvector<PulseType*>Pulse types emitted to all PulseReactorComponent instances on any entering entity.

All pulse types in the array fire simultaneously on a single trigger event. Configure multiple pulse types to target reactors on different channels in one emission.


Base Class

PulseType

Abstract base class for all pulse types. Not a component — discovered automatically at startup via EnumerateDerived and reflected into the editor dropdown.

Field / VirtualTypeDescription
TypeId{8A1B2C3D-4E5F-6A7B-8C9D-0E1F2A3B4C5D}RTTI identifier. Each subclass must define its own unique TypeId.
GetChannel()AZStd::stringReturns the channel string this pulse is sent on. Matched against reactor channel strings at runtime.

Subclass PulseType to define custom pulse data (damage values, force vectors, status effect references) and a unique channel name.


Built-in Types

Debug_Pulse

Test and debug pulse for verifying pulsor setups during development.

FieldTypeId
TypeId{123D83FD-027C-4DA4-B44B-3E0520420E44}

Use Debug_Reactor on the receiving entity to verify the pulse is arriving correctly.


Destruct_Pulse

Destruction pulse — triggers destructible reactions on receiving entities.

FieldTypeId
TypeId{98EC44DA-C838-4A44-A37A-FA1A502A506B}

Pair with Destructable_Reactor on entities that should respond to destruction events.


Creating Custom Pulse Types

Use the ClassWizard PulsorPulse template to scaffold a new PulseType subclass with boilerplate already in place — see GS_Interaction Templates. Supply type_display_name and type_category input vars to control how the type appears in the editor dropdown.

To create a custom pulse type manually:

  1. Create a class extending PulseType with a unique RTTI TypeId.
  2. Override GetChannel() to return a unique channel name string.
  3. Add any data fields your pulse carries (damage values, force vectors, status effect references).
  4. Reflect the class using O3DE’s SerializeContext and EditContext. The system discovers the type automatically via EnumerateDerived — no registration step required.
  5. Configure PulsorComponent instances to emit your custom pulse type.

Keep the channel string consistent between your pulse type and the reactor types that should receive it.


See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.1.2 - Reactor Types

Built-in reactor types for the Pulsor interaction system.

Reactor types define how a PulseReactorComponent responds to incoming pulses. Each reactor type is a data class extending the abstract ReactorType base. The PulseReactorComponent holds an array of reactor types and processes all of them when a matching pulse arrives.

For usage guides and setup examples, see The Basics: GS_Interaction.


Component

PulseReactorComponent

Pulse Reactor component in the O3DE Inspector

Owns and processes reactor types when pulses arrive from PulsorComponent instances. Extends AZ::Component.

Bus: PulseReactorRequestBus (ById, Multiple)

FieldTypeDescription
reactorTypesvector<ReactorType*>Reactor types evaluated and executed when a pulse is received.
MethodReturnsDescription
IsReactorboolReturns true if this component has active reactor types. The PulsorComponent checks this before calling ReceivePulses.
ReceivePulsesvoidProcesses incoming pulse events. Iterates all reactor types and calls React() on types whose channel matches the incoming pulse.

The PulsorComponent queries IsReactor() first — only components that return true have ReceivePulses() called. This allows entities with no reactor types to be skipped without iterating all types.


Base Class

ReactorType

Abstract base class for all reactor types. Not a component — discovered automatically at startup via EnumerateDerived and reflected into the editor dropdown. ReactorType is a GS_Core TypeBase Strategy contract: subclass it, reflect the subclass, and it appears in the reactor-type picker. When a reactor processes a pulse, the component emits OnPulseReceived on the GS_Core PulseReactorEmissionBus.

Field / VirtualTypeDescription
TypeId{9B2C3D4E-5F6A-7B8C-9D0E-1F2A3B4C5D6E}RTTI identifier. Each subclass must define its own unique TypeId.
GetChannel()AZStd::stringReturns the channel string this reactor listens on. Matched against incoming pulse channel strings at runtime.
React(pulse, sourceEntity)voidOverride to implement the reaction behavior when a matching pulse is received.

Subclass ReactorType to define custom reaction behavior for a specific channel. The channel string must match the emitting PulseType’s channel exactly.


Built-in Types

Debug_Reactor

Test and debug reactor for verifying reactor setups during development. Logs a message when it receives a Debug_Pulse.

FieldTypeId
TypeId{38CC0EA0-0975-497A-B2E5-299F5B4222F7}

Destructable_Reactor

Handles destruction responses — processes Destruct_Pulse events on the receiving entity.

FieldTypeId
TypeId{47C9B959-2A9F-4E06-8187-E32DDA3449EC}

Add this to any entity that should respond to destruction pulses — props, breakables, or enemy characters.


Creating Custom Reactor Types

Use the ClassWizard PulsorReactor template to scaffold a new ReactorType subclass with boilerplate already in place — see GS_Interaction Templates. Supply the pulse_channel input var — the channel string is baked into the generated header at generation time.

To create a custom reactor type manually:

  1. Create a class extending ReactorType with a unique RTTI TypeId.
  2. Override GetChannel() to return the channel name this reactor listens on.
  3. Override React(pulse, sourceEntity) to implement the reaction logic.
  4. Reflect the class using O3DE’s SerializeContext and EditContext. The system discovers the type automatically via EnumerateDerived — no registration step required.
  5. Add the custom reactor type to PulseReactorComponent instances on entities that should respond.

The channel string in GetChannel() must exactly match the GetChannel() return value of the PulseType you want to receive.


See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.2 - Targeting

Target detection, selection, and cursor management — scanning, filtering, and selecting entities based on proximity and sensory fields.

The targeting system provides spatial awareness and entity selection for units and other entities. A TargetingHandler component serves as the central processor — it receives target registrations from sensory fields, maintains categorized target lists, and selects the best interact target based on proximity and type filtering. The system supports a cursor overlay for visual feedback and an input reader for triggering interactions.

For usage guides and setup examples, see The Basics: GS_Interaction.

Targeting Handler component in the O3DE Inspector Targeting Interaction Field component in the O3DE Inspector Interact Cursor component in the O3DE Inspector Interact Target component in the O3DE Inspector

 

Contents


Components

ComponentPurpose
GS_TargetingHandlerComponentCentral targeting processor. Receives target registrations, selects best interact target.
GS_TargetingInteractionFieldComponentPhysics trigger volume for proximity-based target detection.
GS_TargetComponentBase target marker. Makes an entity detectable by the targeting system.
GS_InteractTargetComponentSpecialized interact target extending GS_TargetComponent.
GS_CursorComponentCursor display and positioning for targeting feedback.
GS_CursorCanvasComponentUI canvas layer for cursor sprite rendering.
InteractInputReaderComponentInput reader that bridges interact input to trigger actions on the current target.

EBus Summary

Request Buses

BusAddressHandlerKey Methods
GS_TargetingHandlerRequestBusByIdMultipleRegisterTarget, UnregisterTarget, RegisterInteractionRangeTarget, UnregisterInteractionRangeTarget, GetInteractTarget
GS_TargetRequestBusByIdMultipleGetTargetSize, GetTargetOffset, GetTargetColour, GetTargetSprite
GS_CursorRequestBusSingleMultipleRegisterCursorCanvas, HideCursor, SetCursorOffset, SetCursorVisuals, SetCursorPosition
GS_CursorCanvasRequestBusByIdMultipleHideSprite, SetCursorSprite

Cross-Gem Contracts

Targeting observation is collected in the GS_Core interfaces layer:

BusAddressHandlerKey Events
GS_Core::TargetingEmissionBusByIdMultipleOnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteract

See Also

For component references:

For related resources:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.2.1 - Targeting Handler

Central targeting processor — receives target registrations from sensory fields, selects best interact target, and manages cursor feedback.

The GS_TargetingHandlerComponent is the central processor for the targeting system. It receives target registrations from sensory fields, maintains categorized lists of detected targets, and selects the best interact target based on proximity and type. It also manages cursor feedback and interaction range tracking.

For usage guides and setup examples, see The Basics: GS_Interaction.


The Targeting Handler sits on the same entity as the unit it serves (typically the player or an AI character). Sensory fields register and unregister targets as they enter and exit detection volumes. The handler evaluates all registered targets each tick to determine the closest interactable, which becomes the active interact target.

 

Contents


How It Works

Target Selection

Each tick, the handler runs ProcessClosestInteractable to evaluate all registered targets. It filters by type, checks interaction range, and selects the closest valid target. The result is emitted via OnUpdateInteractTarget on the GS_Core TargetingEmissionBus.

Interaction Range

A separate interaction range field provides a close-proximity subset of targets. Targets within this range are prioritized for interact selection. The range is driven by a dedicated GS_TargetingInteractionFieldComponent trigger volume.

Cursor Tracking

The handler updates the cursor position to follow the active interact target. Cursor visuals change based on target type (interact, item, unit, etc.).


API Reference

GS_TargetingHandlerRequestBus

Bus policy: ById, Multiple

MethodParametersReturnsDescription
RegisterTargetAZ::EntityId entityboolRegisters a target entity detected by a sensory field.
UnregisterTargetAZ::EntityId entityboolRemoves a target entity from the detection list.
RegisterInteractionRangeTargetAZ::EntityId entityboolRegisters a target within the close interaction range.
UnregisterInteractionRangeTargetAZ::EntityId entityboolRemoves a target from the interaction range list.
GetInteractTarget—AZ::EntityIdReturns the current best interact target.

Cross-Gem Contract: GS_Core::TargetingEmissionBus

Targeting events are observed through the GS_Core TargetingEmissionBus — the in-gem GS_TargetingHandlerNotificationBus was hoisted to GS_Core in the interfaces migration (the ScriptCanvas-facing name is preserved). The command/query surface (RegisterTarget / GetInteractTarget) stays on the in-gem GS_TargetingHandlerRequestBus.

Bus policy: ById, Multiple

EventParametersDescription
OnUpdateInteractTargetAZ::EntityId targetFired when the active interact target changes (invalid = none).
OnEnterStandby—Fired when the interactor enters targeting standby.
OnExitStandby—Fired when the interactor exits targeting standby.
OnInteractAZ::EntityId targetFired when the interactor performs an interaction on its target.

Virtual Methods

MethodParametersReturnsDescription
CheckForTick—voidCalled each tick to evaluate whether target processing should run.
ProcessClosestInteractable—voidDetermines the closest interactable target and sets it as the active interact target.

GS_CursorComponent

Manages cursor display and positioning for targeting feedback.

GS_CursorRequestBus

Bus policy: Single, Multiple

MethodParametersReturnsDescription
RegisterCursorCanvasAZ::EntityId canvasvoidRegisters the UI canvas for cursor rendering.
HideCursorbool hidevoidShows or hides the cursor.
SetCursorOffsetAZ::Vector2 offsetvoidSets the screen-space offset for cursor positioning.
SetCursorVisualsvisual datavoidConfigures cursor appearance.
SetCursorPositionAZ::Vector2 posvoidSets the cursor screen position directly.

GS_CursorCanvasComponent

UI canvas layer that renders the cursor sprite.

GS_CursorCanvasRequestBus

Bus policy: ById, Multiple

MethodParametersReturnsDescription
HideSpritebool hidevoidShows or hides the cursor sprite element.
SetCursorSpritesprite datavoidSets the cursor sprite image.

Extension Guide

The Targeting Handler is designed for extension via companion components. Add custom targeting logic by creating components that handle the GS_Core TargetingEmissionBus on the same entity. Override ProcessClosestInteractable for custom target selection algorithms.


Script Canvas Examples

Getting the current interact target:

Reacting to interact target changes:


See Also

For conceptual overviews and usage guides:

For component references:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.2.2 - Senses & Targeting Fields

Physics-based detection volumes for the targeting system — interaction fields and sensory detection.

Targeting fields are physics-based detection volumes that feed target registrations into the Targeting Handler. The GS_TargetingInteractionFieldComponent provides proximity detection for interact-range targets. Fields use their own collision layers and should be placed on child entities rather than the main unit capsule.

For usage guides and setup examples, see The Basics: GS_Interaction.


GS_TargetingInteractionFieldComponent

A physics trigger volume that detects targets entering and exiting the interaction range. Extends AZ::Component and PhysicsTriggeringVolume. Registers detected entities with the parent Targeting Handler.

API

MethodParametersReturnsDescription
TriggerEnterAZ::EntityId entityboolCalled when an entity enters the field. Registers the entity as an interaction-range target.
TriggerExitAZ::EntityId entityboolCalled when an entity exits the field. Unregisters the entity from the interaction-range list.

Setup

  1. Create a child entity under the unit entity that owns the Targeting Handler.
  2. Add a PhysX collider configured as a trigger shape.
  3. Add the GS_TargetingInteractionFieldComponent.
  4. Set the collider to use a dedicated interaction collision layer — do not share the unit’s physics layer.
  5. Point the field to the parent Targeting Handler entity.

Extension Guide

Custom sensory fields can be created by extending the base field pattern:

  1. Create a component that extends AZ::Component and PhysicsTriggeringVolume.
  2. On trigger enter/exit, call RegisterTarget / UnregisterTarget on the Targeting Handler via GS_TargetingHandlerRequestBus.
  3. Add custom filtering logic (line of sight, tag filtering, team affiliation) in your trigger callbacks.

See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.2.3 - Targets

Target component types — base target markers and specialized interact targets that make entities detectable by the targeting system.

Target components mark entities as detectable by the targeting system. The base GS_TargetComponent provides core target data (size, offset, visual properties). Specialized variants like GS_InteractTargetComponent add interaction-specific behavior.

For usage guides and setup examples, see The Basics: GS_Interaction.


GS_TargetComponent

Base target component. Makes an entity visible to the targeting system with configurable visual properties for cursor feedback.

GS_TargetRequestBus

Bus policy: ById, Multiple

MethodParametersReturnsDescription
GetTargetSize—floatReturns the target’s visual size for cursor scaling.
GetTargetOffset—AZ::Vector3Returns the world-space offset for cursor positioning above the target.
GetTargetColour—AZ::ColorReturns the target’s highlight colour.
GetTargetSprite—sprite refReturns the target’s cursor sprite override, if any.

GS_InteractTargetComponent

Extends GS_TargetComponent with interact-specific properties. Entities with this component can be selected as interact targets by the Targeting Handler and triggered via the Interact Input system.


Cross-Gem Target Types

Additional target types are conditionally compiled when GS_Interaction is available alongside other gems:

TypeSource GemConditionDescription
ItemTargetGS_Item#IF GS_INTERACTIONMakes item entities targetable for pickup and inspection.
UnitTargetGS_Unit#IF GS_INTERACTIONMakes unit entities targetable for interaction and AI awareness.

Extension Guide

Create custom target types by extending GS_TargetComponent:

  1. Create a new component class extending GS_TargetComponent.
  2. Override the target data methods (GetTargetSize, GetTargetOffset, etc.) to return your custom values.
  3. Add any additional data fields specific to your target type.
  4. The targeting system will automatically detect and categorize your custom targets when they enter sensory fields.

See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.2.4 - Interact Input Reader

For usage guides and setup examples, see The Basics: GS_Interaction.

Image showing the InteractInputReader component, as seen in the Entity Inspector.

Interact Input Reader Overview

Extends Input Reader.

This component is a standalone input detector utility to enable Interact Input firing outside of the GS_Unit Gem.

It intakes the Event Name for your chosen interaction input, as defined in your Input Profile and ignores all other input.

Functionality

Interact Input Reader needs to be put in the same entity as the Targeting Handler.

After firing the correct Input Event, the Interact Input Reader requests the current InteractTarget from the Targeting Handler. It then sends a DoInteractAction event, with a reference to its EntityId as caller, to the InteractTriggerSensor component that should be present on the Interact Target.

The World Trigger and Trigger Sensor system takes care of the rest.


Setting Up Your Interact Input Reader

Assure that the component is placed on the same Entity as the Targeting Handler.

Image showing the Interact Input Reader Component alongside the Targeting Handler Component.

Fill the Interact Event Name with the Event Name for your chosen interaction input, as defined in your Input Profile.

So long as the Input Profile that’s set in your GS_OptionsManager has that event set. The rest should work as is.


API

// OptionsManagerNotificationBus
void HandleStartup() override;

// Local Methods (Parent Input Reader Component Class)
void HandleFireInput(AZStd::string eventName, float value) override;

Extending Interact Input Reader


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.3 - World Triggers

Trigger sensor and world trigger system — event-driven world state changes via composable polymorphic type objects on two container components.

The World Trigger system pairs a TriggerSensorComponent (condition side) with a WorldTriggerComponent (response side) to create event-driven world state changes. Each component owns an array of polymorphic type objects — sensor types define what conditions to evaluate, trigger types define what happens when the condition passes. Any combination of types can be composed on a single entity without scripting.

For usage guides and setup examples, see The Basics: GS_Interaction.

 

Contents


Architecture

World Trigger Pattern Graph

Breakdown

World Triggers split conditions from responses using a polymorphic type/component pattern. The two container components sit on the entity; logic lives in the type objects they own.

PartWhat It Does
TriggerSensorComponentOwns arrays of TriggerSensorType objects (andConditions, orConditions). Evaluates all conditions and fires WorldTriggerRequestBus::Trigger on success. Automatically activates a PhysicsTriggerComponent if any sensor type requires it.
WorldTriggerComponentOwns an array of WorldTriggerType objects (triggerTypes). On Trigger(), calls Execute() on every type. On Reset(), calls OnReset(). On Activate(), calls OnComponentActivate() for startup initialization.

No scripting is required for standard patterns. Compose sensor types and trigger types in the editor to cover the majority of interactive world objects.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Trigger Sensors

The sensory side. TriggerSensorComponent holds arrays of TriggerSensorType objects that define evaluation logic — collisions, interactions, record conditions. Supports AND conditions (all must pass) and OR conditions (any must pass).

Trigger Sensors API


World Triggers

The response side. WorldTriggerComponent holds an array of WorldTriggerType objects that execute when triggered — toggling entities, setting records, changing stages, logging messages.

World Triggers API


Extension Guide

Use the ClassWizard templates to generate new sensor and trigger type classes — see GS_Interaction Templates:

  • TriggerSensorType — generates a new TriggerSensorType subclass with EvaluateAction(), EvaluateResetAction(), and lifecycle stubs.
  • WorldTriggerType — generates a new WorldTriggerType subclass with Execute(), OnReset(), and OnComponentActivate() stubs.

Custom Sensor Types

  1. Create a class extending TriggerSensorType.
  2. Override EvaluateAction() to define your condition logic.
  3. Override EvaluateResetAction() if your type supports reset evaluation.
  4. Override Activate(entityId) / Deactivate() for lifecycle setup.
  5. If your type needs a physics trigger volume, return true from NeedsPhysicsTrigger().
  6. For event-driven types, listen on a bus and call TriggerSensorRequestBus::DoAction() or DoResetAction() when the event fires.
  7. Reflect the class manually in GS_InteractionSystemComponent::Reflect().

Custom Trigger Types

  1. Create a class extending WorldTriggerType.
  2. Override Execute(entityId) — implement the world state change here.
  3. Override OnReset(entityId) to reverse or re-arm the effect.
  4. Override OnComponentActivate(entityId) for any initial state that must be set at startup.
  5. Reflect the class manually in GS_InteractionSystemComponent::Reflect().

See Also

For component references:

For related resources:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.3.1 - Trigger Sensors

Trigger sensor component and sensor types — condition evaluation objects for physics, interact, and record-based world trigger activation.

TriggerSensorComponent is the condition container of the World Trigger system. It owns arrays of TriggerSensorType objects that define evaluation logic — collisions, interactions, record checks. When conditions pass, it fires WorldTriggerRequestBus::Trigger on the same entity.

For usage guides and setup examples, see The Basics: GS_Interaction.


Container Component

TriggerSensorComponent

Trigger Sensor component in the O3DE Inspector

Owns and evaluates all sensor type objects. Not subclassed — extended by adding TriggerSensorType objects to its arrays.

Bus: TriggerSensorRequestBus (ById, Single)

FieldTypeDescription
andConditionsvector<TriggerSensorType*>All types must pass for the trigger to fire.
orConditionsvector<TriggerSensorType*>Any type passing is sufficient to fire.
MethodReturnsDescription
DoActionvoidEvaluates all conditions. On pass, fires WorldTriggerRequestBus::Trigger and emits OnTriggered on the GS_Core TriggerSensorEmissionBus.
DoResetActionvoidEvaluates reset conditions and, on pass, emits OnReset on the GS_Core TriggerSensorEmissionBus.

On Activate, automatically activates a PhysicsTriggerComponent on the entity if any sensor type returns true from NeedsPhysicsTrigger().


Base Type Class

TriggerSensorType

Abstract base for all sensor evaluation logic. Not a component — reflected manually in GS_InteractionSystemComponent::Reflect(). Subclass this to add new condition types.

VirtualReturnsDescription
Activate(entityId)voidCalled when the owning component activates. Connect to buses here.
Deactivate()voidCalled when the owning component deactivates. Disconnect from buses here.
EvaluateAction()boolReturns true if the condition is currently satisfied.
EvaluateResetAction()boolReturns true if the reset condition is satisfied. Returns true by default.
NeedsPhysicsTrigger()boolReturn true to signal that a PhysicsTriggerComponent should be activated. Returns false by default.
OnTriggerEnter(entity)voidCalled by the physics trigger when an entity enters the volume.
OnTriggerExit(entity)voidCalled by the physics trigger when an entity exits the volume.

Stores m_ownerEntityId for calling back to TriggerSensorRequestBus::DoAction() or DoResetAction() from event-driven types.


Built-in Sensor Types

SensorType_Collider

Physics overlap sensor. Returns NeedsPhysicsTrigger() = true, causing the owning component to activate a PhysicsTriggerComponent.

  • OnTriggerEnter stores the entering entity.
  • OnTriggerExit clears the stored entity.
  • EvaluateAction() returns true while a valid entity is present.
  • EvaluateResetAction() returns true when no entity is present.

SensorType_Record

Fires when a named save record changes to a configured value. Extends RecordKeeperNotificationBus — connects on Activate and fires DoAction automatically when the matching record changes.

  • EvaluateAction() calls GetRecord and compares against recordValue.

→ Record Sensor Type Reference

FieldDescription
recordNameName of the record to watch.
recordValueValue the record must reach to fire.

SensorType_Interact

Fires when any unit interacts with this entity via the targeting system. Extends InteractTriggerSensorRequestBus.

  • On interact: stores lastCaller and calls DoAction.
  • EvaluateAction() returns true while lastCaller is a valid entity.

SensorType_PlayerInteract

Player-only variant of SensorType_Interact. Extends SensorType_Interact and adds a tag check — lastCaller must have the "Player" tag for the condition to pass.


Extension Guide

To create a custom sensor type:

  1. Create a class extending TriggerSensorType.
  2. Override EvaluateAction() to define when your condition is satisfied.
  3. Override Activate(entityId) / Deactivate() to connect and disconnect from any buses.
  4. For event-driven evaluation, listen on a bus and call TriggerSensorRequestBus::DoAction(m_ownerEntityId) when the event fires — the component will run the full evaluation pipeline.
  5. Return true from NeedsPhysicsTrigger() if your type needs a physics volume.
  6. Reflect the class in GS_InteractionSystemComponent::Reflect() — types are not components and are not registered via CreateDescriptor().

See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.3.1.1 - SensorType_Record

Record-based sensor type — fires when a named RecordKeeper entry reaches a configured value.

For usage guides and setup examples, see The Basics: GS_Interaction.


Overview

SensorType_Record is a TriggerSensorType that fires when a named save record is changed to a configured value. Add it to the andConditions or orConditions array on a TriggerSensorComponent.

Inherits from RecordKeeperNotificationBus — connects automatically on Activate and listens for record changes without polling. When a matching record change arrives, it calls TriggerSensorRequestBus::DoAction on the owning component, which runs the full condition evaluation pipeline.


Fields

FieldTypeDescription
recordNamestringName of the record to watch in the RecordKeeper.
recordValueintValue the record must reach for the condition to pass.

Evaluation

EvaluateAction() calls RecordKeeperRequestBus::GetRecord(recordName) and compares the result to recordValue. Returns true if they match.

The type self-triggers on record change events — you do not need to poll or script a check manually.


Setup

  1. Add a TriggerSensorComponent to your entity.
  2. Add a SensorType_Record entry to the andConditions or orConditions array.
  3. Set recordName to the record you want to watch.
  4. Set recordValue to the value that should fire the trigger.
  5. Add a WorldTriggerComponent with the desired WorldTriggerType objects to the same entity.

API

From TriggerSensorType:

//! Returns true if the watched record currently matches recordValue.
virtual bool EvaluateAction();

//! Connects to RecordKeeperNotificationBus on component activate.
virtual void Activate(AZ::EntityId entityId);

//! Disconnects from RecordKeeperNotificationBus on component deactivate.
virtual void Deactivate();

From RecordKeeperNotificationBus:

//! Called when any record changes. Fires DoAction if recordName and recordValue match.
virtual void RecordChanged(const AZStd::string& name, int value);

Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.3.2 - World Triggers

World trigger component and trigger types — response execution objects that change world state when a trigger sensor fires.

WorldTriggerComponent is the response container of the World Trigger system. It owns an array of WorldTriggerType objects that execute when triggered — toggling entities, setting records, changing stages, logging messages.

For usage guides and setup examples, see The Basics: GS_Interaction.


Container Component

WorldTriggerComponent

World Trigger component in the O3DE Inspector

Owns and drives all trigger type objects. Not subclassed — extended by adding WorldTriggerType objects to triggerTypes.

Bus: WorldTriggerRequestBus (ById)

FieldTypeDescription
triggerTypesvector<WorldTriggerType*>All types are executed on each trigger or reset event.
MethodDescription
Trigger()Calls Execute(entityId) on every type in triggerTypes.
Reset()Calls OnReset(entityId) on every type in triggerTypes.

On Activate(), calls OnComponentActivate(entityId) on every type — use this for initial state seeding (e.g. entity visibility setup before any trigger fires).


Base Type Class

WorldTriggerType

Abstract base for all trigger response logic. Not a component — reflected manually in GS_InteractionSystemComponent::Reflect(). Subclass this to add new response types.

VirtualParametersDescription
OnComponentActivate(entityId)AZ::EntityIdCalled at component activation for startup initialization.
Execute(entityId)AZ::EntityIdCalled on Trigger(). Implement the world state change here.
OnReset(entityId)AZ::EntityIdCalled on Reset(). Reverse or re-arm the effect here.

Built-in Trigger Types

Type ClassResponse
TriggerType_PrintLogPrints logMessage to the development log on Execute.
TriggerType_SetRecordSets recordName to recordProgress via the RecordKeeper on Execute.
TriggerType_ToggleEntitiesToggles toggleEntities[] active/inactive. OnComponentActivate seeds initial state from startActive. Execute and OnReset invert the state each call.
TriggerType_ChangeStageQueues StageManagerRequestBus::ChangeStageRequest(targetStageName, targetExitPoint) on the next tick via Execute.

Extension Guide

To create a custom trigger type:

  1. Create a class extending WorldTriggerType.
  2. Override Execute(entityId) — implement your world state change here.
  3. Override OnReset(entityId) for reversible or re-armable effects.
  4. Override OnComponentActivate(entityId) if your type needs to set up initial state at startup (e.g. TriggerType_ToggleEntities uses this to seed entity visibility).
  5. Reflect the class in GS_InteractionSystemComponent::Reflect() — types are not components and are not registered via CreateDescriptor().

See Also


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.4 - Templates

ClassWizard templates for GS_Interaction — Pulsor pulses, reactors, world triggers, and trigger sensors.

All GS_Interaction extension types are generated through the ClassWizard CLI. The wizard handles UUID generation, cmake file-list registration, and reflection automatically.

For usage guides and setup examples, see The Basics: GS_Interaction.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Pulsor Pulse

Template: PulsorPulse

Creates a Pulse — the sender side of the Pulsor system. A Pulse fires on a named channel and carries a typed payload. Any PulsorReactor on the same entity listening to the same channel will receive it.

Generated files:

  • Source/${Name}_Pulse.h/.cpp

CLI:

python ClassWizard.py --template PulsorPulse --gem <GemPath> --name <Name> \
    --input-var type_display_name="My Pulse" \
    --input-var type_category="Input"

Input vars:

VarTypeDefaultDescription
type_display_nametext${Name}Label shown in the Pulse type dropdown in the editor
type_categorytext(empty)Grouping category in the dropdown (e.g. Input, Physics, Timer)

Post-generation: Implement Fire() with the data payload your channel requires. Match the channel string to the Reactor that will receive it. Each Pulse/Reactor pair is one interaction channel — channels are string-matched.

See also: Pulsors — full pulsor architecture, built-in pulse types, and extension guide.


Pulsor Reactor

Template: PulsorReactor

Creates a Reactor — the receiver side of the Pulsor system. Listens on a specific named channel and executes a response when a matching Pulse fires on the same entity.

Generated files:

  • Source/${Name}_Reactor.h/.cpp

CLI:

python ClassWizard.py --template PulsorReactor --gem <GemPath> --name <Name> \
    --input-var pulse_channel=MyChannel \
    --input-var type_display_name="My Reactor" \
    --input-var type_category="Input"

Input vars:

VarTypeRequiredDescription
pulse_channeltextyesThe channel name this reactor subscribes to — must match the Pulse’s channel
type_display_nametextnoLabel shown in the Reactor type dropdown
type_categorytextnoGrouping category in the dropdown

Important: pulse_channel is required. The channel string is baked into the header at generation time. If you need to change it later, edit the m_channel field directly in the generated header.

Post-generation: Implement OnPulse(...) with the response logic. Multiple Reactor instances of different types can coexist on one entity, each listening to a different channel.

See also: Pulsors — full pulsor architecture, built-in reactor types, and extension guide.


World Trigger

Template: WorldTrigger

Creates a WorldTrigger component — a logical trigger in the world that fires a named event when activated. Used to start dialogue sequences, cutscenes, or other scripted events from gameplay code or Script Canvas.

Generated files:

  • Source/${Name}_WorldTrigger.h/.cpp

CLI:

python ClassWizard.py --template WorldTrigger --gem <GemPath> --name <Name>

Post-generation: Wire the trigger activation to whatever condition suits the design (physics overlap, input event, distance check, etc.). Override Trigger() — call the parent first to check channels and conditions. Override Reset() for reversible triggers.

See also: World Triggers — full world trigger architecture and extension guide.


Trigger Sensor

Template: TriggerSensor

Creates a TriggerSensor — the listening side of a trigger pair. Registers to receive named trigger events from a WorldTrigger and executes a response action.

Generated files:

  • Source/${Name}_TriggerSensor.h/.cpp

CLI:

python ClassWizard.py --template TriggerSensor --gem <GemPath> --name <Name>

Post-generation: Subscribe to the matching trigger event name in Activate(). Override EvaluateAction() to define your trigger condition. Override DoAction() — call the parent first, then add custom behavior if it succeeds. Stack sensors to react to the same trigger in different ways.

See also: Trigger Sensors — full trigger sensor reference.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.7.5 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Interaction.


Get GS_Interaction

GS_Interaction — Explore this gem on the product page and add it to your project.

4.8 - GS_Juice

Game feel and feedback motion system — GS_Motion extension with transform and material tracks for visual feedback effects.

GS_Juice provides motion-based visual feedback effects for game feel. It extends the GS_Motion system with feedback-specific track types — transform animation (position, scale, rotation) and material property animation (opacity, emissive, color). Effects are authored as .feedbackmotion data assets and played by FeedbackEmitter components on entities.

For usage guides and setup examples, see The Basics: GS_Juice.

 

Contents


Feedback System

The feedback system follows the standard GS_Motion domain extension pattern. FeedbackMotionTrack is the domain base class, with two concrete track types. FeedbackMotionAsset holds the track definitions. FeedbackMotion is the instance wrapper struct that manages the runtime lifecycle. FeedbackEmitter is the component that plays effects.

Component / TypePurpose
FeedbackEmitterComponent — plays a feedback motion on its entity or a target entity.
FeedbackMotionInstance wrapper struct — asset reference, proxies, runtime composite.
FeedbackMotionAssetData asset (.feedbackmotion) — holds vector<FeedbackMotionTrack*>.
FeedbackMotionTrackDomain base track — extends GS_Core::GS_MotionTrack.
FeedbackTransformTrackConcrete track — position (Vector2Gradient), scale (FloatGradient), rotation (FloatGradient).
FeedbackMaterialTrackConcrete track — opacity (FloatGradient), emissive (FloatGradient), color (ColorGradient).

Feedback API


PostProcessing (Planned)

Planned for post-processing feedback effects (screen distortion, color grading, vignette). Not yet implemented.


System Components

ComponentPurpose
GS_JuiceSystemComponentRuntime system component for GS_Juice.

Dependencies

  • GS_Core (required — provides GS_Motion base system)

Cross-Gem Contracts

GS_Juice’s feedback-track base and completion signals are collected in the GS_Core Interfaces layer:

ContractKindUsed for
FeedbackMotionTrackTypeBaseAuthor new feedback tracks — they appear in the asset-editor picker.
FeedbackMotionEmissionBusEmitOnFeedbackMotionComplete / OnFeedbackMotionLooped (addressed by the emitter entity).

See the Contract Reference for the full table.


Installation

  1. Enable the GS_Juice gem in your project configuration.
  2. Ensure GS_Core is also enabled.
  3. Create .feedbackmotion assets in the O3DE Asset Editor.
  4. Add FeedbackEmitter components to entities that need feedback effects.

See Also

For conceptual overviews and usage guides:

For sub-system references:

For related resources:


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

4.8.1 - Feedback System

Feedback motion tracks, FeedbackEmitter component, and FeedbackMotionAsset reference.

The Feedback System provides the concrete implementation of GS_Juice’s game feel effects. It includes the FeedbackEmitter component for playback, FeedbackMotionAsset for data storage, and two concrete track types for transform and material animation.

For usage guides and setup examples, see The Basics: GS_Juice.

FeedbackEmitter component in the O3DE Inspector

 

Contents


FeedbackEmitter

The FeedbackEmitter component plays a Feedback Motion on its entity or on a specified target entity.

Inspector Properties

PropertyTypeDescription
FeedbackMotionFeedbackMotionThe motion instance — holds the asset reference, proxy list, and runtime composite.
playOnActivateboolWhen true, the motion plays automatically when the entity activates.

API Reference

MethodParametersReturnsDescription
Play—voidPlays the feedback motion on the owning entity.
PlayOnTargetAZ::EntityId targetvoidPlays the feedback motion on a different entity.
Stop—voidStops the currently playing motion.

FeedbackMotionAsset

The data asset that defines a feedback effect. Created and edited in the O3DE Asset Editor with the .feedbackmotion extension. Extends GS_Core::GS_MotionAsset → AZ::Data::AssetData. Requires GS_AssetReflectionIncludes.h when reflecting — see Serialization Helpers.

Properties

PropertyTypeDescription
m_tracksvector<FeedbackMotionTrack*>The tracks in this motion.
motionNameAZStd::stringDisplay name for the motion.
loopboolWhether the motion loops.

Methods

MethodReturnsDescription
GetTrackInfosvector<GS_TrackInfo>Returns track UUID + label pairs for proxy sync.
CreateRuntimeCompositeGS_MotionComposite*Creates a deep-copy runtime instance of all tracks.

To understand how to author Feedback Motions, refer to Feedback: Authoring Feedback Motions


FeedbackTransformTrack

Animates entity transform properties. All fields use gradient types for full curve control.

FieldTypeWhat It Animates
PositionVector2GradientXY offset from origin (additive).
ScaleFloatGradientUniform scale factor over time.
RotationFloatGradientRotation angle over time.

The track captures the entity’s origin transform on Init() and applies offsets relative to it. Effects are additive — multiple transform tracks on the same entity stack correctly.


FeedbackMaterialTrack

Animates entity material properties via the render component.

FieldTypeWhat It Animates
OpacityFloatGradientMaterial opacity.
EmissiveFloatGradientEmissive intensity.
ColorColorGradientColor tint.

Adding Custom Tracks

Use the FeedbackMotionTrack ClassWizard template to generate a new world-space track — see GS_Juice Templates. The only manual step after generation is adding a Reflect(context) call in {$Gem}DataAssetSystemComponent.cpp. Once reflected, the new track type is discovered automatically and appears in the asset editor type picker.

Override Init(ownerEntityId) to cache entity context, and Update(easedProgress) to drive the target property via its bus.

void ${Custom}Track::Init(AZ::EntityId ownerEntity)
{
    // Always call the base first — it sets m_owner.
    GS_Juice::FeedbackMotionTrack::Init(ownerEntity);

    // Cache the entity's origin value so the track can apply additive offsets.
    // Example:
    //   AZ::TransformBus::EventResult(m_originPosition, ownerEntity, &AZ::TransformInterface::GetWorldTranslation);
    //   AZ::TransformBus::EventResult(m_originRotation, ownerEntity, &AZ::TransformInterface::GetWorldRotationQuaternion);
}

void ${Custom}Track::Update(float easedProgress)
{
    // Evaluate the gradient at the current eased progress and apply to the entity.
    // easedProgress is in [0, 1] and already passed through the track's CurveType.
    // Example:
    //   const AZ::Vector3 offset = valueGradient.Evaluate(easedProgress);
    //   AZ::TransformBus::Event(m_owner, &AZ::TransformInterface::SetWorldTranslation, m_originPosition + offset);
}

See Also

For conceptual overviews and usage guides:

For component references:

For related resources:


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

4.8.2 - Third Party Implementations

Integration guides for third-party feedback systems with GS_Juice.

This section will contain integration guides for connecting third-party game feel and feedback tools with the GS_Juice system.

For usage guides and setup examples, see The Basics: GS_Juice.


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

4.8.3 - Templates

ClassWizard templates for GS_Juice — custom feedback motion tracks for world-space game-feel effects.

All GS_Juice extension types are generated through the ClassWizard CLI. The wizard handles UUID generation and cmake file-list registration automatically.

For usage guides and setup examples, see The Basics: GS_Juice.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Feedback Motion Track

Template: FeedbackMotionTrack

Creates a custom animation track for GS_Juice feedback sequences. A FeedbackMotionTrack child animates a world-space entity property (transform, material, audio, particle, etc.) via O3DE buses over a normalised [0,1] eased progress value. Tracks are referenced from GS_FeedbackSequence data assets.

Generated files:

  • Include/${GemName}/GS_Feedback/MotionTracks/${Name}Track.h
  • Source/GS_Feedback/MotionTracks/${Name}Track.cpp

CLI:

python ClassWizard.py --template FeedbackMotionTrack --gem <GemPath> --name <Name>

Post-generation — manual registration required:

In GS_JuiceDataAssetSystemComponent.cpp, add:

#include <path/to/${Name}Track.h>
// inside Reflect(context):
${Name}Track::Reflect(context);

Extensibility: Same polymorphic pattern as UiMotionTrack — any number of track types, discovered automatically via EnumerateDerived. World-space tracks differ from UI tracks in that they act on entity buses (TransformBus, material buses, etc.) rather than LyShine element interfaces. Init(ownerEntityId) caches the entity context; Update(easedProgress) drives the property.

See also: Feedback — the full feedback system architecture, built-in track types, and domain extension pattern.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_Juice

GS_Juice — Explore this gem on the product page and add it to your project.

4.9 - GS_Item

Data-driven inventory containers and equipment slot management for collectible, usable, and equippable items in GS_Play projects.

GS_Item provides the inventory and equipment systems for GS_Play. It gives entities inventory containers that hold stacks of item data, and an equipment system that maps items to named slots on a character entity. Items are defined as data assets, making the full catalog editable without code changes. GS_Item is under active development — the API surface is evolving and additional item behaviours will be added in future releases.

For usage guides and setup examples, see The Basics: GS_Item.

 

Contents


Inventory

The inventory system manages ordered collections of item stacks on an entity. Stacks track item type, quantity, and metadata. The inventory component exposes add, remove, query, and transfer operations and broadcasts notifications when contents change.

AreaContents
Inventory ComponentPer-entity container that holds item stacks and manages quantity limits.
Item Data AssetsData-driven item definitions: display name, icon, stack limit, categories, and custom properties.
Stack OperationsAdd, remove, transfer, split, and query by item type or category.
NotificationsChange events broadcast when items are added, removed, or quantities updated.

Inventory API


Equipment

The equipment system maps item assets to named slots on a character or entity. Equipping an item occupies its designated slot and can trigger stat modifications via GS_RPStats integration. Unequipping returns the item to inventory or discards it based on configured policy.

AreaContents
Equipment ComponentPer-entity slot manager. Maps slot names to currently equipped item assets.
Slot DefinitionsNamed slot configuration (e.g. weapon_main, armour_chest, accessory_1) defined per entity type.
Equip / Unequip FlowValidates slot compatibility, optionally modifies stats via GS_RPStats, and notifies listeners.
NotificationsChange events broadcast when slots are filled, cleared, or swapped.

Equipment API


Installation

GS_Item is a standalone gem. GS_RPStats integration is optional but recommended for stat-modifying equipment.

  1. Enable GS_Item and GS_Core in your O3DE project’s gem list.
  2. Create item data assets in your project’s asset directory and populate them in the Editor.
  3. Add the inventory component to any entity (player, chest, NPC) that should hold items.
  4. Add the equipment component to character entities that support equippable slots.
  5. Configure slot names on the equipment component to match your game’s equipment schema.
  6. Optionally enable GS_RPStats to wire equipment stat modifiers into the character stat pipeline.

Note: GS_Item is under active development. Check the changelog for the latest additions before starting integration work.


See Also

For conceptual overviews and usage guides:

For sub-system references:

For related resources:


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

4.9.1 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Item.


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

4.9.2 - Equipment

For usage guides and setup examples, see The Basics: GS_Item.


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

4.9.3 - Inventory

For usage guides and setup examples, see The Basics: GS_Item.


Get GS_Item

GS_Item — Explore this gem on the product page and add it to your project.

4.10 - GS_Performer

Modular character rendering via skin slots, paper billboard performers, velocity-driven locomotion, head tracking, and audio babble integration.

GS_Performer is the character rendering and presentation gem for GS_Play. It provides a slot-based skinning system for composing modular character appearances at runtime, a paper (billboard) performer system for 2.5D and top-down projects, velocity-driven locomotion parameter hooks, head tracking, and audio babble. The gem depends on GS_Core and integrates optionally with EMotionFX and GS_Unit.

For usage guides and setup examples, see The Basics: GS_Performer.

 

Contents


Manager

The Performer Manager is a GS_Play manager that owns the global performer registry and coordinates skin slot configuration across the level.

ComponentPurpose
GS_PerformerManagerComponentGS_Play manager. Global performer registry. Coordinates skin slot profiles and performer lookups.

Performer API


Skin Slots

A slot-based system for composing character appearance from swappable actor meshes and material sets. Each slot holds an actor asset and material list. The handler component manages the full set of slots for a character and applies PerformerSkinSlotsConfigProfile presets.

Component / AssetPurpose
PerformerSkinSlotComponentIndividual skin slot. Holds one actor mesh and its material overrides. Addressed by slot ID.
SkinSlotHandlerComponentManages the full collection of skin slots for a character entity. Applies profile presets.
SkinSlotDataData structure holding actor asset and material asset list for a single slot.
PerformerSkinSlotsConfigProfileAsset class defining a named preset of skin slot assignments. Loaded and applied at runtime.

Skin Slot API


Paper Performer

Billboard-based character rendering for 2.5D and top-down games. The facing handler keeps sprite quads oriented toward the camera or a configurable facing target. A camera-aware variant (in GS_Complete) extends this with PhantomCam integration.

ComponentPurpose
PaperFacingHandlerBaseComponentAbstract base for billboard facing logic. Extend to implement custom facing strategies.
PaperFacingHandlerComponentConcrete paper-facing implementation. Orients the entity quad toward the active camera each tick.

Paper Performer API


Locomotion

Velocity-driven animation parameter hooks. The locomotion component samples entity velocity each tick and pushes values into the animation graph, driving blend trees without manual parameter management.

ComponentPurpose
VelocityLocomotionHookComponentReads entity velocity and writes locomotion parameters to the EMotionFX animation graph each tick.
PrefabAnimAssetsReloaderComponentHot-reloads prefab animation assets during development without requiring a full level restart.

Performer API


Head Tracking

Procedural head look-at targeting for performer entities. Drives bone orientation toward a world-space target using configurable angle limits and spring damping.

Head Tracking API


Babble

Audio babble synchronized to dialogue typewriter output. The Babble component generates procedural vocalisation tones keyed to speaker identity and tone configuration, complementing the Typewriter system in GS_Cinematics.

Babble API


Installation

GS_Performer requires GS_Core. EMotionFX and GS_Unit are optional integrations.

  1. Enable GS_Performer and GS_Core in your O3DE project’s gem list.
  2. Add GS_PerformerManagerComponent to your Game Manager prefab and include it in the Startup Managers list.
  3. For skin slots, add SkinSlotHandlerComponent to the character root entity, then add one PerformerSkinSlotComponent per modular slot.
  4. For paper performers, add PaperFacingHandlerComponent to sprite entities that should billboard toward the camera.
  5. For locomotion, add VelocityLocomotionHookComponent to entities with an EMotionFX actor and configure the parameter name bindings.

See Also

For conceptual overviews and usage guides:

For sub-system references:

For related resources:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.1 - Performer Manager & Skin Slots

Performer manager lifecycle, skin slot system for modular character appearance, and config profile assets.

The Performer Manager and Skin Slot system provide modular character appearance management at runtime. The manager handles global performer registration and lookup. Skin slots define individual mesh/material combinations that can be swapped independently, enabling runtime costume and equipment changes.

For usage guides and setup examples, see The Basics: GS_Performer.

 

Contents


GS_PerformerManagerComponent

Singleton manager extending GS_ManagerComponent. Handles global performer registration, coordinates skin slot configuration across the level, and provides performer lookup.

Bus: PerformerManagerRequestBus (Single, Single)


Skin Slot System

Skin Slot Configuration Profile in the O3DE Asset Editor

PerformerSkinSlotComponent

Individual skin slot component. Each slot holds one actor mesh asset and its material overrides. Addressed by slot ID.

Bus: PerformerSkinSlotRequestBus (ById, Single)

PropertyTypeDescription
Slot NameAZStd::stringIdentifier for this skin slot.
Skin DataSkinSlotDataActor asset and material list for this slot.

SkinSlotHandlerComponent

Manages the complete collection of skin slots for a character entity. Applies PerformerSkinSlotsConfigProfile presets to swap entire outfits at once.

Bus: SkinSlotHandlerRequestBus (ById, Single)


Data Types

TypeDescription
SkinSlotDataActor asset reference + material asset list for a single slot.
SkinSlotNameDataPairSlot name string + SkinSlotData pair for serialization.
PerformerSkinSlotsConfigProfileAsset class defining a named preset of skin slot assignments. Loaded and applied at runtime for outfit/costume changes.

VelocityLocomotionHookComponent

Velocity Locomotion Hook component in the O3DE Inspector

Reads entity velocity each tick and writes locomotion parameters (speed, direction) to the EMotionFX animation graph. Drives blend trees automatically without manual parameter management.

PropertyTypeDescription
Speed ParameterAZStd::stringThe EMotionFX parameter name to write speed values into.
Direction ParameterAZStd::stringThe EMotionFX parameter name to write direction values into.

PrefabAnimAssetsReloaderComponent

Hot-reloads prefab animation assets during development without requiring a full level restart.


Setup

  1. Add GS_PerformerManagerComponent to the Game Manager prefab and include it in the Startup Managers list.
  2. Add SkinSlotHandlerComponent to the character root entity.
  3. Add one PerformerSkinSlotComponent per modular slot (head, body, arms, legs, etc.) to child entities.
  4. Create PerformerSkinSlotsConfigProfile assets in the Asset Editor for each outfit configuration.
  5. For locomotion, add VelocityLocomotionHookComponent to entities with an EMotionFX actor.

Extension Guide

The Skin Slot system uses the companion component pattern. Custom slot behavior (equipment integration, visual effects on swap) should be added as companion components that listen to SkinSlotHandler bus events on the same entity.


See Also

For related component references:

For conceptual overviews and usage guides:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.2 - Performers

Billboard and 3D character entity types — PaperFacingHandlerComponent for 2.5D rendering and the Avatar pipeline for rigged EMotionFX characters.

The Performers sub-system defines the character entity types in GS_Performer. Each type encapsulates a complete rendering strategy — from the lightweight billboard paper performer to the full 3D avatar pipeline integrating EMotionFX, skin slots, and locomotion.

The performer type determines the fundamental visual structure of the character entity. Performer Features then layer onto it, independent of type.

For usage guides and setup examples, see The Basics: Performers.

 

Contents


Core Pattern

Each performer type is a self-contained entity configuration. The Paper Performer requires only sprite geometry and a facing handler component. The Avatar Performer drives an EMotionFX actor and composes with skin slots and the locomotion hook. Both types participate in Performer Manager registration and support the full Performer Features set.


Paper Performer

Billboard-based character rendering for 2.5D and top-down games. The facing handler orients the sprite quad toward the active camera or a configurable target each tick. The base component allows custom facing strategies through extension.

ComponentPurposeReference
PaperFacingHandlerBaseComponentAbstract base for billboard facing logic. Extend to implement custom facing strategies.Paper Performer
PaperFacingHandlerComponentConcrete implementation. Orients the entity quad toward the active camera each tick.Paper Performer

Paper Performer API


Avatar Performer

The full 3D character pipeline. Drives a rigged EMotionFX actor with support for skin slot equipment, velocity locomotion parameter binding, and animation asset hot-reloading during development.

ComponentPurposeReference
VelocityLocomotionHookComponentReads entity velocity each tick and writes speed and direction blend parameters to the EMotionFX animation graph.Avatar Performer
PrefabAnimAssetsReloaderComponentHot-reloads prefab animation assets without a level restart. Development utility.Avatar Performer

Avatar Performer API


Extension Guide

Custom paper facing strategies are supported by extending PaperFacingHandlerBaseComponent. Override the tick method to compute any desired facing rotation and apply it to the entity transform each frame. See Paper Performer API for the full extension walkthrough.


See Also

For conceptual overviews and usage guides:

For related component references:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.2.1 - Avatar Performer

How to work with GS_Play avatar performers.

Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.2.2 - Paper Performer

Billboard-based character rendering — paper facing handlers that orient sprite quads toward the camera.

The Paper Performer system provides billboard-based character rendering for 2.5D and top-down games. Facing handler components keep sprite quads oriented toward the camera or a configurable facing target each tick.

For usage guides and setup examples, see The Basics: GS_Performer.

 

Contents


PaperFacingHandlerBaseComponent

Abstract base component for billboard facing logic. Extend this to implement custom facing strategies (face camera, face movement direction, face target entity).


PaperFacingHandlerComponent

Concrete paper-facing implementation. Orients the entity’s quad toward the active camera each tick. Suitable for standard 2.5D billboard characters.

PropertyTypeDescription
Face ModeenumHow the billboard faces: toward camera, along movement direction, or toward a target.

Camera-Aware Variant

A camera-aware paper facing handler is provided by GS_Complete as a cross-gem component (PhantomCam + Performer):

ComponentGemsDescription
CamCorePaperFacingHandlerComponentPerformer + PhantomCamUses CamCore notifications to adjust paper performer facing direction relative to the active phantom camera.

See Utility: Angles Helper for details.


Extension Guide

Create custom facing strategies by extending PaperFacingHandlerBaseComponent:

  1. Create a class extending PaperFacingHandlerBaseComponent.
  2. Override the tick/update method to compute your desired facing rotation.
  3. Apply the rotation to the entity transform each frame.

See Also

For related component references:

For conceptual overviews and usage guides:

For related resources:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.3 - Performer Features

Universal capabilities for any performer entity — procedural head tracking, typewriter-synchronized babble, and mesh swapping.

Performer Features are components that apply to any performer entity, independent of performer type. They represent capabilities that layer onto the character rather than define it — procedural bone targeting via head tracking, typewriter-synchronized vocalization via babble, and runtime mesh swapping.

The performer type (Paper or Avatar) defines the character’s rendering pipeline. Performer Features extend what that character can do within it.

For usage guides and setup examples, see The Basics: Performer Features.

 

Contents


Head Tracking

Procedural look-at targeting for performer bones. Drives bone orientation toward a world-space target each tick using configurable angle limits and spring damping, enabling characters to naturally track targets, speakers, or points of interest.

ComponentPurposeReference
Head Tracking ComponentReads a target position each tick and computes bone rotation within clamped horizontal and vertical limits, smoothed by spring damping.Head Tracking

Head Tracking API


Babble

Procedural vocalization tones synchronized to Typewriter text output. Fires audio tone events on each character reveal, keyed to speaker identity, creating the characteristic character-voice effect.

ComponentPurposeReference
BabbleComponentGenerates babble tones on OnTypeFired events. Returns BabbleToneEvent for audio playback. Speaker mapped via SpeakerBabbleEvents.Babble

Babble API


Mesh Swap

Runtime mesh and material swapping for performer entities. Provides lightweight visual variation without the full slot-based equipment system.

Mesh Swap API


See Also

For conceptual overviews and usage guides:

For related component references:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.3.1 - Locomotion

Velocity-driven animation hooks and prefab animation asset reloading for performer entities.

The Locomotion system bridges entity movement to animation. The VelocityLocomotionHookComponent samples entity velocity each tick and pushes values into the EMotionFX animation graph, driving blend trees without manual parameter management. The PrefabAnimAssetsReloaderComponent supports hot-reloading animation assets during development.

For usage guides and setup examples, see The Basics: GS_Performer.

 

Contents


VelocityLocomotionHookComponent

Reads the entity’s current velocity each tick and writes locomotion parameters (speed, direction) to the EMotionFX animation graph. This drives blend trees automatically — characters walk, run, and idle based on their actual movement speed.

PropertyTypeDescription
Speed ParameterAZStd::stringThe EMotionFX parameter name to write speed values into.
Direction ParameterAZStd::stringThe EMotionFX parameter name to write direction values into.

PrefabAnimAssetsReloaderComponent

Hot-reloads prefab animation assets during development without requiring a full level restart. Monitors animation asset files for changes and reapplies them to the entity’s EMotionFX actor.


Setup

  1. Add VelocityLocomotionHookComponent to an entity with an EMotionFX actor and a movement system (GS_Unit mover or physics rigidbody).
  2. Configure the speed and direction parameter names to match your EMotionFX blend tree parameters.
  3. Add PrefabAnimAssetsReloaderComponent during development for faster animation iteration.

See Also

For related component references:

For conceptual overviews and usage guides:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.3.2 - Head Tracking

Procedural head look-at targeting — drives bone orientation toward a world-space target with configurable angle limits and damping.

The Head Tracking system provides procedural head look-at targeting for performer entities. It drives bone orientation toward a world-space target using configurable angle limits and spring damping, enabling characters to naturally track targets, speakers, or points of interest.

For usage guides and setup examples, see The Basics: GS_Performer.

 

Contents


How It Works

The head tracking component reads the target position each tick and computes the desired bone rotation to face it. The rotation is clamped to configurable angle limits (horizontal and vertical) and smoothed via spring damping to avoid snapping.


Setup

  1. Add the head tracking component to an entity with an EMotionFX actor.
  2. Configure the target bone (typically the head or neck bone).
  3. Set angle limits for horizontal and vertical rotation.
  4. Configure spring damping parameters for smooth tracking.
  5. Set the look-at target via code or companion component.

See Also

For related component references:

For conceptual overviews and usage guides:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.3.3 - Babble

Audio babble synchronized to dialogue typewriter output — procedural vocalization tones keyed to speaker identity.

The Babble system generates procedural vocalization tones synchronized to the dialogue Typewriter output. Each speaker can have unique babble tone events that fire with each character reveal, creating the characteristic “character voice” effect used in games like Animal Crossing or Undertale.

For usage guides and setup examples, see The Basics: GS_Performer.

Babble component in the O3DE Inspector

 

Contents


BabbleComponent

Bus: BabbleRequestBus (ById)

MethodParametersReturnsDescription
GetBabbleEvent—BabbleToneEventReturns the current babble tone event for audio playback.

Data Types

TypeDescription
BabbleToneEventAudio event configuration for a single babble tone.
SpeakerBabbleEventsCollection of babble events mapped to a specific speaker/actor.

How It Works

  1. The TypewriterComponent (GS_Cinematics) reveals text character by character.
  2. On each OnTypeFired notification, the BabbleComponent triggers its configured babble tone.
  3. The tone varies based on the speaker’s SpeakerBabbleEvents configuration.
  4. The result is a procedural “voice” that matches the text reveal rhythm.

See Also

For related component references:

For conceptual overviews and usage guides:


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.10.4 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Performer.


Get GS_Performer

GS_Performer — Explore this gem on the product page and add it to your project.

4.11 - GS_PhantomCam

Priority-based virtual camera framework — composable stage pipeline, channel-aware instancing, blend profiles, influence fields, group targets, tug fields, and noise.

Rather than moving a single camera directly, GS_PhantomCam lets you place lightweight virtual cameras — “phantoms” — throughout the scene. Each phantom holds a complete camera state and a priority value. The Cam Core component drives the real camera to match whichever phantom currently has the highest effective priority. Transitions are animated by Blend Profiles that specify duration, easing, blend shape, and optional state inheritance. Influence Fields modify camera selection spatially or globally without changing base priorities.

Each phantom is not a fixed-behavior camera. It hosts a composable Body → Aim → Reposition additives → Noise additives stage pipeline — so the same component drives follow cams, orbital cams, tracking dollies, third-person shoulder cams, and cinematic stingers depending on which stages are slotted. The framework also runs a channel system that scales from arcade single-cam through 4-player split-screen co-op with no changes to the per-cam authoring surface.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

 

Contents


Architecture

Camera Blend Pattern Graph

Breakdown

Each phantom camera publishes a pose every tick by running its stage pipeline. The Cam Manager arbitrates priority across all registered phantoms inside a channel and notifies the channel’s Cam Core when the winner changes. The Cam Core then blends from the outgoing pose to the new one, optionally inheriting pose state across the transition and applying mid-blend interrupt correction when a new winner appears before the current blend completes.

StepWhat it means
1 — Per-tick pipelineEach phantom threads a CameraState through Body → Aim → Reposition additives → Noise additives → Finalize. The committed transform is published to the entity.
2 — Priority arbitrationThe Cam Manager re-evaluates base + sum(influences) per channel. Channel 0 covers all legacy / single-player flows.
3 — Dominance changeWhen a channel’s winner changes, the Cam Manager fires SettingNewCam (or SettingNewCamOnChannel when channel instancing is on).
4 — Profile queryCam Core calls GetBestBlend(fromCam, toCam) on the assigned Blend Profile. The matched entry returns blend time, easing curve, blend shape (Linear / Spherical / Cylindrical), an inherit-state flag, and a pivot source selector.
5 — State inheritanceIf inherit-state is set, the outgoing cam publishes a CamPoseSnapshot; the incoming cam’s Body adopts it through its own kinematic interpretation (orbit yaw/pitch, track param, lead-follow seed).
6 — InterpolationCam Core blends position, rotation, and FOV over the entry’s blend time using the configured easing + shape. If a new blend interrupts this one, a correction window pulls the new start curve back from the rendered TM.

E Indicates extensible classes and methods.

Patterns - Complete list of system patterns used in GS_Play.


Cam Manager

The Cam Manager is the singleton controller for the entire camera system. It extends GS_ManagerComponent and now owns six responsibilities:

  • Registration — Every phantom camera and Cam Core self-registers on activate; the Cam Manager holds authoritative per-channel tables.
  • Priority evaluation — When any camera’s priority changes, EvaluatePriority re-sorts each channel and determines its dominant camera. A dominance change fires SettingNewCam (instancing off) or SettingNewCamOnChannel (instancing on).
  • Influence routing — Priority influences from Influence Fields or gameplay code are routed to the channel of the affected target via AddCameraInfluence(sourceEntity, targetEntity, camName, influence). Influences whose target isn’t bound to any channel are silently dropped.
  • Target system — Per-channel target bindings via SetChannelTarget(channelId, target). Legacy single-arg SetTarget forwards to channel 0.
  • Group target registry — Named Group Targets self-register on activate so stages can resolve them by name.
  • Dispatch overrides & active main-view — Cross-channel cinematic dispatch (DispatchCamToCamCore) and engine main-view selection (SetActiveChannel / SetActiveCamCore) wrap O3DE’s MakeActiveView for orchestrated multi-view flows.
ComponentPurpose
GS_CamManagerComponentSingleton manager. Channel registries, priority arbitration, influence routing, group-target registry, dispatch, active-view selection, rig spawning.

Cam Manager API


Cam Core

The Cam Core is the per-frame driver that makes the real O3DE camera match the dominant phantom for its channel. It lives on the main camera entity, which must be a child entity of the Cam Manager entity (or of a rig-prefab root in Tier 3) so it spawns and despawns with the camera system.

Every frame, when locked to a phantom, the Cam Core parents the main camera to that phantom entity and reads its position, rotation, and FOV directly. During a blend transition:

  1. The Cam Core receives SettingNewCam (or SettingNewCamOnChannel) from the Cam Manager.
  2. It queries the assigned Blend Profile for the best matching entry between the outgoing and incoming cameras.
  3. The matched entry returns duration, easing, blend shape, an inherit-state flag, and a pivot source. If no profile entry matches, the Cam Core falls back to its own default blend time, easing, and shape.
  4. If inherit-state is set, the outgoing cam’s Body publishes a CamPoseSnapshot and the incoming cam’s Body adopts it before the blend executes.
  5. Over the blend duration, position, rotation, and FOV are interpolated using the configured easing curve and shape (Linear / Spherical-around-pivot / Cylindrical-around-pivot).
  6. If a new blend starts mid-flight, a mid-blend correction window pulls the new start curve back from the rendered TM so the camera does not snap.
  7. On completion, the Cam Core parents the main camera to the new dominant phantom, locking them together until the next transition.
ComponentPurpose
GS_CamCoreComponentCore camera driver. Blend execution (including interrupt correction), inheritance handoff site, drives the engine view, channel-aware via CamCoreRequestBus.

Cam Core API


Phantom Cameras

A Phantom Camera is a single entity component — GS_PhantomCameraComponent — that holds priority, lens, snap/focus state, target routing, and one stage pipeline slot authored by the user. It does not render anything. It registers with the Cam Manager on activation (auto-routing to the correct channel via its ChannelStampComponent ancestor) and publishes a per-tick pose from its Body + Aim + Additive stages.

Where previous versions of GS_PhantomCam shipped separate components per behavior (clamped-look, static-orbit, track, etc.), all of those behaviors are now stage variants layered on top of the single base component. Authors pick a Body stage, an Aim stage, and zero-or-more Additive stages from the editor’s type-picker.

ComponentPurpose
GS_PhantomCameraComponentBase virtual camera. Carries the stage pipeline slot, priority, target routing, lens, snap/focus/blendingOut state.
AlwaysFaceCameraComponentBillboard utility — keeps an entity facing the active camera. Not a camera type itself.

Phantom Cameras API

Retired components. StaticOrbit_PhantomCamComponent, ClampedLook_PhantomCamComponent, and Track_PhantomCamComponent no longer exist as separate components. Their behavior is now provided by Body / Aim stage variants. Legacy URLs redirect to the matching stage docs.


Stage Pipeline

Every phantom camera runs the same fixed pipeline per tick:

Body → Aim → Reposition additives → Noise additives → Finalize
  • Body writes state.position. Examples: DefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBody.
  • Aim writes state.rotation. Examples: DefaultAim, ClampedLookAim. (Other gems — notably gs_performer — register additional aim variants.)
  • Reposition additives correct the smoothed pose. Run BEFORE noise so noise can’t be undone. Examples: collision pushback, occlusion, tug listeners.
  • Noise additives perturb the final pose. Examples: PerlinNoise (continuous handheld shake), ImpulseNoise (event-triggered ADSR burst).
  • Finalize publishes the committed transform and lens.

Each stage owns its own damping. Stages are reflected polymorphic types — derived from IBodyStage / IAimStage / IAdditiveStage, not AZ::Component. The editor’s type-picker enumerates all registered derivations from the SerializeContext class hierarchy, so other gems can extend the catalog cleanly.

Stage Pipeline API


Channels & Instancing

A channel is one player viewpoint slot. Each channel owns its own rig (spawned from a prefab), its own Cam Core, its own target binding, its own priority table, and its own active influence set. Channel 0 is the implicit default for all single-player flows.

The system progressively discloses across three authoring tiers:

TierSetupBehavior
Tier 1 — ArcadeDrop CamCore + PhantomCams in the level. Call SetTarget(player).Legacy fallback. Cam Manager synthesizes channel 0 around level-placed cams.
Tier 2 — Standard single-playerSet m_primaryRigPrefab on the Cam Manager. Call SetTarget(player).Cam Manager spawns the rig at startup; the prefab’s CamCore + cams self-register to channel 0.
Tier 3 — Co-op / split-screen / cinematicToggle m_enableInstancedChannels = true. Fill m_channelConfigs.Per-channel arbitration, channel-aware influence routing, cross-channel dispatch, active-main toggling.
ConceptPurpose
Channel systemScope enum (Local / AllChannels / TrueUnique), ChannelStampComponent runtime plumbing, spawn lifecycle, cross-channel dispatch, active-view selection.

Channels & Instancing API

Pending — multi-view rendering. Multi-view rendering ships with the AttImage render-target binding work. Until then, the engine renders one channel at a time even when multiple are arbitrating internally.


Blend Profiles

Blend Profiles are data assets (.camblendprofile) that define how the Cam Core transitions between phantom cameras. Each profile contains a list of blend entries. Each entry specifies a From camera, a To camera, a blend duration, an easing curve, a blend shape, an inherit-state flag, and a pivot source. This allows every camera-to-camera transition in your project to have unique timing, feel, and geometry.

Entry Resolution Order

  1. Exact match — From name and To name both match.
  2. Any-to-specific — From is blank/“any”, To matches the incoming camera.
  3. Specific-to-any — From matches the outgoing camera, To is blank/“any”.
  4. Default fallback — The Cam Core’s own default blend settings.

Blend Entry Fields

FieldDescription
FromCameraOutgoing phantom camera entity name. Blank/“any” matches all.
ToCameraIncoming phantom camera entity name. Blank/“any” matches all.
BlendTimeTransition duration in seconds.
EasingTypeInterpolation curve applied during the blend. See Curves Utility.
BlendShapeLinear, Spherical-around-pivot, or Cylindrical-around-pivot. Spherical sweeps the camera through a great-circle arc around a pivot; cylindrical does the same but only on the yaw axis.
InheritStateWhen set, the outgoing cam publishes a CamPoseSnapshot and the incoming cam’s Body adopts it before the blend executes. See State Inheritance.
PivotSourceWhich point the shape uses as its pivot — target world position, body anchor, or look-at point.

Camera names correspond to entity names of the phantom camera entities in the scene.

AssetPurpose
GS_PhantomCamBlendProfileBlend settings asset. Per-pair transition duration, easing, shape, inherit-state, pivot source.

Blend Profiles API


Camera Influence Fields

Influence components modify the effective priority of phantom cameras without touching their base priority values. They work by calling AddCameraInfluence(sourceEntity, targetEntity, camName, influence) on the Cam Manager bus, identified by camera name. Multiple influences on the same camera stack additively.

The signature carries two entity arguments: the source (the field emitting the influence — used as the per-channel storage key so overlapping fields don’t collide) and the target (the entity that triggered the influence — used as the routing key to look up which channel the influence applies to). Influences whose target isn’t bound to any channel are silently dropped, which gives clean isolation between players in co-op.

GlobalCameraInfluenceComponent

Applies a constant priority modifier for its entire active lifetime. Useful for gameplay states that should always favor a particular camera — boosting a cutscene camera’s priority during a scripted sequence, for example.

Placement: Place GlobalCameraInfluenceComponent on the StageData entity. It activates and deactivates automatically with the stage, keeping camera influence scoped to the level that defines it.

CameraInfluenceFieldComponent

Applies a priority modifier only when the camera subject enters a defined spatial volume. Requires a PhysX Collider (set as trigger) on the same entity to define the volume. On entry, the influence is added; on exit, it is removed. See Physics Trigger Volume Utility for setup details.

Useful for level design — switching to an overhead camera when the player enters a room, or boosting a scenic camera in a vista area.

ComponentPurpose
GlobalCameraInfluenceComponentGlobal, constant priority modifier for a named camera. Place on the StageData entity.
CameraInfluenceFieldComponentSpatial priority modifier. Activates when the camera subject enters the trigger volume. Requires a PhysX trigger collider.

Camera Influence Fields API


Group Targets

A Group Target is a target entity whose world transform is the weighted centroid of a subject set. Phantom Cameras point at it like any other target. The Cam Manager hosts a named registry so stages can resolve a group entity by string name.

ComponentPurpose
GroupTargetComponentWeighted multi-subject focal entity. Centroid modes (weighted mean, bbox center, sphere center). Optional derived rotation. Physics-aware cadence.

Group Targets API


Tug Fields

Tug Fields are collider-driven spatial reposition sources. A volume in the world pulls the camera’s body position and/or aim rotation toward a configured destination while a proxy attached to the rig is in contact with the volume. The system is decoupled into three components — CameraTugVolumeComponent (the trigger volume), CameraTugSourceComponent (the falloff geometry and destination), and TugFieldProxyComponent (the cam-side receiver) — and is PhysX-paired through dedicated TugProxy / TugField collision layers so the engine itself filters contacts. No registry, no Cam Manager involvement.

Naming note. Tug-field m_channels are arbitrary string tags (“Cinematic”, “Combat”) that match volumes to listeners. They are not the same as instancing ChannelIds, which are integer per-player slots — see Channels & Instancing.

ComponentsPurpose
Tug FieldsVolume / source / proxy three-component model, tug listeners (TugAimListener, TugBodyListener), PhysX layer contract.

Tug Fields API


Noise & Impulse

Noise stages perturb the final pose in the Noise phase of the pipeline, autonomous from Body / Aim. Two stages ship: PerlinNoise (continuous handheld / idle shake driven by a .camnoiseprofile asset) and ImpulseNoise (event-triggered ADSR burst). Multiple noise stages compose freely on one cam.

AssetPurpose
CameraNoiseProfile.camnoiseprofile — layered Perlin noise definitions for the NoiseStage. Six per-axis layer lists (position xyz, rotation xyz).

Noise Profiles API


Orbit Profiles

CameraOrbitShape is a .camorbit asset that defines the shape of an orbital sweep — a power-bulge family that smoothly interpolates between a diamond (Roundness 0), a sphere (Roundness 0.5), and a cube (Roundness 1). Arc-length reparameterization keeps spatial speed constant as the camera traces the shape. Consumed by DynamicOrbitBody.

AssetPurpose
CameraOrbitShape.camorbit — power-bulge orbit shape with arc-length reparameterization.

Orbit Profiles API


State Inheritance

State inheritance lets the incoming cam in a transition start from a kinematic interpretation of the outgoing cam’s pose, rather than its own ideal. The protocol is universal: every Body stage may publish a CamPoseSnapshot (TryGetPoseSnapshot) and may adopt one (TryAdoptPoseSnapshot). Orbit back-derives yaw / pitch from the snapshot. Track projects to its spline. LeadingFollow seeds at the band-natural distance from the source’s facing.

Inheritance is opt-in per blend entry via the InheritState flag on the matched Blend Profile entry. The default is OFF; enable it only when the smoother handoff is worth the kinematic cost.

State Inheritance API


Camera Input Reader

The Camera Input Reader provides yaw / pitch input deltas to Body and Aim stages that drive their pose from player input (notably DynamicOrbitBody). It implements the OrbitInputProvider interface and integrates with PlayerControllerInputReader from gs_unit. It distinguishes sensitivity (raw input scale) from halflife (damping rate) and applies ResetPendingInput semantics so a stage that didn’t tick this frame doesn’t accumulate a stale input buffer.

ComponentPurpose
GS_CameraInputReaderComponentOrbit input provider. Sensitivity and halflife controls. Per-cam pending input drain.

Camera Input Reader API


Cross-Gem Contracts

The Cam Core exposes the camera as an observable service through the GS_Core Interfaces layer — the observable-service trio: learn a core exists (Emit), query it (Exchange), observe its updates (Emit):

ContractKindUsed for
CamCoreEmissionBusEmitUpdateCameraPosition (per-frame pose), OnCamCoreRegistered / OnCamCoreUnregistered (rig lifetime). Global broadcast.
CamCoreExchangeBusExchangeGetCamCore — resolve the active Cam Core’s entity.

The internal SetPhantomCam command stays an in-gem bus (Cam Manager → Cam Core), not a cross-gem contract. See the Contract Reference for details.


Installation

GS_PhantomCam requires only GS_Core.

  1. Enable GS_PhantomCam in Project Manager or project.json.
  2. Add GS_CamManagerComponent to a dedicated entity and register it in the Game Manager Startup Managers list. Save this entity as a prefab.
  3. Author a rig prefab containing GS_CamCoreComponent on a main O3DE camera entity plus the phantom cameras you want to ship with the rig. Assign this prefab to the Cam Manager’s m_primaryRigPrefab for Tier 2 (single-player) or to m_channelConfigs[i].m_rigPrefab for Tier 3 (per-channel). For Tier 1 / legacy, hand-place the CamCore as a child of the Cam Manager entity instead.
  4. Assign a Blend Profile asset to the Cam Core’s Blend Profile slot. Configure default blend time, easing, and shape on the component for fallback transitions.
  5. Place phantom camera entities (either in the rig prefab or in the level). Author their Body, Aim, and Additive stages from the type-picker.
  6. For spatial influence zones, add CameraInfluenceFieldComponent alongside a PhysX Collider (trigger) to define the volume.
  7. For global per-stage influence, add GlobalCameraInfluenceComponent to the StageData entity.
  8. For multi-player or split-screen, toggle m_enableInstancedChannels = true on the Cam Manager and populate m_channelConfigs. See Channels & Instancing.

For a full guided walkthrough, see the PhantomCam Set Up Guide.


See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.1 - Cam Manager

Camera system lifecycle controller — channel registry, per-channel priority arbitration, group target registry, influence routing, dispatch overrides, and active main-view selection.

The Cam Manager is the singleton controller for the GS_PhantomCam camera system. It extends GS_ManagerComponent and owns the full lifecycle of virtual cameras: per-channel registration of phantom cameras and Cam Cores, per-channel priority arbitration, channel-aware target binding, influence routing, the named Group Target registry, cross-channel cinematic dispatch, and engine main-view selection.

When a phantom camera activates, it walks transform ancestors looking for a ChannelStampComponent, identifies its channel, and self-registers with the Cam Manager. When priorities change — through direct calls, enable/disable toggling, or influence fields — the Cam Manager re-evaluates the affected channel(s) and notifies the channel’s Cam Core to begin blending toward the new winner.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

Cam Manager component in the O3DE Inspector

 

Contents


How It Works

Camera Registration

Every Phantom Camera registers with the Cam Manager on activation and unregisters on deactivation. The registration path depends on the cam’s CamChannelScope and whether it lives under a ChannelStampComponent ancestor — see Channels. For legacy / single-player flows (no stamp ancestor), the cam routes through channel 0 via the legacy RegisterPhantomCam method.

Priority Evaluation

When any camera’s priority changes, the Cam Manager calls EvaluatePriority(), which iterates every active channel. Each channel computes effective = base + sum(influences) per cam and picks the highest. A change in a channel’s winner triggers SettingNewCamOnChannel(channelId, targetCam) (when channel instancing is enabled) or the legacy SettingNewCam(targetCam) (when it is not). The channel’s Cam Core listens for this to begin its blend transition.

Camera Influences

Camera influences are named priority modifiers that shift a camera’s effective priority. They are added and removed through AddCameraInfluence(sourceEntity, targetEntity, camName, influence) and RemoveCameraInfluence(sourceEntity, targetEntity). The sourceEntity is the storage key (so multiple overlapping fields don’t collide); the targetEntity is the routing key — the Cam Manager looks up the channel via GetChannelForTarget(targetEntity). Influences whose target isn’t bound to any channel are silently dropped. See Camera Influence Fields.

Target Assignment

Each channel holds its own target binding. SetChannelTarget(channelId, target) sets the target for one channel; the legacy SetTarget(target) forwards to channel 0. Setting a valid target on a channel propagates it to every cam in the channel via SetCameraTarget and pushes a synchronous SnapCameraNow so the cam’s evaluated pose lands before downstream consumers query its transform. Clearing the target (invalid EntityId) does not push snap — this preserves the depossess-guard pose-hold behavior.


Authoring Tiers

The Cam Manager exposes a tiered author surface via progressive disclosure. Three top-level inspector fields control which tier is active:

TierConfigurationBehavior
Tier 1 — Standard single-playerm_primaryRigPrefab set; m_enableInstancedChannels = falseCam Manager spawns one rig from the primary prefab on startup; everything routes through channel 0. Most projects use this tier.
Tier 2 — Legacy level-placedNo m_primaryRigPrefab; m_enableInstancedChannels = falseThe CamCore is hand-placed in the level instead of spawned from a rig prefab. Legacy author flow — supported but not preferred.
Tier 3 — Multi-channelm_enableInstancedChannels = true; m_channelConfigs populatedPer-channel rig spawning, per-channel arbitration, channel-aware notifications, cross-channel cinematic dispatch. Split-screen and co-op projects use this tier.
Inspector fieldVisibilityPurpose
m_primaryRigPrefabAlwaysDefault rig prefab. Universal fallback for any channel with no override.
m_enableInstancedChannelsAlwaysMaster gate. When OFF, channel-aware fields are hidden and legacy bus signatures are routed. When ON, the full channel surface is live.
m_channelConfigsWhen master gate is ONArray of CameraChannelConfig rows.
m_activeChannelCountWhen master gate is ONLobby-driven cap. Channels configured beyond this count stay dormant at startup.

CameraChannelConfig per-row fields:

FieldInspector labelPurpose
m_channelNameChannel NameDisplay label (“P1”, “P2”). Not a lookup key.
m_policyChannel PolicyPerChannelInstance (default — Cam Manager spawns this channel’s rig) or LegacyLevelPlaced (channel uses level-placed cams, channel 0 only).
m_rigPrefabRig Override PrefabPer-channel rig. Leave empty to inherit m_primaryRigPrefab.
m_enabledByDefaultEnabled By DefaultWhether this channel spawns on startup.

Channels

A channel is one player viewpoint slot. Each channel owns its own rig, target binding, priority table, active influence set, and Cam Core. Channel 0 (DefaultChannelId) is the implicit default for legacy / single-player flows.

Channel addressing covers most of the Cam Manager’s runtime surface. The bus exposes paired methods — a legacy single-arg form that forwards to channel 0, and a channel-aware multi-arg form for Tier 3:

ConcernLegacyChannel-aware
Set the targetSetTarget(entity)SetChannelTarget(channelId, entity)
Get the targetGetTarget()GetChannelTarget(channelId)
Register a camRegisterPhantomCam(cam)RegisterPhantomCamForChannel(cam, stampEntity, channelId, token)
Register a CamCoreRegisterCamCore(camCore)RegisterCamCoreToChannel(camCore, stampEntity, channelId, token)
Dominance notificationSettingNewCam(cam)SettingNewCamOnChannel(channelId, cam)

Direct registration variants (RegisterPhantomCamDirect, RegisterPhantomCamShared) cover author-explicit CamChannelScope::TrueUnique cams that bypass the stamp-walk path. See Channels & Instancing for the full scope enum and stamp-walk semantics.

MethodUse
GetChannelForTarget(target)Reverse lookup — returns the channel an entity is bound to (or InvalidChannelId). Used by influence routing.
SetActiveChannelCount(count)Pre-startup lobby cap. Configured-but-extra channels stay dormant.
EnableChannel(channelId)Spawn a configured channel mid-session. Fires ChannelSpawned.
DisableChannel(channelId)Despawn a spawned channel. Fires ChannelDespawned.
GetChannelCamCore(channelId)Query the bound CamCore entity for a channel.

Group Target Registry

The Cam Manager is the single global authority for named Group Targets. A GroupTargetComponent self-registers on activate; stages with CamTargetMode::GroupTarget resolve the group’s entity by name.

MethodUse
RegisterGroupTarget(name, groupEntity)Called by GroupTargetComponent::Activate.
UnregisterGroupTarget(groupEntity)Called by GroupTargetComponent::Deactivate.
FindGroupTargetByName(name)Stages query this to resolve a group entity.
GetRegisteredGroupTargetNames()Returns the full list. Used by editor dropdowns and debug.

Dispatch Overrides

For cinematic / cross-channel scenarios — e.g. player 3’s wide cam should temporarily render on player 1’s Cam Core — the Cam Manager exposes a dispatch override. Channel arbitration still runs internally during dispatch (SettingNewCamOnChannel still fires with the arbitrated winner); only the physical Cam Core route is overridden.

MethodUse
FindCamForTarget(logicalName, targetEntity)Resolves a cam by logical (entity) name with multi-tier fallback: channel-of-target match → shared-cam scan → reserved AllChannels duplicate path.
DispatchCamToCamCore(cam, camCoreEntity)Force the Cam Core to render the specified cam. Persists until release. Fires OnCamCoreDispatched.
ReleaseCamCoreDispatch(camCoreEntity)Clear the override. Re-runs arbitration so the Cam Core routes back to its arbitrated winner. Fires OnCamCoreDispatchReleased.

Active Main View

Wraps O3DE’s AzFramework::Camera::CameraRequests::MakeActiveView so the channel system can orchestrate which Cam Core is the engine’s main view. Orthogonal to dispatch (what cam) and render-target binding (where pixels go).

MethodUse
SetActiveChannel(channelId)Resolves the channel’s Cam Core, delegates to SetActiveCamCore.
SetActiveCamCore(camCoreEntity)Calls MakeActiveView on the Cam Core. Idempotent.
GetActiveChannel()Reverse query — returns the channel that owns the engine’s active camera, or InvalidChannelId if the active cam isn’t channel-bound.
GetActiveCamCore()Direct passthrough to the engine.

Listeners receive OnActiveCameraChanged(channelId, camCoreEntity). External MakeActiveView calls made outside this API are not observed — subscribe to AzFramework::Camera::CameraNotificationBus directly if you need to detect engine-level changes.


Spawn Lifecycle

The Cam Manager’s OnStartupComplete hook (driven by the Game Manager) spawns all configured rigs before broadcasting HandleStartup so spawned cams settle into the same startup wave. Stage transitions (BeginLoadStage / LoadStageComplete) despawn and respawn rigs cleanly.

OnStartupComplete:
  if m_enableInstancedChannels == false:
    Tier 1: if m_primaryRigPrefab valid → spawn one rig for channel 0
    Tier 2: no spawn (assumes level-placed CamCore)
  else:
    Tier 3: iterate m_channelConfigs[0 .. min(size, m_activeChannelCount)]
      for each PerChannelInstance config with valid prefab and m_enabledByDefault:
        SpawnChannelRig(channelId, GetEffectiveRigPrefab(channelId))

SpawnChannelRig uses an EntitySpawnTicket with two callbacks:

  1. Pre-insertion — Cam Manager attaches a ChannelStampComponent to the spawn root and calls StampChannel(channelId). This bumps the channel’s stamp token and fires OnStamped to any subscribers.
  2. Completion — Entities activate. Cam Cores and PhantomCams walk ancestors, find the stamp, and self-register to the channel via token-aware methods. Cam Manager then walks activated entities into channel.m_rigSpawnedEntities, marks the channel active, folds shared TrueUnique cams into the channel’s priority table, validates a Cam Core registered, broadcasts ChannelSpawned(channelId, camCoreEntity), and re-runs EvaluatePriority so the arbitration result reaches the Cam Core.

Mid-session EnableChannel / DisableChannel reuse the same spawn / despawn paths and fire the same notifications.


Setup

  1. Add GS_CamManagerComponent to a dedicated entity (commonly inside a Cam Manager prefab).
  2. Register the manager prefab in the Game Manager Startup Managers list.
  3. Choose your tier:
    • Tier 1 — Assign a rig prefab (containing a Cam Core entity and your phantom cameras) to m_primaryRigPrefab. Leave m_enableInstancedChannels off.
    • Tier 2 — Place the Cam Core entity as a child of the Cam Manager entity directly. Leave m_primaryRigPrefab empty and m_enableInstancedChannels off.
    • Tier 3 — Toggle m_enableInstancedChannels on and populate m_channelConfigs with one row per supported player slot. Use m_primaryRigPrefab as the universal default and per-row Rig Override Prefab for asymmetric setups.
  4. Place or author phantom cameras with priorities, target modes, and stage compositions.

For a full walkthrough, see the PhantomCam Set Up Guide.


API Reference

Request Bus: CamManagerRequestBus

Commands sent to the Cam Manager. Global bus — single address, single handler. Extends GS_Core::ManagerBaseRequests.

Registration

MethodParametersReturnsDescription
RegisterPhantomCamAZ::EntityId camvoidLegacy registration. Routes the cam through channel 0. Used by cams without a ChannelStampComponent ancestor.
UnRegisterPhantomCamAZ::EntityId camvoidLegacy unregister.
RegisterPhantomCamForChannelAZ::EntityId cam, AZ::EntityId stampEntity, ChannelId claimedChannelId, AZ::u32 claimedTokenvoidStamp-aware registration. Cam Manager verifies the (channelId, token) against the live stamp on stampEntity before mutating channel tables — mismatched tokens are rejected.
UnRegisterPhantomCamFromChannelAZ::EntityId cam, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware unregister.
RegisterPhantomCamDirectAZ::EntityId cam, ChannelId channelIdvoidAuthor-explicit binding via CamChannelScope::TrueUnique + m_boundChannelId. Bypasses stamp-walk.
UnRegisterPhantomCamDirectAZ::EntityId cam, ChannelId channelIdvoidDirect unregister.
RegisterPhantomCamSharedAZ::EntityId camvoidAdds the cam to every active channel’s priority table. Used by CamChannelScope::TrueUnique + m_allChannelsShare.
UnRegisterPhantomCamSharedAZ::EntityId camvoidShared unregister.
RegisterCamCoreAZ::EntityId camCorevoidLegacy Cam Core registration (channel 0).
UnregisterCamCoreAZ::EntityId camCorevoidLegacy unregister.
RegisterCamCoreToChannelAZ::EntityId camCore, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware Cam Core registration.
UnregisterCamCoreFromChannelAZ::EntityId camCore, AZ::EntityId stampEntity, ChannelId, AZ::u32 tokenvoidStamp-aware unregister.

Priority and influence

MethodParametersReturnsDescription
ChangeCameraPriorityAZ::EntityId cam, AZ::u32 priorityvoidSets base priority. Cam Manager walks every channel where the cam is registered and re-evaluates.
AddCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntity, AZStd::string camName, AZ::u32 influencevoidChannel-routed. sourceEntity is the per-channel storage key; targetEntity is the routing key (channel resolved via GetChannelForTarget). Silently dropped if the target isn’t channel-bound.
RemoveCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntityvoidChannel-routed removal by (source, target) key.

Targets

MethodParametersReturnsDescription
SetTargetAZ::EntityId targetvoidLegacy. Forwards to SetChannelTarget(DefaultChannelId, target).
GetTarget—AZ::EntityIdLegacy. Returns channel 0’s target.
SetChannelTargetChannelId, AZ::EntityId targetvoidChannel-aware. Propagates the new target to every cam in the channel via SetCameraTarget, and pushes SnapCameraNow when binding to a valid target so evaluated pose lands before downstream queries. Clearing the target does not push snap.
GetChannelTargetChannelIdAZ::EntityIdReturns the channel’s current target binding.
GetChannelForTargetAZ::EntityId targetChannelIdReverse lookup. Returns InvalidChannelId if the entity isn’t bound.
GetCamManager—AZ::EntityIdReturns the Cam Manager’s own entity id.
GetPhantomCamAZStd::string camNameAZ::EntityIdResolves a cam by entity name.

Runtime channel management

MethodParametersReturnsDescription
SetActiveChannelCountAZ::u32 countvoidPre-startup only. Lobby-driven cap. Mid-session calls warn and are ignored — use EnableChannel / DisableChannel instead.
GetActiveChannelCount—AZ::u32Query.
EnableChannelChannelIdvoidSpawn a configured channel mid-session. Fires ChannelSpawned.
DisableChannelChannelIdvoidDespawn. Fires ChannelDespawned. No-op if not spawned.
GetChannelCamCoreChannelIdAZ::EntityIdReturns the bound Cam Core entity, invalid if none.

Dispatch and active view

MethodParametersReturnsDescription
FindCamForTargetAZStd::string logicalName, AZ::EntityId targetEntityAZ::EntityIdMulti-tier resolution. Channel-of-target first, then shared-cam scan.
DispatchCamToCamCoreAZ::EntityId cam, AZ::EntityId camCoreEntityvoidForce a Cam Core to render the specified cam. Persists until release.
ReleaseCamCoreDispatchAZ::EntityId camCoreEntityvoidClear override; restore arbitration.
SetActiveChannelChannelIdvoidResolves the channel’s Cam Core and delegates to SetActiveCamCore.
SetActiveCamCoreAZ::EntityId camCoreEntityvoidCalls MakeActiveView. Idempotent.
GetActiveChannel—ChannelIdReturns the channel owning the engine’s active camera.
GetActiveCamCore—AZ::EntityIdDirect passthrough to the engine.

Group target registry

MethodParametersReturnsDescription
RegisterGroupTargetAZStd::string name, AZ::EntityId groupEntityvoidCalled by GroupTargetComponent::Activate.
UnregisterGroupTargetAZ::EntityId groupEntityvoidCalled by GroupTargetComponent::Deactivate.
FindGroupTargetByNameAZStd::string nameAZ::EntityIdResolves a group entity for stages set to CamTargetMode::GroupTarget.
GetRegisteredGroupTargetNames—AZStd::vector<AZStd::string>Returns the full list.

Notification Bus: CamManagerNotificationBus

Events broadcast by the Cam Manager. Multiple handler bus — any number of components can subscribe. Extends GS_Core::ManagerBaseNotifications.

Legacy (instancing OFF)

EventDescription
EnableCameraSystemFired when the camera system is fully initialized.
DisableCameraSystemFired when the camera system is shutting down.
SettingNewCam(targetCam)A new phantom camera became dominant in channel 0. The Cam Core listens for this to begin blending.

Channel-aware (instancing ON)

EventDescription
SettingNewCamOnChannel(channelId, targetCam)Per-channel arbitration produced a new winner. Replaces the legacy SettingNewCam when instancing is on.
ChannelSpawned(channelId, camCoreEntity)A configured channel finished spawning AND its Cam Core self-registered. Listeners can treat this as a ready-to-render signal.
ChannelDespawned(channelId)Fires before entity teardown when a channel rig is despawned.
OnAllChannelsActivatedSharedCam(sharedCam)Edge-triggered: every active channel simultaneously selected the same shared TrueUnique cam. Typical cause: a shared cinematic cam paired with a Group Target at convergence radius.
OnCamCoreDispatched(camCoreEntity, dispatchedCam)A cross-channel dispatch override was applied.
OnCamCoreDispatchReleased(camCoreEntity)Dispatch override cleared.
OnActiveCameraChanged(channelId, camCoreEntity)The engine’s main-view active camera changed via this API. channelId == InvalidChannelId when the active cam isn’t channel-bound. External MakeActiveView calls outside this API are not observed.

Virtual Methods

Override these when extending the Cam Manager. Always call the base implementation.

MethodParametersReturnsDescription
EvaluatePriority()—voidTop-level arbitration entry point. Iterates every active channel. Override to add system-wide rules (gameplay-state gates, distance weighting, lock rules).
EvaluatePriorityForChannel(channelId)ChannelIdAZ::EntityIdPer-channel arbitration. Computes effective = base + sum(influences) and returns the winner. Override for per-channel custom logic in Tier 3 projects.

Extending the Cam Manager

Extend the Cam Manager to add custom priority logic, additional registration behavior, or project-specific camera selection rules. Extension is done in C++.

Header (.h)

#pragma once
#include <GS_PhantomCam/GS_CamManagerBus.h>
#include <Source/GS_CamManagerComponent.h>

namespace MyProject
{
    class MyCamManager : public GS_PhantomCam::GS_CamManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCamManager);

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

    protected:
        void EvaluatePriority() override;
        AZ::EntityId EvaluatePriorityForChannel(GS_PhantomCam::ChannelId channelId) override;
    };
}

Implementation (.cpp)

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

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

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

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

    void MyCamManager::EvaluatePriority()
    {
        // Call base to run standard per-channel arbitration.
        GS_CamManagerComponent::EvaluatePriority();

        // Custom system-wide logic here.
    }

    AZ::EntityId MyCamManager::EvaluatePriorityForChannel(GS_PhantomCam::ChannelId channelId)
    {
        // Apply per-channel rules before falling back to base arbitration.
        // Example: lock to a specific cam during a scripted sequence.
        // if (m_lockedCam.IsValid()) return m_lockedCam;
        return GS_CamManagerComponent::EvaluatePriorityForChannel(channelId);
    }
}

Script Canvas Examples

Enabling and disabling the camera system:

Setting the global camera target:

Reacting to a new dominant camera:


See Also

For related PhantomCam components:

For foundational systems:

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.1.1 - Channels & Instancing

PhantomCam channel system — per-player viewpoint slots, scope enum, ChannelStampComponent, rig spawn lifecycle, cross-channel dispatch, active main-view selection.

A channel is one player viewpoint slot. Each channel owns its own rig (spawned from a prefab), its own Cam Core, zero or more Phantom Cameras registered to it, a bound target, and its own priority table. Channel 0 (DefaultChannelId) is the implicit default for all legacy / single-player flows.

The channel system progressively discloses across three authoring tiers — Tier 1 single-cam, Tier 2 single-player rig, Tier 3 multi-channel. The per-cam authoring surface is identical across tiers; only the Cam Manager configuration changes.

Pending — multi-view rendering (I.9b). The channel system architecturally supports per-channel render targets via AttachmentImage assets, but the Atom RPI binding has not landed yet. Until it does, the engine renders one channel at a time even when multiple are arbitrating internally. The active main-view selector decides which channel reaches the engine framebuffer; see Active Main View.

 

Contents


Three Authoring Tiers

The Cam Manager exposes three top-level inspector fields that control the system tier. See Cam Manager — Authoring Tiers for the full author surface.

TierMaster GateChannel CountUse case
Tier 1 — Single playerm_enableInstancedChannels = false, m_primaryRigPrefab set1 (channel 0)Most projects. Spawn one rig from the primary prefab.
Tier 2 — Single player, level-placed CamCorem_enableInstancedChannels = false, no primary prefab1 (channel 0)Legacy. Author hand-places Cam Core in the level.
Tier 3 — Multi-channelm_enableInstancedChannels = true, m_channelConfigs populatedN (lobby-driven)Split-screen, per-player rigs, cross-channel cinematic dispatch.

The Master Gate is m_enableInstancedChannels. When OFF, channel-aware fields are hidden in the inspector and only the legacy bus signatures (SetTarget, SettingNewCam) route through. When ON, the full channel surface is live and channel-aware notifications (SettingNewCamOnChannel) replace the legacy ones.


ChannelId

using ChannelId = AZ::u32;
static constexpr ChannelId InvalidChannelId = static_cast<ChannelId>(-1);
static constexpr ChannelId DefaultChannelId = 0;

Channel ids are integers (0 … max − 1). The default channel id is 0. Legacy single-arg bus calls (SetTarget(target), RegisterPhantomCam(cam)) forward to channel 0.

Logical cam names — the entity names of phantom cameras inside a rig prefab — are scoped within their channel. The same name can appear in all four channels’ rigs without collision. Authors never type _i1 / _i2 suffixes.


Cam Channel Scope

Each Phantom Camera authors a CamChannelScope that decides how it participates in the channel system:

enum class CamChannelScope : AZ::u8 {
    Local       = 0,  // Default. Lives in its owning channel's scope.
    AllChannels = 1,  // In-rig natural duplicate — every rig instance carries one.
    TrueUnique  = 2,  // Exactly one instance total; bound or shared.
};
ScopeStamp-walk behaviorUse for
Local (default)Walks ancestors for a ChannelStampComponent. Found → registers to that channel via RegisterPhantomCamForChannel. No stamp → legacy RegisterPhantomCam (channel 0).Cams inside a rig prefab. Each spawned rig instance hosts its own copy.
AllChannelsSame stamp-walk as Local for the in-rig case. Out-of-rig (no stamp ancestor) warns and falls back to channel 0 (full runtime cloning is deferred).Per-player tailored broadcast cam inside a rig prefab.
TrueUniqueBypasses the stamp-walk. Sub-modes via the fields below.Hero-perspective or shared cinematic cams that need explicit binding.

TrueUnique sub-fields (visible only when scope is TrueUnique):

FieldVisibilityPurpose
m_boundChannelIdAlways when TrueUniqueExplicit channel binding for direct mode.
m_allChannelsShareWhen m_showAdvanced is trueShared mode — cam appears in every active channel’s priority table.
m_showAdvancedWhen TrueUniqueReveals advanced fields.

Author recipes:

  • Cam inside a rig prefab — leave as Local.
  • Per-player tailored broadcast cam in the level — AllChannels (in-rig); deferred for out-of-rig.
  • Hero-perspective cam for a specific player — TrueUnique + m_boundChannelId = N.
  • Shared cinematic collapse cam — TrueUnique + advanced + m_allChannelsShare = true. Pair with a Group Target to trigger OnAllChannelsActivatedSharedCam.

ChannelStampComponent

Pure runtime plumbing. Marks an entity (typically a rig root) with a (ChannelId, stampToken) pair. Phantom Camera and Cam Core components walk transform ancestors looking for this stamp at activate time to determine which channel they belong to.

Authored visibility: none. The stamp is reflected for serialization only — no EditContext block. The author never sees or sets it; the Cam Manager auto-attaches one during the spawn pre-insertion callback and calls StampChannel(channelId) to bump the token.

Why the token

m_stampToken bumps on every StampChannel(id) call (including idempotent re-stamps). The Cam Manager validates the supplied (channelId, token) against the live stamp on every registration call — mismatched tokens are rejected with a warning. This prevents stale messages from a previous stamp generation overwriting fresh bindings.

Buses

BusTypePurpose
ChannelStampRequestBusPer-entity, read-sideGetStampedChannelId, GetStampToken, IsStamped. Presence-tested via HasHandlers.
ChannelStampNotificationBusPer-entity, multi-handlerOnStamped(channelId, token) — every PhantomCam / CamCore beneath the stamp subscribes.

Static helper

static AZ::EntityId ChannelStampComponent::FindStampAncestor(AZ::EntityId start);

Walks transform ancestors via the request bus and returns the closest stamped entity (or invalid id if no stamp ancestor).


Rig Prefab Resolution

Authored rig prefab in the O3DE Editor

Three inspector fields on the Cam Manager control where each channel’s rig comes from:

FieldTierPurpose
m_primaryRigPrefabAlways visibleThe default rig. Tier 1 / 2 single-rig source. In Tier 3 it is the universal fallback for any channel whose own override is empty.
m_channelConfigs[i].m_rigPrefabTier 3 (master gate ON)Per-channel override. Inspector label: “Rig Override Prefab”. Leave empty to inherit the primary.
m_enableInstancedChannelsAlways visibleMaster gate.

The Cam Manager’s GetEffectiveRigPrefab(channelId) returns:

  1. m_channelConfigs[channelId].m_rigPrefab if set.
  2. Else m_primaryRigPrefab.
  3. Else invalid (the channel emits a warning at spawn time and stays inert).

Symmetric multi-channel co-op (all players use the same rig) sets the primary once; every channel inherits. Asymmetric setups (e.g. P1 third-person, P2 top-down) set per-channel overrides.


Spawn Pipeline

The Cam Manager spawns configured rigs on startup (driven by OnStartupComplete) and re-spawns them on stage transitions.

OnStartupComplete:
  if m_enableInstancedChannels == false:
    Tier 1: m_primaryRigPrefab valid → SpawnChannelRig(DefaultChannelId, m_primaryRigPrefab)
    Tier 2: no spawn (assumes level-placed CamCore)
  else:
    Tier 3: iterate m_channelConfigs[0 .. min(size, m_activeChannelCount)]
      for each PerChannelInstance config with m_enabledByDefault and valid prefab:
        SpawnChannelRig(channelId, GetEffectiveRigPrefab(channelId))

SpawnChannelRig uses an EntitySpawnTicket with two callbacks:

  1. Pre-insertion callback (entities created, not yet activated):
    • Cam Manager identifies the spawn root (first entity in the spawn container).
    • Attaches a ChannelStampComponent to it.
    • Calls StampChannel(channelId) — bumps the token, fires OnStamped to any subscribers.
  2. Completion callback (entities activated):
    • Cam Manager walks activated entities into channel.m_rigSpawnedEntities.
    • Marks the channel m_active = true.
    • Folds any TrueUnique-shared cams into the new channel’s priority table.
    • Validates that a Cam Core registered (warns if not).
    • Broadcasts ChannelSpawned(channelId, camCoreEntity).
    • Re-runs EvaluatePriority(channelId) so the channel’s arbitration result reaches its Cam Core.

Stage transitions

BeginLoadStage    → DespawnAllChannelRigs (lock arbitration first)
LoadStageComplete → SpawnAllConfiguredRigs BEFORE broadcasting HandleStartup
                    so fresh-stage cams come up in the startup wave.

Stamp-Walk Registration

When a Phantom Camera (or Cam Core) activates, it walks ancestors for a ChannelStampComponent and branches:

RegisterWithCamManager:
   branch on m_channelScope (PhantomCam only — CamCore is always Local-equivalent):

   ┌── Local:
   │      stamp = FindStampAncestor(myEntity)
   │      if stamp valid:
   │         RegisterPhantomCamForChannel(myEntity, stamp, channelId, token)
   │         registrationMode = StampAware
   │      else:
   │         RegisterPhantomCam(myEntity)             // legacy channel 0
   │         registrationMode = LegacyChan0
   │
   ├── AllChannels:
   │      stamp = FindStampAncestor(myEntity)
   │      if stamp valid:
   │         RegisterPhantomCamForChannel(myEntity, stamp, channelId, token)
   │         registrationMode = StampAware
   │      else:
   │         warn (out-of-rig cloning deferred)
   │         fall back to legacy channel 0
   │         registrationMode = LegacyChan0
   │
   └── TrueUnique:
          if m_boundChannelId valid AND !m_allChannelsShare:
             RegisterPhantomCamDirect(myEntity, m_boundChannelId)
             registrationMode = Direct
          elif !m_boundChannelId AND m_allChannelsShare:
             RegisterPhantomCamShared(myEntity)
             registrationMode = (Shared, stored as Direct for unregister routing)
          else:
             warn (fragile manual-control mode)
             stay unregistered
             registrationMode = None

registrationMode is cached on the component so UnregisterFromCamManager can route symmetrically.

OnStamped — mid-session re-stamp handling

If a rig is re-stamped mid-session (rare; typically only during stage transitions or hot-reload), the cam handles it:

OnStamped(newChannelId, newToken):
   Only acts in StampAware or None modes.
   In Direct or LegacyChan0 modes, the signal is ignored — author intent
   takes precedence over stamps.

   UnregisterFromCamManager (old binding)
   m_resolvedChannelId  = newChannelId
   m_resolvedStampToken = newToken
   RegisterWithCamManager (new binding)

The Cam Core mirrors the same pattern.


Shared Cams and Collapse Detection

RegisterPhantomCamShared(cam) adds the cam to every active channel’s priority table. The Cam Manager tracks the list in m_sharedCams. A shared cam:

  • Wins arbitration in every channel where it has the highest priority.
  • Gains focus from any channel selecting it.
  • Never DROPS focus via channel notifications — a shared cam losing in channel N doesn’t preclude winning in channel M.

Collapse detection

EvaluatePriority collects per-channel winners. If every active channel picked the same cam AND that cam is in m_sharedCams, the Cam Manager edge-triggers OnAllChannelsActivatedSharedCam(sharedCam) via m_lastSharedCollapsedCam — fires once on entry into the condition, and once on cam-A → cam-B shared-cam transitions.

Typical use case: a shared cinematic cam paired with a Group Target tracking all players. When players converge such that the shared cam wins in every channel, UI receives the collapse signal and can switch from split-screen layout to single-view layout.

No counterpart “uncollapsed” event exists — listeners dedupe locally if they care about exit.


Cross-Channel Dispatch

For cinematic / cross-channel scenarios — e.g. player 3’s wide cam should temporarily render on player 1’s Cam Core — the Cam Manager exposes a dispatch override.

MethodUse
FindCamForTarget(logicalName, targetEntity)Multi-tier resolution. (1) if targetEntity is channel-bound, look up logicalName in that channel’s m_logicalToEntity. (2) Walk m_sharedCams matching by entity name. (3) Reserved for AllChannels-duplicate fallback. Returns invalid EntityId on no match.
DispatchCamToCamCore(cam, camCoreEntity)Force the Cam Core to render the specified cam. Persists until release. Fires OnCamCoreDispatched.
ReleaseCamCoreDispatch(camCoreEntity)Clear the override; re-run arbitration so the Cam Core routes back to its arbitrated winner. Fires OnCamCoreDispatchReleased.

Channel arbitration still runs internally during dispatch. SettingNewCamOnChannel continues to fire with the arbitrated winner — only the physical Cam Core route is overridden. This means downstream listeners still see what “would have” arbitrated, useful for HUD or AI behavior that shouldn’t follow the cinematic override.


Active Main View

Wraps O3DE’s AzFramework::Camera::CameraRequests::MakeActiveView so the channel system can orchestrate “this player’s view is on main right now” without callers reaching into camera bus plumbing. Orthogonal to dispatch (what cam) and render-target binding (where pixels go).

MethodUse
SetActiveChannel(channelId)Resolves the channel’s Cam Core, delegates to SetActiveCamCore.
SetActiveCamCore(camCoreEntity)Calls MakeActiveView. Idempotent — silently no-ops if already active.
GetActiveChannel()Reverse query: returns the channel owning the engine’s active camera, or InvalidChannelId if the active cam isn’t channel-bound.
GetActiveCamCore()Direct passthrough to the engine.

Listeners receive OnActiveCameraChanged(channelId, camCoreEntity). channelId == InvalidChannelId when the active cam isn’t channel-bound (e.g. a direct SetActiveCamCore to an external entity). External MakeActiveView calls made outside this API are not observed — subscribe to AzFramework::Camera::CameraNotificationBus directly if you need to detect engine-level changes.


Runtime Channel API

MethodUse
SetActiveChannelCount(count)Lobby-driven cap. Pre-startup only. Mid-session calls warn and are ignored — use EnableChannel / DisableChannel instead.
GetActiveChannelCount()Query.
EnableChannel(channelId)Spawn a configured channel mid-session. Validates id in bounds, not already spawned, policy is PerChannelInstance, rig prefab assigned. Fires ChannelSpawned.
DisableChannel(channelId)Despawn. Fires ChannelDespawned. No-op if not spawned.
GetChannelCamCore(channelId)Returns the bound Cam Core EntityId, invalid if no Cam Core.

Intended lobby flow: author sets m_channelConfigs to N (primed max), pre-startup the lobby calls SetActiveChannelCount(actualPlayerCount), then OnStartupComplete spawns just actualPlayerCount rigs. Mid-session join / drop uses EnableChannel / DisableChannel.


Notifications

Channel-related events on CamManagerNotificationBus. See Cam Manager — Notifications for the full notification surface.

EventWhen
SettingNewCamOnChannel(channelId, targetCam)Per-channel arbitration produced a new winner. Replaces legacy SettingNewCam when instancing is ON.
ChannelSpawned(channelId, camCoreEntity)A configured channel finished spawning AND its Cam Core self-registered. Ready-to-render signal.
ChannelDespawned(channelId)Fires before entity teardown when a channel rig is despawned.
OnAllChannelsActivatedSharedCam(sharedCam)Edge-triggered. Every active channel simultaneously selected the same shared TrueUnique cam.
OnCamCoreDispatched(camCoreEntity, dispatchedCam)A cross-channel dispatch override was applied.
OnCamCoreDispatchReleased(camCoreEntity)Dispatch override cleared.
OnActiveCameraChanged(channelId, camCoreEntity)Engine main-view active camera changed via this API.

Tier 3 Author Workflow

  1. On the Cam Manager: toggle m_enableInstancedChannels = true.
  2. Set m_primaryRigPrefab to the project’s default rig (used as universal fallback).
  3. Populate m_channelConfigs with one entry per supported player slot:
    • Set m_channelName for inspector readability (“P1”, “P2”, …).
    • Optionally set m_rigPrefab (the “Rig Override Prefab”) — leave empty to inherit the primary.
    • Set m_enabledByDefault = true for slots that should spawn on startup.
  4. Optionally set m_activeChannelCount for the lobby cap, or call SetActiveChannelCount at runtime before startup.
  5. Per-player cams placed inside the rig prefab will auto-stamp on spawn — no per-cam authoring required for normal Local-scope cams.
  6. For shared cinematic cams (one cam to rule them all), set the cam’s m_channelScope = TrueUnique + m_showAdvanced = true + m_allChannelsShare = true.
  7. For author-explicit per-channel cams (rare), use m_channelScope = TrueUnique + m_boundChannelId = N + m_allChannelsShare = false.

For the basics-side walkthrough with screenshots and step-by-step recipes, see The Basics: Channels & Instancing.


Naming Collision Callout

Tug-field m_channels ≠ instancing ChannelId. Tug-field channels are arbitrary string tags (“Cinematic”, “Combat”) that match tug volumes to listeners — see Tug Fields. Instancing channels documented on this page are integer ChannelIds for per-player viewpoint routing. The two systems are unrelated despite the shared word. The system uses the same identifier name in the codebase for both; the docs make the distinction prominent here and in the tug-fields page.


See Also

For related PhantomCam pages:

For the basics-side walkthrough:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.2 - Cam Core

The runtime camera driver — applies phantom camera state to the real camera with blend interpolation, mid-blend interrupt correction, state inheritance handoff, and orbital blend shapes.

The Cam Core is the runtime driver of the GS_PhantomCam system. It lives on a camera entity (the main O3DE camera entity for single-player, or a per-channel Cam Core entity for multi-channel projects) and is responsible for one job: making the engine’s camera match the dominant Phantom Camera for its channel. Every frame, the Cam Core reads the target phantom camera’s position, rotation, and field of view, then applies those values to the actual camera entity — either instantly (when locked) or through a timed blend transition.

When the Cam Manager determines a new dominant phantom camera for the Cam Core’s channel, it fires a per-channel notification and routes the new cam to the Cam Core via SetPhantomCam. The Cam Core then looks up the best matching blend in the active Blend Profile, optionally inherits state from the outgoing cam, and begins interpolating toward the new phantom camera over the specified duration, easing curve, and blend shape. If a new transition starts before the current blend completes, a small correction window pulls the new blend back from the rendered TM so the cam does not snap. Once a blend completes, the main camera locks to the phantom camera as a child entity, matching its transforms exactly until the next transition.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

Cam Core component in the O3DE Inspector

 

Contents


How It Works

Blend Transitions

When a camera transition is triggered:

  1. The Cam Core receives SetPhantomCam(targetCam) from the Cam Manager, routed to its own entity address.
  2. It queries the assigned Blend Profile for the best matching blend entry between the outgoing and incoming cameras (by entity name).
  3. The matched entry returns duration, easing curve, blend shape (Linear / Spherical / Cylindrical), an inherit-state flag, and a pivot source. If no entry matches, the Cam Core falls back to its own defaults.
  4. If the entry’s inherit-state is set, the state inheritance handoff runs before the blend starts so the destination cam’s body adopts a kinematic interpretation of the outgoing cam’s pose.
  5. The outgoing cam’s m_blendingOut flag is set so it continues ticking through the transition (keeps the source pose live if the target is moving).
  6. Over the blend duration, the Cam Core interpolates position (using the chosen shape), rotation (slerp), and lens (FOV / near / far) from the outgoing state to the incoming state.
  7. If a new blend interrupts this one, the interrupt correction window pulls the new start curve back so the cam doesn’t snap.
  8. On completion, the Cam Core parents the main camera to the new dominant phantom, locking them together until the next transition.

Blend Profile Resolution

The Cam Core holds a reference to a GS_PhantomCamBlendProfile asset. When a transition occurs, it calls GetBestBlend(fromCam, toCam) on the profile to find the most specific matching entry. Resolution order:

  1. Exact match — From camera name to To camera name.
  2. Any-to-specific — “Any” to the To camera name.
  3. Specific-to-any — From camera name to “Any”.
  4. Default fallback — The Cam Core’s own default blend time, easing, and shape.

See Blend Profiles for full details.

Camera Locking

Once a blend completes, the main camera becomes a child of the dominant phantom camera entity. All position, rotation, and property updates from the phantom camera are reflected immediately on the real camera. This lock persists until the next transition begins.


Blend Shape and Pivot

Each blend entry specifies a shape that controls how position interpolates:

ShapeBehavior
LinearStraight-line lerp from source to destination. The default and the cheapest. No pivot used.
CylindricalSweeps through a yaw arc around a pivot, preserving the heading change as a rotation rather than a translation. Pitch and radius interpolate independently.
SphericalSweeps through a great-circle arc around a pivot. Both yaw and pitch animate around the pivot, producing an orbital flight path between source and destination.

The pivot point is resolved by the entry’s pivot source:

Pivot sourceResolution
SharedMidpoint of both cams’ published pivots. Falls back gracefully if either is missing.
SourceOutgoing cam’s pivot. Falls back to the incoming cam, then to Linear if neither publishes.
DestinationIncoming cam’s pivot. Falls back to the outgoing cam, then to Linear.

Cams publish their pivot as the pivotPos field on CamPoseSnapshot (see State Inheritance). Stages that don’t publish a pivot disqualify themselves from being the pivot source.

The math is implemented by the Orbital Solver Utility in GS_Core — BlendPositionAroundPivot(from, to, pivot, alpha, shape, axis).


State Inheritance Handoff

When a blend entry has m_inheritState = true, the Cam Core runs a handoff between the outgoing and incoming cams before starting the blend:

  1. Pull a CamPoseSnapshot from the outgoing cam via PhantomCameraRequests::TryGetPoseSnapshot.
  2. If the snapshot is valid, push it to the incoming cam via PhantomCameraRequests::TryAdoptPoseSnapshot.

The Get / Adopt calls forward to the cam’s Body stage. Bodies that don’t implement the protocol return false; the handoff is a silent no-op and the blend proceeds normally.

The handoff runs before StartBlend, so by the time the blend captures source and destination poses, the destination cam’s body has already adopted the seed pose. This makes inherited blends feel like a continuation of the outgoing cam’s motion rather than a clean start.

See State Inheritance for the full protocol and per-stage adoption semantics.


Interrupt Correction

When a blend A → B is in flight and a new blend A’ → C kicks off, snapping the cam’s source TM produces a perceptible velocity jolt — the cam visibly changes direction at the interrupt moment. The Cam Core’s mid-blend correction window prevents this.

Mechanism

  1. Capture the cam’s currently-rendered TM at the interrupt moment → m_interruptSnapshotTM.
  2. Compute T_natural = 1 - oldBlendFactor — the curve point on the new blend at which the cam’s velocity matches its current motion.
  3. Compute T_start = max(0, T_natural − m_interruptCorrection) — pull the new blend back by the correction window.
  4. Seed the new blend at curve point T_start.
  5. Over the window [T_start, T_natural], the rendered pose is a Y-blend between the snapshot and the new blend’s natural pose, with the Y factor sweeping 0 → 1.
  6. After T_natural, the Y-blend yields fully to the new blend; pure new-blend interpolation continues with live source / destination TMs.

The cam starts in the snapshot pose at the interrupt, ramps gracefully into the new trajectory across the correction window, and proceeds normally.

Window width

m_interruptCorrection is authored on the Cam Core, default 0.03 (3% of the new blend’s curve). Larger windows feel smoother but produce more visible lag in the handoff territory; smaller windows feel snappier but reintroduce the velocity jolt. Setting it to 0 degenerates to legacy hard-anchor behavior.


Channel Addressing

The Cam Core self-registers with the Cam Manager on activate. It walks transform ancestors looking for a ChannelStampComponent to determine its channel:

  • Stamp found — Registers via RegisterCamCoreToChannel(camCore, stampEntity, channelId, token). Listens at its own entity address on CamCoreRequestBus so the Cam Manager can route per-channel SetPhantomCam calls.
  • No stamp — Registers via the legacy RegisterCamCore. Routes through channel 0 in single-player projects.

If the Cam Core’s parent rig is re-stamped (rare; typically during stage transitions or hot-reload), the ChannelStampNotificationBus::OnStamped handler un-registers from the old channel and re-registers to the new one. The Cam Manager’s token validation rejects mismatched stamp tokens to prevent stale messages from overwriting fresh bindings.

One Cam Core per channel. Multi-channel projects (Tier 3) spawn one per active channel; single-player projects use a single Cam Core at channel 0.


Setup

  1. Add GS_CamCoreComponent to a camera entity. For Tier 1 / Tier 2 (single-player), this is the main O3DE camera entity, parented under the Cam Manager entity. For Tier 3 (multi-channel), include one Cam Core per rig inside the rig prefab.
  2. Create a Blend Profile data asset in the Asset Editor and assign it to the Cam Core’s Blend Profile slot.
  3. Configure default blend time, easing, and blend shape on the Cam Core for cases where no profile entry matches. See Curves Utility for available easing types.
  4. Optionally adjust Interrupt Correction (default 0.03) if your project has unusually short or long blends.

API Reference

Request Bus: CamCoreRequestBus

Internal command bus, Cam Manager → Cam Core. Per-entity — each Cam Core listens at its own entity id so the Cam Manager can route per-channel events; Broadcast hits every connected Cam Core (stage-transition global clear). This is not a cross-gem contract and is not script-reflected — external systems observe the camera through the two GS_Core contracts below.

MethodParametersReturnsDescription
SetPhantomCamAZ::EntityId targetCamvoidSets the phantom camera that the Cam Core should blend toward or lock to. Called by the Cam Manager when arbitration produces a new winner. Triggers the inheritance handoff and blend start. EntityId() clears it.

Cross-Gem Contract: CamCoreExchangeBus

The query half of the camera’s observable-service trio, housed in GS_Core. Addressed by the Cam Core’s EntityId; single provider.

MethodParametersReturnsDescription
GetCamCore—AZ::EntityIdResolves the Cam Core’s entity (late-joiner / poll for the current core). Was CamCoreRequestBus::GetCamCore before the interfaces migration.

Cross-Gem Contract: CamCoreEmissionBus

The emit half of the trio, housed in GS_Core. Global broadcast, multiple handlers — any number of components can subscribe without depending on GS_PhantomCam. Was CamCoreNotificationBus before the interfaces migration; the ScriptCanvas-facing name is preserved by GS_PhantomCam’s binder, so existing graphs keep resolving.

EventParametersDescription
UpdateCameraPositioncamPos, camFacing, deltaTimeFired each tick with the committed camera position, forward vector, and frame delta. Subscribe to react to camera movement in real time (audio, UI, gameplay readback).
OnCamCoreRegisteredAZ::EntityId camCoreA Cam Core came online (rig lifetime). Pair with GetCamCore to resolve it, then observe its updates.
OnCamCoreUnregisteredAZ::EntityId camCoreA Cam Core went away.

Inspector Fields

FieldDefaultPurpose
defaultBlendTypeCurveType::LinearEasing curve used when no Blend Profile entry matches.
defaultBlendTime1.0Fallback blend duration in seconds.
defaultBlendShapeBlendShape::LinearFallback blend shape when no profile entry matches.
activeBlendProfileAsset—The active .camblendprofile asset.
m_interruptCorrection0.03Width of the mid-blend interrupt correction window as a fraction of the new blend’s curve. See Interrupt Correction.

Usage Examples

C++ — Querying the Main Camera Entity

#include <GS_Core/Interfaces/Camera/CamCoreExchangeBus.h>

AZ::EntityId mainCameraId;
GS_Core::CamCoreExchangeBus::BroadcastResult(
    mainCameraId,
    &GS_Core::CamCoreExchange::GetCamCore);

For a multi-channel project, query the Cam Manager for a specific channel’s Cam Core instead:

#include <GS_PhantomCam/GS_CamManagerBus.h>

AZ::EntityId p1CamCore;
GS_PhantomCam::CamManagerRequestBus::BroadcastResult(
    p1CamCore,
    &GS_PhantomCam::CamManagerRequests::GetChannelCamCore,
    GS_PhantomCam::ChannelId(0));

C++ — Listening for Camera Updates

#include <GS_Core/Interfaces/Camera/CamCoreEmissionBus.h>

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

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

    void UpdateCameraPosition(
        const AZ::Vector3& camPos,
        const AZ::Vector3& camFacing,
        float deltaTime) override
    {
        // React to per-tick camera position / facing changes.
    }
};

Script Canvas

Reacting to camera position updates:


Extending the Cam Core

The Cam Core can be extended in C++ to customize blend behavior, add post-processing logic, or integrate with external camera systems.

Header (.h)

#pragma once
#include <GS_PhantomCam/Core/GS_CamCoreBus.h>
#include <Source/Core/GS_CamCoreComponent.h>

namespace MyProject
{
    class MyCamCore : public GS_PhantomCam::GS_CamCoreComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCamCore);

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

Implementation (.cpp)

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

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

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

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyCamCore>("My Cam Core", "Custom camera core driver")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }
}

See Also

For related PhantomCam components:

For utilities used by the blend math:

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.2.1 - Blend Profiles

Data assets defining camera transition behavior — blend duration, easing curves, blend shape, pivot source, and state inheritance per camera pair.

Blend Profiles are data assets that control how the Cam Core transitions between Phantom Cameras. Each profile contains a list of blend entries. Each entry defines a From camera, a To camera, a blend duration, an easing curve, a blend shape, a pivot source, and an inherit-state flag. This allows every camera-to-camera transition in your project to have unique timing, feel, and geometry.

The GS_PhantomCamBlendProfile asset is authored in the Asset Editor as a .camblendprofile file. It is registered at startup by PhantomCamDataAssetsSystemComponent and assigned to the Cam Core component. When a camera transition occurs, the Cam Core queries the profile for the best matching blend entry.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

Cam Blend Profile asset in the O3DE Asset Editor

 

Contents


How It Works

Blend Entries

Each entry in a Blend Profile defines a single transition rule:

  • From Camera — The name of the outgoing camera (the entity name of the phantom camera being left).
  • To Camera — The name of the incoming camera (the entity name of the phantom camera being transitioned to).
  • Blend Time — The duration of the transition in seconds.
  • Blend Type — The interpolation curve applied during the blend. See Curves Utility for the full list.
  • Blend Shape — Linear, Cylindrical, or Spherical. See Blend Shape.
  • Pivot Source — Source, Destination, or Shared. See Pivot Source. Only consulted when blend shape is not Linear.
  • Inherit State — Opt-in cam-to-cam pose handoff. See Inherit State.

Camera names correspond to the entity names of your phantom camera entities in the scene.

Best Target Blend

When the Cam Core needs to transition, it calls GetBestBlend(fromCam, toCam) on the assigned Blend Profile. The system evaluates blend entries in order of specificity:

  1. Exact match — An entry with both the From and To camera names matching exactly.
  2. Any-to-specific — An entry with From set to blank or “any” and To matching the incoming camera name.
  3. Specific-to-any — An entry with From matching the outgoing camera name and To set to blank or “any”.
  4. Default fallback — If no entry matches, the Cam Core uses its own default blend time, easing, and shape configured on the component.

This layered resolution allows you to define broad defaults (“any” to “any” at 1.0 seconds) while overriding specific transitions (“MenuCam” to “GameplayCam” at 2.5 seconds with ease-in-out and a spherical sweep).

The first match within a specificity tier wins (no further tie-breaking).


Blend Shape

The blend shape controls how the camera’s position interpolates between source and destination. Defaults to Linear so unconfigured / pre-v3 assets retain straight-lerp behavior.

ShapeBehavior
LinearStraight world-space lerp. No orbital math. The default.
CylindricalArc around the resolved pivot on the yaw axis. Pitch and radius interpolate independently. Right for ground cams sharing a target.
SphericalFull 3D arc around the resolved pivot. Right when source and destination cams sit at different heights around a shared target.

When the shape is not Linear, the Cam Core calls the Orbital Solver Utility — BlendPositionAroundPivot(from, to, pivot, alpha, shape, axis) — to compute each tick’s position.

Auto-fallback to Linear. When neither cam reports a pivot (e.g. environmental cams without follow / lookAt targets), the orbital solver isn’t called and the blend lerps straight regardless of the authored shape.

Rotation always slerps; the shape only affects position.


Pivot Source

The pivot is the world-space point that Cylindrical / Spherical shapes arc around. It is resolved at blend start from the cams’ pivots. The pivot source selects which cam’s pivot to use:

Pivot SourceBehavior
SourceOutgoing cam’s pivot. “Leave A elegantly” — the source cam’s framing reference defines the arc.
DestinationIncoming cam’s pivot. “Arrive at B elegantly” — the destination’s framing reference defines the arc.
Shared (default)Midpoint of both pivots. Collapses identically when both cams target the same point.

Pivots come from the cams’ bodies via GetCameraPivot. A cam that doesn’t publish a pivot disqualifies itself from being the pivot source; Cam Core falls back to whichever cam does publish one, or to Linear if neither does.

This field is ignored when Blend Shape is Linear.


Inherit State

Setting Inherit State opts the matched pair into the state inheritance protocol. When the Cam Core is about to start this blend, it:

  1. Calls TryGetPoseSnapshot on the outgoing cam — the body publishes a CamPoseSnapshot.
  2. Calls TryAdoptPoseSnapshot on the incoming cam — the body consumes the snapshot, re-deriving its internal kinematic state to start from a continuation of the outgoing pose.
  3. Bodies that don’t speak the protocol silently no-op and the blend proceeds without inheritance.

The blend then runs from the source cam’s pose to the destination cam’s already-inherited pose — reads as a small smooth drift rather than a swing.

Default is false so cinematic shots authored at specific poses land at those poses unless the author opts in.

Authoring gotcha. If the inherit flag is unchecked on a matched pair, the destination cam falls through to its snap-on-activation seed. For a LeadingFollowBody this looks like “cam orients behind player’s travel direction regardless of source facing.” For DynamicOrbitBody or OrbitBody the cam swings to authored yaw / pitch and blends from there. Both symptoms point at the same root cause — verify the flag in the asset.


Data Model

GS_PhantomCamBlendProfile

The top-level asset class. Extends AZ::Data::AssetData. Registered through PhantomCamDataAssetsSystemComponent. Authored as .camblendprofile.

FieldTypeDescription
BlendListAZStd::vector<PhantomBlend>The list of blend entries defining camera transitions.

PhantomBlend

A single blend entry within the profile. Reflected at Version 4 with a version converter that defaults Blend Shape / Pivot Source to Linear / Shared (pre-v3 assets) and Inherit State to false (pre-v4 assets).

FieldTypeDefaultDescription
FromCameraAZStd::string""Entity name of the outgoing phantom camera. Blank or “any” matches all outgoing cameras.
ToCameraAZStd::string""Entity name of the incoming phantom camera. Blank or “any” matches all incoming cameras.
BlendTimefloat1.0Duration of the blend transition in seconds.
BlendTypeGS_Core::CurveTypeLinearThe easing curve. See Curves Utility for the full enum.
BlendShapeGS_Core::Math::BlendShapeLinearPath geometry — Linear / Cylindrical / Spherical. See Blend Shape.
PivotSourceGS_Core::Math::PivotSourceSharedWhich cam’s pivot to use — Source / Destination / Shared. See Pivot Source.
InheritStateboolfalseOpt-in cam-to-cam pose handoff. See Inherit State.

API Reference

GS_PhantomCamBlendProfile Methods

MethodParametersReturnsDescription
GetBestBlendAZStd::string fromCam, AZStd::string toCamconst PhantomBlend*Returns the best matching blend entry for the given camera pair, or nullptr if no match is found. Resolution follows the specificity hierarchy.

Creating a Blend Profile

  1. Open the Asset Editor in O3DE.
  2. Select New and choose GS_PhantomCamBlendProfile from the asset type list.
  3. Add blend entries using the + button on the Blend List array.
  4. For each entry:
    • Set the From Camera name (or leave blank for “any”).
    • Set the To Camera name (or leave blank for “any”).
    • Set the Blend Time in seconds.
    • Choose a Blend Type (easing curve) from the dropdown.
    • Choose a Blend Shape (Linear / Cylindrical / Spherical).
    • Choose a Pivot Source (Source / Destination / Shared) — ignored when Blend Shape is Linear.
    • Tick Inherit State if you want the incoming cam’s body to seed from the outgoing cam’s pose.
  5. Save the asset.
  6. Assign the asset to the Cam Core component’s Blend Profile inspector slot.

For a full walkthrough, see the PhantomCam Set Up Guide.


See Also

For related PhantomCam components:

For related utilities:

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.2.2 - State Inheritance

PhantomCam cam-to-cam pose handoff protocol — CamPoseSnapshot universal trait, m_inheritState blend toggle, per-body-stage Get/Adopt implementations, ANGULAR vs POSITION mode, consumedAdoption gate.

State inheritance is a universal pose-handoff protocol: when the Cam Core transitions cam A → cam B, the matched Blend Profile entry may opt into inheritance via the InheritState flag. The outgoing cam publishes a CamPoseSnapshot; the incoming cam consumes it through its own kinematic model. The blend then reads as a small smooth drift instead of a swing.

Inheritance is body-only by design. Aim and additive stages don’t participate — the destination cam’s aim runs fresh on the inherited body pose.

 

Contents


The CamPoseSnapshot Trait

Universal pose-handoff struct. Runtime only — not serialized.

struct CamPoseSnapshot
{
    // Universal — every cam can produce this from its committed transform.
    AZ::Vector3  worldPos = Vector3::Zero();
    AZ::Vector3  worldFwd = Vector3::AxisY();

    // Source cam's resolved pivot in world space at the time of Get.
    // Orbit-style adopters use this to back-derive angular state in the
    // SOURCE cam's frame — preserves orientation even when the incoming
    // cam resolves a different pivot.
    AZ::Vector3  pivotPos      = Vector3::Zero();
    bool         pivotPosValid = false;

    AZ::EntityId pivotEntity;

    // Orbit-specific authored angular state. Populated by orbit-style Get
    // implementations; consumed by orbit-style Adopters directly, bypassing
    // back-derivation. Preserves A's exact yaw / pitch on B across different
    // orbit shapes — angular continuity wins over spatial back-fit.
    float        yawRad             = 0.0f;
    float        pitchRad           = 0.0f;
    bool         angularStateValid  = false;

    // Producer signal — set by TryGetPoseSnapshot when the snapshot is usable.
    bool         valid = false;

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

A snapshot may carry partial information. worldPos + worldFwd are always populated when valid is true. pivotPos is populated by bodies that publish a pivot (orbit-style, lead-follow). angularStateValid is set only by orbit-style bodies that publish authored yaw / pitch directly.


Opting In — The Blend Profile Toggle

Authored per-pair on PhantomBlend entries inside a .camblendprofile:

struct PhantomBlend {
    // ... other fields ...
    bool m_inheritState = false;
};

Default false so cinematic shots authored at specific poses land at those poses unless the author opts in. See Blend Profiles — Inherit State for the authoring surface.


The IBodyStage Virtuals

class IBodyStage {
public:
    // ... other interface ...

    // Default returns false — stage doesn't speak the protocol.
    virtual bool TryGetPoseSnapshot(CamPoseSnapshot& out)        const { return false; }
    virtual bool TryAdoptPoseSnapshot(const CamPoseSnapshot& in)       { return false; }
};

A body stage may implement only one side of the protocol. Static-shot bodies (OrbitBody) implement Get but leave Adopt at default false — their authored angles define their identity. Other body stages typically implement both.

Forwarders on the Phantom Camera

The component forwards both calls to the body slot:

bool GS_PhantomCameraComponent::TryGetPoseSnapshot(CamPoseSnapshot& out) const {
    if (IBodyStage* body = GetBody()) {
        return body->TryGetPoseSnapshot(out);
    }
    return false;
}

bool GS_PhantomCameraComponent::TryAdoptPoseSnapshot(const CamPoseSnapshot& in) {
    if (IBodyStage* body = GetBody()) {
        return body->TryAdoptPoseSnapshot(in);
    }
    return false;
}

Exposed on PhantomCameraRequestBus so the Cam Core can address cams uniformly.


Cam Core Handoff Site

Inside SetPhantomCam, after lastCam / currentCam are determined and before StartBlend:

const bool wantInherit = (bestBlend && bestBlend->m_inheritState
                          && lastCam.IsValid() && currentCam.IsValid());

if (wantInherit)
{
    CamPoseSnapshot snap;
    bool gotPose = false;
    PhantomCameraRequestBus::EventResult(gotPose, lastCam,
        &PhantomCameraRequests::TryGetPoseSnapshot, snap);

    if (gotPose && snap.valid)
    {
        bool adopted = false;
        PhantomCameraRequestBus::EventResult(adopted, currentCam,
            &PhantomCameraRequests::TryAdoptPoseSnapshot, snap);
        (void)adopted;
    }
}

// ... then StartBlend.

The handoff runs before StartBlend, so by the time the blend captures source and destination poses, the destination cam’s body has already adopted the seed pose. The blend then interpolates from the source’s literal position to the destination’s already-inherited pose.

Bodies that don’t speak the protocol (or that reject the inbound pose) return false; the handoff is silent and the blend proceeds normally.


Per-Variant Implementations

DefaultFollowBody

SideBehavior
GetNot implemented (default false).
AdoptNot implemented (default false).

Inheritance into a Default Follow falls through to the Blend Profile’s visual bridge.

OrbitBody (Get-only)

SideBehavior
GetPublishes the cleanest snapshot in the framework. worldPos = m_idealPosition, worldFwd toward cached pivot, pivotPos = m_lastPivot (post-offset target, pre-orbit), AND direct authored yawRad / pitchRad + angularStateValid = true. Orbit-style adopters receive ANGULAR-mode handoff with no back-derivation.
AdoptDisabled (default false). The authored yaw / pitch define the static shot’s identity; accepting inbound pose would defeat author intent.

DynamicOrbitBody (full Get + Adopt)

Get:

out.worldPos          = m_idealPosition;
out.worldFwd          = (m_lastResolvedPivot - m_idealPosition).GetNormalizedSafe();
out.pivotPos          = m_lastResolvedPivot;
out.pivotPosValid     = true;
out.pivotEntity       = m_lastResolvedPivotEntity;
out.yawRad            = m_targetYaw;
out.pitchRad          = m_targetPitch;
out.angularStateValid = true;
out.valid             = true;
return true;

Requires a committed pose AND a cached pivot. First-tick adopters that haven’t ticked yet return false.

Adopt stashes the snapshot. The actual derivation happens in the next Evaluate where pivot resolution has run:

if (m_hasPendingAdoption && m_orbitShape.Get())
{
    if (m_pendingAdoption.angularStateValid)
    {
        // ANGULAR mode (preferred). Inherit raw yaw / pitch directly.
        m_targetYaw   = WrapYawToPi(
            ShortestYawTarget(m_targetYaw, m_pendingAdoption.yawRad));
        m_targetPitch = AZ::GetClamp(m_pendingAdoption.pitchRad,
                                     CameraOrbitShape::kPitchAtLow,
                                     CameraOrbitShape::kPitchAtHigh);
    }
    else
    {
        // POSITION mode (fallback). Back-derive yaw from worldPos.
        const AZ::Vector3 referencePivot = m_pendingAdoption.pivotPosValid
            ? m_pendingAdoption.pivotPos
            : pivot;
        const AZ::Vector3 off = m_pendingAdoption.worldPos - referencePivot;

        const float yawObserved    = std::atan2(off.GetY(), off.GetX());
        const float radiusObserved = std::sqrt(off.GetX()*off.GetX() + off.GetY()*off.GetY());
        const float heightObserved = off.GetZ();

        m_targetYaw   = WrapYawToPi(ShortestYawTarget(m_targetYaw, yawObserved));
        m_targetPitch = FindPitchOnShape(*m_orbitShape.Get(), radiusObserved, heightObserved);
    }

    m_idealPosition       = m_pendingAdoption.worldPos;   // seed at adopted pose
    m_hasIdeal            = true;
    consumedAdoption      = true;
    m_hasPendingAdoption  = false;
}

The ANGULAR path is preferred — it preserves authored angular state across different orbit shape assets, which the position back-derivation cannot. The POSITION path is the safety net for source bodies that don’t publish angular state.

LeadingFollowBody (facing-seeded standoff)

Get returns m_idealPosition, world-forward toward cached target, pivotPos = m_lastTargetPoint. No angular state — lead-follow has no degree of freedom other than position-in-band.

Adopt stashes the snapshot. The consume block in Evaluate reads source worldFwd, flattens to the XY plane, and seeds the cam at band-natural standoff distance:

AZ::Vector3 inboundFwd = m_pendingAdoption.worldFwd;
inboundFwd.SetZ(0.0f);
inboundFwd = inboundFwd.GetNormalizedSafe();
if (inboundFwd.GetLengthSq() < epsilon)
    inboundFwd = GetHorizontalForward(stageTargetTM);   // fallback

const float seedDist = AZ::Lerp(m_innerRadius, m_outerRadius, 0.6f);
m_idealPosition = targetPoint - inboundFwd * seedDist;

m_hasIdeal           = true;
m_idleTimer          = 0.0f;
m_hasLastTarget      = true;
consumedAdoption     = true;
m_hasPendingAdoption = false;

Why facing-seeded, not position-seeded? Lead-follow is heading-agnostic. Seeding with the source’s literal worldPos would lock the cam at an off-band angle, defeating the body’s authored intent. The cam inherits the source’s facing at this body’s preferred standoff. Z naturally lands at targetPoint.Z (inboundFwd is XY-flattened) — a from-below source produces a band-natural pose, not stuck-below framing.

TrackBody (path-projection with threshold)

Get returns m_idealPosition plus world-forward toward cached target. No angular state, no pivot — the path is the kinematic reference, not a point.

Adopt stashes. The consume block projects the source’s position onto the spline and rejects when the source sits too far from the path:

EnsureSplineResolved();
if (!m_spline) { m_hasPendingAdoption = false; return; }

AZ::Vector3 nearestWorld;
const float dist = m_spline->FindClosestWorldPoint(
    m_pendingAdoption.worldPos, nearestWorld, /*outT*/);

if (dist > m_adoptionPathThreshold)
{
    // Source sits too far from path — reject, fall back to plain blend.
    m_hasPendingAdoption = false;
    return;
}

m_idealPosition  = m_pendingAdoption.worldPos;
m_springVelocity = AZ::Vector3::CreateZero();
m_hasIdeal       = true;
consumedAdoption = true;
m_hasPendingAdoption = false;

m_adoptionPathThreshold is authored on the TrackBody (default 5.0 m). Prevents teleport-snap when the source cam sits well off the dolly’s path.


The consumedAdoption Gate

Problem: With ctx.snapThisFrame = true on activation, every body’s damping clause snap-clobbers m_idealPosition = desiredPos, discarding the adoption seed. Symptom: cam jerks to its natural ideal then blends from there.

Fix: The snap-on-activation block in every body is gated on !consumedAdoption:

if ((ctx.snapThisFrame || !m_hasIdeal) && !consumedAdoption)
{
    // Snap-seed path. Skipped when adoption already placed m_idealPosition.
}

Adoption-tick uses standard damping from the adopted seed, not snap-seed. When inheritance fires, the seed pose is preserved through the activation tick.


Authoring Gotcha — Matched but Not Inheriting

If m_inheritState is unchecked on a matched blend pair, the destination cam falls through to its snap-on-activation seed instead of consuming the source’s pose. Symptoms vary by body type but all point to the same root cause:

BodySymptom
LeadingFollowBody“Cam orients behind player’s travel direction regardless of source facing.” The snap block uses GetHorizontalForward(stageTargetTM) = target’s BasisY.
DynamicOrbitBody / OrbitBodyCam swings to authored yaw / pitch then blends from there.
TrackBodyDolly starts at its untouched starting spline parameter.

The fix is data-side: check the Inherit State flag on the matched blend entry inside the .camblendprofile asset.

If diagnostic prints are added during debugging, the smoking gun looks like:

Handoff: from=OrbitCam to=LeadCam inherit=0 (matched=1)

matched = 1, inherit = 0 means the blend pair IS in the asset, but m_inheritState is unchecked on it.


Adoption Coverage Matrix

Body VariantGetAdoptSource publishes
DefaultFollowBody———
OrbitBody (static)✓— (Get-only)worldPos, worldFwd, pivotPos, full angular
DynamicOrbitBody✓✓ (ANGULAR / POSITION)worldPos, worldFwd, pivotPos, full angular
LeadingFollowBody✓✓ (facing-seeded standoff)worldPos, worldFwd, pivotPos (= target), no angular
TrackBody✓✓ (path-project + threshold)worldPos, worldFwd, no angular, no pivot

See Also

Related PhantomCam pages:

Basics-side authoring guide:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.2.3 - Interrupt Blending

Mid-blend interrupt correction window — m_interruptCorrection, T_start / T_natural derivation, snapshot Y-blend. Prevents velocity discontinuities when a new blend starts before the current one finishes.

When a blend A → B is in flight and a new blend A’ → C kicks off, snapping the cam’s source pose produces a perceptible velocity jolt — the cam visibly changes direction at the interrupt moment. The Cam Core’s mid-blend correction window prevents this by seeding the new blend at a velocity-matched curve point and Y-blending between the snapshot pose and the new blend’s natural pose across a small window.

This replaces the legacy BlendFromCore hard-anchor approach, which produced a velocity-zero discontinuity at every interrupt.

 

Contents


The Problem

Consider a blend A → B running at 50% progress. A new winner C is arbitrated; the system needs a new blend A’ → C. Two naive approaches both fail:

ApproachBehaviorFailure
Snap to mid-blend pose, start new blend from thereSource pose = currently-rendered TMThe new blend’s start velocity is whatever its curve dictates at t=0 (often zero) — does not match the cam’s current motion vector. Visible direction change at interrupt.
Hard-anchor the snapshot as the new blend’s source for the entire durationSource TM frozen at interruptThe cam’s velocity drops to zero at t=0 of the new blend — instant velocity discontinuity.

The legacy BlendFromCore flag implemented the second approach. The new correction window replaces it.


The Mechanism

The Cam Core introduces a small correction window at the start of the new blend during which the rendered TM is a Y-blend between the snapshot pose and the new blend’s natural pose. After the window, pure new blend with live source / destination TMs.

Step by step

  1. Capture the cam’s currently-rendered TM at the interrupt moment → m_interruptSnapshotTM.
  2. Compute oldBlendFactor = oldCurTime / oldTargetTime (how far through the OLD blend we were).
  3. Compute T_natural = 1 − oldBlendFactor — the curve point on the new blend at which the cam’s velocity matches its current motion.
  4. Compute T_start = max(0, T_natural − m_interruptCorrection) — pull the new blend back by the configurable window width.
  5. Seed curBlendTime = T_start * blendTime so the new blend starts at curve-point T_start.
  6. Over the window [T_start, T_natural], the rendered pose is a Y-blend between m_interruptSnapshotTM and the new blend’s natural pose at the current curve point. The Y-factor sweeps 0 → 1 across the window.
  7. After T_natural, the Y-blend yields fully to the new blend; pure new-blend interpolation continues with live source / destination TMs.

Net effect: the rendered cam starts in the snapshot pose at the interrupt, ramps gracefully into the new blend’s natural trajectory across the correction window, and proceeds normally.


Window Width

m_interruptCorrection is authored on the Cam Core, default 0.03 (3% of the new blend’s curve duration). The tradeoff:

SettingBehavior
Larger window (e.g. 0.08)Smoother handoff, but the cam is “stuck” in snapshot territory for longer — can feel like lag.
Smaller window (e.g. 0.01)Quicker handoff, less smoothness.
0Degenerates to legacy hard-anchor behavior with the perceptible jolt.

3% is a balance that’s invisibly smooth for typical blend durations (~1s → 30ms correction window).


Edge Cases

Old blend nearly complete

If oldBlendFactor is close to 1 (the old blend was almost done), T_natural ≈ 0 and T_start ≈ −m_interruptCorrection. The window may compress to nothing — T_start clamps at 0:

T_start = max(0.0, T_natural - m_interruptCorrection);

When clamped, the new blend effectively starts from its natural beginning. The cam may still have a small handoff jolt because the correction can’t pull back further.

Old blend just started

If oldBlendFactor is close to 0 (old blend barely started), T_natural ≈ 1 and T_start ≈ 1 − m_interruptCorrection. Most of the new blend happens in pure new mode after a brief correction window. The snapshot influence is short-lived.

Non-blending interrupt (cold start of new blend)

If IsBlending == false when StartBlend is called, no interrupt — set m_inCorrectionWindow = false and run the new blend normally.


Cam Core State

class GS_CamCoreComponent {
    // ...
    float          m_interruptCorrection = 0.03f;     // authored

    bool           m_inCorrectionWindow   = false;
    float          m_correctionStart      = 0.0f;     // blendFactor where window begins
    float          m_correctionEnd        = 0.0f;     // blendFactor where window ends (= T_natural)
    AZ::Transform  m_interruptSnapshotTM  = AZ::Transform::CreateIdentity();
};

StartBlend Detection

void GS_CamCoreComponent::StartBlend(
    float blendTime,
    GS_Core::CurveType blendType,
    GS_Core::Math::BlendShape blendShape,
    GS_Core::Math::PivotSource pivotSource)
{
    if (IsBlending)
    {
        // Mid-blend interrupt.
        const float oldBlendFactor = curBlendTime / targetBlendTime;
        const float T_natural      = 1.0f - oldBlendFactor;
        const float T_start        = AZ::GetMax(0.0f, T_natural - m_interruptCorrection);

        // Snapshot current rendered TM.
        AZ::TransformBus::EventResult(m_interruptSnapshotTM, m_entityId,
            &AZ::TransformInterface::GetWorldTM);

        m_inCorrectionWindow = true;
        m_correctionStart    = T_start;
        m_correctionEnd      = T_natural;

        // Seed new blend's curve position so velocity matches.
        curBlendTime = T_start * blendTime;
    }
    else
    {
        // Cold start.
        m_inCorrectionWindow = false;
        curBlendTime = 0.0f;
    }

    // Capture source / dest poses, blend params.
    prevTransform      = ... ;          // cam's current world TM at this moment
    targetBlendTime    = blendTime;
    currentBlendType   = blendType;
    currentBlendShape  = blendShape;
    currentPivotSource = pivotSource;
    IsBlending         = true;
}

OnTick Application

void GS_CamCoreComponent::OnTick(float deltaTime, AZ::ScriptTimePoint)
{
    if (!IsBlending)
    {
        // Locked phase — read currentCam's TM, write it directly.
        return;
    }

    curBlendTime += deltaTime;
    const float blendFactor = AZ::GetMin(1.0f, curBlendTime / targetBlendTime);
    const float easedFactor = ApplyCurve(blendFactor, currentBlendType);

    // Compute the "natural" blended pose for this curve point.
    AZ::Vector3 blendedPos;
    if (currentBlendShape == BlendShape::Linear)
    {
        blendedPos = AZ::Vector3::Lerp(
            prevTransform.GetTranslation(),
            currentTM.GetTranslation(),
            easedFactor);
    }
    else
    {
        AZ::Vector3 pivot = ResolvePivot(currentPivotSource, lastCam, currentCam);
        blendedPos = GS_Core::Math::BlendPositionAroundPivot(
            prevTransform.GetTranslation(),
            currentTM.GetTranslation(),
            pivot, easedFactor, currentBlendShape, AZ::Vector3::CreateAxisZ());
    }
    AZ::Quaternion blendedRot = prevTransform.GetRotation().Slerp(
        currentTM.GetRotation(), easedFactor);

    AZ::Vector3    appliedPos;
    AZ::Quaternion appliedRot;

    if (m_inCorrectionWindow && blendFactor < m_correctionEnd)
    {
        // Y-blend the snapshot with the natural pose.
        float yFactor = (blendFactor - m_correctionStart) / m_interruptCorrection;
        yFactor = AZ::GetClamp(yFactor, 0.0f, 1.0f);

        appliedPos = AZ::Vector3::Lerp(
            m_interruptSnapshotTM.GetTranslation(), blendedPos, yFactor);
        appliedRot = m_interruptSnapshotTM.GetRotation().Slerp(blendedRot, yFactor);
    }
    else
    {
        // Past the correction window — pure new blend.
        appliedPos = blendedPos;
        appliedRot = blendedRot;
    }

    // Write final TM. Apply lens interpolation similarly.
    AZ::TransformBus::Event(m_entityId,
        &AZ::TransformInterface::SetWorldTranslation, appliedPos);
    AZ::TransformBus::Event(m_entityId,
        &AZ::TransformInterface::SetWorldRotationQuaternion, appliedRot);

    if (blendFactor >= 1.0f)
    {
        CompleteBlend();
    }
}

Inspector Field

FieldDefaultPurpose
m_interruptCorrection0.03Fraction of the new blend’s curve over which the cam’s snapshot pose at interrupt blends to the new blend’s natural pose. Smaller = snappier interrupts but more visible jolts. 0 = legacy hard-anchor.

What This Replaces

Pre-correction, the system used a BlendFromCore flag on the Cam Core. When set, the blend’s source pose was hard-anchored to the cam’s currently-rendered TM at the interrupt moment, for the entire duration of the new blend. The new blend’s velocity at t = 0 was therefore zero — an instant velocity discontinuity at the interrupt point.

The correction-window approach replaces this entirely. BlendFromCore is retained as a field on the Cam Core for back-compat but is not used in the new flow.


See Also

Related PhantomCam pages:

  • Cam Core — owns StartBlend and OnTick.
  • State Inheritance — runs BEFORE StartBlend; the destination cam’s pose is already inherited when the correction-window snapshot is captured.
  • Blend Profiles — author the entries that trigger blends.
  • Phantom Cameras — Execution States — m_blendingOut keeps the outgoing cam ticking during the blend so its source pose stays live.

Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3 - Phantom Cameras

The base virtual camera component — priority, target routing, lens, channel scope, snap and focus state, and the composable Body / Aim / Additive stage pipeline.

A Phantom Camera is a single entity component — GS_PhantomCameraComponent — that publishes a candidate camera pose every tick. It holds priority, target routing, lens, channel scope, and snap / focus / blendingOut state, plus the stage pipeline slot that authors fill in to give the cam its behavior. Phantom cameras do not render anything themselves; they register with the Cam Manager and the Cam Core drives the engine view toward whichever phantom currently wins arbitration.

Where previous versions of GS_PhantomCam shipped separate components for each behavior (clamped-look, static-orbit, spline tracking, etc.), all of those behaviors now live as stage variants on the single base component. An author picks a Body stage, an Aim stage, and zero-or-more Additive stages from the type-picker, and the same GS_PhantomCameraComponent becomes a follow cam, an orbital cam, a tracking dolly, a third-person shoulder cam, or a cinematic stinger depending on which stages are slotted.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

The Phantom Camera component in the Entity Inspector.

 

Contents


How It Works

Priority-Based Selection

Every phantom camera has a base priority. The camera with the highest effective priority (base plus any active influences) wins arbitration within its channel. You can control which camera wins through several strategies:

  • Raise priority — Set the desired camera’s priority above all others via SetCameraPriority.
  • Disable / Enable — Call DisableCamera to drop the camera’s effective priority, EnableCamera to restore it.
  • Change priority directly — Call ChangeCameraPriority on the Cam Manager bus.
  • Influence — Add temporary priority modifiers through Influence Fields without touching base priorities.

Follow and Look-At

Follow and look-at behavior is no longer hardcoded on the component; it is provided by the Body and Aim stages slotted on the pipeline. Each tick the cam threads a CameraState accumulator through:

Body → Aim → Reposition additives → Noise additives → Finalize
  • The Body stage writes state.position — different variants implement spring-damped follow, orbital sweep, spline traversal, etc.
  • The Aim stage writes state.rotation — including angle-clamped look, head-tracking look, etc.
  • Additive stages correct or perturb the pose without poisoning Body / Aim ideals.

See Stage Pipeline for the per-tick contract.

Camera Data

Each phantom camera stores its lens and target configuration in a PhantomCamData structure. This data is read by the Cam Core during blending and by stages during evaluation. The stage list lives separately on the component (m_bodySlot, m_aimSlot, m_additives).


Stage Pipeline

Each phantom camera is a container for stages. The component owns three slots:

A fully-configured Phantom Camera with body, aim, and additive stages slotted

SlotTypeCardinalityExamples
m_bodySlotIBodyStageOne per cam (single-element vector by policy)DefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBody.
m_aimSlotIAimStageOne per cam (single-element vector by policy)DefaultAim, ClampedLookAim. Other gems (notably gs_performer) register additional variants.
m_additivesIAdditiveStageZero or more, each self-declares Reposition or Noise phaseNoiseStage (Perlin), ImpulseStage (ADSR), TugAimListener, TugBodyListener, collision / occlusion reposition.

Stages are reflected polymorphic types — derived from IBodyStage / IAimStage / IAdditiveStage, not AZ::Component. The editor type-picker enumerates all registered derivations from the SerializeContext class hierarchy, so other gems can extend the catalog cleanly.

Per-tick orchestration (run by the component each OnTick):

EvaluateCamTick — eligibility check (see Execution States)
   │ tickActive == true
   ▼
EvaluatePose
   ├─► Build CameraContext (target, deltaTime, lifecycle flags, snapThisFrame)
   ├─► IBodyStage::Evaluate    → writes state.position, state.bodyAnchor
   ├─► IAimStage::Evaluate     → writes state.rotation, state.lookAtPoint
   ├─► snapshot m_desiredPose
   ├─► For each Reposition additive → corrects state
   ├─► snapshot m_stablePose
   ├─► For each Noise additive → perturbs state
   ├─► snapshot m_finalPose
   ├─► Resolve cached pivot (state.lookAtPoint, else target world position)
   └─► Commit m_finalPose to TransformBus + apply lens to engine Camera component

See Stage Pipeline reference for the full per-stage contract, init / asset-fresh-load semantics, and variant catalogs.


Channel Scope

Each phantom camera authors a CamChannelScope that decides how it participates in the channel system:

enum class CamChannelScope : AZ::u8 {
    Local,         // Default. Registers to ancestor stamp's channel, or channel 0 if no stamp.
    AllChannels,   // Stamped: per-rig-instance natural duplicate. Unstamped: warns + falls back.
    TrueUnique,    // Single instance. Either bound to a specific channel or shared across all.
};
FieldVisibilityPurpose
m_channelScopeAlwaysThe scope mode.
m_boundChannelIdWhen m_channelScope == TrueUniqueExplicit channel binding for direct mode.
m_allChannelsShareWhen m_channelScope == TrueUnique AND m_showAdvancedShared mode toggle — appears in every active channel’s priority table.
m_showAdvancedWhen m_channelScope == TrueUniqueReveals advanced TrueUnique fields.

Author recipes:

  • Cam inside a rig prefab — leave as Local. Each spawned rig instance hosts its own copy via the stamp-walk path.
  • Per-player tailored broadcast cam in the level — AllChannels (works naturally for in-rig cams; out-of-rig duplication is deferred).
  • Hero-perspective cam for a specific player — TrueUnique + m_boundChannelId = N.
  • Shared cinematic collapse cam — TrueUnique + advanced + m_allChannelsShare = true. Paired with a Group Target, this triggers OnAllChannelsActivatedSharedCam when every channel selects the cam.

See Channels & Instancing for the resolution algorithm.


Execution States

A phantom camera carries three boolean states that gate whether it ticks each frame. The tick-eligibility predicate is:

tickActive = m_hasFocus || m_alwaysUpdate || m_blendingOut

When tickActive is false the cam is fully dormant — its stages don’t run, its body doesn’t integrate input, its damping doesn’t advance.

The three flags

FlagOriginMeaning
m_hasFocusRuntime — set by SettingNewCam / SettingNewCamOnChannel notifications.True while this cam is the channel’s currently-driven cam.
m_alwaysUpdateAuthored on the component.If true, the cam ticks every frame regardless of focus. Use for background-tracking cams that should be up-to-date the moment focus arrives, at the cost of constant CPU.
m_blendingOutRuntime — set by Cam Core via SetBlendingOut while this cam is the outgoing source of an in-flight blend.Keeps a !alwaysUpdate outgoing cam ticking through the blend so its body can keep tracking the target — otherwise the blend’s “from” pose would freeze.

snapThisFrame and m_snapNextEval

When a cam should bypass damping for a single tick — to commit its ideal pose directly without spring lag — the component sets m_snapNextEval = true. The next Evaluate consumes the flag, sets ctx.snapThisFrame = true, and resets m_snapNextEval.

m_snapNextEval is set on:

  • Focus gain — m_hasFocus flipped from false to true (gated on focus specifically; blend-out wake does NOT trigger snap, which would otherwise yank an outgoing cam back to its initial yaw).
  • SetCameraTarget(validEntity) — new follow target.
  • SetTargetFocusGroup(validEntity) — new group target.
  • QueueSnapCamera() — explicit API call.
  • Mid-spawn channel binding — the Cam Manager pushes a synchronous SnapCameraNow when a channel target is already present at registration time. See Cam Manager.

SnapCameraNow vs QueueSnapCamera

MethodTiming
QueueSnapCamera()Sets m_snapNextEval = true. Honored on next Evaluate. Most code paths use this.
SnapCameraNow()Synchronous. Evaluates Body + Aim with dt = 0 and writes the snapped TM straight to the transform bus. Required when the Cam Core is about to read the cam’s TM on the same call stack (mid-spawn binding). Early-returns if neither follow target nor look-at target is set.

Input integration sub-gate

Body stages that drive pose from input (notably DynamicOrbitBody) integrate input on the same predicate, mirrored into CameraContext:

integrateInput = ctx.hasFocus || ctx.alwaysUpdate || ctx.blendingOut

The body still polls the input reader every tick regardless of integrateInput — the poll drains Delta-mode buffers so they don’t accumulate during dormancy. The integration result is discarded when !integrateInput.


Staged Poses

The pipeline produces three pose snapshots per tick:

SnapshotWhere it landsUse it for…
m_desiredPosePost Body + Aim, pre-Reposition. “Clean” ideal — no collision correction, no shake.Debug visualization of the cam’s ideal trajectory.
m_stablePosePost Reposition, pre-Noise. Collision-safe, shake-free.Gameplay readback — Unit movement input driven by camera facing should use this.
m_finalPosePost Noise. What gets written to the transform bus.Anything that needs the actual rendered pose (audio listener placement, screen-space UI).

Three snapshots exist because consumers vary. A Unit that uses camera-forward to drive movement input should query GetStablePose — querying GetFinalPose would let the noise stage drag the character around.

The component also caches a pivot each tick (m_cachedPivot) resolved from the Aim stage’s state.lookAtPoint, falling back to the target’s world position. The Cam Core queries this via GetCameraPivot to drive orbital blend shapes.


Setup

  1. Create an entity and add GS_PhantomCameraComponent.
  2. Set the Priority value. Higher values take precedence within the channel.
  3. Configure FOV, near clip, and far clip on the lens fields.
  4. Assign a Cam Target entity (the follow target). Leave empty to inherit the channel target from the Cam Manager.
  5. From the Body type-picker, slot a body stage. Configure its target mode, offset, and damping halflife.
  6. From the Aim type-picker, slot an aim stage. Configure its target mode, offset, and damping halflife.
  7. Optionally add Additive stages — noise, impulse, tug listeners, collision reposition.
  8. Set the Channel Scope (Local is the default and almost always correct).
  9. Place the entity in your scene or inside a rig prefab. The component registers with the Cam Manager automatically on activation.

For a full walkthrough, see the PhantomCam Set Up Guide.


PhantomCamData Structure

The PhantomCamData structure holds the lens and priority configuration for a phantom camera. Stages are stored separately on the component.

FieldTypeDescription
PriorityAZ::s32Base priority used in arbitration.
FOVfloatField of view in degrees.
NearClipfloatNear clipping plane distance.
FarClipfloatFar clipping plane distance.
CamTargetAZ::EntityIdThe follow target. May be left empty to inherit the channel target.

Follow / look-at offsets, damping halflives, and target modes now live on the stages, not on PhantomCamData.


API Reference

Request Bus: PhantomCameraRequestBus

Commands sent to a specific phantom camera. ById bus — addressed by entity ID, single handler per address.

Lifecycle

MethodParametersReturnsDescription
EnableCamera—voidRestores the cam’s effective priority and registers it for evaluation.
DisableCamera—voidDrops the cam’s effective priority to 0.
IsCamEnabled—boolQuery.

Priority and target

MethodParametersReturnsDescription
SetCameraPriorityAZ::s32 newPriorityvoidSets base priority. Triggers Cam Manager re-evaluation.
GetCameraPriority—AZ::s32Query.
SetCameraTargetAZ::EntityId targetEntityvoidSets the follow target. Triggers a snap on next tick when the entity is valid. Clearing (invalid id) does NOT snap — preserves depossess pose-hold.
SetTargetFocusGroupAZ::EntityId targetFocusGroupvoidSets the target to a Group Target entity. Body / aim stages with CamTargetMode::GroupTarget route through this.
GetCameraData—const PhantomCamData*Returns const pointer to the cam’s lens / priority / target data. Consumed by the Cam Core.

Snap

MethodParametersReturnsDescription
QueueSnapCamera—voidSets m_snapNextEval = true. Honored on next Evaluate.
SnapCameraNow—voidSynchronous snap. Evaluates Body + Aim with dt = 0 and writes the snapped TM. Early-returns if neither follow nor look-at target is set.

Staged pose accessors

MethodParametersReturnsDescription
GetDesiredPose—AZ::TransformPost Body + Aim, pre-Reposition.
GetStablePose—AZ::TransformPost Reposition, pre-Noise. Recommended default for gameplay readback.
GetFinalPose—AZ::TransformCommitted pose (post-Noise).
GetCameraPivotAZ::Vector3& outPivot, bool& outHasPivotvoidReturns the cam’s cached pivot. Used by Cam Core to drive non-Linear blend shapes.

Impulse

MethodParametersReturnsDescription
TriggerCameraImpulsefloat strengthvoidFires every ImpulseNoise additive on this cam. strength multiplies each stage’s AmplitudeGain — typically pass distance-falloff values in 0..1.

Inheritance forwarders

MethodParametersReturnsDescription
TryGetPoseSnapshotCamPoseSnapshot& outboolForwards to the body stage’s virtual. The Cam Core invokes this on the outgoing cam during the inheritance handoff. Returns false if the body doesn’t implement the protocol.
TryAdoptPoseSnapshotconst CamPoseSnapshot& inboolForwards to the body stage’s virtual. The Cam Core invokes this on the incoming cam.

Blend-out lifecycle

MethodParametersReturnsDescription
SetBlendingOutbool isBlendingOutvoidCam Core sets this on the outgoing cam during a blend. While true, the cam ticks (and integrates input) even if !alwaysUpdate && !hasFocus. Cleared on blend completion.

Camera Behavior Types

Retired components. ClampedLook_PhantomCamComponent, StaticOrbit_PhantomCamComponent, and Track_PhantomCamComponent no longer exist as separate components. Their behavior is now provided by stage variants on the base GS_PhantomCameraComponent. Legacy URLs for these subpages redirect to the matching stage docs.

Retired componentReplacement
StaticOrbit_PhantomCamComponentOrbitBody Body stage variant. See Stage Pipeline.
ClampedLook_PhantomCamComponentClampedLookAim Aim stage variant. See Stage Pipeline.
Track_PhantomCamComponentTrackBody Body stage variant. See Stage Pipeline.

AlwaysFaceCameraComponent

A billboard helper component. Keeps the attached entity always facing the active camera. This is not a phantom camera type itself but a utility for objects that should always face the viewer (UI elements in world space, sprite-based effects, etc.). Unchanged by the refactor.


Usage Examples

Switching Cameras by Priority

To make a specific phantom camera dominant, raise its priority above all others:

#include <GS_PhantomCam/Cameras/GS_PhantomCameraBus.h>

GS_PhantomCam::PhantomCameraRequestBus::Event(
    myCameraEntityId,
    &GS_PhantomCam::PhantomCameraRequests::SetCameraPriority,
    100);

Disabling the Current Camera

Disable the current dominant camera to let the next-highest priority camera take over:

GS_PhantomCam::PhantomCameraRequestBus::Event(
    currentCameraEntityId,
    &GS_PhantomCam::PhantomCameraRequests::DisableCamera);

Triggering a Camera Impulse

Fire all ImpulseNoise additives on the active cam at a strength scaled by distance:

const float strength = AZ::GetClamp(1.0f - distance / radius, 0.0f, 1.0f);
GS_PhantomCam::PhantomCameraRequestBus::Event(
    activeCameraEntityId,
    &GS_PhantomCam::PhantomCameraRequests::TriggerCameraImpulse,
    strength);

Script Canvas

Enabling and disabling a phantom camera:

Getting a phantom camera’s data:


Extending Phantom Cameras

Most camera customization now happens by authoring a new stage variant rather than subclassing the component. Stages are reflected polymorphic types — derive from IBodyStage, IAimStage, or IAdditiveStage, register with AZ_RTTI + AZ_CLASS_ALLOCATOR, and reflect under the base interface’s class hierarchy. The editor type-picker discovers the new variant automatically.

Use the PhantomCamera ClassWizard template to generate a new camera component when you need component-level customization (rare) — see GS_PhantomCam Templates.

Header (.h)

#pragma once
#include <GS_PhantomCam/Cameras/GS_PhantomCameraBus.h>
#include <Source/Cameras/GS_PhantomCameraComponent.h>

namespace MyProject
{
    class MyCustomCam : public GS_PhantomCam::GS_PhantomCameraComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCustomCam);

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

Implementation (.cpp)

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

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

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

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyCustomCam>("My Custom Camera", "Custom phantom camera component")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }
}

For stage-level extension (the more common case), see the Stage Pipeline reference.


See Also

For related PhantomCam components:

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.1 - Stage Pipeline

The composable per-tick pipeline each Phantom Camera runs — Body, Aim, Reposition additives, Noise additives. Stage interfaces, CameraState accumulator, target routing, decoupled-ideal pattern, init contract.

Every Phantom Camera runs the same fixed pipeline per tick:

Body → Aim → Reposition additives → Noise additives → Finalize

The Body and Aim stages produce the cam’s smoothed ideal pose; Reposition additives correct it for collision / tug volumes; Noise additives perturb it for shake and impulse; Finalize commits the result to the entity transform and the engine camera component.

Stages are not AZ::Components. They are reflected polymorphic types — derived from IBodyStage, IAimStage, or IAdditiveStage. The editor’s type-picker enumerates registered derivations from the SerializeContext class hierarchy, so other gems can extend the catalog cleanly without touching the base component.

For per-variant detail, see the catalog pages:

  • Body Stage Variants — DefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBody.
  • Aim Stage Variants — DefaultLookAtAim, ClampedLookAim. Plus extensions from gs_performer.
  • Additive Stage Variants — CollisionReposition, OcclusionReposition, TugAimListener, TugBodyListener, PerlinNoise, ImpulseNoise.

 

Contents


Per-Tick Model

Each tick, the owning Phantom Camera threads a CameraState accumulator through the pipeline:

GS_PhantomCameraComponent::EvaluatePose
   │
   ├─► Build CameraContext (target, deltaTime, lifecycle flags, snapThisFrame)
   │
   ├─► CameraState state;  // zero-initialized accumulator
   │
   ├─► IBodyStage::Evaluate(state, ctx, dt)
   │       writes state.position, state.bodyAnchor
   │
   ├─► IAimStage::Evaluate(state, ctx, dt)
   │       writes state.rotation, state.lookAtPoint
   │
   ├─► m_desiredPose = (state.position, state.rotation)
   │
   ├─► For each additive in m_repositionStages:
   │       Evaluate(state, ctx, dt) — may correct state.position/rotation
   │
   ├─► m_stablePose = (state.position, state.rotation)
   │
   ├─► For each additive in m_noiseStages:
   │       Evaluate(state, ctx, dt) — perturbs state.position/rotation
   │
   ├─► m_finalPose = (state.position, state.rotation)
   │
   ├─► Resolve cached pivot from state.lookAtPoint (else fallback to target world pos)
   │
   └─► Commit m_finalPose to TransformBus + apply lens to engine Camera component

The component partitions additives into m_repositionStages and m_noiseStages once at slot-assign time (or whenever the additive list changes) based on each additive’s GetStage() result — per-tick code does not re-check the enum each frame.

CamPipelineStage is a fixed ordered enum:

enum class CamPipelineStage {
    Body,
    Aim,
    Reposition,
    Noise,
    Finalize
};

Reposition and Noise are the values an additive may return from GetStage. Body, Aim, and Finalize exist for symmetry but are not author-selectable.


Stage Interfaces

IBodyStage

Single slot per cam. Computes the smoothed follow position.

class IBodyStage {
public:
    AZ_RTTI(IBodyStage, "{6A78E21A-9A5F-4AB5-8F20-08B4D09E5C01}");
    AZ_CLASS_ALLOCATOR(IBodyStage, AZ::SystemAllocator);

    virtual ~IBodyStage() = default;

    virtual void Init() {}

    virtual void Evaluate(CameraState& state, const CameraContext& ctx, float dt) = 0;
    virtual AZ::EntityId ResolveTarget(AZ::EntityId incoming, TargetSignalKind kind) = 0;
    virtual const char* GetDisplayName() const { return "Body Stage"; }

    // State inheritance — default returns false (no protocol).
    virtual bool TryGetPoseSnapshot(CamPoseSnapshot& out) const  { return false; }
    virtual bool TryAdoptPoseSnapshot(const CamPoseSnapshot& in) { return false; }

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

See Body Stage Variants for the catalog.

IAimStage

Single slot per cam. Computes the smoothed look rotation.

class IAimStage {
public:
    AZ_RTTI(IAimStage, "{9F37E4C6-04AD-4B1F-A9E2-0D3A4B8F1E02}");
    AZ_CLASS_ALLOCATOR(IAimStage, AZ::SystemAllocator);

    virtual ~IAimStage() = default;

    virtual void Init() {}

    virtual void Evaluate(CameraState& state, const CameraContext& ctx, float dt) = 0;
    virtual AZ::EntityId ResolveTarget(AZ::EntityId incoming, TargetSignalKind kind) = 0;
    virtual const char* GetDisplayName() const { return "Aim Stage"; }

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

Aim does not implement state inheritance — inheritance is body-only by design. See Aim Stage Variants.

IAdditiveStage

Stackable. Self-declares phase via GetStage.

class IAdditiveStage {
public:
    AZ_RTTI(IAdditiveStage, "{B7D2F618-3E44-4A71-9C21-0F3B7C5E0D04}");
    AZ_CLASS_ALLOCATOR(IAdditiveStage, AZ::SystemAllocator);

    virtual ~IAdditiveStage() = default;

    virtual void Init() {}

    virtual CamPipelineStage GetStage() const = 0;  // returns Reposition or Noise
    virtual void Evaluate(CameraState& state, const CameraContext& ctx, float dt) = 0;
    virtual const char* GetDisplayName() const { return "Additive Stage"; }

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

See Additive Stage Variants.


CameraState and CameraContext

CameraState

The mutable pose accumulator threaded through every stage. Not serialized.

struct CameraState {
    AZ::Vector3    position    = Vector3::Zero();        // accumulator
    AZ::Quaternion rotation    = Quaternion::Identity();

    // Sidecar hints populated by Body/Aim for downstream stages.
    AZ::Vector3    bodyAnchor  = Vector3::Zero();   // post-offset follow anchor
    AZ::Vector3    lookAtPoint = Vector3::Zero();   // world-space focal point
};

The two sidecar fields exist so Reposition additives can read the “natural” pose Body / Aim intended rather than the live state (which may have been displaced by an earlier additive). Tug listeners, for example, blend toward the source point starting from the Body’s bodyAnchor rather than the cam’s possibly-perturbed state.position — prevents compound feedback across ticks.

CameraContext

Read-only per-tick context. Not serialized.

struct CameraContext {
    GS_PhantomCameraComponent* owner;        // for owner helpers (WriteLensFieldOfView, QueueSnapCamera).

    AZ::EntityId               camEntityId;
    AZ::EntityId               targetEntityId;

    AZ::Transform              camInitialTM;     // cam TM read at start of tick
    AZ::Transform              targetTM;         // resolved target world TM
    AZ::Vector3                targetVelocity;

    bool                       targetIsPhysical;
    bool                       hasFocus;
    bool                       alwaysUpdate;
    bool                       blendingOut;
    bool                       snapThisFrame;    // bypass damping this tick

    float                      deltaTime;
};

hasFocus || alwaysUpdate || blendingOut is the input-integration gate — Body stages that drive pose from input (notably DynamicOrbitBody) integrate input only when any of these is true. See Execution States.

snapThisFrame is set on focus gain, target swap, queued snap, and a few other lifecycle events. Stages honor it by bypassing damping for the one tick it is true.


Target Routing

Every Body and Aim variant authors a CamTargetMode enum and (depending on mode) supporting fields. This is the front-end every variant uses to decide which entity it actually follows.

enum class CamTargetMode : AZ::u8 {
    None               = 0,  // no-op for target resolution
    Transform          = 1,  // follow / look at the dispatched CameraTarget
    TargetOffsetEntity = 2,  // reserved — not yet implemented
    // 3 was FocusGroup — removed; use GroupTarget.
    Head               = 4,  // reserved — not yet implemented
    Override           = 5,  // manually-set m_overrideEntity on this stage
    GroupTarget        = 6   // resolve a named GroupTargetComponent via CamManager
};

TargetSignalKind identifies which bus call dispatched the target signal. Lets stages filter by target mode:

enum class TargetSignalKind {
    CameraTarget,  // SetCameraTarget
    FocusGroup     // SetTargetFocusGroup
};

Legacy scenes storing FocusGroup = 3 deserialize as an unknown value; the author re-picks GroupTarget. The newer mode is the canonical way for a stage to track a named group via Group Targets.


Decoupled Ideal Pattern

A subtle but critical convention: each Body / Aim variant maintains an m_idealPosition / m_idealRotation independent of what gets committed to the entity transform.

Why: Reposition additives (collision, occlusion, tug) and Noise additives mutate state.position and state.rotation AFTER Body / Aim runs. If the Body read the committed transform back next tick, the spring would re-try its advance every frame against a collision clamp — producing shudder. By tracking an internal ideal, the Body’s spring runs in clean kinematic space; the committed transform may differ (clamped by collision, shaken by noise) but the Body’s trajectory stays smooth.

The same applies to Aim — m_idealRotation is the slerp’s working value; downstream rotation-modifying additives can freely displace state.rotation without poisoning next tick’s slerp trajectory.

m_idealPosition / m_idealRotation are also what state inheritance reads for TryGetPoseSnapshot and writes for TryAdoptPoseSnapshot — see State Inheritance.


Damping Convention

There is no separate Smoothing stage. Each stage owns its damping internally:

StageWhat it damps
BodyPursuit of the follow target — halflife on m_idealPosition.
AimSlerp toward the look-at rotation — halflife on m_idealRotation.
Reposition additivesTheir own correction delta — collision pushback, tug-source approach.
Noise additivesOperate in their own time domain (noise time, ADSR envelope).

Different halflives capture different physical slownesses and compose naturally. Damping uses the frame-rate-independent HalflifeAlpha(halflife, dt) = 1 - exp2(-dt / halflife) helper.

ctx.deltaTime is clamped at tick entry (maxEvalDeltaTime = 0.05s default) so editor hitches don’t spike damping.


Init Contract

Each stage’s Init() runs once per Activate, after the slot is populated, before any Evaluate. The default is a no-op.

Critical use case — asset-fresh-load. Stages holding asset references (e.g. DynamicOrbitBody.m_orbitShape, PerlinNoise.m_profile) override Init to force a fresh AssetManager load. The deserialized asset reference only carries an AssetId — without an explicit GetAsset call in Init, in-editor edits to the asset don’t propagate to the runtime instance.

Pattern:

void DynamicOrbitBody::Init()
{
    if (m_orbitShape.GetId().IsValid())
    {
        m_orbitShape = AZ::Data::AssetManager::Instance().GetAsset<CameraOrbitShape>(
            m_orbitShape.GetId(),
            AZ::Data::AssetLoadBehavior::PreLoad);
        m_orbitShape.BlockUntilLoadComplete();
    }
}

Any new stage you author that holds an AZ::Data::Asset<T> should follow this pattern.


Polymorphic-Array Convention

The component’s stage slots hold raw pointers in a vector:

AZStd::vector<IBodyStage*>     m_bodySlot;     // single-element conceptually
AZStd::vector<IAimStage*>      m_aimSlot;      // single-element conceptually
AZStd::vector<IAdditiveStage*> m_additives;

Why vectors instead of single pointers? The vector enables the editor’s type-picker for polymorphic types — the user picks from a dropdown of all registered derived classes. The runtime treats Body and Aim slots as single-element; the dropdown for those slots is constrained to one entry by policy.

The component owns the pointers (newed by the editor’s type instantiation; deleted by the component’s Deactivate / destructor).


StageHelpers Utility

StageHelpers.h provides free inline functions used by multiple variants:

HelperPurpose
ResolveByMode(mode, incoming, override, kind, groupTargetName)Resolves the actual tracked entity from a CamTargetMode. Shared by every Body and Aim variant.
ResolveStageTargetTM(mode, groupTargetName, ctx)Resolves the target world TM. Handles Transform (uses ctx.targetTM), Override (looks up m_overrideEntity), GroupTarget (calls Cam Manager’s group registry).
ApplyFollowOffset(point, targetTM, offset, isRelative)Adds the offset to the point. World axes or target-relative basis.
HalflifeAlpha(halflife, deltaTime)Frame-rate-independent damping alpha: 1 - exp2(-deltaTime / halflife).
WrapYawToPi(yaw)Wraps yaw into (-π, π].
ShortestYawTarget(currentYaw, desiredYaw)Returns the equivalent of desiredYaw that is numerically closest to currentYaw (avoids 2π wraparound jumps for monotonic damping).
FindPitchOnShape(shape, radiusObserved, heightObserved)Inverse search on a CameraOrbitShape — given an observed (radius, height) pair, returns the pitch that produces that point on the curve. Used by DynamicOrbitBody’s POSITION-mode adoption fallback.

Reflection Conventions

Every stage class follows the same Reflect pattern:

void MyStage::Reflect(AZ::ReflectContext* context)
{
    if (auto* sc = azrtti_cast<AZ::SerializeContext*>(context))
    {
        if (sc->FindClassData(azrtti_typeid<MyStage>())) { return; }  // guard

        sc->Class<MyStage, IBodyStage>()   // base = IBodyStage / IAimStage / IAdditiveStage
            ->Version(1)
            ->Field("TargetMode", &MyStage::m_targetMode)
            ->Field("Offset",     &MyStage::m_offset)
            // ...
            ;

        if (auto* ec = sc->GetEditContext())
        {
            ec->Class<MyStage>("Display Name", "Tooltip description")
                ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "<h3>My Stage</h3>")
                ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MyStage::m_targetMode, "Target Mode", "...")
                    ->EnumAttribute(CamTargetMode::Transform, "Transform")
                    // ...
                ;
        }
    }
}

Hard rules for stage authors:

  • The FindClassData guard is mandatory. Multiple stages reflect the shared curve type / common enums chain, and an unguarded reflect call triggers a double-registration crash.
  • EnableForAssetEditor belongs under SerializeContext only, not EditContext. Stages don’t typically need this attribute, but it applies when reflecting asset types.
  • Members are named with the m_ prefix per the O3DE code-style guideline; field strings inside Reflect keep the un-prefixed form for save-data compatibility.

See Also

Stage variant catalogs:

For related PhantomCam pages:

  • Phantom Cameras — the owning component.
  • Cam Core — consumes published poses.
  • State Inheritance — the per-Body TryGet/AdoptPoseSnapshot protocol.
  • Orbit Profiles — .camorbit asset consumed by DynamicOrbitBody.
  • Noise Profiles — .camnoiseprofile asset consumed by PerlinNoise / ImpulseNoise.
  • Tug Fields — volume / source / proxy model; tug listeners are additive stages.
  • Group Targets — CamTargetMode::GroupTarget resolves through Cam Manager’s registry.

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.1.1 - Body Stage Variants

Body stage catalog — DefaultFollowBody, OrbitBody, DynamicOrbitBody, LeadingFollowBody, TrackBody. Each variant’s kinematic model, authored fields, and state-inheritance support.

The Body stage owns state.position for each tick. Five variants ship with GS_PhantomCam — each with its own kinematic model (orbit math, spring damping, path interpolation, band response, etc.). All variants share a target-routing front-end via the CamTargetMode enum documented in Stage Pipeline. Per-variant state inheritance support is summarized below; see State Inheritance for the full protocol.

Body stage slot on the Phantom Camera component

 

Contents


DefaultFollowBody

Simple follow camera. Tracks a target at an authored offset, position-space spring damped.

Kinematic model

destPos        = targetTM.translation + ApplyOffset(offset, targetTM, isRelative)
state.position = SimpleSpringDamperExact(m_idealPosition, m_springVelocity,
                                         destPos, halflife, dt)

Spring runs against an internal m_idealPosition per the decoupled-ideal pattern. On ctx.snapThisFrame, m_idealPosition = destPos directly.

Authored fields

FieldDefaultPurpose
m_targetModeTransformHow to resolve the follow target.
m_overrideEntity(entity slot)Used when m_targetMode == Override.
m_groupTargetName""Used when m_targetMode == GroupTarget.
m_offset(0, 0, 0)Offset applied before damping.
m_offsetIsRelativefalseWorld axes (false) or target-relative basis (true).
m_halflife0.1Spring halflife.

State inheritance

None. Default IBodyStage virtuals return false. Inheritance into a Default Follow falls through to the Blend Profile’s visual bridge.


OrbitBody

Fixed orbital pose around the target — authored yaw, pitch, and radius, no input. The “static orbit cam.” Successor to the retired StaticOrbit_PhantomCamComponent.

Kinematic model

destPos     = targetTM.translation + offset
pitchRad    = clamp(degToRad(m_orbitPitchDeg), -89°, +89°)
yawRad      = degToRad(m_orbitYawDeg)

orbitOffset = m_orbitRadius * (cos(pitch)*cos(yaw), cos(pitch)*sin(yaw), sin(pitch))
destPos    += orbitOffset

state.position = SimpleSpringDamperExact(m_idealPosition, m_springVelocity,
                                         destPos, m_halflife, dt)

The cached pivot m_lastPivot (the post-offset, pre-orbit pivot point) is published through TryGetPoseSnapshot for inheritance into orbit-style adopters.

Authored fields

FieldDefaultPurpose
m_targetModeTransformTarget routing.
m_overrideEntity—When m_targetMode == Override.
m_groupTargetName""When m_targetMode == GroupTarget.
m_offset(0, 0, 0)Pre-orbit offset.
m_offsetIsRelativefalseBasis.
m_orbitRadius5.0Distance from pivot to cam.
m_orbitYawDeg45.0Horizontal angle around pivot.
m_orbitPitchDeg20.0Vertical angle (clamped −89° to +89°).
m_halflife0.1Spring halflife (0 = snap).

State inheritance

DirectionBehavior
GetPublishes the cleanest snapshot in the framework: worldPos = m_idealPosition, worldFwd toward cached pivot, pivotPos = m_lastPivot, AND direct angular state from authored yaw / pitch (yawRad, pitchRad, angularStateValid = true). Orbit-style adopters receive ANGULAR-mode handoff with no back-derivation.
AdoptNot implemented — left at default false. The authored yaw / pitch define the static shot’s identity; accepting inbound pose would defeat author intent. Blend Profile’s visual bridge handles inbound transitions.

DynamicOrbitBody

Input-driven orbit camera around a target, shaped by a CameraOrbitShape (.camorbit) asset. The current go-to body for orbit-style player cams. Successor to the retired OrbitCam variant.

Unified drive model

The stage holds target angles (m_targetYaw, m_targetPitch) and damps the cam’s position toward whatever those angles imply on the orbit surface via the Orbital Solver Utility. Target angles are written by:

  1. Snap seeding (m_initialYawDeg / m_initialPitchDeg) on first activate or ctx.snapThisFrame.
  2. External scripted calls via DynamicOrbitBodyRequestBus::SetOrbit(yawRad, pitchRad).
  3. Per-tick input integration — when an OrbitInputProvider bus handler is bound to the cam’s entity (the Camera Input Reader component), the body polls input deltas and integrates them. Gated on ctx.hasFocus || ctx.alwaysUpdate || ctx.blendingOut.

No drive-mode enum. Multiple writers compose; last write wins per tick; solver damping shapes the visual response.

Static-orbit behavior is also achievable here by authoring initial angles and not installing an input provider — target angles never change, solver damps once, holds. The separate OrbitBody class is retained for the explicit “static shot” identity case.

Kinematic model

// 1. Resolve pivot (post-offset target).
pivot = targetTM.translation + offset
m_lastResolvedPivot = pivot   // cached for inheritance Get

// 2. Inheritance consume (if pending). Sets m_targetYaw / m_targetPitch /
//    m_idealPosition. See [State Inheritance].

// 3. Snap handling — reset target angles to authored seeds on
//    ctx.snapThisFrame (unless inheritance just consumed).
if (ctx.snapThisFrame && !consumedAdoption):
    m_targetYaw   = degToRad(m_initialYawDeg)
    m_targetPitch = degToRad(m_initialPitchDeg)

// 4. Input integration (if cam has an OrbitInputProvider AND
//    ctx.hasFocus || alwaysUpdate || blendingOut).
yawDelta, pitchDelta = poll OrbitInputProvider
m_targetYaw   += yawDelta   * degToRad(m_yawSpeed)   * dt
m_targetPitch += pitchDelta * degToRad(m_pitchSpeed) * dt
m_targetYaw   = WrapYawToPi(m_targetYaw)
m_targetPitch = clamp(m_targetPitch, kPitchAtLow, kPitchAtHigh)

// 5. Orbit shape evaluation.
shapePoint = m_orbitShape->EvaluateAtPitch(m_targetPitch)  // (radius, height)
desiredPos = pivot + (shapePoint.radius * cos(m_targetYaw),
                     shapePoint.radius * sin(m_targetYaw),
                     shapePoint.height)

// 6. Solver damping via BlendPositionAroundPivot.
alpha = HalflifeAlpha(m_blendHalflife, dt)
m_idealPosition = GS_Core::Math::BlendPositionAroundPivot(
                    m_idealPosition, desiredPos, pivot,
                    alpha, m_blendShape, +Z)

state.position = m_idealPosition

Authored fields

FieldDefaultPurpose
m_targetModeTransformTarget routing.
m_overrideEntity—When m_targetMode == Override.
m_groupTargetName""When m_targetMode == GroupTarget.
m_offset(0, 0, 0)Pivot offset from target.
m_offsetIsRelativefalseBasis.
m_orbitShape(asset slot).camorbit asset defining the surface. See Orbit Profiles.
m_yawSpeed90Degrees / sec per unit input on yaw.
m_pitchSpeed60Degrees / sec per unit input on pitch.
m_blendHalflife0.10Damping halflife — the solver call IS the smoothing.
m_blendShapeSphericalSolver shape (Spherical allows arbitrary surface; Cylindrical constrains height).
m_initialYawDeg180.0Snap seed yaw. 180° = directly behind target.
m_initialPitchDeg0.0Snap seed pitch. 0° = mid band height.
m_debugPrintfalsePer-tick AZ_Printf of inputs / target angles / shape output / committed position.

Companion bus

DynamicOrbitBodyRequestBus (per-entity, addressed by cam EntityId):

virtual void  SetOrbit(float yawRad, float pitchRad) = 0;
virtual float GetCurrentTargetYaw()   const = 0;
virtual float GetCurrentTargetPitch() const = 0;

SetOrbit uses ShortestYawTarget to keep the damping arc bounded — prevents the solver taking the long way around for distant yaw writes.

State inheritance

Full Get + Adopt. Two derivation paths:

PathTriggerBehavior
ANGULAR (preferred)snap.angularStateValid == true (source published authored angles)Inherit raw yawRad / pitchRad directly via ShortestYawTarget + WrapYawToPi + pitch clamp. Preserves angular continuity across different shape assets.
POSITION (fallback)Source has no angular state, just worldPos + optional pivotPosBack-derive yaw from (worldPos − pivot) via atan2; search the shape for the matching pitch via FindPitchOnShape.

Both seed m_idealPosition = snap.worldPos and set consumedAdoption = true so subsequent snap-clobber logic is gated.

Get publishes m_idealPosition, world-forward toward cached pivot, pivotPos = m_lastResolvedPivot, pivotEntity = m_lastResolvedPivotEntity, and direct authored m_targetYaw / m_targetPitch + angularStateValid = true.

Init contract

Init() overrides to force-load m_orbitShape via AssetManager::GetAsset + BlockUntilLoadComplete per the asset-fresh-load pattern. Without this, in-editor .camorbit edits don’t propagate to the runtime body until restart.


LeadingFollowBody

Held-position cam that slides along an arc around the target only when the target leaves an authored distance band. Leading-look / “Gears of War”-style follow — heading-agnostic, so abrupt 180° turns by the target don’t swing the cam.

Kinematic model

distXY = horizontal distance between cam and target (at offset's height)

if inner < distXY < outer:
    hold position   // cam stays put within the band
else if distXY > outer:
    slide toward target along an arc until just inside outer
else if distXY < inner:
    slide away from target along an arc until just outside inner

// Slides use the orbital solver around the target as pivot — cam arcs
// around the target during band response rather than cutting through.

// Z tracking: independent linear-halflife lowpass on the Z axis, mirroring
// target Z without affecting XY band math.

// Optional center-on-heading: after a configurable stillness delay, cam arcs
// around target to settle behind (or off-axis behind via m_idleYawOffsetDeg)
// at the current radius.

Authored fields

FieldDefaultPurpose
m_targetModeTransformTarget routing.
m_overrideEntity / m_groupTargetName—Mode-dependent.
m_offset(0, 0, 1.6)Offset applied before band math. Cam rides at this Z height.
m_offsetIsRelativefalseBasis.
m_innerRadius2.0Min cam-target XY distance.
m_outerRadius5.0Max cam-target XY distance.
m_hardClampEnabledfalseOptional absolute outer cap.
m_hardClampDistance8.0Hard cap (when enabled).
m_radialHalflife0.25Arc-slide halflife when outside the envelope.
m_heightHalflife0.50Independent Z follow halflife.
m_blendShapeCylindricalSolver shape for band response — cam stays in working height plane. Downgradable to Linear for short displacements.
m_centerOnHeadingfalseEnable idle reposition.
m_idleVelocityThreshold0.10 m/sXY speed below which the idle timer accumulates.
m_idleDelay1.50 sStillness duration before reposition engages.
m_reorientHalflife0.60Idle reposition arc halflife.
m_idleYawOffsetDeg0Offset from “directly behind” during reposition. ± shifts off-axis.

State inheritance

DirectionBehavior
GetReturns m_idealPosition, world-forward toward cached target, pivotPos = m_lastTargetPoint. No angular state.
AdoptPure stash. Evaluate consume block takes source snap.worldFwd, flattens to XY, places m_idealPosition = targetPoint − inboundFwd * seedDist at band-natural distance (lerp(inner, outer, 0.6)). Cam inherits source’s facing at this body’s preferred standoff. Z naturally lands at targetPoint.Z (inboundFwd is XY-flattened) — a from-below source produces a band-natural pose, not stuck-below framing.

Authoring gotcha. If the matched Blend Profile entry has m_inheritState unchecked, the snap-on-activation block fires instead of the consume block. Symptom: “cam orients behind the player’s travel direction regardless of source facing.” The fix is data-side — check the inherit flag in the asset.


TrackBody

Spline-bound dolly camera. Slides along a SplineComponent, interpolating two authored TrackBodyData blocks (start + end) by spline position (globalT 0..1). Successor to the retired Track_PhantomCamComponent.

Per-endpoint data blocks carry offset, halflife, and an optional FoV override.

Kinematic model

// Resolve nearest spline point to current target.
splineHit = spline->FindClosestWorldPoint(targetTM.translation)
globalT   = splineHit.normalizedDistance   // 0..1 along spline

// Lerp data blocks.
offset    = lerp(m_startData.offset,   m_endData.offset,   globalT)
halflife  = lerp(m_startData.halflife, m_endData.halflife, globalT)
fovDest   = (m_endData.fieldOfView > 0)
            ? lerp(m_startData.fieldOfView, m_endData.fieldOfView, globalT)
            : 0

// Position = spline point + offset (relative or world basis).
destPos        = splineHit.worldPoint + ApplyBasis(offset, splineTangent, isRelative)
state.position = SpringDamp(m_idealPosition, m_springVelocity, destPos, halflife, dt)

// Push FoV to owner via WriteLensFieldOfView so Cam Core picks it up live.
if (fovDest > 0):
    ctx.owner->WriteLensFieldOfView(fovDest)

Authored fields

FieldDefaultPurpose
m_targetModeTransformUsed to compute the closest spline point. Typically Transform.
m_overrideEntity / m_groupTargetName—Mode-dependent.
m_splineTrack(entity slot)Entity carrying a SplineComponent.
m_startData—Endpoint A (TrackBodyData).
m_endData—Endpoint B.
m_adoptionPathThreshold5.0 mInheritance: max distance from spline an inbound pose may sit before adoption is rejected.

TrackBodyData

struct TrackBodyData {
    AZ::Vector3 m_offset            = (0, 0, 0);
    bool        m_offsetIsRelative  = false;
    float       m_halflife          = 0.1f;
    float       m_fieldOfView       = 0.0f;   // 0 = don't push lens
};

State inheritance

DirectionBehavior
GetReturns m_idealPosition + world-forward toward cached m_lastTargetPos. No angular state. No pivot (the path is the kinematic reference, not a point).
AdoptPure stash. Evaluate consume block: EnsureSplineResolved, project snap.worldPos via FindClosestWorldPoint. Reject if Euclidean distance to projected point > m_adoptionPathThreshold — prevents teleport-snap when the source sits well off the dolly’s path. Inside threshold: seed m_idealPosition = snap.worldPos, clear spring velocity.

Spline resolution timing

EnsureSplineResolved is lazy — runs on first Evaluate with a valid target. Caches m_spline and m_splineEntity so subsequent ticks don’t re-look-up.


Cross-Variant Summary

VariantDegrees of FreedomDampingPivotInheritance GetInheritance Adopt
DefaultFollowBodyPosition onlySpringNone——
OrbitBodyAuthored yaw / pitch / radiusSpringPost-offset targetFull angularDisabled (Get-only)
DynamicOrbitBodyDynamic yaw / pitchSolverPost-offset targetFull angularFull (ANGULAR / POSITION)
LeadingFollowBodyXY band + Z followSolver (band-arc)Post-offset targetworldPos + fwdFacing-seeded standoff
TrackBodySpline-boundSpringNone (path)worldPos + fwdProject + threshold

See Also


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.1.2 - Aim Stage Variants

Aim stage catalog — DefaultLookAtAim, ClampedLookAim. Aim-origin convention, decoupled ideal rotation, extension points.

The Aim stage owns state.rotation for each tick. Two variants ship with GS_PhantomCam; other gems (notably gs_performer) register additional variants through the same IAimStage interface. All variants share a target-routing front-end via the CamTargetMode enum documented in Stage Pipeline.

Aim does not implement state inheritance by design — inheritance is body-only. The destination cam’s aim runs fresh on the inherited body pose.

Aim stage slot on the Phantom Camera component

 

Contents


Aim-Origin Convention

A subtle but important rule: every Aim variant uses ctx.camInitialTM.GetTranslation() as the aim origin — the cam’s current world position at the start of the tick — not state.position (which the Body just wrote this tick).

Aiming from a position the cam hasn’t moved to yet would produce a frame-delayed look-at vector. Using camInitialTM keeps the aim ray geometry consistent with where the cam is actually rendered from.

Author your own aim variants the same way.


Decoupled Ideal Rotation

Same convention as the Body stage. Aim variants hold an internal m_idealRotation slerp working value, separate from state.rotation. Downstream rotation-modifying additives (Noise, Impulse, Tug aim listener) can perturb state.rotation without poisoning next tick’s slerp trajectory.

A m_lastValidRotation field preserves the last non-degenerate rotation for zero-forward recovery (the case where the cam ends up coincident with its look-at target and the forward vector collapses).


DefaultLookAtAim

Straightforward look-at. Aims the camera at a target (with optional offset), slerp-damped.

Kinematic model

lookAtPoint = targetTM.translation + ApplyOffset(offset, targetTM, isRelative)
state.lookAtPoint = lookAtPoint        // sidecar — for downstream Reposition consumers

aimOrigin = ctx.camInitialTM.translation     // NOT state.position
forward   = (lookAtPoint - aimOrigin).GetNormalizedSafe()

if (forward.GetLengthSq() < epsilon):
    targetRot = m_lastValidRotation          // zero-forward recovery
else:
    targetRot = Quaternion::CreateLookAt(forward, +Z)
    m_lastValidRotation = targetRot

if (ctx.snapThisFrame || !m_hasIdeal):
    m_idealRotation = targetRot
    m_hasIdeal      = true
else:
    alpha = HalflifeAlpha(m_halflife, dt)
    m_idealRotation = m_idealRotation.Slerp(targetRot, alpha)

state.rotation = m_idealRotation

Authored fields

FieldDefaultPurpose
m_targetModeTransformLook-at target routing.
m_overrideEntity—When m_targetMode == Override.
m_groupTargetName""When m_targetMode == GroupTarget.
m_offset(0, 0, 0)Offset applied to look-at point.
m_offsetIsRelativefalseWorld axes (false) or target-relative basis (true).
m_halflife0.1Slerp halflife.

ClampedLookAim

Aims at a target clamped within a pitch / yaw envelope relative to a starting forward captured on first evaluation. Replacement for the retired ClampedLook_PhantomCamComponent.

Used for first-person and constrained-look scenarios — e.g. the cam can rotate ±45° pitch and ±90° yaw from its initial facing, but not beyond.

Two clamp modes

bool m_localSpace;
ModeBehavior
false (world-space, default)Yaw clamps around world +Z, pitch clamps in the world-vertical plane. Yaw / pitch reconstruction from quaternion.
true (local-space pivot)Pivot relative to m_startingForward. Useful when the cam should rotate within a local envelope independent of world axes.

Kinematic model (world-space mode)

On first Evaluate or ctx.snapThisFrame:
    m_startingForward = ctx.camInitialTM.GetRotation()   // capture once

lookAtPoint = targetTM.translation + offset
forward     = (lookAtPoint - ctx.camInitialTM.translation).GetNormalizedSafe()

// Convert forward to yaw / pitch.
yaw   = atan2(forward.y, forward.x)
pitch = asin(forward.z)

// Clamp to envelope (degrees → radians for comparison).
yaw   = clamp(yaw,   degToRad(m_minRelClamp.z), degToRad(m_maxRelClamp.z))
pitch = clamp(pitch, degToRad(m_minRelClamp.x), degToRad(m_maxRelClamp.x))

// Reconstruct quaternion from clamped angles.
clampedForward = (cos(pitch)*cos(yaw), cos(pitch)*sin(yaw), sin(pitch))
targetRot      = LookAtQuaternion(clampedForward, +Z)

// Slerp damping with decoupled m_idealRotation.
state.rotation = Slerp(m_idealRotation, targetRot, HalflifeAlpha(m_halflife, dt))

Authored fields

FieldDefaultPurpose
m_targetModeTransformLook-at target routing.
m_overrideEntity / m_groupTargetName—Mode-dependent.
m_offset(0, 0, 0)Look-at offset.
m_offsetIsRelativefalseBasis.
m_minRelClamp(-45, 0, -90)Lower bound — x = pitch min, z = yaw min. (Layout mirrors the legacy component for scene-data migration.)
m_maxRelClamp(45, 0, 90)Upper bound — x = pitch max, z = yaw max.
m_localSpacefalseClamp mode (world-space or local-space pivot).
m_halflife0.1Slerp halflife.

Runtime state

FieldPurpose
m_startingForwardCaptured on first Evaluate. The “origin” relative to which the clamp envelope is anchored.
m_hasStartingForwardTrue after first capture.
m_idealRotationDecoupled slerp working value.
m_hasIdealTrue after first ideal write.
m_lastValidRotationLast non-degenerate rotation, used for zero-forward recovery.

Extension Surface

Other gems can ship additional aim stage variants. The editor’s type-picker on the cam’s m_aimSlot enumerates all derived IAimStage classes registered through SerializeContext. gs_performer, for example, registers aim stages that read performer head / eye tracking state.

Convention for authoring an aim variant in another gem:

  • Header in Code/Include/<GemName>/Stages/Variants/<MyAim>.h.
  • Source in Code/Source/Stages/Variants/<MyAim>.cpp.
  • Use AZ_RTTI(<MyAim>, "{UUID}", IAimStage) so the picker finds it.
  • Reflect with the FindClassData guard.
  • Honor the aim-origin convention — use ctx.camInitialTM.translation, not state.position.
  • Use the decoupled ideal rotation pattern if you want downstream Noise / Impulse / Tug aim listener to behave well.

See Also


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.1.3 - Additive Stage Variants

Additive stage catalog — collision and tug listener Reposition stages, Perlin and Impulse Noise stages. Phase semantics, composition rules, per-variant fields.

Additive stages stack — multiple may run per cam. Each declares its phase via GetStage():

  • Reposition — runs BEFORE noise. Corrections (collision, occlusion, tug reposition). State after this phase is captured as m_stablePose.
  • Noise — runs AFTER reposition. Perturbations (camera shake, drift, impulses). State after this phase is captured as m_finalPose.

The Phantom Camera component partitions additives into m_repositionStages and m_noiseStages once at slot-assign time so per-tick code does not re-check the enum each frame.

Additive stages list on the Phantom Camera component

 

Contents


Reposition Additives

CollisionReposition

Soft / hard sphere collision correction.

Header: Stages/StageDefaults.h · Source: Source/Stages/StageDefaults.cpp

Algorithm

Sphere-cast from (target root + anchorOffset) toward the Body’s ideal cam position (state.position). Two radii define behavior:

  • Inner radius — hard clamp. Cam is never permitted closer to the hit surface than this. Correction is immediate.
  • Outer radius — soft buffer. Cam glides toward this preferred distance from the wall over halflife seconds via a damped cached correction delta.

When the Body’s ideal is inside the outer zone (between inner and outer), only the soft correction applies. When it is past the inner zone (closer than inner), hard correction snaps the cam to the inner boundary, and soft continues gliding it toward the outer resting spot.

The cached correction m_appliedCorrection damps cleanly toward zero when the target walks out of collision — no feedback loop fighting the Body’s spring (which runs on its own m_idealPosition per the decoupled-ideal pattern).

Authored fields

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_anchorOffset(0, 0, 1.5)Added to target root to produce the cast origin. Typically cam-height so the cast sweeps at cam altitude.
m_innerRadius0.15Hard clamp distance. Serialized as "Radius" for back-compat.
m_outerRadius0.45Soft buffer distance. Clamped at evaluate time to be ≥ inner.
m_halflife0.15Soft correction halflife (0 = snap).
m_collisionGroupId(null = All)UUID reference to a PhysX collision-group preset. Rendered as a dropdown by PhysX’s property handler — matches the “Collides With” field on PhysX colliders.

OcclusionReposition

Stub. Math is future work. The damping scaffold (m_halflife, m_appliedCorrection) is in place so authored fields are stable when the real implementation lands.

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_halflife0.15Correction halflife.

TugAimListener

Reposition-phase consumer of Tug Fields. Slerps state.rotation toward look-at the smoothed tug source point while a TugFieldProxyComponent on the rig is in contact with a tug volume.

Header: Stages/TugListeners.h · Source: Source/Stages/TugListeners.cpp

Derives from the abstract TugListenerBase which holds the channel matching, proxy resolution, active-source cache, and smoothed-influence state.

Authored fields

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_channels[]String tags the listener matches against tug volume channels. Crc32-cached at activate.
m_blendHalflife0.15Smoothing halflife for influence and source-point.
m_strength1.0Per-cam force multiplier.

Per-tick algorithm

1. ResolveProxy(ctx)
   - If target changed since last tick, walk for proxy descendant.
   - If proxy changed, bind to it and repopulate from volume world.

2. Sum contributions from m_activeSources (weighted by source strength × channel match).
   totalInfluence, weightedSourcePoint = Σ source contributions

3. If totalInfluence == 0 AND m_smoothedInfluence ≈ 0:
     m_dormant = true; early-return.

4. If transitioning out of dormancy:
     m_smoothedSourcePoint = GetSeedSourcePoint(state) — a point along the cam's
     current forward, so the ramp-in produces zero displacement at first engagement.

5. Damp m_smoothedInfluence and m_smoothedSourcePoint toward their target values.

6. ApplyModulation: derive the "natural" rotation from ctx (cam toward primary target)
   rather than reading state.rotation. Avoids compound-feedback issue where prior tug
   modifications would loop into next tick's blend source.

TugBodyListener

Reposition-phase consumer of Tug Fields. Lerps state.position toward the smoothed source point.

Header: Stages/TugListeners.h · Source: Source/Stages/TugListeners.cpp

Same base class, fields, and dormancy semantics as TugAimListener.

Algorithm differences from TugAimListener

StepTugBodyListener
GetSeedSourcePointReturns state.position — first engaged tick produces zero displacement (the cam doesn’t yank).
ApplyModulationDerives the natural pose source from ctx.targetTM and the Body’s published state.bodyAnchor sidecar, not state.position directly. Prevents compound feedback.

A cam can run only the aim listener, only the body listener, both, or neither — each is independent.


Noise Additives

PerlinNoise

Continuous Perlin-noise displacement. Samples a Camera Noise Profile (.camnoiseprofile) through GS_Core::Noise::Perlin1D across six channels (translation X / Y / Z, rotation pitch / yaw / roll).

Header: Stages/NoiseStages.h · Source: Source/Stages/NoiseStages.cpp

Algorithm

m_time += deltaTime

For each of 6 channels in profile:
    sample = Perlin1D(m_time * profile.frequency[i] * m_frequencyGain,
                      profile.octaves[i],
                      profile.persistence[i])
    delta[i] = sample * profile.amplitude[i] * m_amplitudeGain

state.position += (delta[Tx], delta[Ty], delta[Tz])
state.rotation  = state.rotation * Quaternion::CreateFromEuler(
                    delta[Pitch], delta[Yaw], delta[Roll])

Never reads back the committed transform between frames — only adds deltas to state in-flight. Body / Aim ideals stay untouched (decoupled-ideal convention).

Authored fields

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_profile(asset slot).camnoiseprofile asset.
m_amplitudeGain1.0Multiplier over profile amplitudes.
m_frequencyGain1.0Multiplier over profile frequencies.

Init contract

Init() force-loads m_profile via AssetManager per the asset-fresh-load pattern.

ImpulseNoise

Event-triggered one-shot noise burst. Same profile schema as PerlinNoise, same layered Perlin math — only the time source differs.

Stays silent until Trigger(strength) fires, then plays a time-windowed burst shaped by an ADSR envelope (GS_Core::Envelope::ADSREnvelope).

Triggered externally via PhantomCameraRequestBus::TriggerCameraImpulse(strength) on the cam — see Phantom Cameras. The author’s own impulse-source detection system (explosions, foot-stomps, gun kicks) decides when and with what strength.

Stacks freely with PerlinNoise on the same cam — baseline handheld sway plus event-driven shake is the canonical configuration.

Algorithm

On Trigger(strength):
    m_elapsed         = 0.0
    m_triggerStrength = strength
    m_active          = true       // overrides any in-progress release

Per tick (if m_active):
    m_elapsed += deltaTime
    envelopeGain = m_envelope.Evaluate(m_elapsed)   // ADSR 0..1
    if (envelopeGain == 0 AND past sustain end):
        m_active = false
    else:
        // Same Perlin sampling as PerlinNoise,
        // scaled by envelopeGain × triggerStrength × m_amplitudeGain.
        Apply position + rotation deltas to state.

Authored fields

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_profile(asset slot).camnoiseprofile.
m_envelope(ADSR struct)Attack / Decay / Sustain level / Sustain duration / Release. See GS_Core::Envelope::ADSREnvelope.
m_amplitudeGain1.0Multiplier over profile amplitudes.
m_frequencyGain1.0Multiplier over profile frequencies.

Trigger entry point

void ImpulseNoise::Trigger(float strength);

Called by GS_PhantomCameraComponent::TriggerCameraImpulse(strength) for every ImpulseNoise additive on the cam. strength multiplies amplitude for that specific burst. Pass 1.0 for full profile intensity, smaller for attenuated distance falloff, larger to boost.

The owning component routes the call by walking m_noiseStages and dispatching to each ImpulseNoise it finds via RTTI check. Other noise additive types ignore.


Cross-Variant Summary

VariantPhaseReads ctx natural pose?Notes
CollisionRepositionReposition—PhysX sphere-cast pushback. Soft buffer + hard clamp.
OcclusionRepositionReposition—Stub. Damping scaffold present.
TugAimListenerRepositionYes (cam-to-target forward)Slerps rotation toward source.
TugBodyListenerRepositionYes (target / bodyAnchor)Lerps position toward source.
PerlinNoiseNoise—Continuous Perlin sampled by accumulated time.
ImpulseNoiseNoise—Event-triggered; ADSR-gated.

See Also


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.2 - Noise Profiles

CameraNoiseProfile (.camnoiseprofile) — reusable Perlin-noise shape asset for camera shake, handheld feel, and event-triggered impulses.

A Camera Noise Profile is a .camnoiseprofile data asset that defines a layered Perlin-noise shape. It is consumed by the PerlinNoise (continuous) and ImpulseNoise (event-triggered) additive stages. Profiles are reusable — one “Handheld_subtle” profile can drive both a baseline sway and a triggered impact burst at different intensities by adjusting per-stage gains.

Profiles are registered through PhantomCamDataAssetsSystemComponent alongside .camblendprofile and .camorbit. The asset reflects with EnableForAssetEditor so the Asset Editor opens it directly.

Camera Noise Profile asset in the O3DE Asset Editor

 

Contents


Authoring Model — Six Per-Axis Layer Stacks

Six independent layer lists, one per axis:

using LayerList = AZStd::vector<GS_Core::Noise::NoiseLayer>;

LayerList m_posX, m_posY, m_posZ;   // position (world-space meters)
LayerList m_rotX, m_rotY, m_rotZ;   // rotation (Euler degrees, applied as multiplicative deltas)

Each layer is a GS_Core::Noise::NoiseLayer carrying amplitude, frequency, and phase. At evaluate time, the noise stage sums every layer’s Perlin1D contribution per axis, scaled by amplitude.

Layered noise is the common authoring rhythm — three layers per axis: one slow / low-frequency layer for sway, one mid layer for flutter, one fast / low-amplitude layer for micro-jitter. Each layer composes additively at evaluate time.


NoiseLayer

The primitive lives in GS_Core:

namespace GS_Core::Noise {
    struct NoiseLayer {
        float m_amplitude = 0.0f;
        float m_frequency = 1.0f;
        float m_phase     = 0.0f;
    };

    // Sums layers via GS_Core::Noise::LayeredPerlin1D.
    // Each layer samples Perlin1D(time * freq + phase) * amp.
}

The same NoiseLayer primitive is used elsewhere in the engine — UI wobble, unit idle jitter, etc. — so multiple subsystems share the underlying Noise utility. The full per-axis evaluation goes through GS_Core::Noise::LayeredPerlin1D documented there.


Unit Conventions

Axis groupUnitsTypical amplitude
Position layersmeters0.01 – 0.2
Rotation layersdegrees0.1 – 8
Frequency (any axis)HzHandheld profiles 0.1 – 2 Hz; shake / impact 3 – 60 Hz

Authored Fields

FieldDefaultPurpose
m_posX / m_posY / m_posZ{}Layer stacks for translation noise (meters).
m_rotX / m_rotY / m_rotZ{}Layer stacks for rotation noise (degrees).
m_description""Free-form note for preset identity, intent, tweak history.

Consumption Pattern

A profile is shared across multiple cams / stages. Per-cam intensity dialing happens on the additive stage, not on the profile:

// On a PerlinNoise additive:
m_profile        = (asset slot)            // shared profile reference
m_amplitudeGain  = 1.0                     // per-cam intensity multiplier
m_frequencyGain  = 1.0                     // per-cam speed multiplier

So a single “Handheld_subtle” profile can drive both a subtle baseline sway (m_amplitudeGain = 0.3) and a full-strength impact shake (m_amplitudeGain = 2.0) without re-authoring the profile.

Both consumers honor the same fields:

StageUseTime source
PerlinNoiseContinuous handheld sway, driftm_time += deltaTime, accumulated since activation
ImpulseNoiseEvent-triggered burstm_elapsed += deltaTime since Trigger(strength), gated by ADSR envelope

See Additive Stage Variants for the per-stage kinematic model.


Shipped Presets

The gem ships a starter library of .camnoiseprofile assets in gs_phantomcam/Assets/Noise Profiles/. Drop a preset onto a PerlinNoise or ImpulseNoise stage’s Profile slot for an immediate baseline, then either tune intensity per cam via the stage’s m_amplitudeGain / m_frequencyGain or author project-specific profiles.

Handheld lens family

Three lens characters × two intensities. Rotation-dominant; long-period sway with mid-frequency flutter and a touch of micro-jitter.

AssetUse for
Normal_Mild.camnoiseprofileNormal-lens handheld baseline — balanced position and rotation, moderate frequencies.
Normal_Intense.camnoiseprofileAggressive normal-lens handheld — same shape, larger amplitudes.
Telephoto_Mild.camnoiseprofileTelephoto-lens handheld baseline — tighter angular response, low position amplitude.
Telephoto_Intense.camnoiseprofileAggressive telephoto handheld — same shape, larger amplitudes.
Wide_Mild.camnoiseprofileWide-lens handheld baseline — translation dominates; rotation low.
Wide_Intense.camnoiseprofileAggressive wide-lens handheld — larger translation amplitudes.

All-axes shake family

Position + rotation across all six axes. Use for event-triggered impulses (ImpulseNoise) or aggressive ambient shake.

AssetUse for
6D_Shake.camnoiseprofileEarthquake / impact aftermath — all six axes, high-frequency micro-jitter on top of mid-frequency sway.
6D_Wobble.camnoiseprofileFloaty / dreamlike — all six axes, low-frequency long sway.

Authoring more

The full per-axis layer values are inspectable in the Asset Editor when you open any preset. To author additional characters — fast-handheld for chase sequences, ultra-mild for cutscenes, gunfire impulse profiles — follow the authoring conventions above starting from a copy of the closest shipped preset.


Creating a Noise Profile

  1. Open the Asset Editor in O3DE.
  2. Select New and choose CameraNoiseProfile from the asset type list.
  3. For each axis where you want noise, add layers via the + button. Three layers per axis is the typical authoring rhythm (slow / mid / fast).
  4. For each layer:
    • Set the Amplitude in axis-appropriate units (meters for position, degrees for rotation).
    • Set the Frequency in Hz.
    • Optionally set a Phase offset (decorrelates layers from each other when they share frequency).
  5. Set the Description to record the preset’s intent or tuning history.
  6. Save the asset.
  7. Assign the asset to a PerlinNoise or ImpulseNoise additive stage’s Profile slot.

See Also

Consumers:

Underlying utility:

  • GS_Core Noise — the Perlin primitives, NoiseLayer struct, and LayeredPerlin1D that this asset stores and feeds.

Related assets:

Basics-side authoring guide:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.3.3 - Orbit Profiles

CameraOrbitShape (.camorbit) — parametric orbit-surface asset for DynamicOrbitBody. Three-band power-bulge family with arc-length reparameterization for constant spatial speed.

A Camera Orbit Profile is a .camorbit data asset that defines a parametric orbit surface around a target. It is consumed by the DynamicOrbitBody Body stage variant. Authors define three bands (low / mid / high) of (radius, height) and a Roundness slider that walks a power-bulge family — diamond → sphere → cube — across them. Arc-length reparameterization keeps spatial speed constant as the cam traces the shape, for any roundness value.

Profiles are registered through PhantomCamDataAssetsSystemComponent alongside .camblendprofile and .camnoiseprofile. The asset reflects with EnableForAssetEditor so the Asset Editor opens it directly.

Camera Orbit Shape asset in the O3DE Asset Editor

 

Contents


Three-Band Authoring Model

Authors define three (height, radius) bands around the target:

struct OrbitBand {
    float m_height = 0.0f;   // local-Z offset from cam target (meters)
    float m_radius = 5.0f;   // XY-plane distance from cam target (meters)
};
BandDefaultSelected at pitch
m_lowBand(-2.0, 4.0)−π/2 (cam below pivot)
m_midBand( 0.0, 5.0)0 (the “centered / level” rest position)
m_highBand( 3.0, 3.0)+π/2 (cam above pivot)

Z-up convention. m_height is the local-Z offset from the cam target; m_radius is the XY-plane distance.


Pitch Convention

Pitch ∈ [-π/2, +π/2] radians. Pitch is decoupled from the bands’ authored geometry:

  • pitch = 0 → mid band exactly (the “centered / level” rest pose).
  • pitch = +π/2 → high band.
  • pitch = −π/2 → low band.

Authors can put m_midBand.m_height at any value — pitch = 0 always selects mid regardless. Positive pitch always moves the cam toward high; negative always toward low. The bands’ actual heights / radii determine the cam’s spatial position; pitch is a band-interpolation parameter, NOT a literal elevation angle.

This decoupling lets authors think in spatial terms (mid is where the cam rests; low / high are the framing extremes) without needing mid.height = 0 for pitch = 0 to mean “level.”


Shape Family — Roundness Power-Bulge

A single m_roundness slider walks a power-bulge family:

pos(t) = chord(t) + p(t) · offset
where:
    chord(t) = lerp(endpoint, opposite endpoint, t)   // straight line low → high
    offset   = mid − midpoint(low, high)              // how far the bulge pokes out at mid
    p(t)     = 1 − |2t − 1|^n                         // bulge envelope
    n        = piecewise-linear in m_roundness         // 0 → 1 → kMaxBulge

p(t) is 0 at endpoints (t = 0 and t = 1) and 1 at midpoint (t = 0.5) for any n.

m_roundnessnShapeDescription
0.01DiamondPiecewise linear. Sharp corner at mid. Bulge envelope is 1 − |2t − 1| — a tent.
0.52SphereSmooth quadratic through low / mid / high. Bulge envelope is 1 − (2t − 1)². Default.
1.0~6 (kMaxBulge)Square / rounded-squareFlat sides, sharp tapers near low / high. Bulge envelope is 1 − |2t − 1|^6 — almost a square wave.

Mid is always the apex of the bulge for any n. pitch = 0 always lands exactly on mid. Angular velocity stays smooth throughout the slider’s range — no derivative discontinuities.


Curve Mode (Advanced)

For non-monotonic shapes (Bounce, Elastic, Back) that the power-bulge family cannot produce, authors can switch to ShapingMode::Curve and pick a GS_Core::CurveType enum value:

enum class ShapingMode : AZ::u8 {
    Roundness,  // power-bulge family (default)
    Curve,      // CurveType enum (advanced, supports overshoot)
};

When m_shapingMode == Curve, m_curve is consulted instead of m_roundness. The asset’s IsRoundnessVisible / IsCurveVisible predicates hide whichever field doesn’t match the current mode.

Curve mode skips arc-length reparameterization. Overshooting curves expressly want temporal shape, not constant spatial speed.


Arc-Length Reparameterization

EvaluateAtPitch treats input pitch as an arc-length fraction along the (radius, height) curve, not the curve’s intrinsic parameter t.

Why: without reparameterization, the cam covers wildly unequal spatial distances per unit pitch input. On a square shape (high roundness), constant pitch would whip past the tapers near low / high and crawl along the flat sides — feels broken.

With reparameterization:

  1. Internally sample the raw curve at 64 t-points, building a cumulative-length table.
  2. For a requested pitch fraction s ∈ [0, 1], resolve the t such that cumulative arc length up to that t equals s · total length.
  3. Evaluate the raw curve at that t.

Result: constant pitch input → constant spatial speed of the cam along the orbit surface, for any roundness value.

Arc-length reparameterization is applied in Roundness mode only. Curve mode skips it.


Evaluation Entry Point

void CameraOrbitShape::EvaluateAtPitch(
    float  pitchRad,
    float& outRadius,
    float& outHeight) const;
  • pitchRad is clamped to [kPitchAtLow, kPitchAtHigh] = [-π/2, +π/2].
  • Returns (radius, height) — XY-plane distance from the cam target plus local-Z offset.

The DynamicOrbitBody consumer wraps the call:

shape->EvaluateAtPitch(m_targetPitch, radius, height);
desiredPos = pivot + Vector3(radius * cos(m_targetYaw),
                             radius * sin(m_targetYaw),
                             height);

Constants

static constexpr float kPitchAtLow  = -π/2;
static constexpr float kPitchAtMid  =  0.0;
static constexpr float kPitchAtHigh =  π/2;

Available for editor visualization, body pitch clamping, and adoption-time pitch clamping.


Authored Fields

FieldDefaultPurpose
m_lowBand(-2.0, 4.0)(height, radius) at pitch = −π/2.
m_midBand( 0.0, 5.0)(height, radius) at pitch = 0. The rest pose.
m_highBand( 3.0, 3.0)(height, radius) at pitch = +π/2.
m_shapingModeRoundnessRoundness or Curve.
m_roundness0.50..1. Only visible when m_shapingMode == Roundness. 0 = diamond, 0.5 = sphere, 1 = square.
m_curveLinearGS_Core::CurveType enum. Only visible when m_shapingMode == Curve.
m_description""Free-form author description.

Creating an Orbit Profile

  1. Open the Asset Editor in O3DE.
  2. Select New and choose CameraOrbitShape from the asset type list.
  3. Set the three bands:
    • Low Band — (height, radius) for the cam-below-pivot extreme.
    • Mid Band — (height, radius) for the cam’s level / rest pose.
    • High Band — (height, radius) for the cam-above-pivot extreme.
  4. Choose a Shaping Mode:
    • Roundness (default) — set the slider between 0 (diamond), 0.5 (sphere), 1 (square).
    • Curve (advanced) — pick a CurveType enum value for overshoot / bounce effects. Note that curve mode skips arc-length reparameterization.
  5. Set the Description to record the preset’s intent.
  6. Save the asset.
  7. Assign the asset to a DynamicOrbitBody stage’s Orbit Shape slot.

See Also

Consumer:

Math primitive:

Related assets:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.4 - Camera Influence Fields

Global and spatial camera influence components — priority modifiers that affect phantom camera selection without changing base priorities. Channel-aware routing via target entity.

Camera Influence Fields modify the effective priority of Phantom Cameras without changing their base priority values. They call AddCameraInfluence(sourceEntity, targetEntity, camName, influence) and RemoveCameraInfluence(sourceEntity, targetEntity) on the Cam Manager bus. The Cam Manager routes the influence to the channel that owns the target entity. Multiple influences on the same camera stack additively within a channel.

There are two influence component types:

  • GlobalCameraInfluenceComponent — Applies a priority influence globally for its entire active lifetime. Place this on the StageData entity so it activates and deactivates automatically with the stage.
  • CameraInfluenceFieldComponent — Applies a priority influence only when an entity (typically a player unit) enters a defined spatial volume. Requires a PhysX Collider set as a trigger on the same entity. See Physics Trigger Volume Utility.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

 

Contents


How It Works

Global Influence

The GlobalCameraInfluenceComponent applies a constant priority modifier to a named phantom camera. On Activate(), it calls AddCameraInfluence on the Cam Manager bus. On Deactivate(), it calls RemoveCameraInfluence.

Placement: Add this component to the StageData entity. Because StageData activates at stage load and deactivates at stage unload, the camera influence is automatically scoped to the stage that defines it — no manual enable/disable management needed.

Global influences have no specific target entity, so in multi-channel projects they currently route through the channel-0 fallback. For Tier 3 projects that need a “global” influence applied to every channel, the cleaner pattern is one Global influence per channel — or use the Tug Field model which doesn’t go through the influence bus.

Spatial Influence

The CameraInfluenceFieldComponent uses a PhysX Collider (trigger mode) to detect when an entity enters or exits a defined region. On entry, it adds the influence with the crossing entity as the routing target; on exit, it removes the influence. See Physics Trigger Volume Utility for collider setup.

This is useful for level design — switching to an overhead camera when the player enters a specific room, or boosting a scenic camera in a vista area.

Influence Stacking

Multiple influences can be active on the same camera within a channel simultaneously. The Cam Manager sums all active influences with the base priority to compute the effective priority used during EvaluatePriority. The sourceEntity parameter is the per-channel storage key, so multiple overlapping fields applied to the same channel do not collide.


Channel Routing

The bus signature carries two entity arguments:

AddCameraInfluence(
    AZ::EntityId sourceEntity,   // the field volume's entity (storage key)
    AZ::EntityId targetEntity,   // the entity that triggered the influence (routing key)
    AZStd::string camName,
    AZ::u32 influence);

The Cam Manager looks up the channel via GetChannelForTarget(targetEntity):

Resolution resultBehavior
Target is bound to a channelInfluence is stored in that channel’s priority table. Only that channel’s arbitration sees the boost.
Target is not bound to any channelInfluence is silently dropped.

This gives clean isolation between players in co-op (player 1 entering a field does not affect player 2’s cam priorities) and natural behavior for non-player triggers (an enemy walking into a player-cam field doesn’t influence anything).

Subtle limitation. If an entity enters a field BEFORE SetTarget runs on its channel, the influence is dropped at trigger-enter time and not replayed when the target is later bound. Acceptable for typical “set target on player spawn” project patterns.


Cam Influence Data

The component authoring surface uses a CamInfluenceData structure to define the effect of one influence.

FieldTypeDescription
CameraNameAZStd::stringEntity name of the phantom camera to influence. Must match exactly.
InfluenceAZ::u32Priority modifier applied to the named camera’s effective priority during arbitration.

API Reference

The influence bus methods live on CamManagerRequestBus. Field components call the Cam Manager directly; the influence components do not expose their own request bus for AddCameraInfluence / RemoveCameraInfluence.

Request Bus: CamManagerRequestBus (influence methods)

MethodParametersReturnsDescription
AddCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntity, AZStd::string camName, AZ::u32 influencevoidAdds a priority influence to the named camera in the channel that owns targetEntity. Silently dropped if the target isn’t channel-bound.
RemoveCameraInfluenceAZ::EntityId sourceEntity, AZ::EntityId targetEntityvoidRemoves the influence stored under (sourceEntity, targetEntity).

Per-component inspection buses (GlobalCameraRequestBus and similar) expose component-local queries (GetGlobalCamera, etc.) for editor and runtime tooling but do not duplicate the influence-add API.

Note. AddCameraInfluence is not exposed to BehaviorContext / Script Canvas. Field components call it via C++ broadcast. Gameplay code that needs to drive influences from SC should author per-component surfaces or use the Tug Field system.


Component Reference

GlobalCameraInfluenceComponent

Applies a camera priority influence globally for its entire active lifetime.

GlobalCameraInfluenceComponent in the O3DE Inspector

PropertyTypeDescription
CamInfluenceDataCamInfluenceDataThe camera name and influence value to apply.

Behavior: On Activate(), calls AddCameraInfluence(GetEntityId() /*source*/, AZ::EntityId() /*target*/, camName, influence). The invalid target id causes the influence to route through the channel-0 fallback in single-player projects.

On Deactivate(), calls RemoveCameraInfluence(GetEntityId(), AZ::EntityId()).

Placement: Add to the StageData entity to scope the influence to the stage lifecycle.


CameraInfluenceFieldComponent

Applies a camera priority influence when an entity enters a spatial trigger volume.

CameraInfluenceFieldComponent in the O3DE Inspector

PropertyTypeDescription
CamInfluenceDataCamInfluenceDataThe camera name and influence value to apply when triggered.

Behavior: Requires a PhysX Collider (trigger) on the same entity. Inherits from GS_Core::PhysicsTriggerComponent for the trigger logic.

TriggerEnter(crossingEntity):
    CamManagerRequestBus::Broadcast(
        AddCameraInfluence,
        GetEntityId(),    // source — this field volume
        crossingEntity,   // target — what walked in (routing key)
        camName,
        influence);

TriggerExit(crossingEntity):
    CamManagerRequestBus::Broadcast(
        RemoveCameraInfluence,
        GetEntityId(),
        crossingEntity);

The crossingEntity is what makes this component channel-aware. In Tier 3 multi-channel projects, only the channel whose target matches the crossing entity sees the influence.

Setup:

  1. Add CameraInfluenceFieldComponent to an entity.
  2. Add a PhysX Collider (set as trigger) to the same entity. See Physics Trigger Volume Utility.
  3. Configure the PhysX collision filter so the collider detects the entities you want to trigger on (typically player units).
  4. Set the Camera Name to the target phantom camera’s entity name.
  5. Set the Influence value (positive to boost, negative to reduce priority).

Usage Examples

C++ — Adding an Influence Directly

#include <GS_PhantomCam/GS_CamManagerBus.h>

// Boost "CinematicCam" priority by 50 for player1 during a cutscene.
GS_PhantomCam::CamManagerRequestBus::Broadcast(
    &GS_PhantomCam::CamManagerRequests::AddCameraInfluence,
    AZ::EntityId(GetEntityId()),  // source — your gameplay system's entity
    AZ::EntityId(player1Entity),  // target — routes to player1's channel
    AZStd::string("CinematicCam"),
    AZ::u32(50));

// Remove the boost when the cutscene ends.
GS_PhantomCam::CamManagerRequestBus::Broadcast(
    &GS_PhantomCam::CamManagerRequests::RemoveCameraInfluence,
    AZ::EntityId(GetEntityId()),
    AZ::EntityId(player1Entity));

For a Tier 1 / single-player project, pass AZ::EntityId() for targetEntity to route through channel 0’s fallback.


See Also

For related PhantomCam components:

For related utilities:

For conceptual overviews and usage guides:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.5 - Group Targets

GroupTargetComponent — weighted-centroid focal entity. Computes a centroid from a subject list and writes it to its own transform. Phantom Cameras point at it like any other target.

A Group Target is an entity whose world transform is the weighted centroid of a runtime-editable subject set. Phantom Cameras with CamTargetMode::GroupTarget point at it like any other target. The Cam Manager hosts a global name → entity registry so stages can resolve a group entity by string name without holding a direct reference.

Typical use cases:

  • Two-player co-op — a group target tracks both players; the cam follows the group target.
  • “Move when group is contained” cinematic cams — frame all subjects, ignore individual wandering.
  • Combat encounters with multiple participants — track the weighted centroid of all combatants.
  • Collapse-to-single-view in multi-channel projects — pair with a shared TrueUnique cam to detect when all channels converge.

GroupTargetComponent in the O3DE Inspector

 

Contents


Registry Pattern

The Cam Manager owns a global name → entity registry for group targets.

On GroupTargetComponent::Activate:

CamManagerRequestBus::Broadcast(
    &CamManagerRequests::RegisterGroupTarget,
    m_name,
    GetEntityId());

On Deactivate:

CamManagerRequestBus::Broadcast(
    &CamManagerRequests::UnregisterGroupTarget,
    GetEntityId());

Stages resolve a group entity via:

AZ::EntityId groupEntity;
CamManagerRequestBus::BroadcastResult(
    groupEntity,
    &CamManagerRequests::FindGroupTargetByName,
    "MyGroup");

The editor dropdown for m_groupTargetName on Body / Aim stages populates from GetRegisteredGroupTargetNames.

See Cam Manager — Group Target Registry for the manager-side bus.


Evaluation Cadence

The component picks one of two cadences automatically:

CadenceWhenWhy
PhysX post-simulateAny enabled subject is physics-driven (has a RigidBodyRequestBus handler)Ensures the centroid uses post-simulate positions — no lag against physically-moving subjects.
TickBusNo physics subjectsSimple per-frame evaluation.

Cadence is re-evaluated on every subject mutation (AddSubject, RemoveSubject, SetSubjectEnabled). m_boundToPostSim / m_boundToTick track the current binding.


Centroid Modes

enum class CentroidMode : AZ::u8 {
    WeightedMean,            // Σ(pos · weight) / Σ(weight)
    BoundingBoxCenter,       // (min + max) / 2 of axis-aligned bbox, biased toward weighted mean
    BoundingSphereCenter,    // Welzl-style sphere center, biased toward weighted mean
};

The bounding modes blend toward the weighted mean by m_weightBias ∈ [0, 1]:

  • m_weightBias = 0 — pure geometric center.
  • m_weightBias = 1 — pure weighted mean.
  • Intermediate values produce a weighted-biased geometric center.

This lets weights still influence the framing point even when the geometric extent (bbox / sphere) is what really defines the group’s spread.


Orientation Modes

enum class OrientationMode : AZ::u8 {
    Identity,                  // group entity stays at identity rotation
    SpreadAxis,                // forward axis points along the long axis of subjects' spread
    WeightedAverageForward,    // weighted-average of subjects' forward vectors
};

m_publishOrientation is the master toggle. If false, the group entity stays at identity rotation regardless of mode.


Authored Fields

FieldDefaultPurpose
m_name""Registry key. Authored once; stages reference by this name. Empty = unregistered.
m_subjects{}List of GroupSubject rows.
m_modeWeightedMeanCentroid mode.
m_weightBias0.0When mode is not WeightedMean: blend factor toward weighted mean.
m_smoothingHalflife0.0Optional centroid damping (0 = no smoothing).
m_publishOrientationfalseWhether to write rotation to the group entity.
m_orientationModeSpreadAxisRotation derivation (when publishOrientation enabled).
m_deactivateWhenEmptytrueIf true and m_subjects becomes empty, the group entity stops ticking — cam bodies see “no target” and fall back to hold-last pose.

GroupSubject Struct

struct GroupSubject {
    AZ::EntityId  m_entity;
    float         m_weight  = 1.0f;
    AZ::Vector3   m_offset  = (0, 0, 0);   // applied to subject's world pos
    bool          m_enabled = true;
};

Each subject carries an offset and an enabled flag. Toggling m_enabled lets gameplay code temporarily exclude a subject without removing it from the list (cheaper than RemoveSubject + AddSubject since cadence rebind is suppressed).


Request Bus

GroupTargetRequestBus — per-entity addressed (the group’s own entity id).

Subject management

MethodUse
AddSubject(entity, weight)Add with default offset.
AddSubjectWithOffset(entity, weight, offset)Add with custom offset.
RemoveSubject(entity)Remove.
SetSubjectWeight(entity, weight)Adjust weight.
SetSubjectEnabled(entity, enabled)Toggle without removing from list.
ClearSubjects()Remove all.
GetSubjectEntities()Returns list of EntityIds.

Mode and smoothing

MethodUse
SetCentroidMode(mode)Runtime mode swap.
SetWeightBias(bias)Adjust bias.
SetSmoothingHalflife(halflife)Adjust smoothing.
SetPublishOrientation(enabled)Toggle rotation publishing.
SetOrientationMode(mode)Runtime orientation mode swap.

Query / diagnostics

MethodUse
GetCurrentCentroid()Last evaluated centroid (post-smoothing if enabled).
IsEvaluatingPostSim()Diagnostic — returns true if currently bound to PhysX post-sim.

Cadence-affecting mutations (AddSubject, RemoveSubject, SetSubjectEnabled) automatically call RebindEvaluationCadence.


Evaluation Algorithm

EvaluateCentroid(deltaTime):

    Collect (pos, weight) for every enabled subject:
        pos    = subject.entity.worldTM.translation + subject.offset
        weight = subject.weight

    Compute centroid by mode:
        WeightedMean         → Σ(pos · weight) / Σ(weight)
        BoundingBoxCenter    → lerp((min + max) / 2,  weightedMean, m_weightBias)
        BoundingSphereCenter → lerp(welzlCenter,      weightedMean, m_weightBias)

    If m_smoothingHalflife > 0:
        alpha = HalflifeAlpha(m_smoothingHalflife, deltaTime)
        m_lastCentroid = lerp(m_lastCentroid, centroid, alpha)
    Else:
        m_lastCentroid = centroid

    If m_publishOrientation:
        rotation = ComputeOrientation(positions, weights)
    Else:
        rotation = Identity

    Write (m_lastCentroid, rotation) to GetEntityId()'s TransformBus.

Cam-Side Usage

A Phantom Camera body or aim stage authored with m_targetMode = CamTargetMode::GroupTarget sets m_groupTargetName to the group’s registered name. At runtime, the stage resolves the group entity via the Cam Manager registry:

// In the stage's ResolveTarget or StageHelpers::ResolveStageTargetTM:
AZ::EntityId groupEntity;
CamManagerRequestBus::BroadcastResult(
    groupEntity,
    &CamManagerRequests::FindGroupTargetByName,
    m_groupTargetName);

return groupEntity;   // body / aim then reads this entity's world TM as the target

The same pattern applies to Body stages (for follow) and Aim stages (for look-at) — each can independently target a group.


Pairing with Shared Cams

A GroupTargetComponent paired with a shared TrueUnique cam (see Channels & Instancing — Cam Channel Scope) is the canonical “collapse to one view” trigger:

  • The group target tracks all players.
  • The shared cam targets the group entity.
  • When all rigs / channels select the shared cam (typical at convergence radius), OnAllChannelsActivatedSharedCam(sharedCam) fires once on the Cam Manager notification bus.
  • UI switches from split-screen to single-view layout.

See Channels & Instancing — Shared Cams and Collapse Detection for the collapse-detection details.


See Also

Related PhantomCam pages:

Basics-side authoring guide:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.6 - Tug Fields

Spatial cam-pose reposition system — CameraTugVolumeComponent, CameraTugSourceComponent, TugFieldProxyComponent, TugAimListener, TugBodyListener. Decoupled volume / source / proxy, PhysX layer-gated, no central registry.

Tug Fields are a spatial reposition system that modulates the camera’s pose (position and / or rotation) when a designated proxy entity enters a defined volume. Used for environmental cinematography — pulling the cam toward a vista point when the player walks past a railing, slerping the aim toward a focal point as the player passes a mantle, etc.

The system is three-part decoupled:

  1. CameraTugVolumeComponent — spatial gate. Lives on an entity with a PhysX trigger collider on the TugField layer.
  2. CameraTugSourceComponent — geometric pull configuration. Decoupled from the volume so the activation region and the focal point can be physically apart.
  3. TugFieldProxyComponent — opt-in marker on entities that should engage tug fields. Lives on the target (or its descendant, or a cam-side descendant).

The cam-side consumers are reposition-phase additives:

  • TugAimListener — slerps state.rotation toward the smoothed source point.
  • TugBodyListener — lerps state.position toward the smoothed source point.

No central registry. Filtering happens at PhysX layer config (TugProxy ↔ TugField collision-group pair). Tug volumes broadcast on a per-proxy TugProxyNotificationBus address; listeners maintain their own active-source cache. Per-tick cost is O(volumes the proxy is in) — independent of total world volume count.

Tug-field m_channels ≠ instancing ChannelId. Tug-field channels are arbitrary string tags (“Cinematic”, “Combat”) that match volumes to listeners. They are unrelated to the integer ChannelIds on the Channels & Instancing page despite the shared word. The codebase reuses the identifier name; the docs make the distinction prominent here.

 

Contents


CameraTugVolumeComponent

Spatial gate. Wraps GS_Core::PhysicsTriggerComponent. Lives on an entity with a PhysX trigger collider configured on the dedicated TugField layer.

CameraTugVolumeComponent in the O3DE Inspector

Source resolution rules

The volume’s authored m_sourceEntity:

  • Unset → falls back to self (the volume’s own entity).
  • Valid + source resolved → uses that entity’s CameraTugSourceComponent.
  • Set but unresolvable → field is INERT. Warns once at Activate; TriggerEnter early-returns without notifying.

Authored fields

FieldDefaultPurpose
m_channels{}List of channel tag strings (“env”, “cinematic”, etc.). Hashed to Crc32 once at Activate. Listeners filter by intersection.
m_sourceEntity(entity slot)Optional. Unset → use self.

Per-trigger broadcast

TriggerEnter(proxyEntity):
    if not m_resolvedSource:  return false (inert)
    TugProxyNotificationBus::Event(
        proxyEntity,
        &TugProxyNotifications::OnVolumeEnter,
        GetEntityId() /*volume*/,
        m_channelHashes,
        m_resolvedSource);
    return true

TriggerExit(proxyEntity):
    TugProxyNotificationBus::Event(
        proxyEntity,
        &TugProxyNotifications::OnVolumeExit,
        GetEntityId());
    return true

OnVolumeExit broadcasts unconditionally — listeners that didn’t cache the volume on enter erase a non-existent key (silent no-op).

Request bus

CameraTugVolumeRequestBus — per-entity addressed.

MethodUse
IsProxyInContact(proxyEntity)Predicate — does this volume currently contain the proxy?
GetChannelHashes()Returns the cached Crc32 channel hashes.
GetResolvedSource()Returns the resolved source component (or null if inert).
GetVolumeEntityId()Returns the volume’s own entity id.

Used by listeners during proxy rebind to discover volumes already containing a newly-resolved proxy — PhysX doesn’t re-fire enters when the bus reconnects mid-overlap. See Proxy Rebind Walk.


CameraTugSourceComponent

Pure data + helpers that resolve source and destination world-space points. No collider, no triggering.

CameraTugSourceComponent in the O3DE Inspector

Source vs. destination — fully decoupled

ConceptMeaning
Source pointWhere proximity is measured FROM. The radii are centered on this point; the gravity-well falloff evaluates against the proxy’s distance to it.
Destination pointWhere the cam is pulled TOWARD. Modulation drags the cam (Body) or its forward (Aim) toward this point, weighted by the proximity-derived influence.
CaseSetup
Common — source == destinationCam pulls toward where proximity is measured. Leave m_destinationEntity unset.
DecoupledSource measured around a doorway threshold; destination 10 m past the doorway pointing at a vista. Set m_destinationEntity to the destination point’s entity.
FallbackIf m_destinationEntity is unset, destination = source point.

Authored fields

FieldDefaultPurpose
m_tugPointOffset(0, 0, 0)Local-space offset from THIS entity’s transform — places the proximity-reference point distinct from the entity’s pivot.
m_destinationEntity(entity slot)Optional. Valid → destination = that entity’s world TM × m_destinationOffset.
m_destinationOffset(0, 0, 0)Local-space offset from m_destinationEntity. Ignored when unset.
m_innerRadius1.0 mDeadzone. distance ≤ this = full influence.
m_outerRadius8.0 mZero-influence boundary. distance ≥ this = no contribution.
m_innerWeight1.0Influence at deadzone (full strength).
m_outerWeight0.0Influence at outer boundary. Default zero so engagement ramps smoothly.
m_falloffCurveEaseInOutQuadraticGS_Core::CurveType applied across the outer → inner falloff span.

Helper methods

AZ::Vector3 GetSourcePoint()      const;
AZ::Vector3 GetDestinationPoint() const;
float GetInnerRadius()  const;
float GetOuterRadius()  const;
float GetInnerWeight()  const;
float GetOuterWeight()  const;
GS_Core::CurveType GetFalloffCurve() const;

Listeners read these directly via a cached pointer (no per-tick bus call).


TugFieldProxyComponent

Lightweight opt-in marker. Functions as a marker + bus address. No fields. No logic beyond existing.

The PhysX trigger collider that actually overlaps tug volumes must be authored alongside this component on the same entity, set to the dedicated TugProxy collision layer.

Authoring placements

PlacementUse for
On the target entity directly (player root, boss root)Simple cases.
On a child entity of the targetWhen the proxy should be at chest height, vehicle COM, etc. Tug listeners walk target descendants to find proxies.
On the cam entity (or its child)Fallback for cams that should react to environmental fields without target cooperation.

TugProxyNotificationBus

Per-entity addressed (the proxy entity’s id).

class TugProxyNotifications : public AZ::EBusTraits
{
public:
    using BusIdType = AZ::EntityId;
    static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
    static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;

    virtual void OnVolumeEnter(
        AZ::EntityId volumeEntity,
        const AZStd::vector<AZ::Crc32>& volumeChannels,
        const CameraTugSourceComponent* source) {}

    virtual void OnVolumeExit(AZ::EntityId volumeEntity) {}
};

Multi-handler so multiple listeners (Body + Aim on the same cam, plus diagnostic tooling) can subscribe at the same proxy address.


Listener Stages

TugAimListener and TugBodyListener are reposition-phase additives — see Additive Stage Variants. Both derive from a shared TugListenerBase that holds the channel-matching, proxy resolution, active-source cache, smoothed-influence state, and the per-tick algorithm.

Shared authored fields

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_channels{}Channel tag strings to match against volume channels. Empty = match all.
m_blendHalflife0.25Smoothing on engagement / disengagement.
m_strength1.0Overall multiplier.

A cam can run only the aim listener, only the body listener, both, or neither — each is independent.


Per-Tick Algorithm

1. ResolveProxy(ctx)
     Walk target descendants → cam-self descendants → inert.
     If changed since last tick, rebind bus + RepopulateFromVolumeWorld.

2. Sum contributions from m_activeSources:
     For each (volume, source) in m_activeSources:
         distance = (proxy.worldPos - source.GetSourcePoint()).length
         if distance >= source.outerRadius:    skip
         t = (source.outerRadius - distance)
             / (source.outerRadius - source.innerRadius)
         t = ApplyCurve(clamp(t, 0, 1), source.falloffCurve)
         weight = lerp(source.outerWeight, source.innerWeight, t) * m_strength
         contribute (weight, source.GetDestinationPoint())
     totalInfluence, weightedSourcePoint = combined

3. Dormancy check:
     If totalInfluence == 0 AND m_smoothedInfluence ≈ 0:
         m_dormant = true; return.   // full early-exit when settled

4. Dormant → engaged seeding (if transitioning):
     m_smoothedSourcePoint = derived.GetSeedSourcePoint(state)
     // Aim:  point along cam's current forward — zero-displacement transition.
     // Body: state.position — zero-displacement transition.

5. Damping:
     alpha = HalflifeAlpha(m_blendHalflife, dt)
     m_smoothedInfluence   = lerp(m_smoothedInfluence,   totalInfluence,      alpha)
     m_smoothedSourcePoint = lerp(m_smoothedSourcePoint, weightedSourcePoint, alpha)

6. derived.ApplyModulation(state, ctx, m_smoothedInfluence, m_smoothedSourcePoint)

TugAimListener.ApplyModulation

Derives the natural rotation directly from ctx (cam → primary target forward), not from state.rotation. This prevents compound-feedback where prior tug modifications would loop into the next tick’s blend source.

naturalFwd   = (ctx.targetTM.translation - ctx.camInitialTM.translation).normalized
naturalRot   = LookAt(naturalFwd, +Z)
modulatedRot = LookAt((sourcePoint - ctx.camInitialTM.translation).normalized, +Z)
state.rotation = Slerp(naturalRot, modulatedRot, smoothedInfluence * m_strength)

TugBodyListener.ApplyModulation

Derives natural pose source from ctx.targetTM / the Body’s state.bodyAnchor sidecar, not state.position.

naturalPos = state.bodyAnchor       // or ctx.targetTM.translation + body offset
state.position = Lerp(naturalPos, smoothedSourcePoint, smoothedInfluence * m_strength)

Proxy Rebind Walk

When the listener’s resolved proxy changes (target switched, proxy entity destroyed), the listener:

  1. Unbinds from the old proxy’s TugProxyNotificationBus.
  2. Clears m_activeSources cache.
  3. Binds to the new proxy’s bus address.
  4. RepopulateFromVolumeWorld — walks all CameraTugVolumeRequestBus handlers and synthesizes OnVolumeEnter for any that already contain the new proxy.

PhysX does not re-fire enter events when a bus reconnects mid-overlap, so this catchup walk is essential. Without it, a switched proxy would miss every volume it’s already inside until it leaves and re-enters.


PhysX Layer Setup

The system requires a specific PhysX collision-group preset configured at project setup time:

LayerUsed byPair with
TugProxyTugFieldProxyComponent’s sibling colliderTugField
TugFieldCameraTugVolumeComponent’s sibling collider (trigger)TugProxy

Both layers overlap only with each other in the collision-group preset. This ensures tug volumes fire triggers only for registered proxies — no general-world filtering needed at runtime.

This is the single most common authoring failure. If you set up volumes and listeners but nothing fires, the PhysX layer config is the first thing to verify. Without the TugProxy ↔ TugField pair, no triggers ever fire.


Authoring Workflow

  1. Set up PhysX layers (one-time project setup): create TugProxy and TugField layers; pair them via collision-group preset.
  2. On the player entity (or child): Add TugFieldProxyComponent + a PhysX trigger collider on the TugProxy layer.
  3. On each level entity that should be a tug field: Add CameraTugVolumeComponent + a PhysX trigger collider (trigger mode) on the TugField layer. Set m_channels for filtering.
  4. For decoupled source / destination: Add CameraTugSourceComponent to a separate entity at the focal point; point the volume’s m_sourceEntity at it. Set its m_destinationEntity if the pull point differs from the source.
  5. On the cam: Add TugAimListener and / or TugBodyListener to the cam’s m_additives. Set the listener’s m_channels to match the volumes’ channels (or leave empty to match all).

See Also

Related PhantomCam pages:

Basics-side authoring guide:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.7 - Camera Input Reader

GS_CameraInputReaderComponent and OrbitInputProvider bus — translates input profile events into per-tick yaw / pitch deltas for DynamicOrbitBody. Per-axis Axis vs Delta style, sensitivity, buffer drain, ResetPendingInput invariant.

The Camera Input Reader translates GS_InputProfile events into per-tick yaw / pitch deltas for the DynamicOrbitBody Body stage. It subclasses GS_Core::GS_InputReaderComponent (inherits profile binding, channel filtering, deadzone handling) and publishes results on the OrbitInputProvider bus that the orbit body polls each tick.

It mirrors the GS_PlayerControllerInputReaderComponent pattern from gs_unit: the parent resolves which named events to fire; the subclass routes those events to the system that consumes them.

GS_CameraInputReaderComponent in the O3DE Inspector

 

Contents


OrbitInputProvider Bus

The Camera Input Reader implements OrbitInputProvider on the cam’s entity address. The DynamicOrbitBody binds to this bus at first Evaluate and polls it every tick.

class OrbitInputProvider : public AZ::EBusTraits
{
public:
    using BusIdType = AZ::EntityId;
    static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
    static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;

    virtual void GetOrbitInputDelta(float& outYawDelta, float& outPitchDelta) const = 0;
    virtual void ResetPendingInput() = 0;
};

Per-entity addressed, single-handler. The reader component must live on the cam entity (or a child whose ancestor walk resolves to the cam) so the bus address matches ctx.camEntityId.


Per-Axis Input Style

Each axis (yaw, pitch) authors an independent style. The reader handles the two source semantics differently:

StyleSourceBehavior
AxisJoystick / continuous analogLast-write-wins state. Held axis = continuous rotation. The input profile fires StateUpdated each frame and the reader overwrites; release fires value 0 and rotation stops.
DeltaMouse / per-frame deltasAccumulating buffer reset on poll. Per-frame pixel deltas are scaled by sensitivity and accumulated. On poll, the reader returns the accumulated value AND resets the buffer to zero. Mouse rest = no rotation.

A reader can mix styles — joystick yaw + mouse pitch, for example — by setting m_yawStyle = Axis and m_pitchStyle = Delta.


Authored Fields

FieldDefaultPurpose
m_yawEventName"OrbitYaw"The named event in the InputProfile that yaw should listen for.
m_pitchEventName"OrbitPitch"Pitch event name.
m_yawStyleAxisPer-axis style (Axis or Delta).
m_pitchStyleAxisPer-axis style.
m_yawDeltaSensitivity0.05Pixels-per-frame → axis units. Only used in Delta mode. Default 0.05 means a 20-pixel mouse swing in one frame produces 1.0 axis unit (= one tick at the body’s full m_yawSpeed).
m_pitchDeltaSensitivity0.05Same, pitch.
m_invertYawfalseInvert yaw axis.
m_invertPitchfalseInvert pitch axis.

GS_InputProfile asset assignment is inherited from GS_InputReaderComponent — author the profile separately and reference it in the component’s standard input-profile slot.


Runtime State

mutable float m_pendingYaw   = 0.0f;
mutable float m_pendingPitch = 0.0f;

mutable so the const GetOrbitInputDelta override can reset Delta-mode buffers on poll. Axis-mode buffers are written from HandleFireInput and read back unchanged.


Event Handling

When an InputProfile event fires:

HandleFireInput(eventName, value):
    if eventName == m_yawEventName:
        v = m_invertYaw ? -value : value
        if m_yawStyle == Axis:
            m_pendingYaw = v                              // last-write-wins
        else /* Delta */:
            m_pendingYaw += v * m_yawDeltaSensitivity     // accumulate

    elif eventName == m_pitchEventName:
        v = m_invertPitch ? -value : value
        if m_pitchStyle == Axis:
            m_pendingPitch = v
        else /* Delta */:
            m_pendingPitch += v * m_pitchDeltaSensitivity

Poll Handling

When the body polls the bus:

GetOrbitInputDelta(out outYawDelta, out outPitchDelta):
    outYawDelta   = m_pendingYaw
    outPitchDelta = m_pendingPitch

    if m_yawStyle   == Delta:  m_pendingYaw   = 0.0      // drain on read
    if m_pitchStyle == Delta:  m_pendingPitch = 0.0

Axis-mode buffers are NOT drained on poll — the body’s next tick reads the same held value (consistent with “held stick = continuous rotation”). Delta-mode buffers are drained because mouse-rest must mean no rotation.


ResetPendingInput Invariant

ResetPendingInput():
    m_pendingYaw   = 0.0
    m_pendingPitch = 0.0

Called by the DynamicOrbitBody when ctx.snapThisFrame == true. Critical for cams with alwaysUpdate = false:

When such a cam is dormant, mouse motion still accumulates into m_pendingYaw / m_pendingPitch via Delta-mode event handling (the input profile keeps firing). Without the snap-time reset, the cam would “burst” all the accumulated input the moment it became active.

This is defense-in-depth paired with the per-tick drain. The body polls every tick (regardless of whether it integrates the result), so Delta buffers don’t accumulate while the cam ticks but is dormant. ResetPendingInput clears any stragglers at the moment activation snap occurs.

See Phantom Cameras — Execution States for the dormancy + reactivation lifecycle.


DynamicOrbitBody Consumption

// In DynamicOrbitBody::Evaluate:

// 1. Ensure bus binding.
EnsureBusConnected(ctx.camEntityId);

// 2. Poll input deltas every tick (regardless of integration eligibility,
//    so Delta-mode buffers drain whether we integrate or not).
float yawDelta = 0.0f, pitchDelta = 0.0f;
OrbitInputProviderBus::Event(ctx.camEntityId,
    &OrbitInputProvider::GetOrbitInputDelta, yawDelta, pitchDelta);

// 3. Gate integration on input-eligibility predicate.
const bool integrateInput = (ctx.hasFocus || ctx.alwaysUpdate || ctx.blendingOut);

if (integrateInput)
{
    m_targetYaw   += yawDelta   * AZ::DegToRad(m_yawSpeed)   * dt;
    m_targetPitch += pitchDelta * AZ::DegToRad(m_pitchSpeed) * dt;
    // Apply WrapYawToPi + pitch clamp.
}

// 4. On snap: reset reader buffers.
if (ctx.snapThisFrame)
{
    OrbitInputProviderBus::Event(ctx.camEntityId,
        &OrbitInputProvider::ResetPendingInput);
}

Sensitivity vs Halflife vs Speed

A common authoring confusion — three different controls, three different purposes:

ControlLives onEffectWhen to tune
Sensitivity (m_yawDeltaSensitivity / m_pitchDeltaSensitivity)Camera Input Reader, Delta mode onlyConverts raw input units (pixels) → axis units. “How much axis movement per unit of physical input.”Mouse feel — pixel-to-rotation conversion.
Speed (m_yawSpeed / m_pitchSpeed)DynamicOrbitBodyDegrees / sec per unit axis input. “How fast the cam rotates per unit of axis movement.”Overall rotation speed at full stick / full mouse delta.
Halflife (m_blendHalflife)DynamicOrbitBodySolver damping — how fast the cam catches up to the target angles. “How responsive the cam feels.”Visual snap vs glide on the cam’s response.

Don’t conflate. Sensitivity controls input mapping; Speed controls rotation amount per input; Halflife controls visual responsiveness.


Authoring Workflow

  1. Place GS_CameraInputReaderComponent on the cam entity (or its child — the bus is per-entity addressed, so the entity ID must match ctx.camEntityId, which is the cam entity itself).
  2. Assign a GS_InputProfile asset in the parent’s slot (inherited from GS_InputReaderComponent).
  3. Author profile events named OrbitYaw and OrbitPitch (or rename the reader’s m_yawEventName / m_pitchEventName to match your profile).
  4. Set per-axis style:
    • Mouse-driven cams → both axes Delta. Tune m_*DeltaSensitivity to taste.
    • Joystick-driven cams → both axes Axis. Sensitivity is ignored.
    • Mixed (mouse for yaw, gamepad stick for pitch) → mix as needed.
  5. Optionally toggle m_invertYaw / m_invertPitch.

See Also

Consumer:

Related PhantomCam pages:

Parent input system:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.8 - Templates

ClassWizard templates for GS_PhantomCam — custom phantom camera behaviour components.

All GS_PhantomCam extension types are generated through the ClassWizard CLI. The wizard handles UUID generation, cmake file-list registration, and module descriptor injection automatically.

For usage guides and setup examples, see The Basics: GS_PhantomCam.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Phantom Camera Component

Template: PhantomCamera

Creates a custom camera behaviour component, a child of GS_PhantomCameraComponent. Multiple camera components can coexist on a Camera Entity; the GS_CamManagerComponent activates them by priority. Override the follow, look-at, and tick virtuals to define custom camera positioning and aiming logic.

Generated files:

  • Source/${Name}PhantomCamComponent.h/.cpp

CLI:

python ClassWizard.py --template PhantomCamera --gem <GemPath> --name <Name>

Post-generation: None — cmake and module registration are fully automatic. Override the following virtual methods:

MethodPurpose
ProcessPhysicsFollow()Drive camera position each physics tick using VelocitySpringDamper
ProcessPhysicsLookAt()Drive camera rotation using QuaternionSpringDamper
EvaluateCamTick(dt)Per-frame camera logic (blend weights, FOV, offsets)
ProcessTransformFollow()Position follow when physics is not available
ProcessTransformLookAt()Rotation follow when physics is not available

Extensibility: One component per camera mode (e.g. ThirdPerson, Aim, Dialogue, Cinematic). Components declare incompatibility with GS_PhantomCameraComponentService so only one camera behaviour is active at a time on an entity. Swap active cameras by toggling component activation, or let the CamManager handle priority.

See also: Phantom Cameras — full extension guide with complete header and implementation examples.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.11.9 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_PhantomCam.


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.

4.12 - GS_Stats

RPG stat definitions, stat containers, and a polymorphic status effect system for character and entity attribute management.

GS_Stats provides the attribute and status effect layer for GS_Play projects. It gives entities a stat container that holds named, typed values (health, stamina, speed, and any custom stat you define), and a status effect system that applies timed or conditional modifications to those stats. GS_Stats is under active development — the API surface is evolving and some features listed here may expand in future releases.

For usage guides and setup examples, see The Basics: GS_Stats.

 

Contents


Status Effects

The status effect system applies timed or trigger-driven modifications to entity stats. Effects are polymorphic data assets that define what stat they target, how they modify it, and when they expire. Multiple effects stack on the same entity and resolve in a defined order.

AreaContents
Effect AssetsData-driven effect definitions with target stat, modification type, magnitude, and duration.
Effect ContainerRuntime component that holds the active effect stack for an entity and processes tick-based expiry.
Modification TypesAdditive, multiplicative, and override modification strategies.
ConditionsConditional triggers for applying or removing effects based on stat thresholds or game records.

Status Effects API


Installation

GS_Stats is a standalone gem with no external GS gem dependencies beyond GS_Core.

  1. Enable GS_Stats and GS_Core in your O3DE project’s gem list.
  2. Add the stat container component to any entity that requires tracked attributes.
  3. Define stat definitions in your project’s stat registry (data asset).
  4. Add status effect asset files to your project and reference them from abilities, items, or world triggers.

Note: GS_Stats is under active development. Check the changelog for the latest additions before starting integration work.


See Also

For conceptual overviews and usage guides:

For sub-system references:

For related resources:


Get GS_Stats

GS_Stats — Explore this gem on the product page and add it to your project.

4.12.1 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Stats.


Get GS_Stats

GS_Stats — Explore this gem on the product page and add it to your project.

4.12.2 - Status Effect

The core gem for the GS_Play framework.

For usage guides and setup examples, see The Basics: GS_Stats.


Get GS_Stats

GS_Stats — Explore this gem on the product page and add it to your project.

4.13 - GS_Complete

Cross-gem integration layer — reference components that combine multiple GS_Play gems for camera-aware performers, cinematic controllers, and dialogue UI selection.

GS_Complete is the cross-gem integration layer. It depends on every other GS gem and provides reference components that combine functionality from two or more gems — camera-aware paper facing, cinematic-driven unit controllers, dialogue camera effects, and dialogue UI selection buttons. GS_Complete exists as a pattern reference: it proves that gem combination works cleanly via companion components and bus events, without modifying any gem’s source code.

 

Contents


Cinematics + PhantomCam

Dialogue effects that control camera behavior during conversations. These extend DialogueEffect and are discovered automatically through O3DE serialization, demonstrating how external gems add new dialogue effects without modifying GS_Cinematics.

ComponentPurpose
SetCamPriority_DialogueEffectComponentDialogue effect that changes a phantom camera’s priority during a dialogue sequence.
TogglePhantomCam_DialogueEffectComponentDialogue effect that enables or disables a phantom camera during a dialogue sequence.

Cinematics + UI

Connects LyShine button interactions to the dialogue selection system, enabling player choice UI in dialogue sequences.

ComponentPurpose
GS_UIDialogueSelectButtonComponentButton component for dialogue choice selection — bridges LyShine button events to dialogue selection bus events.

GS_UIDialogueSelectButtonComponent in the O3DE Inspector


Performer + PhantomCam

Camera-aware extensions for the Performer system, enabling 2.5D paper characters to face the active camera correctly.

ComponentPurpose
CamCorePaperFacingHandlerComponentCamera-aware paper facing for 2.5D performers — uses CamCore notifications to adjust paper performer facing direction relative to the active camera.

Unit + Cinematics

Unit controller extensions driven by the cinematic system rather than player input.

ComponentPurpose
CinematicControllerComponentUnit controller driven by cinematic sequences — extends GS_UnitControllerComponent to be driven by cinematic playback.

Cinematic Controller component in the O3DE Inspector


Architecture

GS_Complete follows the companion component pattern — each cross-gem component lives on the same entity as the components it integrates, communicating via their existing EBus interfaces. No gem source code is modified.

GS_Complete (integration layer)
    ├── Cinematics × PhantomCam
    │       ├── SetCamPriority_DialogueEffectComponent
    │       └── TogglePhantomCam_DialogueEffectComponent
    ├── Cinematics × UI
    │       └── GS_UIDialogueSelectButtonComponent
    ├── Performer × PhantomCam
    │       └── CamCorePaperFacingHandlerComponent
    └── Unit × Cinematics
            └── CinematicControllerComponent

System Components

ComponentPurpose
GS_CompleteSystemComponentRuntime system component for GS_Complete.

Dependencies

  • All GS gems (required — GS_Core, GS_Audio, GS_Cinematics, GS_Environment, GS_Interaction, GS_Juice, GS_Performer, GS_PhantomCam, GS_UI, GS_Unit, GS_AI, GS_Platform)
  • LyShine (required — for dialogue UI selection)
  • RecastNavigation (required — via GS_Cinematics)

Installation

  1. Enable the GS_Complete gem in your project configuration.
  2. Ensure all other GS gems are also enabled — GS_Complete depends on the full suite.
  3. Add cross-gem components to entities alongside the gem components they integrate.

See Also

For conceptual overviews and usage guides:

For related API references:

4.13.1 - Third Party Implementations

Integration guides for third-party cross-gem components with GS_Complete.

This section will contain integration guides for connecting third-party cross-gem components with the GS_Complete integration layer.

4.14 - GS_UI

The complete UI framework — single-tier page navigation, motion-based animations, enhanced buttons, input interception, load screens, and pause menus.

GS_UI is the gem that builds the game’s interface layer on top of O3DE’s LyShine system. It replaces the old multi-tier Hub/Window/Page hierarchy with a clean single-tier model: the UI Manager owns canvases, and each canvas root is a Page that can parent any depth of nested child Pages. Navigation is fully recursive — any page can push, pop, or swap focus within its subtree. Animations are authored as .uiam data assets using eight LyShine-specific motion tracks (position, scale, rotation, alpha, color, text). Buttons, input interception, load screens, and pause menus round out the feature set.

For usage guides and setup examples, see The Basics: GS_UI.

 

Contents


UI Manager

The singleton that owns all loaded canvases. The UI Manager loads and unloads canvases by name, maintains a global focus stack across canvases, and drives the startup focus sequence deterministically.

ComponentPurpose
GS_UIManagerComponentSingleton manager. Loads canvases, maintains global focus stack, drives startup focus.

UI Manager API


The core navigation system. A single GS_UIPageComponent replaces the old Hub, Window, and Page roles. When m_isRoot is true, the page registers itself with the UI Manager as a root canvas entry point. Pages can parent other pages to form any navigation depth. Focus is managed through push/pop stacks at each level.

ComponentPurpose
GS_UIPageComponentCore navigation component. Handles root canvas registration, child page management, focus push/pop, and show/hide transitions.

Pages API


UI Interaction

The button and input interception layer. GS_ButtonComponent plays motion-based animations on hover and select. GS_UIInputInterceptorComponent captures input events while a canvas is focused, preventing them from reaching gameplay systems.

ComponentPurpose
GS_ButtonComponentEnhanced button. Plays UiAnimationMotion assets on hover, unhover, and select events.
GS_UIInputInterceptorComponentIntercepts configured input events and re-broadcasts on UIInputNotificationBus.

UI Interaction API


UI Animation

A GS_Motion extension with eight LyShine-specific animation tracks. Animations are authored as .uiam assets in the editor and referenced by page transition fields or played directly by the standalone motion component.

TypePurpose
UiAnimationMotionComponentStandalone component for playing a UiAnimationMotion asset on any entity.
UiAnimationMotionAssetData asset (.uiam) that holds a list of UiMotionTracks and playback settings.
UiPositionTrackAnimates LyShine element position offset.
UiScaleTrackAnimates LyShine element scale.
UiRotationTrackAnimates LyShine element rotation.
UiElementAlphaTrackAnimates element-level alpha.
UiImageAlphaTrackAnimates UiImageComponent image alpha.
UiImageColorTrackAnimates UiImageComponent color tint.
UiTextColorTrackAnimates UiTextComponent text color.
UiTextSizeTrackAnimates UiTextComponent font size.

UI Animation API


Widgets

Standalone UI components for game-event-driven scenarios outside the page navigation model — load screens during transitions, pause menus overlaying gameplay.

ComponentPurpose
GS_LoadScreenComponentManages loading screen display during stage transitions.
PauseMenuComponentManages pause state and pause menu overlay.

Widgets API


Cross-Gem Contracts

GS_UI’s motion-track base and its observation signals are collected in the GS_Core Interfaces layer:

ContractKindUsed for
UiMotionTrackTypeBaseAuthor new UI animation tracks — they appear in the asset-editor picker.
UIInteractableEmissionBusEmitOnClicked — a button was activated (addressed by the button entity).
UiMotionEmissionBusEmitOnUiMotionComplete / OnUiMotionLooped (carry the motion name).
UIPageEmissionBusEmitOnPageShown / OnPageHidden — global, fired after the show/hide animation resolves.

See the Contract Reference for the full table and addressing details.


Installation

GS_UI requires GS_Core, LyShine, and LmbrCentral.

  1. Enable GS_UI in Project Manager or project.json.
  2. Add GS_UIManagerComponent to the Game Manager entity and register it in the Startup Managers list.
  3. Create a LyShine canvas and add GS_UIPageComponent (with m_isRoot = true) to the root element.
  4. Set m_uiName on the root page to match the name you will use when calling LoadGSUI.
  5. Nest child pages under the root by adding GS_UIPageComponent to child entities and connecting them through m_defaultChildPage.
  6. Refer to the UI Set Up Guide for a full walkthrough.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.1 - UI Manager

The singleton manager that owns all loaded UI canvases, maintains a global focus stack, and drives the startup focus sequence.

For usage guides and setup examples, see The Basics: GS_UI.

UI Manager component in the O3DE Inspector

The UI Manager is the singleton that controls the lifecycle of every UI canvas in your project. It loads and unloads LyShine canvases by name, maintains a global focus stack that tracks which canvas is currently active, and drives the startup focus sequence so that the correct UI appears automatically when the game finishes initializing.

GS_UIManagerComponent extends GS_ManagerComponent, which means it participates in the Game Manager’s staged startup. It is spawned by the Game Manager alongside other managers and receives OnStartupComplete to begin loading canvases and focusing the startup UI.

 

Contents


How It Works

Canvas Lifecycle

Each UI canvas is identified by a unique name string. You call LoadGSUI(name, path) to load a canvas from a .uicanvas asset path and associate it with the given name. The manager stores this in its m_activeUIs map. When you no longer need the canvas, call UnloadGSUI(name) to destroy it and remove it from the map.

Once a canvas is loaded, its root page component (a GS_UIPageComponent with m_isRoot = true) calls RegisterRootPage(name, entity) to register itself as the entry point for that canvas. The manager returns true if registration succeeds, false if the name is already registered or does not match a loaded canvas.


Global Focus Stack

The UI Manager maintains m_globalFocusStack, which tracks the order of focused canvases. When you call FocusUI(name), the named canvas is pushed onto the focus stack and becomes the active UI. NavLastUI() pops the current canvas and returns focus to the previous one. This allows layered UI flows such as opening a settings menu on top of a pause menu and navigating back through them in order.

ToggleUI(name, on) shows or hides a canvas by name. When toggling on, it calls FocusUI internally; when toggling off, it removes the canvas from the focus stack.

GetFocusedUI() returns the name of the currently focused canvas (the top of the stack).


Startup Sequence

The UI Manager’s startup follows this deterministic sequence:

  1. OnStartupComplete — The Game Manager broadcasts startup complete. The UI Manager loads all configured canvases via LoadGSUI.
  2. Root Page Registration — Each canvas’s root page component calls RegisterRootPage(name, entity) during its own activation.
  3. Startup Focus — The UI Manager checks each registered root page name against m_startupFocusUI. When it finds a match, it calls FocusUI(name) to focus that canvas as the initial UI.

This sequence ensures that canvases are loaded before pages register, and the correct startup UI receives focus without race conditions.


Data Types

UICanvasWindowPair

A pairing of a loaded LyShine canvas entity with its associated root page entity. Used internally by the UI Manager to track loaded canvases.

UINamePathPair

A pairing of a UI name string with a .uicanvas asset path. Used to configure which canvases the UI Manager loads on startup.


Inspector Properties

PropertyTypeDescription
Startup Focus UIAZStd::stringThe name of the UI canvas to focus automatically after startup completes. Must match a registered root page name.
UI Name Path PairsAZStd::vector<UINamePathPair>The list of canvas name/path pairs to load on startup. Each entry maps a unique name to a .uicanvas asset path.

API Reference

Request Bus: UIManagerRequestBus

Commands sent to the UI Manager. Singleton bus — Single address, single handler.

MethodParametersReturnsDescription
LoadGSUIconst AZStd::string& name, const AZStd::string& pathvoidLoads a LyShine canvas from the given asset path and registers it under the given name.
UnloadGSUIconst AZStd::string& namevoidDestroys the canvas registered under the given name and removes it from the active UI map.
RegisterRootPageconst AZStd::string& name, AZ::EntityId entityboolRegisters a root page entity for the named canvas. Returns true on success, false if the name is unrecognized or already registered.
UnregisterRootPageconst AZStd::string& namevoidRemoves the root page registration for the named canvas.
FocusUIconst AZStd::string& namevoidPushes the named canvas onto the global focus stack and activates it.
NavLastUI—voidPops the current canvas from the global focus stack and returns focus to the previous canvas.
ToggleUIconst AZStd::string& name, bool onvoidShows or hides the named canvas. Toggling on focuses it; toggling off removes it from the focus stack.
GetFocusedUI—AZStd::stringReturns the name of the currently focused UI canvas (top of the global focus stack).
GetUICanvasEntityconst AZStd::string& nameAZ::EntityIdReturns the LyShine canvas entity for the named UI.
GetUIRootPageEntityconst AZStd::string& nameAZ::EntityIdReturns the root page entity registered for the named UI.

Usage Examples

C++ – Loading and Focusing a UI Canvas

#include <GS_UI/GS_UIBus.h>

// Load a canvas
GS_UI::UIManagerRequestBus::Broadcast(
    &GS_UI::UIManagerRequestBus::Events::LoadGSUI,
    AZStd::string("TitleScreen"),
    AZStd::string("ui/titlescreen.uicanvas")
);

// Focus it
GS_UI::UIManagerRequestBus::Broadcast(
    &GS_UI::UIManagerRequestBus::Events::FocusUI,
    AZStd::string("TitleScreen")
);

C++ – Navigating Back

// Return to the previous UI in the focus stack
GS_UI::UIManagerRequestBus::Broadcast(
    &GS_UI::UIManagerRequestBus::Events::NavLastUI
);

C++ – Toggling a UI Canvas

// Show the pause menu
GS_UI::UIManagerRequestBus::Broadcast(
    &GS_UI::UIManagerRequestBus::Events::ToggleUI,
    AZStd::string("PauseMenu"),
    true
);

See Also

For conceptual overviews and usage guides:

For component references:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.2 - Page Navigation

The core single-tier navigation component – root canvas registration, nested child page management, focus push/pop stacks, and show/hide transitions.

For usage guides and setup examples, see The Basics: GS_UI.

The GS_UIPageComponent in the UI Editor, configured as a root page with a default child page assigned.

GS_UIPageComponent is the core navigation component of the GS_UI system. It replaces the legacy multi-tier Hub/Window/Page hierarchy with a single, unified component that handles all levels of UI navigation. A single GS_UIPageComponent can serve as a root canvas entry point, a mid-level container, or a leaf page – its role is determined entirely by configuration.

When m_isRoot is set to true, the page registers itself with the UI Manager as the root entry point for its canvas. Non-root pages are nested as children of other pages and managed through their parent’s child page system. This creates a single-tier model where any depth of navigation is achieved through recursive nesting of the same component.

Focus management uses push/pop stacks at each level. When a child page is focused, it is pushed onto its parent’s focus stack. Navigating back pops the stack and restores the previous child. The NavigationReturnPolicy enum controls whether returning to a page restores the last focused child or always resets to the default.

Required Companion Components

Root pages depend on two O3DE components placed on the same entity:

ComponentPurpose
FaderComponentDrives alpha-based fade transitions during show/hide animations. Required for UiAnimationMotion tracks that animate element alpha.
HierarchicalInteractionToggleComponentEnables or disables the entire interactable subtree when the page shows or hides. Prevents input from reaching hidden page elements.

These are standard LyShine/LmbrCentral components — add them alongside GS_UIPageComponent on every root page entity.

 

Root Page component showing its dependency components in the O3DE Inspector

Contents


How It Works

Root Pages

A root page (m_isRoot = true) is the entry point for a UI canvas. During activation, it calls RegisterRootPage(m_uiName, entityId) on the UIManagerRequestBus to register itself. The m_uiName field must match the name used when the canvas was loaded via LoadGSUI.

When m_startEnabled is true, the root page shows itself immediately upon registration. Otherwise, it remains hidden until the UI Manager calls FocusUI for its canvas name.


Child Page Management

When m_manageChildPages is true, the page acts as a container for child pages. Child pages register themselves with their parent during activation via RegisterChildPage(entity). The parent tracks all registered children and manages which one is currently visible and focused.

Each page maintains its own focus stack. When a child is focused, it is pushed onto the parent’s stack via PushFocus. PopFocus restores the previous child. The m_returnPolicy field controls behavior when navigating back to this page:

  • RestoreLast – Restores the last child that was focused before navigating away.
  • AlwaysDefault – Always resets to m_defaultChildPage, ignoring the focus history.

NavigateTo(forced) navigates the UI to show the calling page, regardless of where focus currently sits in the page tree. The algorithm works by building a path from the calling page up to the root, then cascading focus changes downward:

  1. Build path – Walk up from the calling page to the root, collecting each ancestor into a path array.
  2. Find divergence point – Starting from the highest ancestor, find the first level where the parent’s currently focused child does not match the path node. This is the “push point.”
  3. Change pages – From the push point downward, call ChangePage at each level to switch the visible child to the path node.
  4. Push focus – At the push point, call PushFocus to record the new child on the parent’s focus stack.
  5. Cascade – Continue down the path to the target page, focusing each level.

This algorithm ensures that navigating to a deeply nested page correctly updates every intermediate level.


NavigateBack() walks up from the calling page to find the first ancestor with a non-empty focus stack, then pops it:

  1. Walk up – Starting from the calling page, check each ancestor’s focus stack.
  2. Pop focus – At the first ancestor with a non-empty stack, call PopFocus to restore the previous child.
  3. Fallback to UI Manager – If no ancestor has a non-empty stack (the user has navigated all the way back to the root), call NavLastUI() on the UIManagerRequestBus to return to the previous canvas in the global focus stack.

Show/Hide Transitions

Each page can have three UiAnimationMotion assets assigned:

  • m_onShow – Played when the page becomes visible.
  • m_onShowLoop – Played in a loop after the show animation completes.
  • m_onHide – Played when the page is hidden.

These motion assets drive LyShine property animations (position, scale, rotation, alpha, color) to create smooth transitions between pages.


Data Types

Controls how a page restores child focus when navigated back to.

ValueDescription
RestoreLastRestores the last focused child page from the focus stack.
AlwaysDefaultAlways resets to m_defaultChildPage, clearing focus history.

FocusEntry

A single entry in a page’s focus stack.

FieldTypeDescription
focusedChildAZ::EntityIdThe entity ID of the child page that was focused.

Inspector Properties

PropertyTypeDescription
Is RootboolWhen true, this page registers itself with the UI Manager as a root canvas entry point.
UI NameAZStd::string(Root only) The name this root page registers under. Must match the name used in LoadGSUI.
Start Enabledbool(Root only) When true, the root page shows itself immediately upon registration.
Page NameAZStd::stringA human-readable name for this page, used for identification and debugging.
Default Child PageAZ::EntityIdThe child page entity to show by default when this page is focused.
Return PolicyNavigationReturnPolicyControls whether returning to this page restores the last child or always resets to the default.
Manage Child PagesboolWhen true, this page acts as a container that manages nested child pages.
Default InteractableAZ::EntityIdThe LyShine interactable element to focus by default when this page is shown (e.g. the first button).
On ShowUiAnimationMotionAnimation played when this page becomes visible.
On Show LoopUiAnimationMotionLooping animation played after the show animation completes.
On HideUiAnimationMotionAnimation played when this page is hidden.

GS_UIPageComponent in the O3DE Inspector


API Reference

Request Bus: UIPageRequestBus

Commands sent to a specific page by entity ID. Addressed bus – addressed by EntityId.

User-Facing Methods

These methods are intended to be called from gameplay code, ScriptCanvas, or other UI components to drive navigation.

MethodParametersReturnsDescription
NavigateTobool forcedvoidNavigates the entire page tree to show this page. Builds a path to root and cascades focus changes downward. When forced is true, bypasses transition guards.
NavigateBack—voidWalks up from this page to find the first ancestor with focus history and pops it. Falls back to NavLastUI if no ancestor has history.
ChangePageAZ::EntityId target, bool takeFocus, bool forcedvoidSwitches this page’s visible child to the target entity. When takeFocus is true, the target also receives interactable focus.
ToggleShowbool onvoidShows or hides this page. Plays the appropriate show/hide animation.
ChangeUIconst AZStd::string& targetUI, bool takeFocus, bool hideThisvoidSwitches to a different UI canvas by name. Optionally hides this page and gives focus to the target canvas.
FocusChildPageByNameconst AZStd::string& name, bool forcedvoidFinds a child page by its m_pageName and focuses it.

Internal Methods

These methods are used internally by the navigation system. They can be called from C++ when building custom navigation behavior, but are not typically called from ScriptCanvas.

MethodParametersReturnsDescription
FocusPagebool forcedvoidActivates this page, shows it, and focuses its default interactable. Called as part of the NavigateTo cascade.
PushFocusAZ::EntityId child, bool forcedvoidPushes a child page onto this page’s focus stack and focuses it.
PopFocus—voidPops the top entry from this page’s focus stack and restores the previous child. Respects m_returnPolicy.
ClearFocusHistory—voidClears this page’s entire focus stack.
RegisterChildPageAZ::EntityId entityvoidRegisters a child page entity with this parent. Called by child pages during their activation.
NotifyInteractableFocusedAZ::EntityId entityvoidNotifies this page that a LyShine interactable has received focus. Used for tracking the current interactable selection.
GetResolvedInteractable—AZ::EntityIdReturns the currently resolved default interactable for this page, accounting for child page focus state.

Usage Examples

C++ – Navigating to a Specific Page

#include <GS_UI/GS_UIBus.h>

// Navigate to a specific page entity, forcing the transition
GS_UI::UIPageRequestBus::Event(
    settingsPageEntityId,
    &GS_UI::UIPageRequestBus::Events::NavigateTo,
    true  // forced
);

C++ – Navigating Back

// Navigate back from the current page
GS_UI::UIPageRequestBus::Event(
    currentPageEntityId,
    &GS_UI::UIPageRequestBus::Events::NavigateBack
);

C++ – Switching Between UI Canvases

// From the title screen, switch to the gameplay HUD
GS_UI::UIPageRequestBus::Event(
    titlePageEntityId,
    &GS_UI::UIPageRequestBus::Events::ChangeUI,
    AZStd::string("GameplayHUD"),
    true,  // takeFocus
    true   // hideThis
);

Companion Component Pattern

GS_UIPageComponent handles navigation structure, but it does not contain game-specific logic. The intended pattern is to place a companion component on the same entity as the page component. The companion component listens for page show/hide events and drives game-specific behavior (populating lists, starting timers, sending analytics events).

This separation keeps the navigation system generic and reusable while allowing each page to have unique behavior through its companion.


See Also

For component references:

  • UI Manager – The singleton that owns canvases and drives the global focus stack
  • UI Animation – Motion assets used for page show/hide transitions

For conceptual overviews and usage guides:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.3 - UI Animation

GS_Motion extension with eight LyShine-specific animation tracks, data-driven .uiam assets, and a standalone playback component.

UI Animation is a domain extension of the GS_Motion system, purpose-built for animating LyShine UI elements. It provides eight concrete track types that target LyShine component properties (position, scale, rotation, alpha, color, text), a data asset format (.uiam) for authoring animations, and a standalone component for playing them on any entity.

The architecture follows the GS_Motion domain extension pattern: UiMotionTrack extends GS_MotionTrack as the domain base, each concrete track type targets a specific LyShine property, and UiAnimationMotionAsset extends GS_MotionAsset to bundle tracks into a single asset file.

For usage guides and setup examples, see The Basics: GS_UI.

 

Contents


Domain Extension Pattern

To add a custom track type, use the UiMotionTrack ClassWizard template — see GS_UI Templates. The template generates the header and source, and the only manual step is adding a Reflect(context) call in UIDataAssetsSystemComponent.cpp. Once reflected, the new track type appears automatically in the asset editor type picker via EnumerateDerived.

GS_Motion is designed to be extended per domain. GS_UI provides the UI domain extension:

GS_Motion BaseGS_UI Extension
GS_MotionTrackUiMotionTrack – Domain base for all UI tracks
GS_MotionAssetUiAnimationMotionAsset – Asset container for UI tracks
GS_MotionCompositeUsed internally by UiAnimationMotion for multi-track playback
GS_MotionProxyUsed internally by UiAnimationMotion for asset instance binding

See GS_Motion for the full base system reference.


Track Types

UiMotionTrack

The domain base class that all UI-specific tracks extend. Inherits from GS_Core::GS_MotionTrack and provides LyShine entity targeting common to all UI tracks.

Concrete Tracks

Each track type animates a specific LyShine component property. All tracks are authored inside a .uiam asset.

Track TypeTarget ComponentProperty AnimatedValue Type
UiPositionTrackUiTransform2dComponentPosition offsetAZ::Vector2
UiScaleTrackUiTransform2dComponentScaleAZ::Vector2
UiRotationTrackUiTransform2dComponentRotationfloat
UiElementAlphaTrackUiElementComponentElement-level alphafloat
UiImageAlphaTrackUiImageComponentImage alphafloat
UiImageColorTrackUiImageComponentColor tintAZ::Color
UiTextColorTrackUiTextComponentText colorAZ::Color
UiTextSizeTrackUiTextComponentFont sizefloat

UiAnimationMotionAsset

The data asset that holds a complete UI animation. Extends GS_Core::GS_MotionAsset → AZ::Data::AssetData. Requires GS_AssetReflectionIncludes.h when reflecting — see Serialization Helpers.

PropertyTypeDescription
Extension—.uiam
Motion NameAZStd::stringA human-readable name for this animation.
LoopboolWhether this animation loops continuously after completing.
TracksAZStd::vector<UiMotionTrack*>The list of animation tracks that make up this motion. Each track targets a specific LyShine property on a specific entity.

To understand how to author Feedback Motions, refer to UI Animation: Authoring UIAnimation Motions


UiAnimationMotion

A serializable wrapper struct that bridges a .uiam asset to runtime playback. This is not a component – it is a data struct used as a field type in other components (such as GS_UIPageComponent’s m_onShow, m_onHide, and GS_ButtonComponent’s hover/select fields).

FieldTypeDescription
AssetAZ::Data::Asset<UiAnimationMotionAsset>Reference to the .uiam asset to play.
Motion ProxiesAZStd::vector<GS_MotionProxy>Instance-specific bindings that map track targets to actual entities at runtime.
Motion CompositeAZStd::unique_ptr<GS_MotionComposite>The runtime composite that coordinates multi-track playback. Created automatically from the asset data.

UiAnimationMotion struct with proxy bindings configured


UiAnimationMotionComponent

A standalone component that plays a UiAnimationMotion on any entity. Use this when you need to trigger a UI animation outside of the page navigation or button systems – for example, an ambient animation on a background element, or a custom transition triggered from ScriptCanvas.

The component exposes the UiAnimationMotion struct in the Inspector, allowing you to assign a .uiam asset and configure it directly on any entity.

UiAnimationMotionComponent in the O3DE Inspector


Adding Custom Tracks

Use the UIMotionTrack ClassWizard template to generate a new world-space track — see GS_UI Templates. The only manual step after generation is adding a Reflect(context) call in your {$Gem}DataAssetSystemComponent.cpp. Once reflected, the new track type is discovered automatically and appears in the asset editor type picker.

Override Init(ownerEntityId) to cache entity context, and Update(easedProgress) to drive the target property via its bus.

void ${Custom}Track::Init(AZ::EntityId ownerEntity)
{
    // Always call the base first — it sets m_owner.
    GS_UI::UiMotionTrack::Init(ownerEntity);

    // Cache the element's origin value so the track can apply additive offsets.
    // Example:
    //   UiTransformBus::EventResult(m_originPosition, ownerEntity, &UiTransformBus::Events::GetLocalPosition);
}

void ${Custom}Track::Update(float easedProgress)
{
    // Evaluate the gradient at the current eased progress and apply to the element.
    // easedProgress is in [0, 1] and already passed through the track's CurveType.
    // Example:
    //   const AZ::Vector2 offset = valueGradient.Evaluate(easedProgress);
    //   UiTransformBus::Event(m_owner, &UiTransformBus::Events::SetLocalPosition, m_originPosition + offset);
}

Usage Examples

Typical Animation Setup

A page fade-in animation might use two tracks in a single .uiam asset:

  • A UiElementAlphaTrack that fades from 0 to 1 over 0.3 seconds.
  • A UiPositionTrack that slides the element 20 pixels upward over the same duration.

Assign this asset to a page’s On Show field and the page will automatically play the animation whenever it becomes visible.


Button Hover Animation

A button scale-bounce on hover:

  • A UiScaleTrack that scales from 1.0 to 1.1 and back to 1.0 over 0.15 seconds.

Assign this to the button’s Hover Motion field. Assign the reverse (scale from current back to 1.0) to Unhover Motion.


See Also

For component references:

  • Page Navigation – Pages use UiAnimationMotion for show/hide transitions
  • Buttons – Buttons use UiAnimationMotion for hover/select animations

For conceptual overviews and usage guides:

For related resources:

  • GS_Motion – The base motion system that UI Animation extends

Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.4 - UI Interaction

Button animations and input interception — motion-driven hover/select states and input channel management for focused UI canvases.

UI Interaction covers the two systems that handle player input at the UI layer: the enhanced button component that plays motion-based animations on hover and select, and the input interceptor that captures and routes input events while a canvas is focused.

For usage guides and setup examples, see The Basics: GS_UI.

 

Contents


Button

An enhanced button component that extends LyShine interactable behavior with motion-driven hover and select state animations. Attach alongside a UiButtonComponent and assign .uiam assets for hover, unhover, and select states.

ComponentPurpose
GS_ButtonComponentEnhanced button. Plays UiAnimationMotion assets on hover, unhover, and select events.

Button API


UI Input

An input interceptor component that captures and redirects input events for UI-specific handling, preventing input from propagating to gameplay systems while a UI canvas is focused.

Component / TypePurpose
GS_UIInputInterceptorComponentIntercepts configured input events and re-broadcasts them on UIInputNotificationBus.
GS_UIInputProfileData asset defining which input channels to intercept and how to route them.

UI Input API


See Also

For component references:

  • Page Navigation – Pages use the same UiAnimationMotion system for show/hide transitions
  • UI Animation – The motion asset system used by buttons and pages
  • UI Manager – Controls which canvas is focused, activating the input interceptor

For conceptual overviews and usage guides:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.4.1 - Buttons

Enhanced button component with motion-driven hover and select animations, built on top of LyShine interactable notifications.

GS_ButtonComponent is an enhanced button that extends O3DE’s LyShine interactable system with UiAnimationMotion support. Instead of relying on LyShine’s built-in state sprites or color transitions, GS_ButtonComponent plays data-driven motion assets for hover, unhover, and select states, giving you full control over animated button feedback using the same animation system that drives page transitions.

The component attaches to any entity that already has a LyShine interactable (e.g. UiButtonComponent). It listens for LyShine interactable notifications and triggers the appropriate motion asset when the interactable state changes.

For usage guides and setup examples, see The Basics: GS_UI.

GS_ButtonComponent in the O3DE Inspector

 

Contents


How It Works

LyShine Integration

GS_ButtonComponent connects to O3DE’s UiInteractableNotificationBus on the same entity. When LyShine fires hover, unhover, or press events, the component intercepts them and plays the corresponding UiAnimationMotion asset. This means you author your button animations as .uiam assets in the same workflow as page transitions and other UI animations.

The component does not replace LyShine’s interactable – it works alongside it. The LyShine UiButtonComponent still handles click detection, navigation, and accessibility. GS_ButtonComponent adds the visual animation layer on top.


Motion States

Each button can have up to three motion assets assigned:

  • Hover Motion – Played when the interactable receives hover focus (mouse enter or gamepad navigation highlight).
  • Unhover Motion – Played when the interactable loses hover focus.
  • Select Motion – Played when the interactable is pressed/selected.

Each of these is a UiAnimationMotion struct that references a .uiam asset. The motion can animate any combination of the eight UI track types (position, scale, rotation, alpha, color, text size) to create rich button feedback.


Inspector Properties

PropertyTypeDescription
Hover MotionUiAnimationMotionThe animation played when this button receives hover focus.
Unhover MotionUiAnimationMotionThe animation played when this button loses hover focus.
Select MotionUiAnimationMotionThe animation played when this button is pressed.

Usage

Setup

  1. Add a LyShine UiButtonComponent (or other interactable) to your entity.
  2. Add GS_ButtonComponent to the same entity.
  3. Author .uiam assets for the hover, unhover, and select states using the UiAnimationMotion system.
  4. Assign the .uiam assets to the corresponding fields in the Inspector.

The button will now play the assigned animations automatically when the user interacts with it. No ScriptCanvas or C++ code is required for the animation behavior.

A GS_ButtonComponent configured with a UiAnimationMotion proxy for animation target remapping


See Also

For component references:

  • UI Animation – The motion asset system that drives button animations
  • Page Navigation – Pages use the same UiAnimationMotion system for show/hide transitions
  • UI Input – Input interception used alongside buttons in interactive canvases

For conceptual overviews and usage guides:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.4.2 - UI Input

Input interception for UI canvases – captures and redirects input events to prevent gameplay propagation while a UI is focused.

GS_UIInputInterceptorComponent intercepts input events when a UI canvas is active, preventing them from propagating to gameplay systems. This solves the common problem of button presses or navigation inputs leaking through to the game world while a menu is open.

The component uses a GS_UIInputProfile to define which input events to intercept and how to route them. When the associated UI canvas is focused, the interceptor captures configured input events and broadcasts them on the UIInputNotificationBus instead of allowing them to reach gameplay input handlers.

For usage guides and setup examples, see The Basics: GS_UI.

UI Input Profile asset in the O3DE Asset Editor

 

Contents


How It Works

Input Interception

When a UI canvas receives focus through the UI Manager, the input interceptor activates. It listens for input events that match its configured GS_UIInputProfile and consumes them before they reach the gameplay input system. The intercepted events are then re-broadcast on the UIInputNotificationBus so that UI-specific logic can respond to them.

When the canvas loses focus, the interceptor deactivates and input flows normally to gameplay systems.


GS_UIInputProfile

The input profile defines which input channels to intercept and how to map them for UI use. This allows different UI canvases to intercept different sets of inputs – a pause menu might intercept all gameplay inputs, while a HUD overlay might only intercept specific menu navigation inputs.


API Reference

Notification Bus: UIInputNotificationBus

Events broadcast when the interceptor captures input. Multiple handler bus – any number of components can subscribe to receive intercepted input notifications.

Components that need to respond to UI-specific input (such as custom navigation logic or input-driven UI animations) connect to this bus to receive intercepted events while a canvas is focused.


Inspector Properties

PropertyTypeDescription
Input ProfileGS_UIInputProfileDefines which input events to intercept and how to route them for UI handling.

Usage

Setup

  1. Add GS_UIInputInterceptorComponent to the same entity as your root GS_UIPageComponent.
  2. Configure the GS_UIInputProfile to specify which input channels should be intercepted when this canvas is focused.
  3. Any component that needs to respond to intercepted input connects to UIInputNotificationBus.

See Also

For component references:

  • UI Manager – Controls which canvas is focused and therefore which interceptor is active
  • Page Navigation – The page system that drives canvas focus changes
  • Button – Button animations used alongside input interception

For conceptual overviews and usage guides:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.5 - Widgets

Standalone UI widget components — load screens, pause menus, and other self-contained UI elements that operate outside the page navigation model.

Widgets are standalone UI components that handle specific UI scenarios outside the page navigation hierarchy. Unlike pages, which are navigated to and from, widgets activate and deactivate in response to game events — a load screen appears during a stage transition, a pause menu overlays gameplay when the player pauses.

For usage guides and setup examples, see The Basics: GS_UI.

 

Contents


Load Screen

GS_LoadScreenComponent manages loading screen display during stage transitions. It listens for stage load lifecycle events and shows or hides a designated LyShine canvas accordingly.

GS_LoadScreenComponent in the O3DE Inspector

ComponentPurpose
GS_LoadScreenComponentDisplays a loading canvas during stage transitions. Hides automatically when loading is complete.

Pause Menu

PauseMenuComponent manages the pause overlay. It responds to pause input, activates the pause canvas, and suppresses gameplay systems while the menu is open.

ComponentPurpose
PauseMenuComponentToggles pause state and the pause menu canvas overlay.

See Also

For component references:

  • UI Manager – Loads and manages the canvases that widgets display on
  • Page Navigation – The navigation system for menus within widget canvases

For conceptual overviews and usage guides:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.6 - Templates

ClassWizard templates for GS_UI — custom UI animation motion tracks.

All GS_UI extension types are generated through the ClassWizard CLI. The wizard handles UUID generation and cmake file-list registration automatically.

For usage guides and setup examples, see The Basics: GS_UI.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Ui Motion Track

Template: UiMotionTrack

Creates a custom animation track for the GS_UI motion system. A UiMotionTrack child animates one LyShine UI element property (position, scale, alpha, color, rotation, etc.) over a normalised [0,1] eased progress value. Tracks are added to UiMotionAnimations assets in the editor and played by UIMotionAnimationComponent.

Generated files:

  • Include/${GemName}/UIMotions/MotionTracks/${Name}Track.h
  • Source/UIMotions/MotionTracks/${Name}Track.cpp

CLI:

python ClassWizard.py --template UiMotionTrack --gem <GemPath> --name <Name>

Post-generation — manual registration required:

In UIDataAssetsSystemComponent.cpp, add:

#include <path/to/${Name}Track.h>
// inside Reflect(context):
${Name}Track::Reflect(context);

Extensibility: One track class per animatable property. Any number of track types can be registered. Instances are stored as polymorphic pointers in UiMotionAnimation assets — the property editor’s type picker discovers them automatically via O3DE’s serialization reflection (EnumerateDerived).

See also: UI Animation — the full track architecture, domain extension pattern, and built-in track type reference.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.14.7 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_UI.


Get GS_UI

GS_UI — Explore this gem on the product page and add it to your project.

4.15 - GS_Unit

Character and entity control — unit lifecycle, player and AI controllers, input processing, movement, and grounding.

GS_Unit is the gem that gives entities agency. It provides a unit lifecycle system for spawning and possessing controllable entities, a controller layer that separates player and AI concerns, an input pipeline that converts raw device input into movement vectors, a family of mover components covering free 3D, surface-sliding, and physics-driven movement, and grounding components for stable surface contact. The gem depends only on GS_Core and introduces one data asset, GS_UnitMovementProfile, for authoring movement parameters outside of code.

For usage guides and setup examples, see The Basics: GS_Unit.

 

Contents


Unit Manager

The lifecycle system for spawnable, possessable units. The Unit Manager is a singleton that handles unit spawn requests and tracks the active player controller. Individual Unit components identify an entity as a game unit and manage possession state.

ComponentPurpose
GS_UnitManagerComponentSingleton manager. Handles unit spawn requests and player controller registration.
GS_UnitComponentMarks an entity as a unit. Manages possession, controller access, and standby state.

Unit Manager API


Controllers

The controller layer separates how a unit is driven from what the unit is. A base controller handles possession handshake. The player controller adds input-driven behavior. The AI controller provides a hook for script or code-driven NPC logic.

ComponentPurpose
GS_UnitControllerComponentBase controller. Manages unit possession and depossession.
GS_PlayerControllerComponentPlayer-specific controller. Routes player input to the possessed unit.
GS_AIControllerComponentAI controller. Provides the hook point for NPC logic.

Unit Controllers API


Input Data

The input pipeline converts raw device events into a normalised input state that movement and interaction systems can read. Input Reactor components handle discrete button events. Axis Reactor components handle analogue axes. Concrete implementations cover keyboard WASD and joystick movement.

ComponentPurpose
GS_InputDataComponentHolds the current normalised input state for a unit.
GS_InputReactorComponentBase component for reacting to discrete input events.
GS_InputAxisReactorComponentBase component for reacting to analogue axis input.
GS_PlayerControllerInputReaderComponentReads player device input and writes it into GS_InputDataComponent.
KeyboardMovement_InputReactorComponentConverts WASD keyboard input into a movement vector.
JoyAxisMovement_AxisReactorComponentConverts joystick axis input into a movement vector.

Input Data API


Movement

The mover stack translates input vectors and influences into entity motion. The Mover Context component tracks current movement state. Concrete movers cover three locomotion modes. Grounder components maintain stable surface contact and feed grounded state back to the mover. Influence components apply persistent or volume-scoped forces on top of any mover.

ComponentPurpose
GS_MoverComponentBase movement component. All concrete movers extend this.
GS_MoverContextComponentTracks contextual movement state shared across the mover stack.
GS_3DFreeMoverComponentFree 3D movement — suitable for flying or swimming characters.
GS_3DSlideMoverComponentSurface-sliding 3D movement — suitable for ground characters.
GS_PhysicsMoverComponentPhysics-driven movement via rigid body integration.
GS_GrounderComponentBase ground detection component.
GS_PhysicsRayGrounderComponentRaycast-based ground detection.
GlobalMovementInfluenceComponentApplies a global movement modifier (e.g., wind, current) to all units.
MovementInfluenceFieldComponentApplies a movement modifier inside a spatial volume.

Movement API


Unit Action Graph

A visual HFSM editor for authoring character behavior with parallel layers, state transitions, and polymorphic conditions. Built on the gs_graphcanvas framework with a StateMachineGraph topology.

FeaturePurpose
Unit Action GraphHFSM editor — states, transitions, conditions, and parallel layers.

Unit Action Graph


Installation

GS_Unit requires only GS_Core. It is one of the lightest gems in the framework to add.

  1. Enable GS_Unit in Project Manager or project.json.
  2. Add GS_UnitManagerComponent to the Game Manager entity and register it in the Startup Managers list.
  3. Build your unit prefab: add GS_UnitComponent, the appropriate Controller, GS_InputDataComponent, and a Mover for the locomotion type you need.
  4. Add GS_PlayerControllerInputReaderComponent and the relevant input reactor components to the player unit prefab.
  5. Refer to the Unit Set Up Guide for a full walkthrough.

See Also

For conceptual overviews and usage guides:

For related resources:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.1 - Unit Manager

The central manager for unit lifecycle — spawning, registration, standby coordination, and unit tracking across the game session.

The Unit Manager is the GS_Unit gem’s central lifecycle controller. It extends GS_ManagerComponent and coordinates all unit-related operations: spawning new units from prefabs, registering player controllers, tracking which entities qualify as units, and propagating standby signals to the unit subsystem.

Like all GS_Play managers, the Unit Manager is spawned by the Game Manager during the startup sequence and participates in the two-stage initialization pattern. It connects to the GameManagerNotificationBus for lifecycle events and standby coordination.

For usage guides and setup examples, see The Basics: GS_Unit.

Unit Manager component in the O3DE Inspector

 

Contents


How It Works

Unit Spawning

When a system requests a new unit via RequestSpawnNewUnit, the Unit Manager instantiates the provided unit prefab under the specified parent entity. Once the unit entity activates and its GS_UnitComponent registers, the manager broadcasts ReturnNewUnit back to the caller with the new unit’s entity ID.

Player Controller Registration

Player controllers register themselves with the Unit Manager via RegisterPlayerController. This allows the manager to track which controllers are active and route unit possession accordingly.

Unit Validation

Any system can call CheckIsUnit with an entity ID to verify whether that entity is a registered unit. This is useful for targeting systems, interaction checks, and other gameplay logic that needs to distinguish units from ordinary entities.

Standby Propagation

When the Game Manager enters or exits standby, the Unit Manager receives the signal and broadcasts EnterStandby / ExitStandby on the UnitManagerNotificationBus. All unit subsystems (controllers, movers, input readers) should listen for these notifications and pause or resume accordingly.


API Reference

GS_UnitManagerComponent

Extends GS_Core::GS_ManagerComponent. Handles unit spawning, controller registration, and standby propagation.

Data Fields

FieldTypeDescription
debugExitPointAZ::EntityIdEntity used as the spawn/exit point when PlaceUnitAtExit is called. Configured in the Inspector.
m_unitSpawnTicketsAZStd::vector<AzFramework::EntitySpawnTicket>Spawn tickets for all active unit spawn operations. Keeps spawns alive until the unit entity registers.
playerListAZStd::vector<AZ::EntityId>Registered player controller entity IDs. Populated via RegisterPlayerController.

Request Bus: UnitManagerRequestBus

Commands sent to the Unit Manager. Global bus — multiple handlers.

MethodParametersReturnsDescription
RequestSpawnNewUnitAZ::EntityId callingEntityId, SpawnableScriptAssetRef unitPrefab, AZ::EntityId spawnParentEntityIdvoidRequests the manager to spawn a new unit from the given prefab under the specified parent entity. The caller receives the result via ReturnNewUnit.
RegisterPlayerControllerAZ::EntityId controllerIdvoidRegisters a player controller entity with the manager for tracking and routing.
CheckIsUnitAZ::EntityId unitIdboolReturns whether the given entity ID is a registered unit.

Notification Bus: UnitManagerNotificationBus

Events broadcast by the Unit Manager. Multiple handler bus — any number of components can subscribe.

EventParametersDescription
HandleStartup—Fired during the manager startup sequence. Override in subclasses to hook into the two-stage initialization before OnStartupComplete.
ReturnNewUnitAZ::EntityId caller, AZ::EntityId newUnitFired when a requested unit has been spawned and registered. The caller ID matches the original requester.
EnterStandby—Fired when the unit subsystem enters standby. Pause unit-related logic.
ExitStandby—Fired when the unit subsystem exits standby. Resume unit-related logic.

GS_UnitComponent

The GS_UnitComponent is the component that makes an entity a “unit.” It handles possession by controllers, tracks its owning controller, and provides identity via a unique name. For full unit entity setup details, see the Units page.

Request Bus: UnitRequestBus

Commands sent to a specific unit. ById bus — addressed by entity ID, multiple handlers.

MethodParametersReturnsDescription
PossessAZ::EntityId possessingControllervoidAssigns a controller to this unit. The unit stores the controller reference and notifies listeners.
DePossess—voidRemoves the current controller from this unit.
GetController—AZ::EntityIdReturns the entity ID of the currently possessing controller.
GetUniqueName—AZStd::stringReturns the unique name assigned to this unit.

Notification Bus: UnitNotificationBus

Events broadcast by a unit. ById bus — addressed by the unit’s entity ID. Carries the standby signals only; possession moved to the GS_Core PossessionEmissionBus in the interfaces migration.

EventParametersDescription
UnitEnteringStandby—Fired when this unit enters standby.
UnitExitingStandby—Fired when this unit exits standby.

Usage Examples

C++ – Spawning a Unit

#include <GS_Unit/GS_UnitBus.h>

// Request a new unit spawn
GS_Unit::UnitManagerRequestBus::Broadcast(
    &GS_Unit::UnitManagerRequestBus::Events::RequestSpawnNewUnit,
    GetEntityId(),       // caller
    myUnitPrefab,        // SpawnableScriptAssetRef
    spawnParentEntity    // parent entity
);

C++ – Listening for Unit Spawn Results

#include <GS_Unit/GS_UnitBus.h>

class MySpawner
    : public AZ::Component
    , protected GS_Unit::UnitManagerNotificationBus::Handler
{
protected:
    void Activate() override
    {
        GS_Unit::UnitManagerNotificationBus::Handler::BusConnect();
    }

    void Deactivate() override
    {
        GS_Unit::UnitManagerNotificationBus::Handler::BusDisconnect();
    }

    void ReturnNewUnit(AZ::EntityId caller, AZ::EntityId newUnit) override
    {
        if (caller == GetEntityId())
        {
            // This is the unit we requested — possess it, configure it, etc.
        }
    }
};

Script Canvas

Spawning a unit and receiving the result:

Getting the controller on a unit:

Possessing and de-possessing a unit:

Reacting to unit standby events:


Extension Guide

Extend the Unit Manager to add custom spawn logic, additional tracking, or project-specific unit lifecycle behavior. The Unit Manager extends GS_ManagerComponent, so follow the standard Manager extension pattern.

Header (.h)

#pragma once
#include <GS_Unit/GS_UnitBus.h>
#include <Source/Managers/GS_UnitManagerComponent.h>

namespace MyProject
{
    class MyUnitManager : public GS_Unit::GS_UnitManagerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyUnitManager);

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

    protected:
        // Override manager lifecycle hooks
        void OnStartupComplete() override;
        void OnEnterStandby() override;
        void OnExitStandby() override;
    };
}

Implementation (.cpp)

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

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

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

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

    void MyUnitManager::OnStartupComplete()
    {
        GS_UnitManagerComponent::OnStartupComplete();
        // Custom post-startup logic
    }

    void MyUnitManager::OnEnterStandby()
    {
        GS_UnitManagerComponent::OnEnterStandby();
        // Custom standby logic
    }

    void MyUnitManager::OnExitStandby()
    {
        GS_UnitManagerComponent::OnExitStandby();
        // Custom resume logic
    }
}

See Also

For component references:

  • Units – Unit entity setup and configuration
  • Unit Controllers – Controller components that possess units
  • Input Data – Input state and reaction components

For related resources:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.2 - Unit Controllers

The controller-unit possession model — base controller, player controller, and AI controller components for driving unit behavior.

Unit Controllers are the decision-making layer in the GS_Unit architecture. A controller “possesses” a unit entity, giving it authority over that unit’s actions. This possession model cleanly separates the what (the unit with its movement, abilities, and stats) from the who (the controller providing intent — player input or AI logic).

The system provides three controller components:

  • GS_UnitControllerComponent – The base controller with possession logic, placement helpers, and the virtual interface for extension.
  • GS_PlayerControllerComponent – A player-specific controller that integrates with the Input Data subsystem to translate player input into unit commands.
  • GS_AIControllerComponent – An AI controller for NPC units, providing hooks for behavior trees or custom AI logic.

For usage guides and setup examples, see The Basics: GS_Unit.

Player Controller and Input Reader components in the O3DE Inspector

 

Contents


How It Works

Possession Model

Unit Possession Pattern Graph

Breakdown

Every unit has exactly one controller at a time, or no controller at all. Possession is established by calling PossessUnit on the controller and released by calling DePossessUnit. Possession is observed through the GS_Core cross-gem contract PossessionEmissionBus — the controller fires OnPossessedUnit / OnReleasedUnit on its own entity, and the unit fires OnPossessedByController / OnReleasedByController on its entity — so other systems can react without depending on GS_Unit.

ConceptDescription
PossessionA controller attaches to a unit. The unit accepts input and movement commands from that controller only.
DePossessionThe controller releases the unit. The unit halts input processing and enters a neutral state.
Possession eventsFire on the GS_Core PossessionEmissionBus (addressed by entity ID) whenever a unit’s controller changes.
GetControllerReturns the entity ID of the current controller, or an invalid ID if none.
GetUniqueNameReturns the string name assigned by the Unit Manager when this unit was spawned.

Unit Placement

Controllers provide helper methods for positioning their possessed unit:

  • PlaceUnitAtExit – Places the unit at the level’s designated exit point.
  • PlaceUnitAtEntity – Places the unit at a specific entity’s world position.

Unique Names

Both controllers and units maintain a unique name string. The base controller provides a virtual SetUniqueName() method that subclasses override to generate names from their specific context (e.g., player slot number, AI template name).


Component Reference

GS_UnitControllerComponent (Base)

The base controller class. All controller types inherit from this. It handles possession, placement, unique name generation, and the notification plumbing. Extend this class to create custom controller types.


GS_PlayerControllerComponent

Extends AZ::Component. The player controller adds input handling on top of the base controller pattern. It registers itself with the Unit Manager via RegisterPlayerController and integrates with the Input Data subsystem to feed player input into the possessed unit’s movement and action systems.

Typical entity setup for a player controller:

  • GS_PlayerControllerComponent
  • GS_InputDataComponent – Holds the current input state
  • GS_PlayerControllerInputReaderComponent – Reads raw input into InputData
  • One or more InputReactorComponents – Translate input data into movement vectors or action triggers

GS_AIControllerComponent

Extends AZ::Component. The AI controller provides the same possession interface as the player controller but is driven by AI logic rather than player input. It does not connect to the input subsystem. Instead, it exposes hooks for behavior trees, scripted sequences, or custom AI decision-making to issue commands to the possessed unit.


API Reference

Request Bus: UnitControllerRequestBus

Commands sent to a specific controller. ById bus — addressed by the controller’s entity ID.

MethodParametersReturnsDescription
PossessUnitAZ::EntityId targetUnitvoidPossesses the target unit. Fires OnPossessedUnit on the GS_Core PossessionEmissionBus (controller entity).
DePossessUnit—voidReleases the currently possessed unit. Fires OnReleasedUnit on the GS_Core PossessionEmissionBus (controller entity).
PlaceUnitAtExit—voidMoves the possessed unit to the level’s exit point.
PlaceUnitAtEntityAZ::EntityId targetEntityvoidMoves the possessed unit to the world position of the target entity.
GetUnit—AZ::EntityIdReturns the entity ID of the currently possessed unit.
GetUniqueName—AZStd::stringReturns the unique name of this controller.

Cross-Gem Contract: PossessionEmissionBus

Possession is observed through the GS_Core contract bus PossessionEmissionBus — the old in-gem UnitControllerNotificationBus was retired in the interfaces migration. The bus is addressed by EntityId with Multiple handlers; any gem or script may listen without depending on GS_Unit. Controller-vantage events fire on the controller entity:

EventParametersDescription
OnPossessedUnitAZ::EntityId unitFired on the controller entity when it possesses a unit (invalid unit = now possessing none). GS_PlayerControllerInputReaderComponent handles this to begin routing input to the new unit.
OnReleasedUnitAZ::EntityId unitFired on the controller entity when it releases the unit it held.

The matching unit-vantage events (OnPossessedByController / OnReleasedByController) fire on the unit entity — see GS_Unit Units. For how to handle an emission bus, see Core Interfaces → Emit.


Virtual Methods

Override these when extending the base controller. Always call the base implementation.

MethodParametersReturnsDescription
PostActivateProcessing()—voidCalled after the controller’s Activate() completes. Override to add custom initialization that depends on the controller being fully active.
SetUniqueName()—voidCalled during initialization to set the controller’s unique name. Override to generate names from your project’s naming scheme.

Usage Examples

C++ – Possessing a Unit

#include <GS_Unit/GS_UnitBus.h>

// From a controller, possess a unit
GS_Unit::UnitControllerRequestBus::Event(
    GetEntityId(),
    &GS_Unit::UnitControllerRequestBus::Events::PossessUnit,
    targetUnitEntityId
);

C++ – Querying a Controller’s Unit

#include <GS_Unit/GS_UnitBus.h>

AZ::EntityId possessedUnit;
GS_Unit::UnitControllerRequestBus::EventResult(
    possessedUnit,
    controllerEntityId,
    &GS_Unit::UnitControllerRequestBus::Events::GetUnit
);

Script Canvas

Possessing and de-possessing a unit from a controller:

Getting the unit possessed by a controller:


Extension Guide

Use the UnitController ClassWizard template to generate a new controller with boilerplate already in place — see GS_Unit Templates.

Create custom controllers by extending GS_UnitControllerComponent. This is the standard approach for game-specific controller logic such as party management, vehicle control, or specialized AI behaviors.

Header (.h)

#pragma once
#include <GS_Unit/GS_UnitBus.h>
#include <Source/Controllers/GS_UnitControllerComponent.h>

namespace MyProject
{
    class MyCustomController : public GS_Unit::GS_UnitControllerComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCustomController);

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

    protected:
        void PostActivateProcessing() override;
        void SetUniqueName() override;
    };
}

Implementation (.cpp)

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

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

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

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<MyCustomController>(
                    "My Custom Controller", "Project-specific controller logic")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "MyProject")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
            }
        }
    }

    void MyCustomController::PostActivateProcessing()
    {
        GS_UnitControllerComponent::PostActivateProcessing();
        // Custom post-activation logic — connect to AI systems, load profiles, etc.
    }

    void MyCustomController::SetUniqueName()
    {
        // Generate a unique name from your project's naming scheme
        uniqueName = AZStd::string::format("CustomController_%d", m_controllerIndex);
    }
}

See Also

For component references:

  • Unit Manager – Spawning and lifecycle management
  • Units – Unit entity setup and the GS_UnitComponent
  • Input Data – Input state and reaction components used by player controllers
  • Movement – Movement components driven by controller input

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.3 - Input Data

The decoupled input pipeline — reading hardware events on the controller, storing state on the unit, and reacting to state changes through reactor components.

The Input Data subsystem bridges the controller entity (where hardware input is read) and the unit entity (where input is acted on). The controller reads raw device events and routes named event values into the unit’s GS_InputDataComponent. Reactor components on the unit then convert that stored state into movement vectors or game actions.

This separation keeps units ignorant of input bindings — any controller can possess any unit without the unit needing to change.

For usage guides and setup examples, see The Basics: GS_Unit.

 

Contents


How It Works

Unit Input Handling Pattern Graph

Breakdown

The input pipeline has three stages. Each stage is a separate component on the unit entity, and they run in order every frame:

StageComponentWhat It Does
1 — ReadGS_PlayerControllerInputReaderComponentReads raw input events from the active input profile and writes them into GS_InputDataComponent.
2 — StoreGS_InputDataComponentHolds the current frame’s input state — button presses, axis values — as structured data.
3 — ReactReactor componentsRead from GS_InputDataComponent and produce intent: movement vectors, action triggers, etc.

All reactor components downstream of the store stage read from the same GS_InputDataComponent, so there is no duplicated hardware polling and no risk of two reactors seeing different input states for the same frame.


Component Reference

GS_InputDataComponent

Stores the current input state for a unit as a name-value map. Receives input from the controller via InputDataRequestBus.

Data: AZStd::unordered_map<AZStd::string, float> inputEventStates

The owning controller reference is established when a controller takes possession, observed through the GS_Core PossessionEmissionBus (see Player Input Reader).


Reactors

Reacting to input is a GS_Core mechanism. A unit carries a GS_InputReactorComponent holding a reactor profile asset — a list of authored reaction types — and the component classifies each incoming event’s phase (pressed, held, released, analog) before dispatching it to whichever reactors subscribed to that event name.

GS_Unit supplies the reaction types for the movement domain:

Reactor typeDrives
KeyboardMovement_InputReactorFour directional event names to the mover context X and Y axes, with stacking-aware release.
JoypadMovement_InputReactorTwo analog axis event names straight to the mover context X and Y axes.
Jump_InputReactorThe mover context’s jump state — set on press, cleared on release.

See Unit Input Reactors for their authored fields, and Input Reactors for the component, profiles, phases and how to write a reaction type of your own.


GS_PlayerControllerInputReaderComponent

Extends GS_Core::GS_InputReaderComponent. Sits on the controller entity. Routes input to the possessed unit on HandleFireInput. Updates its possessedUnit reference when OnPossessedUnit fires on the GS_Core PossessionEmissionBus (controller entity). See Player Input Reader for full details.


API Reference

Request Bus: InputDataRequestBus

Commands sent to a specific unit’s input data component. ById bus — addressed by unit entity ID.

MethodParametersReturnsDescription
UpdateEventStateAZStd::string stateName, float valuevoidWrites the value for the named event and broadcasts InputStateChanged.
ClearInputState—voidZeros all stored values and broadcasts ClearInput.
GetEventStateAZStd::string stateNamefloatReturns the current float value for the named event.

Notification Bus: InputDataNotificationBus

Events broadcast to reactor components on the unit. ById bus — addressed by unit entity ID.

EventParametersDescription
InputStateChangedAZStd::string stateName, float valueFired when any input event value changes.
ClearInput—Fired when all input states are cleared.

See Also

For detailed component pages:

For related components:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.3.1 - Unit Input Reactors

The reactor types GS_Unit ships — keyboard and joypad movement, and jump — and how they drive a unit’s mover context.

GS_Unit ships the reactor types that turn input events into unit movement. They are ordinary Input Reactors — GS_Core owns the mechanism, and GS_Unit supplies the reactions that belong to the movement domain.

Each one writes to the unit’s Mover Context, which is what the movement system reads. Reactors never move a unit themselves; they set intent, and the movers act on it.

For usage guides and setup examples, see The Basics: GS_Unit.

Input Reactor component in the O3DE Inspector

 

Contents


Setup

  1. Create an Input Reactor Profile asset.
  2. Add the reactor types your unit needs, and fill in the event names each one listens to — they must match the event names in your Input Profile.
  3. Add the Input Reactor component to your unit and assign the profile.

The event names are authored rather than fixed, so a project that calls its jump event "Jump" and one that calls it "Ability_Jump" both work without code changes.


Shipped Reactor Types

KeyboardMovement_InputReactor

Maps four directional event names to the mover context’s X and Y axis input.

FieldPurpose
Move Up EventEvent name for forward movement.
Move Down EventEvent name for backward movement.
Move Left EventEvent name for left movement.
Move Right EventEvent name for right movement.

Release handling is stacking-aware: if a player is holding both left and right and releases one, movement re-routes to the direction still held rather than stopping. This is what makes keyboard movement feel correct when a player rolls between direction keys, and it is the reason this reactor keeps per-instance state.

JoypadMovement_InputReactor

Routes two analog axis event values straight to the mover context’s X and Y axis input.

FieldPurpose
X Axis EventEvent name for the horizontal stick axis.
Y Axis EventEvent name for the vertical stick axis.

Because stick input arrives as an analog value, this reactor passes it through directly — the magnitude of the stick deflection becomes the magnitude of the movement input, so walking and running fall out of how far the player pushes.

Jump_InputReactor

Drives the mover context’s jump state.

FieldPurpose
Jump EventEvent name for jumping.

It sets the state when the event is pressed and clears it when released — a clear illustration of why input phases exist, since the whole reactor is those two responses.


Choosing Between Keyboard and Joypad

Both movement reactors write to the same mover context axes, so a unit can carry both and respond to whichever device the player is using. The keyboard reactor only reacts to its four discrete events and the joypad reactor only to its two analog ones, so they do not fight over the same input.

That is the usual setup: one profile with both movement reactors and a jump reactor, assigned to your player unit.


See Also

For the mechanism these build on:

For related systems:

For conceptual overviews and usage guides:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.3.2 - Player Input Reader

Controller-side component that reads hardware input events and routes them to the possessed unit’s input data component.

GS_PlayerControllerInputReaderComponent is the bridge between the O3DE input system and the GS_Unit input pipeline. It sits on the controller entity alongside GS_PlayerControllerComponent and is responsible for reading raw hardware events and routing them to whichever unit is currently possessed.

For usage guides and setup examples, see The Basics: GS_Unit.

Player Input Reader component in the O3DE Inspector

 

Contents


How It Works

Inheritance Chain

GS_PlayerControllerInputReaderComponent extends GS_Core::GS_InputReaderComponent, which listens to AzFramework::InputChannelNotificationBus. When a hardware input fires, the base class matches the event against the configured GS_InputProfile and calls HandleFireInput(eventName, value).

Routing Input

HandleFireInput(eventName, value) checks whether a unit is currently possessed. If so, it routes the event to the unit:

GS_Unit::InputDataRequestBus::Event(
    possessedUnit,
    &GS_Unit::InputDataRequestBus::Events::UpdateEventState,
    eventName, value
);

If no unit is possessed, the input is silently dropped.

Tracking the Possessed Unit

The component handles the GS_Core PossessionEmissionBus on its own controller entity and overrides OnPossessedUnit. When the controller possesses a new unit, this callback fires and the component updates its local possessedUnit reference. All future input calls then route to the new unit automatically.

Script Canvas - Enabling and Disabling Input Groups

Input Groups handling nodes in the O3DE Script Canvas


Setup

Add GS_PlayerControllerInputReaderComponent to the controller entity alongside:

  1. GS_PlayerControllerComponent — the controller that manages possession
  2. A GS_InputProfile asset assigned to the input reader’s inspector slot — defines which input events to listen for and their names

The input reader does not need to be configured per-unit. Changing possession automatically redirects all future input.


API Reference

GS_PlayerControllerInputReaderComponent does not expose a public request bus. It consumes two buses and produces one:

Consumed

BusEventDescription
AzFramework::InputChannelNotificationBusOnInputChannelEventReceives raw hardware input from O3DE. Matched against the GS_InputProfile.
GS_Core::PossessionEmissionBusOnPossessedUnit(unit)Updates the local possessedUnit reference when the controller possesses a new unit. Handled on the controller’s own entity.

Produced

BusMethodDescription
InputDataRequestBusUpdateEventState(eventName, value)Routes matched input events to the possessed unit’s GS_InputDataComponent.

Virtual Methods

MethodParametersDescription
HandleFireInputAZStd::string eventName, float valueCalled by the base class when a matched input event fires. Routes to the possessed unit if valid.

See Also


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4 - Units

What makes an entity a unit — the GS_UnitComponent, entity configuration, collision setup, and links to movement subsystems.

A “unit” in GS_Play is any entity that can be possessed by a controller and driven through gameplay. The GS_UnitComponent is the marker that transforms an ordinary entity into a unit. It provides the possession interface, unique naming, standby awareness, and registration with the Unit Manager.

Units are the characters, vehicles, creatures, or any other controllable actors in your game. They do not contain decision-making logic themselves — that comes from the controller that possesses them. A unit provides the body: movement, collision, visuals, and stats. The controller provides the brain: player input or AI logic.

For usage guides and setup examples, see The Basics: GS_Unit.

Unit component in the O3DE Inspector

 

Contents


How It Works

Registration

When a GS_UnitComponent activates, it registers itself with the Unit Manager. This allows the manager to track all active units and respond to CheckIsUnit queries. When the component deactivates, it unregisters.

Possession

Units are possessed by controllers through the UnitRequestBus:

  1. A controller calls Possess(controllerEntityId) on the unit.
  2. The unit stores the controller reference and fires OnPossessedByController on the GS_Core PossessionEmissionBus (unit entity).
  3. The controller can now issue commands to the unit’s subsystems (movement, actions, etc.).
  4. Calling DePossess() clears the controller reference.

Standby

Units receive standby signals from the Unit Manager. When entering standby, the unit broadcasts UnitEnteringStandby on its notification bus. Child components (movers, input reactors, etc.) listen for this and pause their processing. UnitExitingStandby reverses the process.


GS_UnitComponent Reference

Request Bus: UnitRequestBus

Commands sent to a specific unit. ById bus — addressed by the unit’s entity ID, multiple handlers.

MethodParametersReturnsDescription
PossessAZ::EntityId possessingControllervoidAssigns a controller to this unit.
DePossess—voidRemoves the current controller from this unit.
GetController—AZ::EntityIdReturns the entity ID of the currently possessing controller.
GetUniqueName—AZStd::stringReturns the unique name assigned to this unit.

Notification Bus: UnitNotificationBus

Events broadcast by a unit. ById bus — addressed by the unit’s entity ID. Carries the standby signals only; the possession signal moved to the GS_Core PossessionEmissionBus in the interfaces migration, where the unit fires OnPossessedByController / OnReleasedByController on its own entity.

EventParametersDescription
UnitEnteringStandby—Fired when this unit enters standby.
UnitExitingStandby—Fired when this unit exits standby.

Virtual Methods

MethodParametersReturnsDescription
SetUniqueName()—voidCalled during initialization to generate the unit’s unique name. Override in subclasses to use project-specific naming.

Setup

Unit Entity Configuration

A minimal unit entity requires:

  1. GS_UnitComponent – Registers the entity as a unit and provides the possession interface.
  2. Movement components – At least a mover and optionally a grounder for ground detection.
  3. PhysX collider – For physics interaction and ground detection.

A fully featured unit entity typically includes:

  • GS_UnitComponent
  • GS_MoverContextComponent – Aggregates movement input from movers
  • A mover component (e.g., GS_3DFreeMoverComponent, GS_3DSlideMoverComponent, or GS_PhysicsMoverComponent)
  • A grounder component (e.g., GS_PhysicsRayGrounderComponent) for surface detection
  • PhysX Rigid Body and Collider components
  • Mesh or Actor component for visuals

Unit Collider Configuration

Collision layers used for a unit collider, as seen in the Entity Inspector.

Units require properly configured PhysX collision layers to interact with the environment and other units. If you have not set up your PhysX Collision Layers or Groups yet, refer to the Setting Up Your Project Environment guide.

Typical collision layer assignments:

  • Unit layer – The unit’s own collider. Collides with environment and other units.
  • Ground detection layer – Used by grounder raycasts. Collides with terrain and walkable surfaces only.

Unit Action Graph

The Unit Action Graph is a visual HFSM editor for authoring character behavioral logic — separate from and complementary to the Movement components. While Movers handle locomotion physics, the Unit Action Graph defines what the character is doing and when it transitions between behaviors.

Unit Action Graph


See Also

For component references:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1 - Movement

The complete movement subsystem — movers, grounders, movement influence, and the movement profile asset for configuring unit locomotion.

The Movement subsystem handles all unit locomotion. It is mode-driven: a single named mode is active at a time, and only the mover and grounder whose mode name matches will run each tick. This makes locomotion fully composable without any mover combining logic.

The subsystem has four layers:

  • Mover Context — Central hub. Transforms raw input into camera-relative and ground-projected vectors, owns mode switching, context states, and profile management.
  • Movers — Translate MoverContext input into physics forces on the rigid body. One mover is active at a time per mode.
  • Grounders — Detect ground contact, slope, and surface normal. Write results to the MoverContext and trigger mode switches.
  • Movement Influence — Spatial zones and global fallbacks that supply the active GS_UnitMovementProfile.

For usage guides and setup examples, see The Basics: GS_Unit.

 

Contents


Architecture

Movers & Grounders Pattern Graph

Breakdown

Movers and Grounders are multiple components on a Unit that are constantly changing activation based on the units state. When a mover is active, it freely processes the unit’s movement based on it’s functionality. At any moment, through internal or external forces, the Movement, Rotation, or Grounding state can change, which disables the old movers, and activates the new one to continue controlling the Unit.

Input Reader
    ↓  InputReactorRequestBus::FeedInputEvent
GS_InputReactorComponent
    ↓  dispatches by event name and phase
Reaction types  (KeyboardMovement / JoypadMovement / Jump)
    ↓  MoverContextRequestBus::SetMoveInputAxis
GS_MoverContextComponent
    ↓  ModifyInputAxis() → camera-relative
    ↓  GroundInputAxis() → ground-projected
    ↓  MoverContextNotificationBus::MovementModeChanged("Free")
GS_3DFreeMoverComponent  [active when mode == "Free"]
    ↓  AccelerationSpringDamper → rigidBody->SetLinearVelocity
GS_PhysicsRayGrounderComponent  [active when grounding mode == "Free"]
    ↓  MoverContextRequestBus::SetGroundNormal / SetContextState("grounding", ...)
    ↓  MoverContextRequestBus::ChangeMovementMode("Slide")  ← when slope too steep
GS_3DSlideMoverComponent  [active when mode == "Slide"]

The Slide mover activates when the Unit is walking on too steep an angle. It takes control over the unit, slides down the hill, then restores the previous movement behaviour.


Movement Profile

GS_UnitMovementProfile is an asset that holds locomotion parameters. Movers read from the active profile via activeProfile pointer, which the MoverContext updates whenever the profile changes.

FieldTypeDescription
LocomotionStyleenumMovement style archetype (affects animation matching).
moveSpeedfloatTarget movement speed (m/s).
speedChangeSmoothingfloatLerp factor for speed transitions between profiles.
canSprintboolWhether this profile allows sprinting.
canJumpboolWhether this profile allows jumping.
canRollboolWhether this profile allows rolling.

Create movement profiles as data assets in the Asset Editor. Assign a default profile to the GS_MoverContextComponent in the Inspector. Spatial influence zones can override the active profile at runtime.

Reading Movement Profile Data - ScriptCanvas


Movement Influence

Movement Influence component in the O3DE Inspector

The MoverContext selects the active profile by priority:

  1. Influence list — MovementInfluenceFieldComponent adds its profile when the unit enters its trigger volume. Multiple fields stack by priority.
  2. Global fallback — if the influence list is empty and allowGlobalInfluence is true, falls back to GlobalMovementRequestBus::GetGlobalMovementProfile.
  3. Default profile — the defaultMoveProfile assigned directly on the GS_MoverContextComponent.

MovementInfluenceFieldComponent

Inherits PhysicsTriggerComponent. On TriggerEnter, calls MoverContextRequestBus::AddMovementProfile(entityId, profile*) on the entering unit. On TriggerExit, calls RemoveMovementProfile(entityId).

Request Bus: MovementInfluenceRequestBus

ById bus — addressed by the influence field’s entity ID.

MethodParametersReturnsDescription
GetPriority—intReturns this field’s priority. Higher values take precedence when multiple fields overlap.

GlobalMovementRequestBus

Broadcast bus — single global instance.

MethodParametersReturnsDescription
GetGlobalMovementProfile—GS_UnitMovementProfile*Returns the global fallback movement profile. Used by the MoverContext when no influence fields are active.

Sub-Sections

  • Mover Context — Input transformation, mode switching, context states, profile management
  • Movers — Mover base class and concrete movers (GS_3DFreeMoverComponent, GS_3DSlideMoverComponent)
  • Grounders — Grounder base class and concrete grounders (GS_PhysicsRayGrounderComponent)

See Also

  • Units — Unit entity setup and GS_UnitComponent
  • Unit Controllers — Controllers that manage possession and drive input routing
  • Input Data — The input pipeline that feeds movement reactors
  • Stage Data — Handler for current stage functionality and global effects
  • Springs Utility — Spring-damper functions used by movers and grounders
  • Physics Trigger Volume — Physics overlap that triggers functionality.

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.1 - Mover Context

The central movement hub — transforms raw input into camera-relative and ground-projected vectors, manages mode switching, context states, and movement profiles.

GS_MoverContextComponent is the central state store for all movement on a unit. It sits between the input layer and the mover/grounder layer, holds all shared movement data, owns the mode-switching logic, and manages the active movement profile.

Movers and Grounders never communicate directly — they all read from and write to the MoverContext.

For usage guides and setup examples, see The Basics: GS_Unit.

Mover Context component in the O3DE Inspector

 

Contents


Input Transformation Pipeline

Raw input from reactor components arrives as a Vector2 (rawMoveInput). The MoverContext applies two sequential transformations each time input changes.

Step 1 — ModifyInputAxis() — Camera-Relative

For a player unit (detected by “Player” tag): fetches the active camera’s world transform, flattens its forward and right vectors to the XY plane, then computes:

modifiedMoveInput = camForward * rawInput.Y + camRight * rawInput.X

For AI units (or when inputOverride = true): uses world cardinal axes (Y=forward, X=right).

Step 2 — GroundInputAxis() — Ground-Projected

Projects modifiedMoveInput onto the ground plane using the current groundNormal:

groundedMoveInput = modifiedMoveInput - groundNormal * dot(modifiedMoveInput, groundNormal)

Direction is then normalized and the original magnitude is restored. Movers use groundedMoveInput so movement always follows the surface contour, even on slopes.

Both steps run automatically whenever SetMoveInputAxis is called and again whenever SetGroundNormal is called (ground changed under a moving unit).


Mode Switching

The MoverContext maintains three independent mode strings, each with a current and last value:

Mode TypeCurrentLast
Movement modecurrentMoveModelastMoveMode
Rotation modecurrentRotateModelastRotateMode
Grounding modecurrentGroundingModelastGroundingMode

ChangeMovementMode("Free") updates the string and broadcasts MoverContextNotificationBus::MovementModeChanged("Free"). Each Mover and Grounder component checks whether its own named mode matches the broadcast and activates or deactivates itself accordingly.

RevertToLastMovementMode() swaps current and last — enabling one-step undo (e.g., return from “Slide” to whatever was active before).


Context States

A generic key-value store for runtime state flags used by movers and grounders:

AZStd::unordered_map<AZStd::string, AZ::u32> contextStates

SetContextState(name, value) writes and broadcasts MoverContextNotificationBus::ContextStateChanged.

Known state keys:

KeySet byValuesMeaning
"grounding"PhysicsRayGrounder0 = Falling, 1 = Grounded, 2 = SlidingGround contact state
"StopMovement"Various1 = stopSignals the Free Mover to zero velocity

Movement Profile Management

The MoverContext selects the active GS_UnitMovementProfile* by priority:

  1. Influence list — AddMovementProfile(influencer, profile*) is called by MovementInfluenceFieldComponent when the unit enters a trigger volume. Multiple fields stack additively by priority via MovementInfluenceRequestBus::GetPriority.
  2. Global fallback — if influenceList is empty and allowGlobalInfluence is true, checks GlobalMovementRequestBus::GetGlobalMovementProfile.
  3. Default profile — the asset-configured defaultMoveProfile on the component.

When the profile changes, MoverContextNotificationBus::MovementProfileChanged(profile*) is broadcast. Movers update their cached activeProfile pointer.

Standby behavior:

  • UnitEnteringStandby → clears the influence list, broadcasts empty mode names (disables all movers/grounders)
  • UnitExitingStandby → re-evaluates profile priority, re-broadcasts current mode names to re-enable the correct movers

Editor-Exposed Fields

FieldDescription
defaultMoveProfileAsset reference to the fallback GS_UnitMovementProfile
allowGlobalInfluenceWhether to accept the global movement profile as a fallback
startingMoveModeMode name to broadcast on activate (one-frame delayed to allow all components to start first)
startingRotateModeRotation mode name to start with
startingGroundingModeGrounding mode name to start with
maxWalkDegreesSlope angle (degrees) above which movement is no longer considered grounded

API Reference

Request Bus: MoverContextRequestBus

Commands sent to a specific unit’s MoverContext. ById bus — addressed by unit entity ID.

Input Axis:

MethodParametersReturnsDescription
SetMoveInputAxisAZStd::string axisName, float axisValuevoidSets the raw input on “x” or “y”. Triggers ModifyInputAxis() and GroundInputAxis(). Clamps total magnitude to 1.0.
GetMoveInputAxis—AZ::Vector2*Returns pointer to rawMoveInput.
GetModifiedMoveInputAxis—AZ::Vector3*Returns pointer to camera-relative modifiedMoveInput.
GetGroundMoveInputAxis—AZ::Vector3*Returns pointer to ground-projected groundedMoveInput.

Ground State:

MethodParametersReturnsDescription
SetGroundNormalAZ::Vector3 newNormalvoidUpdates the ground normal and re-projects modifiedMoveInput.
GetGroundNormal—AZ::Vector3*Returns pointer to current ground normal.
GetSlopeDirection—AZ::Vector3*Returns pointer to the current slope direction vector.
GetSlopeAngle—float*Returns pointer to the current slope angle (dot product with up).
GetMaxWalkAngle—float*Returns pointer to the cosine of maxWalkDegrees.

Context States:

MethodParametersReturnsDescription
SetContextStateAZStd::string stateName, AZ::u32 stateValuevoidWrites a state value and broadcasts ContextStateChanged.
GetContextStateAZStd::string stateNameAZ::u32Returns the current value for the named state.

Mode Switching:

MethodParametersReturnsDescription
ChangeMovementModeAZStd::string targetMoveModevoidSets current move mode and broadcasts MovementModeChanged.
RevertToLastMovementMode—voidSwaps current and last move mode.
ChangeRotationModeAZStd::string targetRotateModevoidSets current rotation mode and broadcasts RotationModeChanged.
RevertToLastRotationMode—voidSwaps current and last rotation mode.
ChangeGroundingModeAZStd::string targetGroundingModevoidSets current grounding mode and broadcasts GroundingModeChanged.
RevertToLastGroundingMode—voidSwaps current and last grounding mode.

Profile Management:

MethodParametersReturnsDescription
AddMovementProfileAZ::EntityId influencer, GS_UnitMovementProfile* profilevoidAdds an influence-field profile to the priority list and re-evaluates.
RemoveMovementProfileAZ::EntityId influencervoidRemoves an influence-field profile and re-evaluates.

Notification Bus: MoverContextNotificationBus

Events broadcast by the MoverContext. ById bus — addressed by unit entity ID. Movers and Grounders subscribe to this.

EventParametersDescription
MovementModeChangedAZStd::string modeNameFired when the movement mode changes. Movers activate or deactivate based on their m_moveModeName.
RotationModeChangedAZStd::string modeNameFired when the rotation mode changes.
GroundingModeChangedAZStd::string modeNameFired when the grounding mode changes. Grounders activate or deactivate based on their m_groundModeName.
ContextStateChangedAZStd::string stateName, AZ::u32 valueFired when a context state value changes.
MovementProfileChangedGS_UnitMovementProfile* profileFired when the active movement profile is replaced. Movers update their activeProfile pointer.

Virtual Methods

Override these when extending the MoverContext.

MethodDescription
ModifyInputAxis()Transforms rawMoveInput into modifiedMoveInput (camera-relative). Override for custom camera or axis mapping.
GroundInputAxis()Projects modifiedMoveInput onto the ground plane into groundedMoveInput. Override for custom surface projection.

Script Canvas Examples

Changing movement mode:

Reverting to the previous movement mode:

Setting a context state flag:


See Also

  • Movers — Mover components that read from the MoverContext
  • Grounders — Grounder components that write ground state to the MoverContext
  • Movement — Movement system overview
  • Input Data — The input pipeline that feeds SetMoveInputAxis

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2 - Movers

Mover base class and concrete mover components — translate MoverContext input into physics-based unit motion via named mode activation.

Movers translate the processed movement input from the MoverContext into physics forces on the unit’s rigid body. Each mover activates for a specific named mode — when the MoverContext broadcasts MovementModeChanged("Free"), only the mover whose m_moveModeName matches "Free" activates. All others deactivate. This makes the locomotion system fully mode-driven and composable.

For usage guides and setup examples, see The Basics: GS_Unit.

Mover component in the O3DE Inspector

 

Contents


Class Hierarchy

GS_MoverComponent (base — mode-aware activation, tick management)
  └── GS_PhysicsMoverComponent (adds RigidBody cache + validity check)
        ├── GS_3DFreeMoverComponent    (mode: "Free")
        └── GS_3DSlideMoverComponent   (mode: "Slide")

GS_MoverComponent (Base)

Tick: AzPhysics::SystemEvents::OnPostSimulateEvent (after the physics step). Optional debugForceTick uses AZ::TickBus for debugging without physics.

Mode-Aware Activation

Listens to MoverContextNotificationBus::MovementModeChanged and RotationModeChanged. Compares the broadcast mode name against its own m_moveModeName and m_rotateModeName. Calls ToggleMovement(true/false) and ToggleRotation(true/false) accordingly. The physics post-simulate handler is only registered when the mover is active, saving ticks when inactive.

Per-Tick Processing

Each tick:

  1. CheckCanOperate() — validates that the required pointers and state are valid
  2. HandleMovement() — calculates and applies movement (no-op in base)
  3. HandleRotation() — calculates and applies rotation (no-op in base)

Key Fields

FieldDescription
m_moveModeNameMode string this mover activates for (set in Activate())
m_rotateModeNameMode string this mover’s rotation activates for
movementActive / rotationActiveWhether movement/rotation processing is currently running
deltaCached frame delta time
activeProfilePointer to current GS_UnitMovementProfile (updated via MovementProfileChanged)
debugForceTickIf true, uses game tick instead of post-simulate (for debugging without physics)

GS_PhysicsMoverComponent

Extends GS_MoverComponent. On activate, fetches AzPhysics::RigidBody* from the entity. CheckCanOperate() verifies the rigid body pointer is valid before allowing tick processing.


Concrete Movers

ComponentMode NameDescription
GS_3DFreeMoverComponent"Free"Standard 3D locomotion — camera-relative, spring-damped velocity and rotation
GS_3DSlideMoverComponent"Slide"Slope-sliding locomotion — activated by the grounder when slope exceeds maxWalkAngle

API Reference

Movers consume two buses and produce one:

Consumed

BusEventDescription
MoverContextNotificationBusMovementModeChanged(modeName)Activates or deactivates movement processing based on mode name match.
MoverContextNotificationBusRotationModeChanged(modeName)Activates or deactivates rotation processing based on mode name match.
MoverContextNotificationBusContextStateChanged(stateName, value)Allows movers to react to state flags (e.g. "StopMovement").
MoverContextNotificationBusMovementProfileChanged(profile*)Updates the cached activeProfile pointer.

Virtual Methods

Override these when extending any mover:

MethodParametersReturnsDescription
ToggleMovementbool onvoidCalled when the movement mode activates or deactivates.
ToggleRotationbool onvoidCalled when the rotation mode activates or deactivates.
HandleMovement—voidCalled each tick when movement is active. Override to implement movement logic.
HandleRotation—voidCalled each tick when rotation is active. Override to implement rotation logic.
CheckCanOperate—boolReturns true if the mover has everything it needs to run this tick.

Extension Guide

Use the Mover ClassWizard template to generate a new mover with boilerplate already in place — see GS_Unit Templates. The template offers two base class options: Physics Mover (default) for rigid-body locomotion, and Base Mover for transform-only movement.

Extend GS_PhysicsMoverComponent to create a custom physics-driven mover.

#pragma once
#include <Source/Unit/Mover/GS_PhysicsMoverComponent.h>

namespace MyProject
{
    class MyCustomMover : public GS_Unit::GS_PhysicsMoverComponent
    {
    public:
        AZ_COMPONENT_DECL(MyCustomMover);
        static void Reflect(AZ::ReflectContext* context);

    protected:
        void Activate() override;

        void HandleMovement() override;
        void HandleRotation() override;

    private:
        // Mode name must be set in Activate():
        // m_moveModeName = "MyMode";
        // m_rotateModeName = "MyMode";
    };
}

In HandleMovement(), read from the MoverContext and write to the rigid body:

void MyCustomMover::HandleMovement()
{
    AZ::Vector3* groundedInput = nullptr;
    GS_Unit::MoverContextRequestBus::EventResult(
        groundedInput, GetEntityId(),
        &GS_Unit::MoverContextRequestBus::Events::GetGroundMoveInputAxis
    );

    if (!groundedInput || groundedInput->IsZero()) return;

    AZ::Vector3 targetVelocity = *groundedInput * activeProfile->moveSpeed;
    m_rigidBody->SetLinearVelocity(targetVelocity);
}

See Also


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2.1 - 3D Free Mover

Standard 3D locomotion — camera-relative movement and rotation driven by spring-damped physics, with optional slope slowdown.

GS_3DFreeMoverComponent is the standard mover for walking characters. It reads the ground-projected input vector from the MoverContext, applies optional slope attenuation, computes a destination velocity, and drives the rigid body via AccelerationSpringDamper. Rotation is handled separately using QuaternionSpringDamper to face the last non-zero movement direction.

Mode names: "Free" (both movement and rotation)

For usage guides and setup examples, see The Basics: GS_Unit.

Mover component in the O3DE Inspector

 

Contents


How It Works

HandleMovement()

Called each post-physics tick while movement mode is "Free":

  1. Reads groundedMoveInput pointer from MoverContextRequestBus::GetGroundMoveInputAxis.
  2. If EnableSlopeSlowdown is true, attenuates the input vector on steep slopes (see Slope Slowdown).
  3. Multiplies the attenuated direction by CalculateSpeed() to get destinationVelocity.
  4. Fetches rigidBody->GetLinearVelocity() as the current velocity.
  5. Calls GS_Core::Springs::AccelerationSpringDamper(position, velocity, cachedAcceleration, destinationVelocity, moveHalflife, delta).
  6. Applies the result: rigidBody->SetLinearVelocity(velocity).

The cachedAcceleration is a member field that persists between frames to produce smooth acceleration response even on sudden direction changes.

HandleRotation()

  1. Reads groundedMoveInput from the MoverContext.
  2. Updates lastDirection only when groundedMoveInput is non-zero — so the unit keeps facing the last direction when stopped.
  3. Computes target yaw from lastDirection using atan2f.
  4. Calls GS_Core::Springs::QuaternionSpringDamper(currentRotation, cachedAngularVelocity, targetRotation, rotateHalflife, delta).
  5. Applies cachedAngularVelocity.Z to rigidBody->SetAngularVelocity().

StopMovement State

When ContextStateChanged("StopMovement", 1) fires, the mover zeros both velocity and cached acceleration, then clears the state back to 0. This allows other systems (e.g. landing from a jump, ability wind-up) to cleanly halt the unit.


Slope Slowdown

When EnableSlopeSlowdown is true, the mover reduces movement speed as the slope angle increases toward maxWalkAngle. The attenuation curve uses an exponent applied to the dot product of the movement direction against the uphill slope direction:

attenuation = 1.0 - uphillSlowStrength * pow(dot(moveDir, slopeDir), uphillSlowExponent)
              × remap(slopeAngle, startSlowAngle, maxWalkAngle, 0, 1)

The slowdown begins at startSlowAngle degrees and reaches maximum reduction at maxWalkAngle. At or beyond maxWalkAngle, the grounder will switch the unit to "Slide" mode regardless.


Speed Calculation

CalculateSpeed() lerps curSpeed toward activeProfile->moveSpeed each frame:

curSpeed = AZ::Lerp(curSpeed, activeProfile->moveSpeed, expf(-speedChangeSmoothing * delta));

speedChangeSmoothing is read from activeProfile. This produces a smooth speed transition when the movement profile changes (e.g. entering a slow zone).


Editor-Exposed Settings

FieldDefaultDescription
moveHalflife0.1Spring halflife for movement velocity. Smaller = snappier acceleration.
rotateHalflife0.2Spring halflife for rotation. Smaller = faster turn response.
EnableSlopeSlowdowntrueWhether steep slopes reduce movement speed.
uphillSlowStrength0.5Maximum speed reduction factor at the max walkable angle (0 = no reduction, 1 = full stop).
uphillSlowExponent2.5Curve shape of the slowdown ramp. Higher = slower onset, steeper drop near max.
startSlowAngle30°Slope angle at which slowdown begins.

Extension Guide

Extend GS_3DFreeMoverComponent to override movement or rotation behavior while keeping the base spring physics.

#pragma once
#include <Source/Unit/Mover/GS_3DFreeMoverComponent.h>

namespace MyProject
{
    class MyFreeMove : public GS_Unit::GS_3DFreeMoverComponent
    {
    public:
        AZ_COMPONENT_DECL(MyFreeMove);
        static void Reflect(AZ::ReflectContext* context);

    protected:
        void HandleMovement() override;
    };
}
void MyFreeMove::HandleMovement()
{
    // Call base for standard spring movement
    GS_3DFreeMoverComponent::HandleMovement();

    // Add custom post-processing (e.g. strafe penalty, footstep IK correction)
}

See Also


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2.2 - Slide Mover

Slope-sliding locomotion — automatically activated when slope exceeds maxWalkAngle, drives the unit down the slope via spring-damped physics with optional input resistance and automatic recovery.

GS_3DSlideMoverComponent handles slope-sliding locomotion. It activates automatically when the grounder detects a slope angle exceeding maxWalkAngle, and deactivates when the slope eases or the unit slows enough to recover.

Mode names: "Slide" (both movement and rotation)

For usage guides and setup examples, see The Basics: GS_Unit.

Mover component in the O3DE Inspector

 

Contents


How It Works

HandleMovement()

Called each post-physics tick while movement mode is "Slide":

  1. Reads slopeDir from MoverContextRequestBus::GetSlopeDirection.
  2. Computes destinationVelocity = slopeDir * baseSlideSpeed.
  3. If EnableInputSlideResistance is true, reads groundedMoveInput and applies an opposing influence:
    destinationVelocity += groundedMoveInput * InputResistanceInfluence
    
    This allows the player to push slightly against or across the slide direction.
  4. Fetches rigidBody->GetLinearVelocity() as the current velocity.
  5. Calls GS_Core::Springs::AccelerationSpringDamper(position, velocity, cachedAcceleration, destinationVelocity, moveHalflife, delta).
  6. Applies the result: rigidBody->SetLinearVelocity(velocity).
  7. Calls FinishSliding() to check whether the slide should end.

HandleRotation()

  1. Reads slopeDir from the MoverContext.
  2. Computes target yaw from slope direction using atan2f — unit faces down the slope.
  3. Calls GS_Core::Springs::QuaternionSpringDamper(currentRotation, cachedAngularVelocity, targetRotation, rotateHalflife, delta).
  4. Applies cachedAngularVelocity.Z to rigidBody->SetAngularVelocity().

Finish Sliding

FinishSliding() is called every movement tick. It returns true and triggers recovery when both conditions are met:

  • The current slope angle is below minSlideAngle
  • The unit’s current speed is below minSpeedToStop

On recovery:

MoverContextRequestBus::ChangeMovementMode(recoveryMoveMode);
MoverContextRequestBus::ChangeRotationMode(recoveryRotateMode);

Both modes default to "Free", returning the unit to standard locomotion.


Editor-Exposed Settings

FieldDefaultDescription
moveHalflife0.1Spring halflife for slide velocity.
rotateHalflife0.15Spring halflife for rotation toward slope direction.
baseSlideSpeed8.0Target speed along the slope direction (m/s).
EnableInputSlideResistancetrueWhether player input can partially resist or redirect the slide.
InputResistanceInfluence2.0Scalar applied to groundedMoveInput when computing resistance. Higher = more player control.
minSlideAngle20°Slope angle below which the unit may recover (if also slow enough).
minSpeedToStop1.5Speed threshold below which the unit may recover (if also on shallow slope).
recoveryMoveMode"Free"Movement mode to switch to on recovery.
recoveryRotateMode"Free"Rotation mode to switch to on recovery.

Extension Guide

Extend GS_3DSlideMoverComponent to override slide behavior while keeping the base spring physics and recovery logic.

#pragma once
#include <Source/Unit/Mover/GS_3DSlideMoverComponent.h>

namespace MyProject
{
    class MySlide : public GS_Unit::GS_3DSlideMoverComponent
    {
    public:
        AZ_COMPONENT_DECL(MySlide);
        static void Reflect(AZ::ReflectContext* context);

    protected:
        void HandleMovement() override;
    };
}
void MySlide::HandleMovement()
{
    // Call base for standard slope physics
    GS_3DSlideMoverComponent::HandleMovement();

    // Add custom post-processing (e.g. audio trigger, particle effect on slide)
}

See Also

  • Movers — Mover base class and class hierarchy
  • GS_3DFreeMoverComponent — Standard free-locomotion mover
  • Mover Context — Provides slopeDir, groundedMoveInput, and mode switching
  • Grounders — Detect slope angle and trigger the switch to Slide mode
  • Springs Utility — AccelerationSpringDamper and QuaternionSpringDamper used internally

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2.3 - 3D Strafe Mover

Aim-relative strafing movement for first-person and third-person units. Not yet implemented.

The 3D Strafe Mover provides aim-relative movement where the unit strafes relative to its facing direction. This is the standard movement model for first-person and third-person shooters where the camera or aim direction determines the movement frame.

This component is not yet implemented. This page will be updated with full API reference and usage documentation when the component is available.

For usage guides and setup examples, see The Basics: GS_Unit.


See Also


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2.4 - Side Scroller Mover

2D side-scrolling movement constrained to a horizontal plane. Not yet implemented.

The Side Scroller Mover provides two-dimensional movement constrained to a horizontal plane with vertical jump support. This is the standard movement model for side-scrolling platformers, beat-em-ups, and other 2D gameplay projected in a 3D environment.

This component is not yet implemented. This page will be updated with full API reference and usage documentation when the component is available.

For usage guides and setup examples, see The Basics: GS_Unit.


See Also

  • Movement – Movement subsystem overview
  • Movers – All available mover components

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.2.5 - Grid Step Mover

Tile-based grid movement for turn-based or grid-locked unit locomotion. Not yet implemented.

The Grid Step Mover provides discrete, tile-based movement for units that move in fixed steps along a grid. This is the standard movement model for turn-based RPGs, tactics games, puzzle games, and any project that uses grid-locked locomotion.

This component is not yet implemented. This page will be updated with full API reference and usage documentation when the component is available.

For usage guides and setup examples, see The Basics: GS_Unit.


See Also

  • Movement – Movement subsystem overview
  • Movers – All available mover components

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.3 - Grounders

Grounder base class and concrete grounder components — detect ground contact, compute ground normals and slope data, and drive mode switching on the MoverContext.

Grounders run each physics tick to determine whether the unit is in contact with the ground, what the slope looks like, and whether the surface is walkable. They write their findings to the MoverContext and switch movement modes when the ground state changes.

For usage guides and setup examples, see The Basics: GS_Unit.

Grounder component in the O3DE Inspector

 

Contents


Class Hierarchy

GS_GrounderComponent (base — mode-aware activation, tick management)
  └── GS_PhysicsRayGrounderComponent  (grounding mode: "Free")

GS_GrounderComponent (Base)

Tick: AzPhysics::SystemEvents::OnPostSimulateEvent (after the physics step).

Mode-Aware Activation

Listens to MoverContextNotificationBus::GroundingModeChanged. Compares the broadcast mode name against its own m_groundModeName. Calls ToggleGrounder(true/false) accordingly. The physics post-simulate handler is only registered when the grounder is active.

Per-Tick Processing

Each tick:

  1. CheckCanOperate() — validates required pointers and state
  2. HandleGrounding() — performs ground detection and updates MoverContext (no-op in base)

Key Fields

FieldDescription
m_groundModeNameGrounding mode string this grounder activates for
deltaCached frame delta time

Concrete Grounders

ComponentGrounding ModeDescription
GS_PhysicsRayGrounderComponent"Free"Raycast-based grounder with coyote time, slope detection, and manual gravity

API Reference

Virtual Methods

Override these when extending any grounder:

MethodParametersReturnsDescription
ToggleGrounderbool onvoidCalled when the grounding mode activates or deactivates.
HandleGrounding—voidCalled each tick when the grounder is active. Override to implement ground detection logic.
GroundingStateChangeAZ::u32 newStatevoidCalled when ground contact state changes. Override to react to grounding transitions.
CheckCanOperate—boolReturns true if the grounder has everything it needs to run this tick.

Consumed Buses

BusEventDescription
MoverContextNotificationBusGroundingModeChanged(modeName)Activates or deactivates this grounder based on mode name match.

Produced Calls

BusMethodDescription
MoverContextRequestBusSetGroundNormal(normal)Updates the ground normal used by the MoverContext for input projection.
MoverContextRequestBusSetContextState("grounding", value)Sets grounding state: 0 = Falling, 1 = Grounded, 2 = Sliding.
MoverContextRequestBusChangeMovementMode(modeName)Switches to "Slide" when slope exceeds maxWalkAngle, or back to "Free" on recovery.

Extension Guide

Use the Grounder ClassWizard template to generate a new grounder with boilerplate already in place — see GS_Unit Templates. The template offers two base class options: Physics Ray Grounder (default, includes raycast, coyote time, and gravity) or Base Grounder for fully custom detection.

Extend GS_GrounderComponent to implement custom ground detection.

#pragma once
#include <Source/Unit/Grounder/GS_GrounderComponent.h>

namespace MyProject
{
    class MyGrounder : public GS_Unit::GS_GrounderComponent
    {
    public:
        AZ_COMPONENT_DECL(MyGrounder);
        static void Reflect(AZ::ReflectContext* context);

    protected:
        void Activate() override;

        void HandleGrounding() override;
        void GroundingStateChange(AZ::u32 newState) override;

    private:
        // Set in Activate():
        // m_groundModeName = "Free";
    };
}

In HandleGrounding(), run your detection and write results to the MoverContext:

void MyGrounder::HandleGrounding()
{
    AZ::Vector3 groundNormal = AZ::Vector3::CreateAxisZ();
    bool isGrounded = false;

    // ... custom detection logic ...

    GS_Unit::MoverContextRequestBus::Event(
        GetEntityId(),
        &GS_Unit::MoverContextRequestBus::Events::SetGroundNormal,
        groundNormal
    );

    AZ::u32 state = isGrounded ? 1 : 0;
    GS_Unit::MoverContextRequestBus::Event(
        GetEntityId(),
        &GS_Unit::MoverContextRequestBus::Events::SetContextState,
        "grounding", state
    );
}

See Also


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.1.3.1 - 3D Free Grounder

Raycast-based grounder for standard 3D locomotion — detects ground contact with coyote time, applies manual gravity when airborne, switches to Slide mode on steep slopes.

GS_PhysicsRayGrounderComponent is the standard grounder for walking characters. Each physics tick it casts a ray downward from the unit’s capsule base, evaluates the slope, applies gravity when airborne, and updates the MoverContext with ground normal and grounding state.

Grounding mode name: "Free"

For usage guides and setup examples, see The Basics: GS_Unit.

Grounder component in the O3DE Inspector

 

Contents


How It Works

Each post-physics tick, the grounder runs two operations in order:

  1. GroundCheck() — determines contact, slope, and normal
  2. GroundingMove() — applies gravity or snap force based on contact result

Ground Check

GroundCheck() casts a sphere (matching the capsule base radius) downward from the unit’s feet:

  1. If the ray hits geometry within groundCheckDistance:
    • Computes slope angle from the hit normal vs. world up.
    • Updates MoverContextRequestBus::SetGroundNormal(hitNormal).
    • If slope angle ≤ maxWalkAngle (from the MoverContext): reports Grounded.
    • If slope angle > maxWalkAngle: reports Sliding.
    • Resets coyote timer.
  2. If no hit:
    • Increments coyoteTimer.
    • If coyoteTimer < coyoteTime: still reports Grounded (coyote grace period).
    • If coyoteTimer ≥ coyoteTime: reports Falling.

Grounding Move

GroundingMove() applies vertical forces based on the current state:

Grounded: Applies a downward TimedSpringDamper to keep the unit snapped to the surface without bouncing.

Falling: Applies manual gravity — adds gravityScale * AZ::Physics::DefaultGravity to the rigid body’s linear velocity each frame:

AZ::Vector3 velocity = rigidBody->GetLinearVelocity();
velocity.SetZ(velocity.GetZ() + gravity * delta);
rigidBody->SetLinearVelocity(velocity);

Sliding: Does not override vertical velocity — the Free Mover’s AccelerationSpringDamper handles the slide direction entirely.


Grounding State Changes

GroundingStateChange(newState) is called whenever the ground state changes:

StateValueTriggerAction
Falling0Lost ground contact beyond coyote timeSetContextState("grounding", 0)
Grounded1Ray hit within walkable angleSetContextState("grounding", 1)
Sliding2Ray hit but slope > maxWalkAngleSetContextState("grounding", 2), ChangeMovementMode("Slide")

When returning to Grounded from Sliding, the grounder calls ChangeMovementMode("Free") to restore locomotion.


Editor-Exposed Settings

FieldDefaultDescription
groundCheckDistance0.15Raycast distance below the capsule base to detect ground.
capsuleRadius0.3Radius of the sphere used for the downward cast (should match capsule).
coyoteTime0.12Seconds of grace period before reporting Falling after losing ground contact.
gravityScale1.0Multiplier on AZ::Physics::DefaultGravity applied when airborne.
snapHalflife0.05Spring halflife for the ground-snap TimedSpringDamper. Smaller = tighter snap.

Extension Guide

Extend GS_PhysicsRayGrounderComponent to add custom ground detection or state reactions.

#pragma once
#include <Source/Unit/Grounder/GS_PhysicsRayGrounderComponent.h>

namespace MyProject
{
    class MyGrounder : public GS_Unit::GS_PhysicsRayGrounderComponent
    {
    public:
        AZ_COMPONENT_DECL(MyGrounder);
        static void Reflect(AZ::ReflectContext* context);

    protected:
        void GroundingStateChange(AZ::u32 newState) override;
    };
}
void MyGrounder::GroundingStateChange(AZ::u32 newState)
{
    // Call base to handle mode switching
    GS_PhysicsRayGrounderComponent::GroundingStateChange(newState);

    if (newState == 0)
    {
        // Unit became airborne — trigger jump animation, etc.
    }
}

See Also

  • Grounders — Grounder base class and class hierarchy
  • Slide Mover — Activated when this grounder reports Sliding state
  • Mover Context — Receives ground normal and state from this grounder
  • Springs Utility — TimedSpringDamper used for ground snap

Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.4.2 - Unit Action Graph

Hierarchical finite state machine editor for authoring character behavior with parallel layers, transitions, and conditions.

The Unit Action Graph is a Hierarchical Finite State Machine (HFSM) editor for authoring character behavior. It uses the gs_graphcanvas framework with a StateMachineGraph topology — multidirectional perimeter connections, tick-based state evaluation, and polymorphic transition conditions.

 

Contents


Opening the Editor

Open the Unit Action Graph editor from the O3DE Editor menu: GS Tools > Unit Action Graph Editor.

The editor manages a single .unitaction file containing all layers. Use File > New to create one, or File > Open to load an existing graph.


Multi-Layer Model

A .unitaction file is a container holding multiple layers, each an independent state machine graph. Layers are managed through the Layer Sidebar dock:

  • Add — Create a new layer with an Entry node
  • Remove — Delete a layer
  • Rename — Double-click to rename
  • Duplicate — Copy an existing layer
  • Priority — Set in the Layer Details panel (higher priority overrides lower)

Click a layer name to open it as a graph tab. All layers save together as one file.


Perimeter Connections

Unlike flow and data-flow graphs that use slot-based bezier curves, state machine graphs use perimeter connections — straight arrows drawn from the edge of one state node to the edge of another. This gives the graph a traditional state machine diagram appearance.

When multiple connections exist between the same pair of nodes, they are offset for readability. Arrows include arrowheads at the target node.


Nodes

NodePurposeTransitions InTransitions Out
EntryMarks the initial stateNoYes (one connection to the starting state)
StateA behavior state with OnEnter/OnTick/OnExit lifecycleYesYes
Compound StateA state containing a nested sub-state-machineYesYes

Entry Node

Auto-spawned in every new graph. Cannot be deleted or duplicated. Has a single outgoing connection to the initial state. Cannot be transitioned into.

State Node

The standard state. Represents a character behavior (Idle, Walk, Run, Attack). Each state carries its own list of outgoing TransitionDescriptor entries — one per outgoing connection.

Compound State Node

A state that contains an entire nested state machine. When entered, the compound state initializes and runs its sub-graph. When exited, the sub-graph stops. Used for complex behaviors with internal sub-states (e.g., a Jump state with Rising/Apex/Falling sub-states).


Transitions

Transitions are the connections between states. Each transition has:

  • Priority — Integer value. When multiple transitions are valid on the same tick, the highest priority fires.
  • Conditions — A polymorphic list of TransitionCondition objects. All conditions must be satisfied for the transition to fire.

Inspecting Transitions

Click a transition arrow in the graph to select it. The Inspector Panel shows:

  • “Transition: Source -> Destination” header
  • Priority field
  • Conditions list with a type picker to add new conditions

Transition Summary on Nodes

When a state node is selected, the inspector shows a summary of all outgoing transitions below the node’s properties. Each entry shows "P{priority}: -> {destination} [{N} conditions]" with a “Select” button to jump to that connection’s inspector.

Auto-Management

Transition descriptors are created and removed automatically when connections are added or removed in the graph. No manual descriptor management is needed.


Built-in Conditions

ConditionDescription
VariableCompareConditionCompares a named variable to a value using a comparison operator (==, !=, <, >, <=, >=)
TimeElapsedConditionFires after a specified number of seconds in the current state. Reads the __stateTime auto-variable.
VariableTrueConditionFires when a named boolean variable is true

Parallel Layers

The defining feature of the Unit Action Graph. Multiple layers evaluate simultaneously, each running its own independent state machine. All layers share the same variable context, enabling cross-layer communication.

Example layout:

UnitActionGraph
├── Layer: Movement (priority 0)   — Idle, Walk, Run, Jump
├── Layer: Action (priority 1)     — None, Attack, Interact
└── Layer: Rotation (priority 2)   — FreeRotation, Locked, Modulated

Higher-priority layers can restrict or override lower-priority outputs. Each layer produces a state output that feeds into the character control system.

This solves the classic “jump while moving” problem — the Movement layer handles lateral motion while the Action layer handles the jump impulse, both evaluating simultaneously without conflicting.


Extending

Custom States

Subclass BaseNode and implement IStateMachineNode:

void OnEnter(GraphExecutionContext& context) override;
void OnTick(GraphExecutionContext& context, float deltaTime) override;
void OnExit(GraphExecutionContext& context) override;

Register with GS_AUTO_REGISTER_NODE_FOR(MyStateNode, "unitaction").

Custom Transition Conditions

Subclass TransitionCondition, add AZ_RTTI, and implement Reflect(). The inspector type picker auto-discovers new conditions via SerializeContext::EnumerateDerived().


Current Status

Implemented:

  • Perimeter connection rendering (engine-level)
  • Multi-layer container editor with sidebar
  • HFSM runtime architecture (StateMachineEvaluator, ParallelLayerEvaluator)
  • All node types (Entry, State, Compound State)
  • Transition inspector with connection selection
  • Built-in transition conditions

Not yet implemented:

  • GS_UnitContext integration (connecting evaluator to unit components and Movers)
  • Compound State sub-graph editing via double-click in the editor
  • Tag-based transition groups and free-entry states
  • GS_CharacterActionComponent bridge to MoverContextRequestBus

See Also

4.15.5 - Templates

ClassWizard templates for GS_Unit — unit controllers, input reactors, mover components, and grounder components.

All GS_Unit extension types are generated through the ClassWizard CLI. The wizard handles UUID generation, cmake file-list registration, and module descriptor injection automatically.

For usage guides and setup examples, see The Basics: GS_Unit.

python ClassWizard.py \
    --template <TemplateName> \
    --gem <GemPath> \
    --name <SymbolName> \
    [--input-var key=value ...]

 

Contents


Unit Controller

Template: UnitController

Creates a Controller component that lives on the Controller Entity (the player or AI controller, not the Unit Entity itself). Manages which Unit is currently possessed, and coordinates possession/unpossession events to sibling controller-side components.

Generated files:

  • Source/${Name}ControllerComponent.h/.cpp

CLI:

python ClassWizard.py --template UnitController --gem <GemPath> --name <Name>

Post-generation: Implement OnPossess(unitEntityId) and OnUnpossess() to route activation to the correct input readers and other controller components. The controller entity is persistent; the unit entity is swappable.

See also: Unit Controllers — full extension guide with header and implementation examples.


Input Reactor

Template: InputReactor

Scaffolds a reaction type — a class listing the named input events it listens to and translating them into MoverContextRequestBus calls. It is added to a reactor profile asset, which the Unit Entity’s Input Reactor component carries.

Generated files:

  • Source/${Name}InputReactor.h/.cpp

CLI:

python ClassWizard.py --template InputReactor --gem <GemPath> --name <Name>

Post-generation:

  • Return your event names from GetSubscribedEvents().
  • Implement OnInputEvent(owner, name, value, phase), branching on the phase where press and release differ.
  • Reflect it against the reactor base and it appears in the reactor profile’s type dropdown. Several reaction types coexist in one profile, each handling different events.

See also: Unit Input Reactors for worked examples, and Input Reactors for the extension contract.


Mover Component

Template: Mover

Creates a Mover component that drives an entity’s movement each tick. Activates when GS_MoverContextComponent switches to the matching mode name, then calls HandleMovement() and HandleRotation() while active.

Two base classes available via --input-var:

OptionBase ClassMovement DrivePhysics Required
Physics Mover (default)GS_PhysicsMoverComponentrigidBody linear/angular velocity, post-simulate tickYes — PhysicsRigidBodyService
Base MoverGS_MoverComponentTransformBus world translation, AZ::TickBusNo

Generated files (Physics Mover):

  • Source/${Name}PhysicsMoverComponent.h/.cpp

Generated files (Base Mover):

  • Source/${Name}MoverComponent.h/.cpp

CLI:

# Physics Mover (default):
python ClassWizard.py --template Mover --gem <GemPath> --name <Name> \
    --input-var mover_base="Physics Mover"

# Base Mover:
python ClassWizard.py --template Mover --gem <GemPath> --name <Name> \
    --input-var mover_base="Base Mover"

Post-generation:

  • Set m_moveModeName and m_rotateModeName strings in Activate() to match the mode names configured in your MoverContext.
  • Implement HandleMovement() and HandleRotation(). The Physics Mover template comes pre-filled with spring-damper stubs using AccelerationSpringDamper and QuaternionSpringDamper.
  • In CheckCanOperate(), lazy-fetch any additional context pointers your mover needs.
  • One Mover per movement mode. All Mover components on a Unit Entity coexist — the MoverContext activates only the matching one at a time.

See also: Movers — full mover architecture and extension guide with code examples.


Grounder Component

Template: Grounder

Creates a Grounder component that determines ground state (Falling / Grounded / Sliding) each tick and reports it back to GS_MoverContextComponent. Active when the MoverContext switches to the matching grounding mode name.

Two base classes available via --input-var:

OptionBase ClassDetectionExtras
Physics Ray Grounder (default)GS_PhysicsRayGrounderComponentDownward raycast from base classCoyote time, spring position correction, gravity application
Base GrounderGS_GrounderComponentBring your own detectionTick only

Generated files (Physics Ray Grounder):

  • Source/${Name}PhysicsRayGrounderComponent.h/.cpp

Generated files (Base Grounder):

  • Source/${Name}GrounderComponent.h/.cpp

CLI:

# Physics Ray Grounder (default):
python ClassWizard.py --template Grounder --gem <GemPath> --name <Name> \
    --input-var grounder_base="Physics Ray Grounder"

# Base Grounder:
python ClassWizard.py --template Grounder --gem <GemPath> --name <Name> \
    --input-var grounder_base="Base Grounder"

Post-generation:

  • Set m_groundModeName in Activate().
  • For Physics Ray Grounder: The base class handles the raycast, spring correction, and gravity. Override HandleGrounding() and GroundingStateChange() only to add custom reactions — call the base first.
  • For Base Grounder: Implement HandleGrounding() with your detection method. Call GroundingStateChange(0/1/2) when state changes (0 = Falling, 1 = Grounded, 2 = Sliding), and SetGroundNormal() on the MoverContext when on a surface.
  • Typically one grounder per unit type. Swap the grounder via mode name for units that switch between ground-based and other locomotion.

See also: Grounders — full grounder architecture and extension guide with code examples.


See Also

For the full API, component properties, and C++ extension guide:

For all ClassWizard templates across GS_Play gems:


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.15.6 - 3rd Party Implementations

For usage guides and setup examples, see The Basics: GS_Unit.


Get GS_Unit

GS_Unit — Explore this gem on the product page and add it to your project.

4.16 - 3rd Party API

API for 3rd party support.

How to handle 3rd party support.

5 - Learn

Lessons for quick, or extended learning.

Lessons around the various features. Installation. And extended tutorials on deeper usage, or project creation.


How This Section Is Organized

Video Tutorials are one or a playlist of videos covering a targeted topic.

Lessons are guide documents that describe in step by step ways, with informational detail, on how to accomplish the targeted goal. Lessons tied to a video tutorial will have a link to the Video Page for use.

5.1 - Lessons

Index of Lesson Guides

Lesson Guides List

5.1.1 - Understanding GS_Play

What is GS_Play and what does it do?

GS_Play Methodology and Purpose

GS_Play is an intermediate to advanced game development and production framework. Because of this it can rapidly create prototypes and prove out gameplay, but is deeply extensible and customizable — allowing the project to grow, and the game to become exactly what you want to make. This does mean the tools are not as “out of the box” as more beginner-friendly options. You should already know how to make videogames, or be actively studying how to develop features, to get the most out of this framework. Check out the library of lessons and guides to get embedded in any GS_Play feature you’d like to explore.

Due to its modularity, your project should only need to target the features most relevant to your intended gameplay and genre, then build on and around the framework to satisfy any additional needs.

GS_Play is built on the idea of simple, intuitive patterns for every feature — patterns that let you think about how to deploy the functionality, not how it works under the hood. Because of this core tenet, you should be able to reason about what the premade functionality can do, and what custom features you want to contribute to that pool, to author your project rapidly and precisely.


What GS_Play Supports

The core functionality is oriented around character-centric, live-action gameplay. This can be slow and subtle — a point-and-click adventure or survival horror where the action revolves around exploration, investigation, and choosing your own path. It can also be extended to high-paced action: character-driven unit gameplay, supported by cinematic performer visuals, where you attack, roll, jump, dodge, and traverse a complex world. Enter and exit game-time cinematics, or transition into rich, fully authored sequences. The PhantomCam system can keep pace with anything you throw at it.

That is not to say other styles and genres are off the table. Many GS_Play feature sets are genre-agnostic and simply complete the needs of a full production. GS_UI can serve as straightforward menus and indicators, but with the dynamic animation system, wealth of widgets, and precise control of input focus, it can anchor heavily UI-reliant gameplay. With deeply extensible unit control, AI, and input handling, you can pursue the group formation and pathing an RTS requires. Any genre, supported by Audio, VFX, Cinematics, and gameplay systems working together.

The ultimate goal for GS_Play is to get you from a blank canvas to the end credits, and everything in between.


Best Practices

Focus on learning the patterns of each feature set. There are many base elements ready to use from the start, but as you work, keep asking: “What would I do if I needed X for my game?” That question is the right lens for every feature — it keeps you thinking about your game’s needs, not the framework’s internals.

Target only what your game requires. GS_Play’s modularity means you are not obligated to use every gem. Start with the features that directly serve your genre and core loop, then expand from there as the project demands it.

Lean on the EBus pattern. Most systems communicate through request and notification buses. Understanding how to listen for state changes and issue requests is the skill that unlocks the entire framework.

Review Best Practices for full coverage.


Specs

15+ gems, with 30+ feature sets across them.

Feature sets covering

  • Operation — Settings, hardware compatibility, and startup sequencing.
  • Character & Action — Unit controls and actions, with and against the world around them.
  • Environment — Rich environmental development: time, day/night cycles, and sky configuration.
  • Cinematics — Camera work, sequencing, dialogue, and character performance.
  • Game Feel — Audio, UI and 3D effect bursts, post processing, and motion-based feedback.

5.1.2 - Simple Project Setup

Easy set up to get started.

A guide to get everything propped up rapidly

Video Tutorial

Embed youtube guide.

Link to video_tutorials.

5.1.2.1 - Configure Project

Configure Project to run GS_Play.

Install Gems

Configure Project Gems

Add Project Dependencies

cmakelists: package build targets

<ProjectGemName>.Private.Object STATIC
BUILD_DEPENDENCIES
    PUBLIC
        Gem:GS_Play_Core.API
        Gem:GS_<your desired module>.API

Setup Project Environment

Environment Setup

Need Help?

Check out the Project Setup Video Tutorial (Links to video_tutorial tag for setup video)

5.1.2.1.1 - Setting Up the Physics Environment

Setting up your Physics Environment

These are the necessary details to create your project and have it run reliably with all GS_Play featuresets.


Physics

Image showing the standard PhysX Configuration necessary to support all GS_Play features.

Collision Layers

Collision Layers configuration panel

Image showing the standard PhysX Collision Layers necessary to support all GS_Play features.

NoCollision Layer

Environment Layer

Doodad Layer

Trigger Layer

Unit Layer

Pulse Layer

Interact Layer

Regions Layer

Collision Groups

Collision Groups configuration panel

Image showing the standard PhysX Collision Groups necessary to support all GS_Play features.

UnitGrounding Group

Triggering Group

Pulse Group

Doodad Group

Interact Group

AllButEnviro Group

OnlyRegions Group


Set Ground Collision Layer

In order to properly introduce a GS_Unit to the default scene you need to create an object with the “Environment” collision layer to stand on.

The easist way to start is to change the collision layer of the default “Ground” entity in the level.

5.1.2.2 - Prepare Managers

Prepare Managers to run GS_Play.

Start Creating Manager Prefabs

  • Blank Entity
  • Create Prefab
  • Add to Game Manager

Need Help?

Check out the Project Setup Video Tutorial (Links to video_tutorial tag for setup video)

5.1.2.3 - Prepare Startup

Prepare Startup to run GS_Play.

Start Creating Manager Prefabs

  • Blank Entity
  • Create Prefab
  • Add to Game Manager

Need Help?

Check out the Project Setup Video Tutorial (Links to video_tutorial tag for setup video)

5.1.2.4 - Prepare Camera

Prepare Camera to run GS_Play.

Start Creating Manager Prefabs

  • Blank Entity
  • Create Prefab
  • Add to Game Manager

Need Help?

Check out the Project Setup Video Tutorial (Links to video_tutorial tag for setup video)

5.1.2.5 - Get Ready to Start!

Prepare Gameplay to run GS_Play.

Resources to start using specific features.

Good things to know before starting.

You’re ready to start!

5.1.3 - Dialogue System Setup

A step-by-step lesson for setting up a working dialogue system with the Dialogue Editor, UI components, and runtime playback.

Dialogue System Setup — Tutorial

This lesson walks through setting up a complete dialogue system in a GS_Play project: creating a dialogue database, authoring a branching conversation, wiring up the runtime components, and displaying dialogue in-game with a typewriter effect.

What You Will Build

By the end of this lesson you will have:

  • A dialogue database with multiple sequences
  • Branching dialogue with player choices
  • Performer definitions for your characters
  • Runtime entities with the sequencer, manager, and UI bridge components
  • A working in-game dialogue display with typewriter text reveal

Prerequisites

  • A GS_Play project with GS_Core and GS_Cinematics gems enabled
  • Familiarity with the O3DE Editor (placing entities, adding components)
  • Basic understanding of prefabs and spawning

 

Pages

  1. Create the Database
  2. Author a Conversation
  3. Wire Up the Runtime
  4. Display Dialogue In-Game
  5. Add Player Choices
  6. Conditions and Effects

Overview

The GS_Cinematics dialogue system has three layers:

  • Editor — The Dialogue Editor where you visually author conversations as node graphs
  • Runtime — The DialogueSequencerComponent that executes the graph at runtime
  • UI — DialogueUIComponent and SelectionUIComponent that display text and choices to the player

This tutorial connects all three layers into a working pipeline.

5.1.3.1 - Step 1: Create the Database

Create a dialogue database and define performers.

Step 1 — Create the Database

Open the Dialogue Editor

From the O3DE Editor menu bar, go to GS Tools > Dialogue Editor. The editor opens with three page tabs at the top: Sequences, Performers, and System.

Create a New Database

  1. Go to File > New
  2. Choose a save location in your project’s assets folder (e.g., Assets/Dialogue/)
  3. Name the file (e.g., town_npcs.dialoguedatabase)
  4. The editor opens with an empty database

Define Performers

Switch to the Performers page by clicking the tab at the top.

Performers represent the characters who speak in your dialogues. Add one performer for each character:

  1. Click Add Performer
  2. Set the Name — this is the identifier you will reference in Text nodes (e.g., "Merchant", "Guard")
  3. Optionally configure:
    • Portrait — Reference to a UI image asset shown during dialogue
    • Voice — Audio configuration for this character’s voice

Add at least two performers for this tutorial (e.g., "Player" and "Merchant").

Create Your First Sequence

Switch back to the Sequences page.

  1. In the Sequence Sidebar (left dock), click Add
  2. Name the sequence (e.g., "MerchantGreeting")
  3. The sequence opens as a graph tab with a single Start node

Your database now has one empty sequence ready for authoring. Save the database with File > Save (Ctrl+S).

What You Have So Far

town_npcs.dialoguedatabase
├── Performers: Player, Merchant
└── Sequences: MerchantGreeting (empty)

Next: Author a Conversation

5.1.3.2 - Step 2: Author a Conversation

Build a simple linear dialogue sequence using Text and End nodes.

Step 2 — Author a Conversation

Build a Linear Sequence

With the MerchantGreeting sequence open, you have a Start node on the canvas. Now add dialogue nodes:

  1. From the Node Palette (right dock), drag a Text node onto the canvas
  2. Connect the Start node’s FlowOut slot to the Text node’s FlowIn slot by clicking and dragging between the slots
  3. Select the Text node and set its properties in the Inspector:
    • Speaker: "Merchant" (matches your performer name)
    • Text: "Welcome to my shop! Looking for something special?"

Add More Lines

Repeat the process to add a second Text node for the player’s response:

  1. Drag another Text node onto the canvas
  2. Connect the first Text node’s FlowOut to this new node’s FlowIn
  3. Set properties:
    • Speaker: "Player"
    • Text: "Just browsing, thanks."

End the Sequence

  1. Drag an End node from the palette
  2. Connect the last Text node’s FlowOut to the End node’s FlowIn

Your sequence now reads:

Start → [Merchant: "Welcome to my shop!"] → [Player: "Just browsing."] → End

Test the Flow

The node footers preview each line of dialogue, making it easy to read through the conversation visually on the canvas. Verify the flow makes sense by following the connections from Start to End.

Save the database (Ctrl+S).

Tips

  • Undo/Redo: Ctrl+Z / Ctrl+Y work on all graph operations
  • Node selection: Click a node to see its full properties in the Inspector
  • Moving nodes: Drag nodes to rearrange the layout for readability
  • Copy/Paste: Select nodes and use Ctrl+C / Ctrl+V to duplicate sections

Next: Wire Up the Runtime

5.1.3.3 - Step 3: Wire Up the Runtime

Place the runtime components needed to play dialogue sequences in-game.

Step 3 — Wire Up the Runtime

The Dialogue Editor is for authoring. To play dialogue at runtime, you need three components on entities in your level.

The Dialogue Manager

The Dialogue Manager is a GS_Play manager that owns the active dialogue database and provides the top-level API.

  1. Your Game Manager prefab should already spawn a GS_DialogueManager (it is part of the GS_Cinematics manager set)
  2. If not, create a manager entity and add the GS_DialogueManagerComponent
  3. In the component properties, set the Dialogue Database field to your .dialoguedatabase asset

The manager provides the entry point API: StartDialogueSequenceByName("MerchantGreeting").

The Dialogue Sequencer

The sequencer is the component that actually executes the graph — stepping through nodes, waiting for text display, and handling player choices.

  1. On your NPC entity (or a dedicated dialogue entity), add a DialogueSequencerComponent
  2. No configuration needed — the sequencer receives commands from the manager

The Dialogue UI Bridge

The UI Bridge connects the sequencer to your UI display components. It routes dialogue text to the correct UI panel and relays player choices back.

  1. On the same entity as the sequencer (or a shared UI entity), add a DialogueUIBridgeComponent
  2. It will automatically discover registered UI components in the scene

Triggering Dialogue

To start dialogue from gameplay code or ScriptCanvas:

// C++ via EBus
DialogueManagerRequestBus::Broadcast(
    &DialogueManagerRequests::StartDialogueSequenceByName,
    "MerchantGreeting"
);

Or connect it to an interaction trigger — when the player interacts with the merchant entity, fire StartDialogueSequenceByName.

Component Summary

ComponentEntityPurpose
GS_DialogueManagerComponentManager entityOwns database, provides start/stop API
DialogueSequencerComponentNPC or dialogue entityExecutes the graph step by step
DialogueUIBridgeComponentSame or shared entityRoutes text/choices between sequencer and UI

Next: Display Dialogue In-Game

5.1.3.4 - Step 4: Display Dialogue In-Game

Set up dialogue UI components for text display and typewriter effect.

Step 4 — Display Dialogue In-Game

Dialogue UI Component

The DialogueUIComponent handles displaying dialogue text on screen. It receives text from the UI Bridge and shows it in a UI element.

  1. Create a UI canvas with a text panel for dialogue display (speaker name label + dialogue text area)
  2. On the UI entity, add a DialogueUIComponent
  3. Register the UI entity with the bridge by calling RegisterDialogueUI(panelType, entityId) or by placing it where the bridge can discover it

When a Text node executes, the sequencer sends the speaker name and dialogue text through the bridge to the UI component, which updates the display.

Typewriter Effect

The TypewriterComponent reveals text character by character for a classic RPG dialogue feel:

  1. On the same entity as the DialogueUIComponent, add a TypewriterComponent
  2. Set the Default Speed — letters per second (e.g., 30 for moderate speed)
  3. The typewriter automatically hooks into the dialogue display

When text arrives, the typewriter reveals it gradually. The player can press a button to call ForceComplete() for instant reveal.

World-Space Dialogue (Speech Bubbles)

For dialogue that appears above characters in the 3D world:

  1. Use WorldDialogueUIComponent instead of DialogueUIComponent
  2. Configure a spawnable prefab for the speech bubble UI
  3. The component tracks the speaking entity’s position and places the UI above them

Flow

Sequencer executes Text node
    → UIBridge.RunDialogue(text, speakerName)
        → DialogueUIComponent.DoDialogue(text, performerData)
            → TypewriterComponent.StartTypewriter(text)
                → Text reveals character by character
                    → Player presses button → ForceComplete()
                        → Sequencer advances to next node

Next: Add Player Choices

5.1.3.5 - Step 5: Add Player Choices

Add branching dialogue with Selection nodes and choice UI.

Step 5 — Add Player Choices

Add a Selection Node

Go back to the Dialogue Editor and open your MerchantGreeting sequence.

  1. Delete the connection between the merchant’s greeting and the player’s response
  2. Drag a Selection node from the palette and place it between them
  3. Connect the merchant’s Text node FlowOut to the Selection node’s FlowIn

Configure Choices

Select the Selection node. In the Inspector:

  1. Set Question Text to "What would you like to do?" (optional — displayed above the choices)
  2. Add options in the Selection Options list:
    • Option 1: "Browse your wares"
    • Option 2: "Tell me about the town"
    • Option 3: "Goodbye"

Each option creates a dynamic FlowOut slot on the node.

Branch the Conversation

Now connect each output to different dialogue paths:

  1. “Browse your wares” FlowOut → New Text node (Merchant: "Take a look around!") → End
  2. “Tell me about the town” FlowOut → New Text node (Merchant: "This town was founded...") → End
  3. “Goodbye” FlowOut → New Text node (Merchant: "Safe travels!") → End

Your graph now branches based on the player’s choice and each branch leads to a different response.

Selection UI Component

On the UI side, the DialogueUISelectionComponent displays the choices to the player:

  1. On a UI entity, add a DialogueUISelectionComponent
  2. Configure it with a button prefab for spawning choice options
  3. Register it with the UI Bridge

When the sequencer hits a Selection node:

Sequencer reaches Selection node
    → UIBridge.RunSelection(options)
        → DialogueUISelectionComponent.DoSelection(options, performerData)
            → Spawns choice buttons
                → Player clicks a choice
                    → OnSelection(index) fires
                        → Sequencer follows the chosen FlowOut

Random Branching

For variety without player choice, use a Random node instead. It picks a random output each time the sequence reaches it. Optionally configure weights to make some branches more likely.

Next: Conditions and Effects

5.1.3.6 - Step 6: Conditions and Effects

Gate dialogue branches with conditions and trigger game effects from dialogue.

Step 6 — Conditions and Effects

Using Variables

Declare variables in the Variable Panel to track dialogue state:

  1. Open the Variable Panel dock
  2. Click Add and create a boolean variable: "hasVisitedMerchant" (default: false)

Conditions on Nodes

Any dialogue node can have conditions that gate whether it executes. If conditions fail, the evaluator skips that node and tries the next connection.

  1. Select a Text node in the inspector
  2. In the Conditions section, click the type picker dropdown
  3. Add a Boolean_DialogueCondition
  4. Set:
    • Variable Name: "hasVisitedMerchant"
    • Expected Value: true

Now that Text node only executes if the player has visited before. You can create two branches from a single output — one with hasVisitedMerchant = true and one without conditions as a fallback.

Effects Nodes

Effects nodes trigger game actions during dialogue. They are the bridge between conversation and gameplay.

  1. Drag an Effects node from the palette
  2. Place it in the flow where you want the effect to happen
  3. Select it and use the type picker in the Inspector to add effect types

For example, after the merchant’s greeting, add an Effects node that sets hasVisitedMerchant = true so subsequent visits show different dialogue.

Performance Nodes

Performance nodes trigger character actions — animations, movement, posing:

  1. Drag a Performance node into the flow
  2. Add performance types via the Inspector type picker:
    • MoveTo_DialoguePerformance — Move a character to a position
    • PathTo_DialoguePerformance — Move along a path
  3. Enable Wait to Continue if the dialogue should pause until the performance finishes

Creating Custom Types

The polymorphic extension system makes it simple to add project-specific conditions, effects, and performances:

  1. Create a subclass of DialogueCondition, DialogueEffect, or DialoguePerformance
  2. Add AZ_RTTI and implement Reflect() with SerializeContext and EditContext
  3. Include and reflect it in DialogueSequencerComponent::Reflect()

The inspector automatically discovers your new type — no registry code needed.

Complete Example

Here is a complete branching dialogue with conditions and effects:

Start
  → [Merchant greeting (no condition): "Welcome, stranger!"]
  → [Merchant greeting (hasVisitedMerchant=true): "Welcome back, friend!"]
      → Selection: "Buy" / "Sell" / "Leave"
          → "Buy" → Effects(OpenShopUI) → End
          → "Sell" → Effects(OpenSellUI) → End
          → "Leave" → [Merchant: "Come again!"] → Effects(Set hasVisitedMerchant=true) → End

Summary

You now have a complete dialogue pipeline:

  • Database with performers and multiple sequences
  • Visual authoring with Text, Selection, Random, Effects, and Performance nodes
  • Runtime components (Manager, Sequencer, UI Bridge) executing the graphs
  • UI display with typewriter text reveal and player choice buttons
  • Conditions for dynamic branching based on game state
  • Effects for triggering gameplay from within dialogue

For the full API reference, see the Dialogue Editor documentation.

5.1.4 - Building a Graph Tool

Step-by-step guide to creating a custom visual graph editor using the gs_graphcanvas framework.

Building a Graph Tool with gs_graphcanvas

This lesson walks through the full process of creating a new visual graph editor tool using the gs_graphcanvas framework. By the end, you will have a working editor with nodes, a palette, save/load, undo/redo, variables, and runtime execution.

The pipeline is proven across three implementations: the Dialogue Editor (FlowGraph), the Audio Event Graph (DataFlowGraph), and the Unit Action Graph (StateMachineGraph).

What You Provide vs What the Framework Gives You

You provide (per tool):

  1. A GraphSystemDescriptor — identity and topology choice
  2. A GraphContext subclass — domain-specific data types (if any)
  3. Node types — registered via macros
  4. An editor window — MainWindow subclass (or direct use)
  5. An editor system component — reflects nodes, registers the view pane
  6. A runtime component — (optional) bridges graphs to game systems

The framework gives you (free):

  • Full editor window with menus, toolbar, palette, canvas
  • Multi-document tabs with dirty tracking
  • Save/load (XML wrapper + binary graph buffer)
  • Undo/redo (snapshot-based, per-graph)
  • Variable system (declarations, Get/Set nodes, references, drag-to-canvas)
  • Inspector panel (auto-generated from node EditContext)
  • Node palette with drag-drop
  • Full-window page system
  • Three execution engines (flow, data-flow, state machine)
  • Copy/paste, selection, bookmarks

 

Pages

  1. Step 1: Choose Your Topology
  2. Step 2: Create the Descriptor
  3. Step 3: Create the GraphContext
  4. Step 4: Create Nodes
  5. Step 5: Create the Editor Window
  6. Step 6: Register the System Component
  7. Step 7: Build Runtime (Optional)
  8. Pitfalls Checklist

Step 1: Choose Your Topology

This is the fundamental design decision. It determines connections, execution model, and slot types.

TopologyUse CaseExecutionConnections
FlowGraphSequential processes, dialogue trees, scriptingStep/wait/resume via FlowIn/FlowOutDirectional (left-to-right)
DataFlowGraphSignal processing, audio, shaders, procedural generationTopological dirty-propagationDirectional (left-to-right)
StateMachineGraphState machines, behavior trees, HFSMTick-based state transitionsMultidirectional (perimeter arrows)

Key constraints:

  • DataFlowGraph requires a DAG (no cycles) for topological sorting
  • StateMachineGraph inherently supports cycles
  • FlowGraph optionally supports cycles via allowConnectionLoopback

Step 2: Create the Descriptor

Create a header that returns a filled-out GraphSystemDescriptor:

#pragma once
#include <GS_GraphCanvas/GraphSystemDescriptor.h>

namespace GS_MyTool
{
    inline GS_GraphCanvas::GraphSystemDescriptor CreateMyToolDescriptor()
    {
        GS_GraphCanvas::GraphSystemDescriptor desc;

        // ===== Identity =====
        desc.systemId = "mytool";
        desc.systemName = "My Tool Editor";
        desc.fileExtension = ".mytool";
        desc.mimeType = "application/x-gs-mytool-node";
        desc.saveIdentifier = "MyToolEditor";
        desc.editorId = GraphCanvas::EditorId("MyToolEditor");

        // ===== Topology =====
        desc.topology = GS_GraphCanvas::GraphTopology::DataFlowGraph;

        // ===== Features =====
        desc.variablesEnabled = true;
        desc.allowConnectionLoopback = false;

        // ===== UI =====
        desc.windowTitle = "My Tool Editor";
        desc.menuCategory = "GS Tools";

        return desc;
    }
}

Every field in the descriptor is documented in the Descriptor & Topology reference.


Step 3: Create the GraphContext

Register any domain-specific data types beyond the built-in set (Bool, Int, Float, String, Vector2, Vector3, Color, EntityId).

#pragma once
#include <GS_GraphCanvas/Core/CommonDataTypes.h>
#include <GraphModel/Model/GraphContext.h>

namespace GS_MyTool
{
    class MyToolGraphContext : public GraphModel::GraphContext
    {
    public:
        MyToolGraphContext();

        static void RegisterDataTypes(GraphModel::GraphContextPtr context)
        {
            // Built-in CommonDataTypes are registered automatically.
            // Add domain-specific types here if needed:
            // context->RegisterDataType(MyCustomEnum, azrtti_typeid<MyStruct>(),
            //     MyStruct{}, "MyCustomType");
        }
    };
}

If your tool only uses the built-in types, the context subclass can be minimal.


Step 4: Create Nodes

Basic Node Template

#pragma once
#include <GS_GraphCanvas/Nodes/BaseNode.h>
#include <GS_GraphCanvas/Widgets/SimpleNodeMacros.h>
#include <GS_GraphCanvas/NodeRegistry.h>

namespace GS_MyTool
{
    class MyTool_ExampleNode : public GS_GraphCanvas::BaseNode
    {
    public:
        AZ_RTTI(MyTool_ExampleNode, "{GENERATE-UNIQUE-UUID}", GS_GraphCanvas::BaseNode);
        AZ_CLASS_ALLOCATOR(MyTool_ExampleNode, AZ::SystemAllocator);

        static constexpr const char* TITLE = "Example Node";
        static constexpr const char* CATEGORY = "Processing";

        static void Reflect(AZ::ReflectContext* context);
        const char* GetTitle() const override { return TITLE; }

    protected:
        void RegisterSlots() override
        {
            GS_INPUT_SLOT_TYPED("value_in", "Value", CommonDataTypes::Float, 0.0f);
            GS_OUTPUT_SLOT_TYPED("result_out", "Result", CommonDataTypes::Float);
        }

        float m_multiplier = 1.0f;
    };

    GS_AUTO_REGISTER_NODE_FOR(MyTool_ExampleNode, "mytool")
}

Reflect Implementation

void MyTool_ExampleNode::Reflect(AZ::ReflectContext* context)
{
    if (auto* sc = azrtti_cast<AZ::SerializeContext*>(context))
    {
        if (sc->FindClassData(azrtti_typeid<MyTool_ExampleNode>())) { return; }
        sc->Class<MyTool_ExampleNode, GS_GraphCanvas::BaseNode>()
            ->Version(1)
            ->Field("Multiplier", &MyTool_ExampleNode::m_multiplier)
            ;
    }
    if (auto* ec = azrtti_cast<AZ::EditContext*>(context))
    {
        ec->Class<MyTool_ExampleNode>("Example Node", "A processing node")
            ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
            ->DataElement(AZ::Edit::UIHandlers::Default, &MyTool_ExampleNode::m_multiplier,
                "Multiplier", "Scale factor")
            ;
    }
}

Implementing Execution

Choose the interface matching your topology:

FlowGraph — IExecutableNode:

FlowResult Execute(GraphExecutionContext& context) override
{
    float input = context.GetInputValue<float>(this, "value_in");
    context.SetOutputValue(this, "result_out", input * m_multiplier);
    return FlowResult::Continue;  // or Wait, Stop
}

DataFlowGraph — IDataFlowNode:

void Process(GraphExecutionContext& context) override
{
    auto inputAny = context.GetInputValueAny(this, "value_in");
    if (inputAny.empty())
    {
        context.SetOutputValueAny(this, "result_out", AZStd::any{});
        return;
    }
    float input = AZStd::any_cast<float>(inputAny);
    context.SetOutputValue(this, "result_out", input * m_multiplier);
}

StateMachineGraph — IStateMachineNode:

void OnEnter(GraphExecutionContext& context) override { /* ... */ }
void OnTick(GraphExecutionContext& context, float deltaTime) override { /* ... */ }
void OnExit(GraphExecutionContext& context) override { /* ... */ }

Step 5: Create the Editor Window

Simple: Standalone File Editor

If each graph is its own file (like the Audio Event Graph), use MainWindow directly:

class MyToolEditorWindow : public GS_GraphCanvas::MainWindow
{
public:
    MyToolEditorWindow(const GS_GraphCanvas::GraphSystemDescriptor& desc)
        : GS_GraphCanvas::MainWindow(desc)
    {}
};

This gives you a fully functional editor with no additional code.

Advanced: Container/Database Editor

If multiple graphs live in one file (like dialogue sequences or unit action layers), override file operations and add management UI:

class MyToolEditorWindow : public GS_GraphCanvas::MainWindow
{
protected:
    // Override file menu for container-level operations
    void AddFileNewAction() override;     // New container
    void AddFileOpenAction() override;    // Open container
    void AddFileSaveAction() override;    // Save entire container
    void AddFileSaveAsAction() override;

    // Add non-graph pages
    void SetupPages()
    {
        SetGraphPageTitle("Graphs");
        AddFullWindowPage("Settings", m_settingsPage);
    }

    // Container save pattern
    void SaveContainer()
    {
        for (auto& [graphId, entry] : m_openGraphs)
            CaptureGraphToAsset(graphId, entry.graphData, serializeContext);

        AZ::Utils::SaveObjectToFile(m_path, AZ::DataStream::ST_XML, &m_container);
        ClearAllDocumentDirty();
    }
};

Step 6: Register the System Component

The editor system component reflects all node types and registers the view pane:

class GS_MyToolEditorSystemComponent : public AZ::Component
{
public:
    AZ_COMPONENT(GS_MyToolEditorSystemComponent, "{UNIQUE-UUID}");

    static void Reflect(AZ::ReflectContext* context)
    {
        // Reflect all node types
        MyTool_ExampleNode::Reflect(context);
        // ... all your nodes ...

        // Reflect MIME events for palette drag-drop
        GS_GraphCanvas::NodeRegistry::Get().ReflectAllMimeEvents(context);

        // Reflect the component
        if (auto* sc = azrtti_cast<AZ::SerializeContext*>(context))
        {
            sc->Class<GS_MyToolEditorSystemComponent, AZ::Component>()
                ->Version(1);
        }
    }

    void Activate() override
    {
        AzToolsFramework::ViewPaneOptions options;
        options.paneRect = QRect(50, 50, 1200, 800);
        AzToolsFramework::RegisterViewPane<MyToolEditorWindow>(
            "My Tool Editor", "GS Tools", options);
    }

    void Deactivate() override
    {
        AzToolsFramework::UnregisterViewPane("My Tool Editor");
    }
};

Add the component descriptor to your gem’s editor module:

// In GS_MyToolEditorModule.cpp
m_descriptors.push_back(GS_MyToolEditorSystemComponent::CreateDescriptor());

Step 7: Build Runtime (Optional)

If your tool needs runtime execution (not just an editor-only design tool):

  1. Load the graph asset (directly or via a template cache for pooling)
  2. Create a GraphInstance via GraphInstance::CreateFromAsset()
  3. Create the appropriate evaluator (FlowGraphEvaluator, DataFlowGraphEvaluator, or StateMachineEvaluator)
  4. Expose an API via EBus for triggering and controlling execution

Pooling Pattern (High-Frequency Execution)

For graphs that are instantiated frequently (like audio events), follow the template cache pattern:

TemplateCache
  -> Caches GraphDocumentAsset per file path
  -> Pools idle GraphInstance objects
  -> CreateFromAsset() for new instances
  -> Return to idle pool on release

Pitfalls Checklist

Reflection

  • Every Reflect() must have a FindClassData guard (gs_graphcanvas is a static lib)
  • Every sc->Enum<T>() must be guarded — enums crash on double-registration
  • All UUIDs must be globally unique (collision = crash in ResolvePointer)
  • Polymorphic containers must use AZStd::vector<Base*> (raw pointers, not smart pointers)
  • EnableForAssetEditor goes on SerializeContext only, never EditContext
  • Field names must be unique, non-empty PascalCase strings
  • Use AZ_DISABLE_COPY on classes with polymorphic pointer vectors
  • Do not use id as a member variable name (conflicts with AZ_RTTI macro)

Data Flow

  • Inactive nodes must output AZStd::any{}, not default-constructed structs
  • Multi-input slots must be declared explicitly per-slot
  • Node auto-registration must use the correct systemId string

Editor

  • NodeRegistry.h must be included before StandardNodePaletteItem.h (MSVC requirement)
  • Editor system component must reflect all node types and call ReflectAllMimeEvents()
  • View pane must be registered in Activate() and unregistered in Deactivate()

Save/Load

  • Graph document asset must be a plain struct (not AZ::Data::AssetData)
  • Graphs must be serialized as byte buffers (not as shared_ptr fields)
  • Transient display data (cached titles, etc.) must be rebuilt after deserialization

See Also

5.1.5 - Understanding Agentic Guidelines

Human-facing explanation of how the Agentic Guidelines page works — its structure, language rules, and how to keep it accurate. A how-to reference, not an open invitation to edit.

What the Agentic Guidelines Page Is

The parent page — Agentic Guidelines — is not a documentation page in the normal sense. It contains no prose meant for a human to read comfortably. It is a structured seed document, intended to be pasted or loaded into an AI agent session before any GS_Play framework work begins.

Think of it as a mission briefing card. It is dense, compressed, and declarative. Every line exists to be parsed and acted on by a machine — not to be understood leisurely by a person.

Why it exists: Agents working without this document make predictable, repeatable errors. They guess EBus names incorrectly, place EnableForAssetEditor in the wrong reflection context, write polymorphic containers with the wrong pointer type, or generate C++ where a scripting answer was needed. The agentic guidelines page is the countermeasure for all of those failures.

What it is not: It is not a tutorial, an overview, or a feature summary. Those documents live elsewhere in the framework documentation. This page exists only to give an agent precise, actionable constraints and lookup data before it starts working.


Who Uses This Framework and Why It Matters

The framework has two distinct user groups. This distinction determines what kind of guidance an agent should give, and it is the single most important thing to keep in mind when editing this document.

Designer / Scripter

This user works entirely inside the O3DE editor. They configure components in the entity inspector, connect logic with Script Canvas or Lua, set up prefabs, and tweak exposed properties. They never write C++. For this user, an agent should provide guidance about:

  • Which components to add to an entity
  • How to configure component properties
  • How to wire EBus events through Script Canvas nodes
  • How to structure prefabs and entity hierarchies

Engineer / Developer

This user writes C++ to extend the framework. They inherit from GS_Play base classes, create new gems or project-level systems, write new components, and build on top of the existing architecture. For this user, an agent should provide guidance about:

  • Which base class to inherit from
  • How to implement EBus interfaces
  • How to register new types (dialogue effects, pulse types, etc.)
  • How to structure reflection, module registration, and CMake targets

Engineers have full freedom to combine gems. GS_Complete is the framework’s reference integration layer — it demonstrates cross-gem patterns but is not a restriction on what engineers can do in their own projects. An engineer may freely include headers from multiple gems in their project code.


How the Document Is Structured

The page uses three tiers of information. Understanding these tiers is the most important thing before contributing.

Tier 1 — Invariants

Hard rules with no exceptions. These live at the very top of the document in the INVARIANTS section. An agent that reads only the first 600 tokens of the document will encounter all of them.

Invariants are:

  • Specific, not general (“polymorphic containers use BaseT*”, not “use the right container type”)
  • Complete enough to act on without reading anything else
  • Framed as prohibitions or exact requirements — never suggestions

Do not add to the Invariants section unless a rule is a hard technical constraint that has no contextual exceptions and causes a silent failure or editor malfunction when violated.

Tier 2 — Ontology and Reference

This is the structural description of what the system is: the SYSTEM ONTOLOGY paragraph, the GEM INDEX table, the EBUS DISPATCH REFERENCE, and the HOT PATHS section. This tier answers the question: what is this system and how do I operate it?

This tier is:

  • Table-first. Prose is minimal.
  • Accurate. A single wrong EBus method name here means an agent fails silently.
  • Compressed. One row per concept, not one paragraph per concept.

Tier 3 — Conditional Depth

This tier does not provide information directly. It tells the agent when to stop and go deeper. The CONTEXT ANCHORS, CONFIDENCE THRESHOLDS, and CLARIFICATION TRIGGERS sections all belong here. They point the agent toward more information and define the exact conditions under which to seek it.


The Token Grammar

The document uses a small, fixed vocabulary of bracketed tokens. These act as machine-readable instruction labels. Agents across all backends pick them up reliably through pattern matching.

TokenMeaningLocation
[INVARIANT]Hard constraint. Never violate. No exceptions.INVARIANTS section
[ANCHOR]Stop and read the linked documentation before proceeding.CONTEXT ANCHORS, inline in HOT PATHS
[MEMORY]Persist this content if a memory system is available.MEMORY PROTOCOL section
[ASK]Do not infer. Pause and ask the user before proceeding.CLARIFICATION TRIGGERS, inline in HOT PATHS
[ANTIPATTERN]What not to do and briefly why.ANTIPATTERN CATALOG section

Use only these tokens. Do not invent new ones. Tokens work because they are stable — an agent learns to treat [INVARIANT] as a certain category of instruction. A new, undiscussed token may be ignored or misinterpreted.

If you genuinely believe a new token category is needed, document the reasoning and discuss it before adding it. The bar is high: the existing five tokens cover almost every instruction type needed.


Editing the Agentic Guidelines: Step-by-Step Workflow

Step 1 — Identify what changed

Before opening the file, answer these questions:

  • Was there a codebase change (new EBus, new component, renamed method)?
  • Was there an error an agent actually made — something it got wrong that this document should have prevented?
  • Is there a new common task that agents are regularly asked to do with no hot path covering it?
  • Is there a new documentation page that an agent should be directed to?

If none of these are true, you probably do not need to edit the parent document. Editing it without a concrete reason adds noise.

Step 2 — Locate the right section

Use this map to find where your change belongs:

Type of changeTarget section
A rule that must never be brokenINVARIANTS
A new gem or renamed managerGEM INDEX
A new or renamed EBus or methodEBUS DISPATCH REFERENCE
A new common code taskHOT PATHS
A mistake agents keep makingANTIPATTERN CATALOG
A decision that needs user inputCLARIFICATION TRIGGERS
A documentation page that gives deep contextCONTEXT ANCHORS
Something agents can safely assumeCONFIDENCE THRESHOLDS
A memory entry agents should persistMEMORY PROTOCOL

If your change doesn’t fit any of these cleanly, it is probably better expressed in the framework’s technical documentation pages rather than here.

Step 3 — Apply the minimum change

Do not add surrounding context, explanations, or improvements to adjacent content. The agentic document is optimized for token density — adding prose that an agent does not need to act on degrades its quality.

Apply the minimum correct change:

  • One new row in a table
  • One new [INVARIANT] entry
  • One revised method name
  • One new numbered step in a hot path

Resist the impulse to expand.

Step 4 — Verify accuracy

Before saving, verify every name, method, and path in your change against the source:

  • EBus method names — Open the source header for that bus. Copy the method name exactly. Do not paraphrase.
  • Doc paths — Confirm the path actually exists. A broken anchor is worse than no anchor.
  • Base class names — Confirm against the gem’s public Include/ headers.
  • TypeIds — Do not add TypeId values unless you have copied them from GS_{Name}TypeIds.h.

Step 5 — Apply the self-test

Read only the section you changed and ask: does an agent reading only this section know what to do, or does it still need to infer?

If it still needs to infer, the entry is incomplete. If it specifies exact names, exact locations, exact conditions, and exact patterns — it is ready.

Step 6 — Update this contributing page if structure changed

If you added a new section to the parent document, added a new token type, changed the tier structure, or changed any guidance about how to contribute — update this page to match. The two documents must stay in sync. See the section below: When to Update This Page.


Section-by-Section Editing Reference

BOOTSTRAP PROTOCOL

Edit when: The loading order or session re-entry behavior of the document should change.

Rules:

  • Keep this section under 8 lines. It is the first thing an agent reads.
  • Instructions must be ordered. Numbered list only.
  • Do not add explanatory prose. Each line is an action, not a description.

INVARIANTS

Edit when: A new silent-failure rule has been discovered through a real agent error, or an existing invariant is no longer accurate.

Qualifying test for a new invariant — all must be true:

  1. It is a specific technical rule, not a general principle.
  2. Violating it causes a failure that is silent or extremely hard to diagnose.
  3. It applies in all contexts, with no exceptions.
  4. It cannot be reliably derived from general O3DE knowledge.

Format:

`[INVARIANT]` **Short title.** One or two sentences of exact, actionable constraint.
Optionally: a code block showing the correct pattern.

Do not add O3DE general best practices here. They do not belong in the invariants list — agents already know them.


MEMORY PROTOCOL

Edit when: A new section is added to the parent document that should be persisted to memory, or a memory key is renamed.

Rules:

  • Memory keys are lowercase with underscores: gs_play_invariants, not GS Play Invariants.
  • Only add memory entries for sections that are dense enough to be worth re-loading from memory rather than re-reading.
  • The freshness rule (source code wins over stored memory) must never be removed.

SYSTEM ONTOLOGY

Edit when: The engine version changes, the build toolchain changes, or the platform target changes.

Rules:

  • The attribute table is for hard facts only: version numbers, language, build system, platform.
  • The “Core architectural rules” bullets must remain at three or fewer. If you need to add a fourth, one of the existing three should be absorbed into a more general statement.
  • Do not add GS_Complete or internal gem restrictions as architectural rules. These are framework-internal development constraints, not user-facing rules.

GEM INDEX

Edit when: A new gem is added, a gem is renamed, a manager component is added or renamed, a primary bus is renamed, or a documentation path changes.

Rules:

  • One row per gem. Do not split a gem across two rows.
  • The dependency graph must be updated any time the gem dependency structure changes.
  • Doc paths must be real paths. Verify before adding.
  • GS_Complete remains in the index as the integration layer reference — it documents a real gem. Its description should clarify it is Genome Studios’ reference layer, not a restriction on user code.

EBUS DISPATCH REFERENCE

Edit when: A new EBus is added to any gem, a method is renamed, or the dispatch policy of a bus changes.

Rules:

  • Method names are copied from source headers. Not paraphrased, not summarized — exact.
  • Include only the most commonly-called methods. This is a quick-lookup reference, not a full API.
  • If a bus is entity-addressed, the Dispatch column must say ById. If it is a singleton broadcast, the column says Broadcast. If it is a listener-only bus, say Listen.
  • Adding a new gem requires a new ### {GemName} buses subsection.

HOT PATHS

Edit when: A new common task emerges that agents are regularly asked to perform, or an existing hot path’s steps become inaccurate.

Qualifying test for a new hot path — all must be true:

  1. Agents are asked to do this task regularly.
  2. There is a correct pattern that is non-obvious or differs from standard O3DE conventions.
  3. There is a genuine way to get it wrong that the hot path would prevent.

Format:

### HOT PATH N — Short task description

1. First discrete action.
2. Second discrete action.
3. Third discrete action.

`[ANCHOR]` Condition requiring deeper reading → /docs/path/to/page/

Rules:

  • Steps are single, discrete actions. Not compound sentences.
  • No prose between steps.
  • Add an [ANCHOR] at the end if there is a documentation page that provides necessary deeper detail.
  • Hot paths are for engineer (C++ extension) tasks. Designer tasks belong in component or scripting documentation, not here.

ANTIPATTERN CATALOG

Edit when: A real agent error is identified that this document should prevent, or an existing antipattern is no longer relevant.

Qualifying test — all must be true:

  1. An agent actually made this mistake, or will predictably make it without the warning.
  2. The failure mode is silent or ambiguous — not a compile error that would surface immediately.
  3. The correct alternative is already documented (in INVARIANTS or HOT PATHS).

Format:

`[ANTIPATTERN]` **Short title.** One sentence describing the failure. One sentence pointing to the correct behavior or its location in this document.

Do not add antipatterns that are just general bad practices. Every entry here should represent a failure mode specific to GS_Play or to O3DE patterns that agents commonly get wrong.


CONFIDENCE THRESHOLDS

Edit when: A GS_Play-specific assumption has been confirmed safe (add to “proceed” list) or a pattern that agents incorrectly assume they know turns out to need verification (add to “stop” list).

Rules:

  • The “proceed” list contains patterns where O3DE general knowledge is accurate and sufficient.
  • The “stop” list contains patterns where GS_Play diverges from general O3DE knowledge, or where API details are too precise to guess.
  • Do not add items to the “proceed” list unless they have been verified accurate across the framework.

CONTEXT ANCHORS

Edit when: A new documentation page is added that meaningfully fills a knowledge gap an agent would otherwise have to guess around, or an anchor’s target path changes.

Qualifying test for a new anchor:

  1. There is a specific documentation page at a specific path.
  2. An agent working without that page would make structural errors on this task.
  3. The condition is narrow enough to fire at the right moment (“before writing unit movement code”) — not so broad that it triggers on general O3DE work.

Format:

`[ANCHOR]` **Before doing [specific task]:**
→ Read /docs/path/to/page/ for [what the agent needs from it — be specific].
→ `[ASK]` Optional inline clarification prompt if a user decision is required here.

Note on GS_Complete anchors: GS_Complete is a valid reference target. An anchor pointing to it as a reference (“review GS_Complete for examples”) is appropriate. An anchor that frames it as a restriction (“you must implement this there”) is not — that is a framework-internal structural convention, not a rule that applies to user project code.


CLARIFICATION TRIGGERS

Edit when: A new category of user decision is identified where agents predictably infer the wrong answer, or an existing trigger is too broad to fire usefully.

Qualifying test:

  1. The agent cannot determine the correct answer from documentation alone — it requires user intent.
  2. Agents predictably get this wrong when left to infer.
  3. Getting it wrong has real consequences (wrong code generated, wrong scope, irreversible decisions).

The USER CONTEXT trigger at the top of this section must never be removed. It is the most important clarification in the document — it gates all subsequent behavior between designer/scripter guidance and engineer guidance.

The two documentation sections referenced in USER CONTEXT are:

  • The Basics (/docs/the_basics/) — Scripter and designer documentation. Covers component usage, property configuration, and scripting for all gems.
  • Framework API (/docs/framework/) — Engineer documentation. Covers C++ interfaces, EBus signatures, base class architecture, and reflection patterns for all gems.

Both sections cover the same featureset across the same gem set: core, cinematics, environment, interaction, performer, phantomcam, ui, unit, ai, item, rpstats. Update the USER CONTEXT doc links if any gem is added, renamed, or its documentation path changes in either section.


What Makes Bad Agentic Content

Understanding what to avoid is as important as understanding what to write.

Prose explanations. The parent document must never read like a tutorial. “The EBus system in O3DE works by…” is wasted tokens. An agent already knows what EBus is. It needs the specific name and specific dispatch syntax.

Vague headings. “Common Patterns”, “Unique Details” — these tell neither a human nor a machine what type of instruction to expect. Every section heading in the parent document is also a category of instruction.

Soft language. “You should consider”, “it is generally recommended” — agents do not weight soft language correctly. If something must be done a specific way, say so directly. Preferences belong in other documentation pages, not here.

Information without a trigger condition. Raw information with no instruction about when to use it adds cognitive load without adding value. Every fact in the parent document is attached to a pattern, a constraint, a lookup trigger, or a warning.

Framework-internal conventions framed as user-facing rules. Conventions about how the framework’s own gem structure is organized (like where cross-gem components live within the framework itself) should not appear as [INVARIANT] or [ANTIPATTERN] entries. Engineers extending the framework in their own projects have full freedom to organize code as their project requires.

Outdated EBus names or method signatures. This is the most damaging type of error in the document. An agent trusting a wrong bus name fails silently, and the diagnosis will be non-obvious. Before adding or updating anything in the EBus section, verify against the current source header.


Maintaining Accuracy

When any of the following changes in the codebase, update the corresponding section:

Codebase changeSection to update
New EBus addedEBUS DISPATCH REFERENCE — add table row
EBus method renamedEBUS DISPATCH REFERENCE — update row; scan HOT PATHS for any direct references
New gem addedGEM INDEX — new row; update dependency graph
New Manager Component added or renamedGEM INDEX — update Manager column and Primary Bus column
New documentation page publishedCONTEXT ANCHORS — add anchor if it fills a knowledge gap
Documentation page moved or renamedCONTEXT ANCHORS — update the path; if it is a gem root page in The Basics or Framework, also check CLARIFICATION TRIGGERS USER CONTEXT links
New gem added to the frameworkGEM INDEX, EBUS DISPATCH REFERENCE, CLARIFICATION TRIGGERS USER CONTEXT gem list (both /docs/the_basics/ and /docs/framework/ paths)
O3DE API change affecting Reflect() patternsINVARIANTS and HOT PATH 1, 5 — verify code blocks still compile
New common agent task identifiedHOT PATHS — add new numbered path
New silent-failure pattern discoveredANTIPATTERN CATALOG — add entry
GS_Play base class API changesCONFIDENCE THRESHOLDS — verify the “stop” list is still accurate

When to Update This Contributing Page

This contributing page should stay in sync with the parent document’s structure. Update it when:

  • A new section is added to the parent document — add a corresponding entry to the Section-by-Section Editing Reference above.
  • A token is added to the grammar — add it to the Token Grammar table.
  • The tier structure changes — update the “How the Document Is Structured” section.
  • The distinction between user contexts changes (new user type, renamed category) — update “Who Uses This Framework”.
  • Any “rules” in the section-specific guidance become outdated.

This page is allowed to have prose. It is written for humans. Explanations, reasoning, and examples are all appropriate here. The parent document is the one that must remain dense and machine-optimized.


Testing Your Contribution

Before saving any change to the parent document, apply this test:

Read only the section you changed. Ask: does an agent reading only this section know exactly what to do, or does it still need to infer?

If it still needs to infer, the entry is incomplete. It is ready when it specifies:

  • Exact names (method names, class names, bus names)
  • Exact locations (file paths, doc paths, section names)
  • Exact conditions (the trigger that fires this instruction)
  • Exact patterns (the correct code form or step sequence)

A strong contribution eliminates a whole class of agent errors. A weak contribution adds words without eliminating uncertainty. When in doubt, add nothing and write the knowledge into the relevant framework documentation page instead. Let a [ANCHOR] point to it.

5.2 - Recipes

Step-by-step recipes for composing specific configurations of GS_Play systems, organized by gem.

Recipes are step-by-step setups for producing specific configured outcomes. Each gem has its own categorical section; within a section, recipe collections group related setups around a single subject.

Read the matching basics page first for the concept. Then come here for the composition steps.


Recipes by Gem

  • PhantomCam — virtual camera composition, channels, noise, tug fields, group targets, influence fields.

Additional gem sections will land here as their recipe libraries are authored.

5.2.1 - PhantomCam Recipes

Step-by-step recipes for the GS_PhantomCam virtual camera system — composing cams from stages, configuring noise, tug fields, group targets, influence fields, and the channel tier system.

Recipes for composing specific configurations of the GS_PhantomCam virtual camera system. Read the matching basics page first for the concept; then come here for the composition steps.

For the underlying gem reference, see GS_PhantomCam API and The Basics: PhantomCam.


Recipe Collections

5.2.1.1 - PhantomCam Configurations

Step-by-step recipes for composing common camera behaviors from Body, Aim, and Additive stages on a single Phantom Camera component.

A Phantom Camera’s behavior is the composition of one Body stage, one Aim stage, and zero-or-more Additive stages. The same GS_PhantomCameraComponent becomes a follow cam, an orbital cam, a tracking dolly, or a third-person shoulder cam depending on what is slotted from the editor’s type-picker.

Each recipe below names a Body pick, an Aim pick, and any Additive picks, plus the key field values that produce the target behavior. For the concept, the per-tick pipeline, and per-stage field references, see The Basics: Phantom Cameras and the Stage Pipeline API.

A fully-configured Phantom Camera with body, aim, and additive stages slotted

 

Contents


Recipe: Third-Person Shoulder Cam

A camera that follows the player at standoff distance, holds position when the player is moving inside an authored band, and slides smoothly around to re-frame when the player travels in a new direction. The canonical leading-look feel.

SlotPickKey fields
BodyLeadingFollowBodyOffset = (0, 0, 1.6) (cam Z height), InnerRadius = 2.0, OuterRadius = 5.0, BlendShape = Cylindrical
AimDefaultLookAtAimOffset = (0, 0, 1.5) (look point at chest height), Halflife = 0.1
Additives (Reposition)CollisionRepositionInnerRadius = 0.15, OuterRadius = 0.45, Halflife = 0.15, configure Collides With to match the player’s collision layer

Why this combination:

  • LeadingFollowBody holds position inside its band — abrupt 180° turns by the player don’t swing the cam.
  • DefaultLookAtAim smoothly tracks the player’s chest.
  • CollisionReposition sphere-casts from the player to the cam and pushes the cam inside walls or geometry, gliding back out via the soft buffer when the player walks away.

Optional: Add PerlinNoise with a Normal_Mild.camnoiseprofile profile for a touch of handheld feel. See Camera Noise Configurations.


Recipe: Orbital Player Cam

A camera that orbits around the player driven by player input (right-stick or mouse). The current go-to for third-person action games.

SlotPickKey fields
BodyDynamicOrbitBodyAssign an .camorbit asset to OrbitShape, BlendHalflife = 0.10, InitialYawDeg = 180 (start behind player), InitialPitchDeg = 0
AimDefaultLookAtAimHalflife = 0.1
Additives (Reposition)CollisionRepositionAs above

Required companion: Add a GS_CameraInputReaderComponent to the same entity. It implements the OrbitInputProvider interface that DynamicOrbitBody polls each tick. Configure sensitivity (raw input scale) and halflife (input damping) on the reader.

Why this combination:

  • DynamicOrbitBody holds target yaw and pitch angles internally; input integration nudges them each tick, and the orbital solver damps the cam’s position toward whatever the angles imply on the orbit surface.
  • The .camorbit shape asset defines the surface (sphere, oblate spheroid, rounded box) — different shapes give different “feel” for the orbit motion.

Recipe: Static Orbit Showcase Cam

A camera locked to a fixed orbital pose around its target — no input, no movement. Useful for menu / inventory / character-detail screens. Replacement for the retired StaticOrbit_PhantomCamComponent.

SlotPickKey fields
BodyOrbitBodyOrbitRadius = 5.0, OrbitYawDeg = 45, OrbitPitchDeg = 20
AimDefaultLookAtAimHalflife = 0.1

No additives needed for the basic case.

Static via DynamicOrbitBody. You can also get a static orbit using DynamicOrbitBody by simply not adding a GS_CameraInputReaderComponent — without an input provider, target angles never change and the solver damps once, then holds. Use the dedicated OrbitBody when you want the explicit “this is a fixed shot” identity (and the inheritance Get-only behavior that comes with it).


Recipe: First-Person Clamped Look

A first-person camera whose look-at rotation is clamped within authored pitch and yaw bounds. Replacement for the retired ClampedLook_PhantomCamComponent.

SlotPickKey fields
BodyDefaultFollowBodyOffset = (0, 0, 1.7) (eye height), OffsetIsRelative = true, Halflife = 0.05
AimClampedLookAimMinRelClamp = (-45, 0, -90), MaxRelClamp = (45, 0, 90), LocalSpace = true, Halflife = 0.05

ClampedLookAim captures a starting forward on first evaluation, and the envelope clamps relative to that origin. Setting LocalSpace = true keeps the clamp axes aligned with the cam’s starting orientation rather than world axes.


Recipe: Tracking Dolly

A camera that slides along a spline path while tracking a target. The “dolly track” cinematic shot. Replacement for the retired Track_PhantomCamComponent.

Authoring steps:

  1. Add a SplineComponent to a separate entity. Author the spline shape in the editor (linear, bezier, etc.).
  2. On your Phantom Camera entity:
SlotPickKey fields
BodyTrackBodyAssign the spline entity to SplineTrack. Author StartData (offset, halflife, FoV) and EndData (offset, halflife, FoV).
AimDefaultLookAtAimHalflife configured to match the dolly feel.

TrackBody projects the target onto the spline at each tick and interpolates between StartData and EndData by spline parameter. If EndData.FieldOfView > 0, the cam’s FoV animates along the dolly too — useful for “zoom in as the cam reaches the climax point.”


Recipe: Group-Framing Cam

A camera that frames multiple subjects (e.g. all party members, all combatants in an encounter) using a Group Target.

Authoring steps:

  1. Create a Group Target entity. Add GroupTargetComponent, give it a name (e.g. "PartyCentroid"), set its centroid mode (WeightedMean is the common choice), and add subjects with their weights. See Group Target Configurations for full subject-management patterns.
  2. On your Phantom Camera entity:
SlotPickKey fields
BodyLeadingFollowBody or DynamicOrbitBodySet TargetMode = GroupTarget, set GroupTargetName = "PartyCentroid".
AimDefaultLookAtAimSame target-mode pattern — set TargetMode = GroupTarget, GroupTargetName = "PartyCentroid".

The group target’s transform IS the weighted centroid of its subjects, so the cam tracks “where the group is” rather than any single member.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

5.2.1.2 - Camera Noise Configurations

Step-by-step recipes for camera shake — continuous handheld feel via PerlinNoise, event-triggered bursts via ImpulseNoise, and stacking the two.

Camera shake comes in two flavors, both as Noise additives on a Phantom Camera’s stage list: PerlinNoise for continuous handheld sway and ImpulseNoise for event-triggered ADSR-gated bursts. Both consume the same .camnoiseprofile asset and the same six-axis Perlin layer model; they stack freely.

For the concept, the preset library, and how to trigger impulses from gameplay code, see The Basics: Noise & Impulse. For the underlying asset format, see Noise Profiles (Framework API).

 

Contents


Recipe: Handheld Baseline

Continuous low-amplitude sway that makes the camera feel “hand-held” rather than locked to a tripod.

SlotPickKey fields
Additive (Noise phase)PerlinNoiseAssign Normal_Mild.camnoiseprofile (shipped in gs_phantomcam/Assets/Noise Profiles/). AmplitudeGain = 1.0, FrequencyGain = 1.0.

That’s all — PerlinNoise is always sampling, so the moment the stage is added, the cam picks up handheld feel. Swap to Telephoto_Mild for tighter angular response on a zoomed lens, or Wide_Mild for translation-dominant feel on a wide lens.

To dial intensity per cam without re-authoring the profile, adjust AmplitudeGain:

AmplitudeGainResult
0.3Very subtle — barely-noticed sway.
1.0Profile’s authored intensity (the default).
2.0Doubles the amplitude — aggressive handheld.

FrequencyGain similarly speeds up or slows down the noise without changing the amplitude.


Recipe: Event-Triggered Impact

A burst of shake that fires when gameplay code calls TriggerCameraImpulse(strength) on the cam.

SlotPickKey fields
Additive (Noise phase)ImpulseNoiseAssign 6D_Shake.camnoiseprofile (shipped) for high-frequency impact micro-jitter across all six axes. Configure the ADSR envelope: typically Attack = 0.02, Decay = 0.1, Sustain Level = 0.7, Sustain Duration = 0.1, Release = 0.3.

The ImpulseNoise stage stays silent until triggered. See The Basics: Triggering an Impulse from Gameplay Code for the call patterns (distance-attenuated source, active-cam lookup, etc.).


Recipe: Stacked Handheld + Impact

The most common configuration for action games: handheld baseline plus event-triggered impact bursts.

SlotPickProfile
Additive (Noise phase)PerlinNoiseNormal_Mild.camnoiseprofile (continuous handheld baseline).
Additive (Noise phase)ImpulseNoise6D_Shake.camnoiseprofile (event-triggered impact).

Both run in the Noise phase. The PhantomCam component dispatches TriggerCameraImpulse to every ImpulseNoise additive on the cam, so if you stack multiple impulse stages with different profiles (light kick + heavy explosion), one trigger fires all of them — pass the right strength to attenuate.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

5.2.1.3 - Camera Tug Configurations

Step-by-step recipes for tug-field cinematic pulls — vista pull, doorway glance, and decoupled source / destination patterns.

Tug Fields pull the camera’s pose toward a designated point while a proxy entity is inside a defined PhysX trigger volume. Each recipe below assumes the PhysX layer setup (TugProxy ↔ TugField collision-group pair) is already configured for your project.

For the concept, the three-component model, and the PhysX layer setup walkthrough, see The Basics: Tug Fields. For the per-tick algorithm and per-field reference, see Tug Fields (Framework API).

Before starting any recipe: Confirm the PhysX layer setup. If TugProxy and TugField aren’t paired (and only paired) in your collision-group preset, nothing will fire — that is the single biggest authoring failure for this system.

 

Contents


Recipe: Vista Pull

Pull the cam’s gaze toward a vista point when the player approaches a railing.

Entities

EntityComponents
Player root (or a child entity)TugFieldProxyComponent + PhysX trigger collider (small sphere or capsule) on the TugProxy layer.
“Vista trigger” (level entity, placed at the railing)CameraTugVolumeComponent + PhysX trigger collider (a box covering the railing approach) on the TugField layer.
(Source on the same entity as the volume — default)CameraTugSourceComponent configured with the vista point as the destination.

Configuration

On the volume’s CameraTugSourceComponent:

FieldSet to
Tug Point OffsetLocal offset to the proximity-reference point (default (0,0,0) works if the volume is centered on the railing).
Destination Entity(leave empty — destination = source for the simple case)
Inner Radius1.0 m — within this radius, full pull.
Outer Radius4.0 m — beyond this, no pull.
Inner Weight1.0
Outer Weight0.0
Falloff CurveEaseInOutQuadratic

On the cam:

  • Add a TugAimListener to the m_additives list.
  • Channels → leave empty (matches any).
  • Blend Halflife → 0.4 (slow engagement / disengagement).
  • Strength → 1.0.

The cam’s gaze now drifts toward the vista as the player approaches the railing, full at deadzone, none outside the outer radius.


Recipe: Doorway Glance

The cam glances through a doorway as the player walks past, then returns. Body stays put; only aim engages.

Same shape as the vista pull, but:

  • The volume’s trigger collider is a flat slab across the doorway approach.
  • The cam uses only TugAimListener (not body). The cam continues to follow the player normally; aim drifts toward what’s beyond the doorway.
  • Configure a short halflife (0.2) on the listener so the glance is responsive — engage as the player enters, release as the player exits.

Recipe: Decoupled Source and Destination

You want the volume to fire at one location but the cam to be pulled toward a different location. Common case: the player crosses a threshold, but the cam should look 10 m past it.

Setup

  1. Place the volume entity with CameraTugVolumeComponent at the threshold.
  2. Place a separate “destination entity” with TransformComponent only (no CameraTugSourceComponent!) at the vista point.
  3. Place a separate source entity with CameraTugSourceComponent somewhere convenient (often on the volume entity itself, or as a child of it). Configure:
    • Destination Entity → point at the destination entity from step 2.
    • Destination Offset → local offset from that entity (default (0,0,0) works).
  4. On the volume’s m_sourceEntity field, point at the source from step 3.

Now proximity is measured around the threshold (where the volume is), but the cam is pulled toward the vista point (the destination entity).

If you leave Destination Entity unset on the source component, destination falls back to source. The decoupled setup is only needed when they should differ.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

5.2.1.4 - Group Target Configurations

Step-by-step recipes for GroupTargetComponent — two-player party cam, combat encounter framing, and collapse-to-single-view triggers.

A Group Target is an entity whose world transform is the weighted centroid of a runtime-editable subject list. You point a camera at it like any other target, and the cam frames “where the group is” rather than chasing any single subject.

For the concept, centroid modes, subject-management API, and pitfalls, see The Basics: Group Targets. For the full per-field reference and the registry bus, see Group Targets (Framework API).

 

Contents


Recipe: Two-Player Party Cam

A single cam that frames both players, sliding smoothly as they spread apart and converge.

Steps

  1. Create a Group Target entity in your level (a plain entity with TransformComponent is fine).

  2. Add GroupTargetComponent to it. Configure:

    • Name → "Party" (or any string — this is your registry key).
    • Centroid Mode → WeightedMean.
    • Smoothing Halflife → 0.2 (gentle smoothing so the centroid doesn’t jitter when players move).
  3. Add Subjects at level start (or whenever players spawn):

    GS_PhantomCam::GroupTargetRequestBus::Event(
        groupEntityId,
        &GS_PhantomCam::GroupTargetRequests::AddSubject,
        player1EntityId, 1.0f);    // equal weight
    
    GS_PhantomCam::GroupTargetRequestBus::Event(
        groupEntityId,
        &GS_PhantomCam::GroupTargetRequests::AddSubject,
        player2EntityId, 1.0f);
    
  4. On the Phantom Camera: Set the Body and Aim stages’ Target Mode to GroupTarget and Group Target Name to "Party". The stages will resolve the group entity through the Cam Manager registry at runtime.

The cam now points at the weighted midpoint of both players. As they move, the centroid follows.

Tip — pull the cam back as the spread grows. Pair this group cam with a DynamicOrbitBody and adjust the orbit shape’s radius at runtime based on subject spread. The group’s centroid is the pivot; the cam stays a healthy distance back so both subjects stay in frame. See the Group-Framing Cam recipe for the stage composition side.


Recipe: Combat Encounter Frame

The cam frames an evolving encounter. Subjects are added when combatants engage and removed when they disengage or die.

Steps

  1. Create a Group Target named "CurrentCombat" (or similar).

  2. On combat start, add the player and the engaged enemies:

    AddSubject(player, weight: 2.0);          // weight player higher
    for (each enemy in encounter):
        AddSubject(enemy, weight: 1.0);
    
  3. On enemy death or disengage, remove the subject:

    RemoveSubject(deadEnemy);
    
  4. On combat end, clear:

    ClearSubjects();
    
  5. Set the cam’s Body and Aim to GroupTarget mode with name "CurrentCombat".

Why weight the player higher? Pure equal-weighting means a single enemy at long range drags the cam half-way to them. Weighting the player higher keeps framing centered on the player while still acknowledging the enemy’s presence.

When m_deactivateWhenEmpty is true (default), the cam falls back to hold-last-pose if the encounter clears completely — handy for the moment between encounters.


Recipe: Collapse to Single View

For multi-channel projects: when all players converge, switch from split-screen to a single shared cam, then receive a UI signal at the moment of collapse.

Steps

  1. Create a Group Target tracking all players (same as the party cam recipe).

  2. Author a shared cam outside any rig prefab. Set:

    • Channel Scope → TrueUnique.
    • Show Advanced → on.
    • All Channels Share → on.
    • Body / Aim Target Mode → GroupTarget, name → "Party".
  3. Tune priority so the shared cam wins arbitration when players are close enough (e.g. use a CameraInfluenceField volume around the players’ convergence point).

  4. Listen for the collapse signal in your UI controller:

    void OnAllChannelsActivatedSharedCam(AZ::EntityId sharedCam)
    {
        // All channels selected the same shared cam — switch to single-view layout.
    }
    

When players converge such that the shared cam wins in every channel simultaneously, the Cam Manager fires OnAllChannelsActivatedSharedCam once on the edge. UI hides split-screen dividers and shows the single shared view.

See Channel Tier Configurations for the surrounding multi-channel setup.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

5.2.1.5 - Influence Field Configurations

Step-by-step recipes for camera priority shifts — spatial room boosts and stage-scoped always-on cinematic boosts.

Influence Fields modify the effective priority of Phantom Cameras without changing their base priority values. The spatial form fires when an entity enters a PhysX trigger volume; the global form applies for the component’s entire active lifetime.

For the concept, the channel-aware bus signature, and the priority-stacking model, see The Basics: Influence Fields. For component properties and the bus surface, see Camera Influence Fields (Framework API).

 

Contents


Recipe: Boost a Cam Inside a Room

The player should see a different camera when they walk into a vista room — same player priority, but a “VistaCam” that frames the view jumps to dominance inside the room.

Steps

  1. Place the VistaCam as a normal Phantom Camera entity in the room (or in your rig prefab). Give it a low base priority, e.g. 10. Author its Body and Aim stages to frame the vista.
  2. Author the trigger volume:
    • Create a new entity at the room’s entrance.
    • Add a PhysX Collider (set to trigger mode), shaped to cover the room.
    • Configure the collider’s Collides With to match the player’s collision layer.
    • Add CameraInfluenceFieldComponent to the same entity.
  3. Configure the influence:
    • Camera Name → VistaCam (the target Phantom Camera’s entity name).
    • Influence → 100 (enough to overcome the player’s normal cam priority).
  4. Place the entity in the level.

When the player walks in, the trigger fires AddCameraInfluence(volumeEntity, playerEntity, "VistaCam", 100). The Cam Manager routes the influence to the player’s channel; VistaCam’s effective priority jumps to 110 and wins arbitration. On exit, the influence is removed and the player’s normal cam wins again.


Recipe: Always-On Cinematic Boost

A specific stage should always favor a cinematic cam — say, the title screen’s slow-orbit cam should always be dominant when the title-screen stage is loaded.

Steps

  1. Author the cinematic cam with a moderate base priority (e.g. 20).
  2. On the StageData entity for the title-screen stage:
    • Add GlobalCameraInfluenceComponent.
    • Set Camera Name to the cinematic cam’s entity name.
    • Set the influence value (e.g. 200) high enough to dominate any other cams in the stage.
  3. Save the stage.

When the stage activates, the global influence registers; the cinematic cam wins. When the stage unloads, the influence is removed.

This is cleaner than authoring the cinematic cam at priority 220 directly because the priority boost is scoped to the stage — the same cinematic cam at base 20 won’t accidentally win arbitration if it’s referenced elsewhere.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

Related utilities:

5.2.1.6 - Channel Tier Configurations

Step-by-step walkthroughs for the PhantomCam channel system — Tier 1 single cam, Tier 2 level-placed, Tier 3 multi-channel co-op, common Tier 3 patterns, and the lobby flow for variable player counts.

GS_PhantomCam can be configured at three different tiers depending on what your project needs. Most projects use Tier 1 (single-cam) or Tier 2 (single-player rig). Co-op, split-screen, and per-player-cinematic projects use Tier 3 (multi-channel). The per-cam authoring surface is identical across tiers — what changes is the Cam Manager configuration and how many rigs the system spawns.

For the concept of channels, the choose-a-tier table, and pitfalls, see The Basics: Channels & Instancing. For the full bus surface and internal state, see Channels & Instancing (Framework API).

Multi-view rendering is pending. Until the AttachmentImage work lands, the engine renders one channel’s view at a time even when multiple channels are arbitrating internally. The Cam Manager’s active main-view API selects which channel reaches the framebuffer. Arbitration / dispatch / target binding all work today — only the simultaneous-rendering piece is gated.

Authored rig prefab in the O3DE Editor

 

Contents


Recipe: Tier 1 — Single Cam

The Cam Manager spawns one rig prefab on startup, and every Phantom Camera inside it registers to channel 0.

Steps

  1. Author a rig prefab. Create a prefab containing:

    • A root entity (anything — it can be empty, or a logical “Camera Rig” entity).
    • A child entity carrying GS_CamCoreComponent plus the engine’s standard CameraComponent. This is the main camera.
    • Zero or more child entities carrying GS_PhantomCameraComponent with their authored Body / Aim stages.
  2. On the Cam Manager component:

    • Set Primary Rig Prefab to your rig prefab.
    • Leave Enable Instanced Channels OFF (default).
  3. Bind a target when the player spawns:

    CamManagerRequestBus::Broadcast(SetTarget, playerEntity);
    
  4. The Cam Manager spawns the rig at startup. The rig’s Cam Core and Phantom Cameras self-register to channel 0. The SettingNewCam legacy notification fires when the dominant cam changes.

That’s it. No further per-cam setup required.


Recipe: Tier 2 — Single-Player Rig (Legacy)

Same single-player behavior, but the Cam Core lives in the level rather than in a spawned prefab. Use this when you have an existing project that hand-places the Cam Core and you don’t want to refactor.

Steps

  1. Hand-place the Cam Core entity in your level (as a child of the Cam Manager prefab, or any persistent entity).
  2. On the Cam Manager:
    • Leave Primary Rig Prefab empty.
    • Leave Enable Instanced Channels OFF.
  3. Bind a target as in Tier 1.

The level-placed Cam Core and Phantom Cameras self-register to channel 0 via the legacy bus path (no ChannelStampComponent ancestor, so the stamp walk misses and the legacy registration falls through).


Recipe: Tier 3 — Multi-Channel Co-op

The Cam Manager spawns one rig per active channel. Each channel arbitrates independently. Per-player target binding routes through SetChannelTarget.

Steps

  1. Author your rig prefab as in Tier 1. The same prefab can be reused across all channels (symmetric setup) or you can author multiple prefabs for asymmetric setups (e.g. P1 third-person, P2 top-down).

  2. On the Cam Manager:

    • Toggle Enable Instanced Channels ON. The channel-aware fields appear in the inspector.

    • Set Primary Rig Prefab to your default rig (used as universal fallback — channels with no per-channel override inherit this).

    • Populate Channel Configs with one entry per supported player slot:

      FieldSet to
      Channel Name"P1", "P2", etc. (display label only — not a lookup key).
      Channel PolicyPerChannelInstance (default — the rig spawns once per channel).
      Rig Override PrefabLeave empty for symmetric (every channel uses the primary). Set to a per-channel prefab for asymmetric.
      Enabled By Defaulttrue for slots that should spawn on startup.
    • Optionally set Active Channel Count to cap how many channels actually spawn at startup (the lobby may override this — see Lobby Flow).

  3. Per-player target binding — when each player spawns:

    CamManagerRequestBus::Broadcast(SetChannelTarget, channelId, playerEntity);
    

    The Cam Manager pushes the target to every cam in that channel and triggers a synchronous snap so the cam jumps to its evaluated pose before the Cam Core latches.

  4. The Cam Manager spawns each enabled channel’s rig at startup. Each rig’s Cam Core registers to its channel; each Phantom Camera walks its ancestors to find the auto-attached ChannelStampComponent and registers to the same channel.


Common Tier 3 Patterns

Per-player cam (in-rig)

Drop a Phantom Camera inside the rig prefab with m_channelScope = Local (default). When the rig spawns for channel N, the cam’s auto-stamp-walk finds the rig’s ChannelStampComponent and registers to channel N. Each spawned rig instance carries its own copy — players don’t see each other’s cams.

Same logical cam name across channels

Keep entity names like "third_person_rig_cam" inside the rig prefab. The name is scoped within the channel — channel 0 and channel 1 each have their own "third_person_rig_cam" and they don’t collide. Authors never type _i1 / _i2 suffixes.

Shared cinematic cam — collapse-to-one-view

Author a Phantom Camera outside any rig prefab (in the level, or in a “shared cinematic” prefab that is not the channel rig). Set:

  • Channel Scope → TrueUnique.
  • Show Advanced → on.
  • All Channels Share → on.

The cam appears in every active channel’s priority table. When players converge (e.g. a Group Target tracks all players and the shared cam targets the group), the shared cam may win arbitration in every channel simultaneously. At that edge, the Cam Manager broadcasts OnAllChannelsActivatedSharedCam(sharedCam) — UI can detect this and switch from split-screen layout to single-view. See the Collapse to Single View recipe for the group-target side.

Hero-perspective cam for a specific player

A cam that should only ever apply to one specific channel:

  • Channel Scope → TrueUnique.
  • Bound Channel Id → the channel number (e.g. 0 for P1).
  • Leave All Channels Share off.

The cam is registered directly to that channel via RegisterPhantomCamDirect — bypasses the stamp-walk path entirely.

Cross-channel cinematic dispatch

Force a specific cam to render on a specific Cam Core, regardless of arbitration:

CamManagerRequestBus::Broadcast(DispatchCamToCamCore, cinematicCamEntity, p1CamCore);
// ... cinematic plays ...
CamManagerRequestBus::Broadcast(ReleaseCamCoreDispatch, p1CamCore);

Channel arbitration still runs internally during dispatch — SettingNewCamOnChannel continues firing with the arbitrated winner. Only the Cam Core route is overridden, so HUD or AI behavior that listens for arbitration changes still sees the “natural” winner.


Lobby Flow

The typical lobby pattern for variable player counts:

  1. Author m_channelConfigs to hold N entries (the max supported player count for your project — e.g. 4).

  2. Pre-startup (before the Cam Manager’s OnStartupComplete fires), the lobby calls:

    CamManagerRequestBus::Broadcast(SetActiveChannelCount, actualPlayerCount);
    
  3. On startup, only actualPlayerCount channels spawn. The remaining configured channels stay dormant.

  4. Mid-session join / drop uses EnableChannel(channelId) / DisableChannel(channelId) to spawn or despawn channels as players come and go.

SetActiveChannelCount must run pre-startup. Mid-session calls warn and are ignored — use EnableChannel / DisableChannel instead.


See Also

Related recipe collections:

Basics this builds on:

Framework API:

5.3 - Video Tutorials

Index of Video based tutorials.

See our GS_Play Youtube Channel

Tutorial Sets

5.3.1 - Simple Project Setup

Easy set up to get started.

A guide to get everything propped up rapidly

Video Tutorial

Embed youtube guide.

Link to video_tutorials.

6 - Legal & Press

Licence terms, privacy policy, branding guidelines, and press resources for GS_Play.

The legal basics for using GS_Play — what you’re allowed to do with the framework, and what data the framework and this site touch — plus the branding rules and press material for writing about it.

Licence terms

Licence terms are split by tier. Read the set that matches the version of GS_Play you hold.

  • Free — the Licence Notice and EULA governing the free tier.
  • Premium — a separate Licence Notice and EULA for the premium tier. Not yet published; they will appear here alongside the free terms rather than replacing them.
  • Third-Party Notices — The third-party software and assets distributed inside GS_Play modules, and the attributions to carry into your own game.
  • Privacy — What GS_Play collects (nothing) and what this documentation site records.

Press

  • Branding Guidelines — How to use the GS_Play name, logo, and colours.
  • Press — Fact sheet, boilerplate, and what you may publish about GS_Play.
  • O3DE’s own licence — GS_Play is a third-party gem suite for O3DE; the engine itself is Apache 2.0 and governed separately.
  • O3DE branding guidelines — the engine’s own trademark and logo rules, which apply to your use of the O3DE name and marks.
  • Support — for licensing questions, bundling requests, media inquiries, or data concerns.

6.1 - Free Licence

The licence terms that govern the free tier of GS_Play.

The terms that govern the free tier of GS_Play — the gems, source, and documentation available at no charge.

6.1.1 - Free Licence Notice

The GS_Play free-tier Licence Notice - a plain-language summary of your licence.

Scope

This notice covers the free tier of GS_Play only. The premium tier is governed by its own separate Licence Notice and EULA, published under Legal & Press when available.

What this page will cover

When published, the Licence Notice will be a plain-language summary of the free-tier End User Licence Agreement — what you may do with the GS_Play gems and their source, what you may do with the games and projects you build using them, and the restrictions that apply. It will be a summary only. Where the summary and the EULA differ, the EULA controls.

What is not in question

Two points hold regardless of the final terms, and are worth stating now:

  • Your projects are yours. Genome Studios does not acquire ownership of your games, assets, code, builds, or configurations merely because you built them with GS_Play.
  • O3DE is governed separately. GS_Play is a third-party gem suite for the Open 3D Engine. The engine is licensed under Apache 2.0 by the Open 3D Foundation, and nothing in the GS_Play licence changes or restricts your rights to it. See o3de.org/license.

6.1.2 - Free End User Licence Agreement

The GS_Play free-tier End User Licence Agreement from Genome Studios Inc.

Scope

This agreement covers the free tier of GS_Play only. The premium tier will be governed by its own separate End User Licence Agreement, published under Legal & Press when available.

The parties

GS_Play is developed and licensed by Genome Studios Inc., a corporation incorporated under the laws of Alberta, Canada.

GS_Play is a suite of proprietary gems for the Open 3D Engine (O3DE). It is an independent third-party product. It is not an official O3DE product and is not affiliated with, sponsored by, or endorsed by the Open 3D Foundation, the Linux Foundation, or the O3DE project.

6.2 - Privacy

What GS_Play collects (nothing) and what this documentation site records.

Short version: the GS_Play gems have no telemetry and no analytics. They don’t collect, transmit, sell, or monetize anything about you, your machine, or your projects. This documentation website does keep basic, anonymous traffic statistics — self-hosted, cookieless, and detailed below.

The framework

GS_Play is a set of C++ gems that compile into your O3DE project. They run inside your editor and your game.

What the gems do not collect

  • Your name, email, IP address, or machine identifiers
  • Which projects you have, what they contain, or where they live
  • Which gems you enable, what you build, or how you configure it
  • Any content from your source, assets, prefabs, or project.json

There is no telemetry system, no usage analytics, no licence phone-home, and no crash-reporting service built into the gems. They make no network connections of their own.

Anything your shipped game transmits is code you wrote or a third-party service you integrated. GS_Play adds nothing to that surface.

Local data

Gems that persist state — save games, options, input profiles — write to the standard O3DE user paths on your own machine. That data stays on your device, and it belongs to you. GS_Play never uploads it.

This documentation site

gsplay.genomestudios.ca is a static Hugo site. It has no accounts, no logins, and no user profiles.

ConnectionPurposeWhat it sees
Umami analytics — metrics.genomestudios.caAnonymous page-view counts, so we know which docs pages are actually usedPage URL, referrer, screen size, country, browser. Self-hosted on Genome Studios infrastructure — no third-party ad network is involved
jQuery and Lunr — public CDNsSite scripting and the offline search indexYour IP address, as an unavoidable part of serving a file over HTTP
Contact form — FormspreeDelivers the message you choose to send usOnly what you type into the form, plus your email address so we can reply
Embedded video, Discord, and social linksCommunity and tutorial linksNothing until you click or play — these are ordinary links and standard embeds, governed by those services’ own policies

Umami is cookieless. It sets no cookies, does not fingerprint, does not track you across sites, and stores no personal data. We use it to count page views, not to identify readers. Nothing collected is sold, shared, or used for advertising.

Licence acceptance on the download pages. When you accept the free-tier licence to enable the downloads, three things happen. Your acceptance is remembered in your own browser’s local storage — on your device, never sent anywhere — so you aren’t asked again. A download or subscribe click records the module and version through the same Umami system described above. And the acceptance itself records a Umami event carrying the licence version, the module, and the time.

Because Umami is cookieless, none of that is linked to you: no IP address is stored, no account, no identifier that persists between visits. It tells us how many people accepted which version of the licence, not who they were. Clearing your browser storage clears the acceptance and you’ll be asked again.

The contact form is the one place you deliberately hand us personal information. We use what you send to answer you, and for nothing else.

Your rights

The gems collect nothing, so there’s nothing for us to delete, export, or correct on your behalf. For anything you sent through the contact form, ask and we’ll delete it.

If you believe a bug caused GS_Play to transmit something it shouldn’t, contact us — we’ll treat it as a defect and fix it.

Changes to this policy

Material changes will be noted in the Changelog.

6.3 - Third-Party Notices

The third-party software and assets distributed inside GS_Play modules, and the licences they are provided under.

GS_Play modules include third-party software and assets. This page lists what is distributed inside the gems, and under which terms.

It covers what ships in the modules. The engine itself is separate — O3DE is Apache 2.0 and governed by its own licence terms.


GS_Audio

Free tier.

ComponentRoleLicence
Steam AudioHRTF binaural spatialization and distance attenuation. The runtime library is vendored with the gem and loaded at runtime.Apache 2.0
SoLoudThe lineage of the gem’s DSP layer. The effect and source node implementations derive from SoLoud, and its generators feed the effect nodes.zlib/libpng
sfxrThe procedural sound-effect model behind .sfxrprofile assets. The parameter model follows the original DrPetter sfxr design.MIT

The DSP nodes in this gem were extracted into the framework’s own header-only layer so they can be wired directly into the audio graph. They remain derivative of SoLoud and are sourced from it, so the attribution belongs here in the free tier rather than only alongside the voice system that uses SoLoud more directly.

miniaudio reaches GS_Audio through O3DE’s MiniAudio gem rather than being vendored here.


GS_AudioPro

Premium tier.

ComponentRoleLicence
SoLoudThe speech synthesis engine behind Klatt voice.zlib/libpng
Steam AudioGeometric acoustics — scene and simulator, occlusion and reflections. Uses the context shared by GS_Audio.Apache 2.0

GS_Core

Free tier.

Fonts

Accessibility typefaces ship with the gem for use through the accessibility settings category.

AssetLicence
Atkinson HyperlegibleSIL Open Font License 1.1
OpenDyslexicSIL Open Font License 1.1

Both are redistributed under the OFL, which permits bundling and use in a shipped product. The full licence text accompanies the fonts inside the gem.


Your Obligations

Shipping a game built with GS_Play means redistributing these components, so their notices travel with your build. Practically:

  • Keep the licence files that ship inside the gems. They are placed alongside the assets and libraries they cover.
  • Reproduce the attributions in your game’s credits or legal screen. This page is a reasonable source to copy from.
  • The permissive licences here — Apache 2.0, MIT, zlib and the OFL — all allow commercial use and redistribution. None of them require you to open your own source.

If your project strips a subsystem it does not use, the corresponding notice can go with it.


6.4 - Branding Guidelines

How to use the GS_Play name, logo, and colours when you write about or reference GS_Play.

The short version

If you are reviewing, teaching, reporting on, or streaming GS_Play, you already have permission — go ahead. You do not need our approval, and you may monetize it. The rules below matter when you deliberately use our logo or wordmark in your own promotional material.

Using the GS_Play name

  • “GS_Play” is written with a capital G, capital S, an underscore, and a capital P. Not “GSPlay”, not “GS Play”, not “gs_play”, not “GS_PLAY”.
  • Refer to the product as GS_Play or the GS_Play framework. Its descriptive name is the O3DE Gameplay Framework.
  • Individual gems keep their own casing — GS_Core, GS_Unit, GS_PhantomCam, GS_Cinematics, and so on. Underscore, no space, internal capitals as documented.
  • Do not use GS_Play, or anything confusingly similar, as the name of your own product, service, gem, or company.
  • Do not incorporate GS_Play into your own logo, wordmark, domain name, or social handle in a way that suggests it is ours or that we back it.

Accurate, factual references are always fine — “built with GS_Play”, “GS_Play tutorial”, “a review of GS_Play”. Saying your game uses GS_Play is a statement of fact and needs nothing from us.

Approved logo forms

The GS_Play brand is supplied as a logo-and-wordmark lockup in four colourways. Use the one that suits your background:

ColourwayUse it on
Navy on transparentLight backgrounds — the default for print and light-mode layouts
White on transparentDark backgrounds — the default for dark-mode layouts and video
Black on transparentSingle-colour print, and anywhere navy would not reproduce
Gold on transparentAccent use on deep navy or black, where the mark is decorative rather than identifying

Use the supplied artwork as-is. Do not rebuild, retrace, approximate, or set the wordmark yourself in a substitute typeface.

What you may not do with brand assets

GS_Play brand assets may not be:

  • modified, distorted, stretched, rotated, or otherwise altered;
  • recoloured beyond the four supplied colourways, including tinting, gradients, or duotone treatments;
  • animated, or given effects such as shadows, glows, or bevels;
  • combined or locked up with another logo to form a new mark;
  • used as the primary source identifier for your own product, service, or gem, without our written approval.

You also may not remove or obscure copyright, trademark, or attribution notices, and you may not state or imply that Genome Studios sponsors, endorses, certifies, or is affiliated with you or your work unless we’ve agreed to it in writing.

Colour palette

These are the colours GS_Play uses today. Match these values when you need GS_Play’s colours in a layout.

ColourHexRole
Navy#184172Primary — buttons, links, headings
Sky#5097CCSecondary accent
Gold#F4D13DHighlight accent
Navy dark#051323Dark base surface
Navy darkest#030A12Deepest surface
Cream#F7F5EBLight base surface

The palette is for surrounding layout, not for restyling the mark. Use one of the four supplied logo colourways rather than recolouring the artwork into a palette value yourself.

Screenshots, video, and streams

You may capture, publish, and monetize screenshots and recordings of GS_Play — the O3DE Editor with GS_Play tools open, the gem components in the inspector, the Dialogue Editor and graph tools, and games running on the framework. No approval needed, and no special treatment required.

Two asks, both practical rather than legal: show a current version where you can, and if you’re demonstrating something that has since changed, say which version you captured. The current framework version is 0.7.0.

GS_Play and O3DE are different things

GS_Play is an independent, third-party gem suite for O3DE. It is not an official O3DE product, and it is not affiliated with, sponsored by, or endorsed by the Open 3D Foundation, the Linux Foundation, or the O3DE project. Please don’t describe it as official, bundled, or endorsed.

Open 3D Engine, O3DE, and related names and logos are trademarks of their respective owners, and nothing here grants any right to use them. If you need to use the O3DE name or marks alongside ours, follow the O3DE branding guidelines.

Requesting assets or approval

For the asset pack, a co-branding or partnership arrangement, or written approval for a use the rules above don’t cover — get in touch.

6.5 - Press

Fact sheet, boilerplate, and what you may publish about GS_Play.

Writing about GS_Play, recording a tutorial, or putting together a review? Everything you need is on this page. You do not need our permission, and you do not need to check in with us first.

In one line

GS_Play is a modular gameplay framework for the Open 3D Engine — cameras, cinematics, dialogue, UI, audio, character control, and more, each a self-contained gem you enable only if you need it.

Boilerplate

Quotable, and safe to reproduce verbatim:

GS_Play is a modular gameplay framework for the Open 3D Engine (O3DE), built as a suite of independently enableable gems covering cameras, cinematics, dialogue, UI, audio, character control, world interaction, and RPG systems. It is developed by Genome Studios Inc., a corporation incorporated in Alberta, Canada. GS_Play is not an official O3DE product and is not affiliated with, sponsored by, or endorsed by the Open 3D Foundation, the Linux Foundation, or the O3DE project.

Fact sheet

ProductGS_Play — the O3DE Gameplay Framework
DeveloperGenome Studios Inc. — Alberta, Canada
CategoryGameplay framework and gem suite for the Open 3D Engine (O3DE)
Version0.7.0
Engine supportO3DE 26.05+
Scope15+ gems — core infrastructure, character systems, cameras, cinematics, dialogue, UI, audio, world interaction, RPG stats, and inventory
ArchitectureSelf-contained O3DE gems; C++ with Script Canvas and EBus surfaces; every component designed for inheritance
SourceFull source included
LicenceProprietary. Not open source. See Legal
TelemetryNone — see Privacy
TaglineThe Framework That Gets Out of Your Way
Websitegsplay.genomestudios.ca
Documentationgsplay.genomestudios.ca/docs
CommunityDiscord · r/gs_play
VideoYouTube — @GS_Play_Framework
Media contactContact form — or support@genomestudios.ca

What you may publish

You may create, publish, distribute, and monetize media that truthfully depicts, demonstrates, reviews, teaches, or comments on GS_Play — screenshots, editor recordings, tutorials, reviews, livestreams, articles, and presentations. Games you build with GS_Play are yours to show and sell without restriction from us.

No advance approval is required for reviews, criticism, tutorials, journalism, educational material, streams, or screenshots. That includes unfavourable coverage.

The one place to check the rules is deliberate use of our logo or wordmark in your own promotional material — see the Branding Guidelines. Using the name GS_Play in prose, or showing the software as it looks, needs nothing from us beyond writing GS_Play correctly.

Assets

A downloadable press kit — logos, editor screenshots, and key art — is being assembled and will be published here.

In the meantime: the Features pages carry current product screenshots you’re welcome to use, and you can request specific assets. Tell us what you’re working on and what resolution you need.

Please don’t

  • Describe GS_Play as an official O3DE product, or as bundled with, endorsed by, or affiliated with the Open 3D Foundation, the Linux Foundation, or the O3DE project.
  • State pricing, tiers, or availability dates we haven’t published.
  • Redistribute the GS_Play gems yourself. Link to gsplay.genomestudios.ca instead.
  • Present a modified build, or screenshots of one, as GS_Play.