How-To: Expose Another Plugin's Settings

Goal: surface options that belong to a different plugin (camera, input, a gameplay system) in a settings screen - without CrimsonSettings depending on that plugin, and without adding fields to CrimsonSettings. The keyed store makes this a data-authoring task, not a code-coupling one.

The rule this respects
CrimsonSettings stores only opaque Key -> value pairs; it never names a camera or gameplay concept. The consuming module (which already sees both plugins) reads those keys and applies them. So neither plugin gains a dependency on the other.

1. Author the options with storage keys

Add the options to a settings data asset section, tick Persist To Shared Store, and give each a stable, namespaced Storage Key. For camera options, CrimsonCore reads these exact keys:

Screenshot pendingImages/CrimsonSettings/howto-expose-dataasset.png
DA_CameraSettings -> Section 'Camera' -> Scalar 'Field of View' (Storage Key Camera.FieldOfView), Discrete 'Occlusion' (Camera.OcclusionEnabled), etc. Persist To Shared Store ticked on each.
OptionTypeStorage Key
Field of viewScalarCamera.FieldOfView
Screen-shake intensityScalarCamera.ShakeScale
Default zoomScalarCamera.ZoomDistance
Shoulder sideDiscreteCamera.Shoulder
Occlusion on/offDiscreteCamera.OcclusionEnabled
Occlusion strengthScalarCamera.OcclusionStrength
Lock-on indicatorsDiscreteCamera.LockOnIndicators
Camera lag on/off + speedDiscrete + ScalarCamera.LagEnabled / Camera.LagSpeed
Auto-recenter on/off + delay/speedDiscrete + ScalarCamera.AutoRecenterEnabled / Camera.AutoRecenterDelay / Camera.AutoRecenterInterpSpeed
Verify
The options appear in your screen and persist. Nothing is wired yet - the next step makes them take effect.

2. Apply the values in a module that sees both plugins

A consumer reads the keys and pushes them onto the target system. For camera, CrimsonCore already does this for you: its player camera manager reads the Camera.* keys each frame and calls the CrimsonCamera runtime setters (SetFieldOfViewOverride, SetCameraShakeScale, SetOcclusionEnabled, the lag/recenter setters, ...). For your own system, mirror the pattern:

cpp
// In YOUR module (which depends on CrimsonSettings AND the target plugin):
if (UCrimsonSharedSettingsSubsystem* Sub = UCrimsonSharedSettingsSubsystem::Get(LocalPlayer))
{
if (UCrimsonSettingsShared* S = Sub->GetSharedSettings())
{
Camera->SetFieldOfViewOverride(S->GetFloatValue(FName("Camera.FieldOfView"), 0.f));
Occlusion->SetOcclusionEnabled(S->GetBoolValue(FName("Camera.OcclusionEnabled"), true));
}
}
// Re-apply on UCrimsonSettingsShared::OnSettingChanged so live edits take effect.
Where the apply layer lives
The consumer must depend on both CrimsonSettings and the target plugin - so it belongs in your game module or an aggregator like CrimsonCore, never inside CrimsonSettings or the target plugin. That is exactly how the camera options above are wired.
React to changes
UCrimsonSettingsShared broadcasts OnSettingChanged whenever any keyed value changes, and UCrimsonSharedSettingsSubsystem auto-saves on a short debounce. Subscribe to that event to re-apply live while the player drags a slider.

See also

  • CrimsonCamera wiki -> How-To: Expose Camera Options as User Settings - the runtime setters this drives.
  • Concept: Storage Model - the keyed store and the shared-settings subsystem.
  • How-To: Persist Settings with Zero Glue - the authoring half of this pattern.