Quick Start
By the end of this page you have a settings screen with a couple of options that persist per player - no game-code binding, no fields added anywhere. Making an option actually change the game comes next (How-To: Bind a Setting to Game Code).
CrimsonSettings is installed, and you have a UI layer to push a screen to - your own layout, or any CommonUI activatable-widget stack (step 6 shows one).Tick Show Plugin Content in the Content Browser's Settings menu - plugin assets are hidden until you do, and several pages here point at shipped ones.
1. Enable the plugin
Open Edit -> Plugins, type Crimson, tick CrimsonSettings, and restart when prompted. For a Blueprint project that's the whole install.
2. Register the GameUserSettings class
This step is not optional and there is no in-editor equivalent - it is one line of config, and without it the plugin asserts. Add to Config/DefaultEngine.ini, then restart the editor:
[/Script/Engine.Engine]GameUserSettingsClassName=/Script/CrimsonSettings.CrimsonSettingsLocal
The engine allocates exactly one UGameUserSettings object and takes its class from this value. UCrimsonSettingsLocal::Get() is a CastChecked over GEngine->GetGameUserSettings(), so with a plain UGameUserSettings allocated the cast asserts - and every shipped video, audio, gamepad and mouse row calls Get(). It is the same shape of registration a GAS project makes with AbilitySystemGlobalsClassName: one ClassName line under [/Script/Engine.Engine].
GameUserSettingsClassName=/Script/MyGame.MyGameUserSettings.UCrimsonSettingsLocal::Get(). A missing line shows up as an assert inside GetGameUserSettings, not as a log warning - so if nothing asserts, it is registered.3. Create a settings data asset
Right-click in the Content Browser -> Miscellaneous -> Data Asset -> CrimsonSettingsDataAsset. Add a Section (e.g. Gameplay), then add Settings entries. For each entry set the Type, DevName, DisplayName, and - to persist it with no code - tick Persist To Shared Store (leave StorageKey empty to reuse DevName).
Scalar shows range/step/default; a Discrete shows options/default index; an Action shows its button text. DevName must be unique within a registry.4. Create a registry
The registry turns the asset into live settings. Make a Blueprint child of UCrimsonSettingRegistry, and in one of the category hooks call Populate From Data Asset with your asset.
In BP_GameplaySettingsRegistry, open My Blueprint -> Functions -> Override and pick K2_RegisterGameplaySettings. Wire the event it drops in: K2_RegisterGameplaySettings -> Populate From Data Asset (Data Asset = DA_GameplaySettings, Parent Collection = empty). Populate From Data Asset is a Settings | Registry node on the registry itself, so its target pin is Self.
The four override events - K2_RegisterVideoSettings, K2_RegisterAudioSettings, K2_RegisterInputSettings and K2_RegisterGameplaySettings - all fire in that order during initialization and are otherwise interchangeable. Pick the one that matches what your asset holds; nothing breaks if you register everything from one of them.
Populate From Data Asset accepts your asset with no warnings in the Output Log.5. Create the settings screen
Make a Blueprint widget child of UCrimsonSettingScreen. Add a UCrimsonSettingPanel named exactly `Settings_Panel` (a bind-widget). Override `Create Registry` to return your registry, initialized for the owning local player.
In the Designer, add the one child the base class binds by name. Get this name wrong and the Blueprint refuses to compile - BindWidget is a hard requirement, not a convention:
| Widget class | Name (exact) | Why |
|---|---|---|
UCrimsonSettingPanel | Settings_Panel | The screen hands it the registry and it renders every row. A BindWidget, so it is required and the name must match character for character. |
Then in the Graph, use My Blueprint -> Functions -> Override and pick Create Registry. Wire Create Registry -> Construct Object from Class (Class = BP_GameplaySettingsRegistry, Outer = Get Owning Local Player) -> Initialize (In Local Player = the same Get Owning Local Player), then feed the constructed registry into the Return Node.
K2_Register... events and so builds every row. A registry you construct and return without it comes back empty - the screen renders with no settings and nothing errors. A second call is rejected, so calling it once here is safe.Settings_Panel bound (no "BindWidget" error) and Create Registry returns your registry class.6. Push the screen
Push the screen onto a UI layer from a menu button (or BeginPlay for a test).
On the menu button: OnClicked -> Get Primary Game Layout (Player Controller = Get Owning Player) -> Get Layer Widget (Layer Name = your menu layer tag, e.g. UI.Layer.Menu) -> Push Widget (Activatable Widget Class = WBP_SettingsScreen).
Get Primary Game Layout and Get Layer Widget stand for your own layout's accessors (a Lyra-style primary layout, for example); Push Widget is CommonUI's own node on the activatable-widget container. Using a different layout? Add the widget however that layout adds widgets - the screen is an ordinary UCommonActivatableWidget.