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
- Option Types
- OptionValue
- Apply Targets
- Presets
- Persistence and Launch
- API Reference
- Adding Your Own Setting Kind
- See Also
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:
| Field | Purpose |
|---|---|
| Key | The stable persistence key, e.g. audio.master. This is what code and presets refer to. |
| Display Name | The UI label. |
| Category | Groups the option in menus — Graphics, Audio, Display, Gameplay, Accessibility, Controls. |
| Description | Explanatory text for tooltips. |
| Apply | The apply target — what this option actually does when its value changes. |
Four concrete types ship:
| Type | Widget | Authored fields |
|---|---|---|
| BoolOption | Toggle | Default |
| SliderOption | Slider | Min, Max, Step (0 for continuous), Default |
| EnumOption | Dropdown | A list of entries, each with a Label and a Value |
| ColorOption | Colour picker | Default |
For an EnumOption, the label is not the value
Each entry carries a Label shown in the dropdown and a Value that is stored and handed to the apply target. They are separate strings, and the stored thing is always the value. This is what trips people up when writing presets — a preset entry must carry the value, not the label, and not the index.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.
In ScriptCanvas, print the accessor
AnOptionValue is userdata in ScriptCanvas. Printing the object itself is not useful — call AsString() or AsBool() and print that.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
| Target | What it does |
|---|---|
| DataFlagApply | Nothing. The value is stored and read by game code — for options your own systems consume. |
| ConsoleVarApply | Runs a console command with the value. Bools emit 1 / 0. Carries the variable name. |
| WindowModeApply | Sets the window mode. Carries the width and height used when returning to windowed. |
| WindowResolutionApply | Sets resolution from a WxH string, or native. |
| WindowFullscreenApply | Direct fullscreen toggle. |
| WindowVsyncApply | Sets 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
| Target | What it does |
|---|---|
| AudioMasterApply | Drives master volume. |
| AudioChannelApply | Drives 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.
| Path | Carries | When |
|---|---|---|
| Launch | Anything expressible as a console command | Before the window exists |
| Runtime | Live changes, and everything non-cvar — window mode, audio, data flags | On 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/Stringand 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.
Keep renderer dependencies out of the shared API
The options reflection chain compiles into GS_Core’s public API, which many gems depend on — including headless server builds. An Atom-dependent apply target placed there leaks the renderer into all of them.
That is why the gamma target lives in the gem’s private module layer, with its header outside the public include tree so it cannot leak, reflected from the gem’s system component. Apply-target reflection is idempotent, so reflection order does not matter.
Follow the same rule for your own targets: if a target needs a heavy dependency, keep it in your gem’s private layer.
See Also
For related systems:
- Options Manager — the runtime that drives all of this
- Display & Window Modes — what the window targets actually do
- Input Options — the other branch of GS_Options
- ImGui Debug Menu — a live editor for every option in the catalog
For conceptual overviews and usage guides:
Get GS_Core
GS_Core — Explore this gem on the product page and add it to your project.