Concept: Value Types & Data Sources

A setting's value type decides its widget; its source decides where the value comes from. Mixing these deliberately gives you the persist / bind / advanced choices.

Value types

TypeWidgetClasses
ScalarSliderUCrimsonSettingValueScalar -> _BP (delegates + optional store) / Dynamic (C++ data source)
DiscreteOptions selectorUCrimsonSettingValueDiscrete -> _BP / Dynamic (+ _Bool, _Number, _Enum, ...)
ActionButtonUCrimsonSettingAction (fires a named action tag or delegate)
CollectionSection headerUCrimsonSettingCollection / ...Collapsible (expandable)

Sources (how a value is read/written)

SourceSet up byBest for
Keyed storeTick Persist To Shared StorePlain per-player persistence, no code
Blueprint delegatesK2_BindSetting (OnGetValue/OnSetValue, OnGetIndex/OnSetIndex)Driving live game state from a Blueprint registry
C++ data sourceSetDynamicGetter/SetDynamicSetter on a Dynamic valueEngine/machine settings via reflected function paths
InternalBind nothingA value your game reads elsewhere

C++ data sources

UCrimsonSettingValueScalarDynamic / ...DiscreteDynamic resolve their value through a FCrimsonSettingDataSource. The shipped sources are FCrimsonLocalSettingDataSourceStatic (resolves through UCrimsonSettingsLocal::Get() via the GET_CRIMSON_LOCAL_SETTINGS_FUNCTION_PATH macro), FCrimsonSettingDataSourceDynamic (a reflected property path from the local player), and FCrimsonKeyedSharedSettingDataSource (the keyed store, used by Persist To Shared Store).

cpp
auto* Gamma = NewObject<UCrimsonSettingValueScalarDynamic>(this);
Gamma->SetDevName(TEXT("Brightness"));
Gamma->SetSourceRangeAndStep(TRange<double>(0.0, 1.0), 0.01);
Gamma->SetDynamicGetter(GET_CRIMSON_LOCAL_SETTINGS_FUNCTION_PATH(GetDisplayGamma));
Gamma->SetDynamicSetter(GET_CRIMSON_LOCAL_SETTINGS_FUNCTION_PATH(SetDisplayGamma));
Gamma->SetDisplayFormat(UCrimsonSettingValueScalarDynamic::Raw);
Precedence inside a _BP value
A _BP value returns, in order: its preview value (during live preview), its storage source (if persisting), its OnGet* delegate (if bound), else its internal field. Writes go to the storage source and the OnSet* delegate when each is present - so a setting can both persist and fire a live callback.