Save User Settings
Crystal Save – UserSettingsManager (GitBook Guide)
A lightweight, opt‑in way to persist and apply player options (graphics, audio, camera FOV, post‑processing, language, etc.) in one JSON file. It works in built‑in/URP/HDRP, supports Unity’s AudioMixer, and auto‑reapplies settings on scene load.
What it does
Keeps all user options in a
UserSettings.json
file under Crystal Save’s root path (orApplication.persistentDataPath
if SaveManager isn’t ready).Loads once on startup, applies immediately, and re‑applies after every scene load.
Lets you read & write settings at runtime (e.g., from your options menu) with one call:
SaveSettings()
.
🚀 Lifecycle Overview
Mermaid Flowchart
1) Install & scene setup
Add the component
Create an empty GameObject in your bootstrap scene (or reuse an existing singleton holder).
Add
UserSettingsManager
.It will persist across scenes automatically.
(Optional) Wire references
Main Camera (assign if you don’t want auto‑detection).
Global Volume (URP/HDRP) or PostProcessVolume (PPv2) if you want to toggle effects from settings.
Audio Mixer and its exposed parameter names (e.g.,
Master
,Music
,SFX
,Voice
).
Define symbols/packages (as used in your project)
URP: install URP; the manager will use URP volume overrides when
REMEMBERME_URP_PRESENT
is defined.HDRP: install HDRP; the manager will use HDRP volume overrides when
REMEMBERME_HDRP_PRESENT
is defined.PPv2: install the Post‑Processing v2 package to enable built‑in pipeline overrides.
Unity Localization: add the Localization package to persist
LocaleCode
.(Optional) NVIDIA DLSS for HDRP: enables DLSS fields when available.
Tip: You can leave camera/volume fields empty and turn on the “auto reference” toggles — the manager will try to find the right objects in each scene.
2) How it runs (lifecycle)
Awake
Ensures a single instance (singleton) +
DontDestroyOnLoad
.Subscribes to
SceneManager.sceneLoaded
.Loads the JSON file (or builds a default data object if missing).
Immediately applies settings.
On scene loaded
Re‑finds camera/volume (when set to auto).
Re‑applies settings so per‑scene visuals/audio stay in sync.
3) Quick start: options menu hooks
Below are the common snippets to hook your UI to the manager.
3.1 Access the manager
using Arawn.CrystalSave.Runtime;
var settings = UserSettingsManager.Instance; // singleton
3.2 Change resolution / fullscreen
void OnResolutionPicked(int width, int height, bool fullscreen)
{
// Update and apply immediately
var mgr = UserSettingsManager.Instance;
// The manager applies from its data model; easiest is:
Screen.SetResolution(width, height, fullscreen);
mgr.SaveSettings(); // persists to UserSettings.json
}
3.3 Quality level / VSync / target FPS
void OnQualityChanged(int qualityIndex)
{
QualitySettings.SetQualityLevel(qualityIndex, true);
UserSettingsManager.Instance.SaveSettings();
}
void OnVSyncChanged(int vsyncCount) // 0,1,2
{
QualitySettings.vSyncCount = vsyncCount;
UserSettingsManager.Instance.SaveSettings();
}
void OnTargetFpsChanged(int fps) // e.g., 30/60/120, or -1 for platform default
{
Application.targetFrameRate = fps;
UserSettingsManager.Instance.SaveSettings();
}
3.4 Audio mixer volumes (sliders 0..1)
// Convert linear 0..1 into dB happens inside the manager
void OnMasterVolumeChanged(float linear01)
{
var m = UserSettingsManager.Instance;
// Easiest path: push value into AudioMixer and Save()
// (Manager will read back dB -> linear and persist that.)
var mixer = /* your AudioMixer ref */;
mixer.SetFloat("Master", Mathf.Lerp(-80f, 0f, linear01));
m.SaveSettings();
}
3.5 Camera FOV
void OnFovChanged(float fov)
{
var cam = Camera.main;
if (cam != null && !cam.orthographic)
cam.fieldOfView = fov;
UserSettingsManager.Instance.SaveSettings();
}
3.6 URP/HDRP: toggle post‑processing overrides
URP example (same pattern for HDRP with the matching effect types):
// Toggle Motion Blur in a URP Global Volume
void OnUrpMotionBlurToggled(bool isOn)
{
var mgr = UserSettingsManager.Instance;
// Get your global Volume once and assign:
// mgr.SetGlobalVolumeReference(globalVolume);
// Flip the override in the VolumeProfile directly or via your own helper
// ...then persist what’s currently active:
mgr.SaveSettings();
}
3.7 Localization (if installed)
using UnityEngine.Localization.Settings;
async void OnLanguageChanged(string localeCode) // e.g., "en", "fr", "ja"
{
var locales = LocalizationSettings.AvailableLocales;
var locale = locales.Locales.FirstOrDefault(l => l.Identifier.Code == localeCode);
if (locale != null)
{
await LocalizationSettings.InitializationOperation.Task;
LocalizationSettings.SelectedLocale = locale;
UserSettingsManager.Instance.SaveSettings();
}
}
4) Full apply / load / save / sync API
LoadSettings()
ReadsUserSettings.json
. Creates defaults if missing. Immediately applies and refreshes Audio/HDRP camera bits.ApplySettings()
Re‑evaluates references (main camera, global volume, PPv2 volume if any) and pushes every field into:QualitySettings
(quality index, AA, shadow distance, VSync, texture mipmap limit, target FPS)Screen.SetResolution(...)
Post‑processing overrides (URP/HDRP volumes or PPv2 profile)
Camera FOV
Logs what it applied for quick debugging.
ApplyAudioMixerSettings()
Writes linear volumes (0..1) from the data model into the AudioMixer (in dB).ApplyHDRPDynamicResolutionSettings()
(only when HDRP present) Pushes HDRP camera flags (dynamic resolution, DLSS/FSR2, AA mode).SyncFromRuntime()
Pulls the current runtime state (resolution/fullscreen, quality, AA, shadow distance, VSync, FPS, FOV, mixer dB → linear, locale, and volume override flags) back into the data model. 👉 Useful if you changed settings via code and want to persist whatever is currently active.SaveSettings()
CallsSyncFromRuntime()
, then writes pretty‑printed JSON toUserSettings.json
.Reference helpers
SetMainCameraReference(Camera)
SetGlobalVolumeReference(Volume)
(URP/HDRP)SetPostProcessVolumeReference(PostProcessVolume)
(PPv2)
5) Data model (what gets saved)
UserSettingsData
(serialized to JSON) contains, among others:
Display/Quality:
ResolutionWidth/Height
,FullScreen
,QualityLevel
,QualityAntiAliasing
,QualityShadowDistance
,QualityVSyncCount
,TextureQuality
,TargetFPS
.Camera:
CameraFOV
, plus per‑pipeline camera AA & upscalers (HDRP: dynamic resolution, DLSS/FSR2; URP AA).Audio:
MasterVolume
,MusicVolume
,SfxVolume
,VoiceVolume
(stored linear, converted to dB for AudioMixer).Locale:
LocaleCode
.Post‑processing overrides:
URP: MotionBlur, Bloom, FilmGrain, ChromaticAberration, ColorAdjustments, ColorCurves, ColorLookup, DepthOfField, LensDistortion, LiftGammaGain, Panini, ScreenSpaceLensFlare, ShadowsMidtonesHighlights, SplitToning, Tonemapping, Vignette, WhiteBalance.
HDRP: MotionBlur, Bloom, FilmGrain, ChromaticAberration, ColorCurves, ColorAdjustments, DepthOfField, LensDistortion, LiftGammaGain, PaniniProjection, ScreenSpaceLensFlare (Unity 6+), ShadowsMidtonesHighlights, SplitToning, Tonemapping, Vignette, WhiteBalance, ScreenSpaceAmbientOcclusion.
Built‑in (PPv2): MotionBlur, DepthOfField, Bloom, LensDistortion, ChromaticAberration, AutoExposure, ColorGrading, Vignette, Grain, ScreenSpaceReflections, AmbientOcclusion.
You don’t need to touch this class directly—use the manager’s methods and let it keep the JSON in sync.
6) Where is UserSettings.json
saved?
UserSettings.json
saved?By default:
<CrystalSave Root>/UserSettings.json
ifSaveManager
is initialized (so it lives next to your save files).Falls back to
Application.persistentDataPath/UserSettings.json
if not.
This keeps user options consistent with your chosen save location (local, mirrored, or custom root).
7) Common patterns
Autosave on change
Call UserSettingsManager.Instance.SaveSettings()
after each UI change. It will:
Pull current runtime values (
SyncFromRuntime()
).Persist to JSON.
Apply current JSON again (e.g., after returning to main menu)
UserSettingsManager.Instance.ApplySettings();
UserSettingsManager.Instance.ApplyAudioMixerSettings();
Swap cameras/volumes at runtime (multi‑camera scenes)
UserSettingsManager.Instance.SetMainCameraReference(otherCam);
UserSettingsManager.Instance.SetGlobalVolumeReference(otherGlobalVolume); // URP/HDRP
// PPv2:
// UserSettingsManager.Instance.SetPostProcessVolumeReference(otherPpv2);
8) Troubleshooting
“Nothing happens when I change settings” Make sure you’re calling
SaveSettings()
(which syncs and writes), orApplySettings()
after setting fields programmatically.“Volumes don’t change” Verify the AudioMixer is assigned and parameters are exposed with the exact names you typed.
“Post‑processing toggles don’t do anything” Ensure a Global Volume (URP/HDRP) or PostProcessVolume (PPv2) is assigned or auto‑found, and the target effects are present in that Volume Profile with “override” checkboxes available.
“FOV doesn’t update” FOV applies to a non‑orthographic camera. If you switch cameras, call
SetMainCameraReference()
or enable auto‑reference.“JSON not created where I expect” If
SaveManager
isn’t initialized yet, the path falls back toApplication.persistentDataPath
. Initialize Crystal Save early, or move the file later.
9) Minimal working sample (menu glue)
using UnityEngine;
using UnityEngine.UI;
using Arawn.CrystalSave.Runtime;
public class OptionsMenu : MonoBehaviour
{
[SerializeField] Slider masterVol; // 0..1
[SerializeField] Slider fov; // 60..100
[SerializeField] Dropdown quality; // indices
void Start()
{
// Read current runtime → reflect into UI (optional)
// If you want exact data values instead, call LoadSettings() first.
var m = UserSettingsManager.Instance;
m.ApplySettings(); // ensures runtime matches JSON on this scene
m.ApplyAudioMixerSettings();
// TODO: initialize sliders from your mixer/current camera if desired
}
public void OnMasterVolChanged(float v)
{
// Push to mixer right away (or let your own audio system handle it)
// Then persist whatever is active:
UserSettingsManager.Instance.SaveSettings();
}
public void OnFovChanged(float v)
{
var cam = Camera.main;
if (cam && !cam.orthographic) cam.fieldOfView = v;
UserSettingsManager.Instance.SaveSettings();
}
public void OnQualityChanged(int index)
{
QualitySettings.SetQualityLevel(index, true);
UserSettingsManager.Instance.SaveSettings();
}
}
10) FAQ
Q: Do I need to manually create UserSettings.json
?
No. If the file doesn’t exist, the manager creates defaults on first run and writes when you call SaveSettings()
.
Q: Does it support quick switching between URP/HDRP/Built‑in? Yes—only the parts relevant to your pipeline are used, guarded by the corresponding packages/scripting symbols.
Q: Can I store extra, custom settings?
Add fields to UserSettingsData
, and set/get them through your UI. Call SaveSettings()
to persist; LoadSettings()
+ ApplySettings()
to re‑apply.
Last updated