Hi Chris,
We found three cases of unnecessary options saves in AC 1.86.7, using Unity 6000.6.5f1, Unity UI menus, Rewired and the PlayerPrefs options handler.
We are preparing for console ports and would like to avoid storage-write frequency or platform-compliance problems later. We have not measured a console quota violation, but would prefer a supported AC solution rather than maintaining our own changes to its internals.
To reproduce, open Options and drag a Music, SFX or Speech slider with AC debug logging enabled. The Editor repeatedly logs that the options PlayerPrefs key was saved.
The call chain is:
Slider.onValueChanged → MenuSlider.ProcessClick → UpdateValue
→ Options.SetMusicVolume / SetSFXVolume / SetSpeechVolume
→ Options.SetVolume → SavePrefs → SaveOptions
There is also a path through MenuSlider.ProcessContinuousClick, which can repeat the same value.
In OptionsFileHandler_PlayerPrefs.cs, each save calls PlayerPrefs.SetString, and PlayerPrefs.Save() is explicitly called under UNITY_PS4 || UNITY_SWITCH || UNITY_WEBGL. We understand that the macOS Editor's “saved” message does not itself prove a physical disk flush, but the repeated serialization/save requests and explicit flushes on those targets are our concern.
Suggested behaviour: apply volume changes immediately for live audio feedback, but save once when the user releases the slider. Keyboard/controller navigation should commit when its input returns to neutral, with menu closure or deselection as fallbacks.
Our local implementation adds overloads with bool savePrefs to SetVolume, SetMusicVolume, SetSFXVolume and SetSpeechVolume in Options.cs. Existing signatures retain their original immediate-save behaviour. The new channel-specific overloads still apply volume, raise the existing events and update the mixer when saving is disabled.
MenuSlider.cs uses these overloads through a runtime helper for Unity UI volume sliders. The helper tracks actual changes and calls Options.SavePrefs() after release in LateUpdate. Repeated identical values do not schedule another save. Pending changes also flush on deselection, menu closure, disable, focus loss, application pause or quit, with a guard against saving pending state into a different profile.
Our project code only supplies Rewired navigation state and the menu-close callback. Its UI axes are UIHorizontal/UIVertical, which differ from AC's configured menu axes. A built-in solution would need to account for different input modules; our helper falls back to saving on deselection/closure when no navigation adapter is supplied. This prototype targets Unity UI volume sliders, not AC-rendered sliders.
With saved volumes differing from the sliders' prefab values, simply opening Options produces one save each for Speech, Music and SFX. The log shows:
Menu.TurnOn → InitUIElements → MenuSlider.PreDisplay
→ Slider.value → onValueChanged → ProcessClickUI → UpdateValue
→ Options.Set[Speech/Music/SFX]Volume → SavePrefs
In MenuSlider.PreDisplay, displaying the stored value triggers a change callback:
uiSlider.value = visualAmount;
Our local fix is:
uiSlider.SetValueWithoutNotify(visualAmount);
This updates the visuals without treating a display refresh as user input. Actual user changes still invoke callbacks. This change also affects programmatic display refreshes of variable/custom Unity UI sliders, so compatibility with custom listeners should be considered.
To reproduce, enable separateVoiceAndTextLanguages in the Speech Manager and configure a Language Cycle as TextAndVoice. Clicking it produces two saves, from adjacent calls in MenuCycle.ProcessClick:
Options.SetLanguage(trueIndex);
Options.SetVoiceLanguage(trueIndex);
Each method calls SavePrefs(). The first saves the new text language with the old voice language; the second saves both new values. The Language Cycle has no ActionList-on-click assigned, and the log identifies these two built-in calls. The same pair exists in the TextAndVoice branch of ActionOptionSet.cs (Options: Set).
Our local fix adds Options.SetLanguage(int i, bool savePrefs), preserving the original one-argument API, then changes both combined-language branches to:
Options.SetLanguage(index, false);
Options.SetVoiceLanguage(index);
The second call saves both updated fields once. Language events and asset-bundle calls retain their existing order. TextOnly, VoiceOnly and synchronized-language branches are unchanged. Different text and voice languages remain supported; only TextAndVoice intentionally selects the same language for both.
Would these changes be difficult to incorporate into AC? In particular, could AC support silent slider display updates, one save for a combined language change, and an optional save-on-release policy or a public apply-without-saving API?
If adding this is not straightforward, what supported approach would you recommend for console ports—such as batching through a custom options file handler? We want to avoid excessive writes and platform-specific problems when porting, while preserving reliable settings persistence. We also understand that one save per release is not a complete write-rate limit if the user makes many separate adjustments quickly; guidance on where to handle that would be appreciated.
It looks like you're new here. If you want to get involved, click one of these buttons!
Comments
Thanks for the detailed breakdown.
The various Slider / Toggle etc settings that allow you to link your elements automatically to various Options data - while convenient - aren't strictly necessary if you want to delay actually writing to your OptionsData.
For example, you could create a set of Global Float variables to represent your various Volume levels, link those to the Sliders, and only when you click an Apply button / close the Options menu, update the Options data with these values.
I wonder, though, if you could bypass a lot of the above headaches by using a custom iOptionsFileHandler script that has its SaveOptions function simply delay any calls to write to the data stream if it's called while your Options menu is open. For example, delay any call by 1 second, and let that call be interrupted by another during that time. That way, any bulk calls, or quickly-repeated calls, would be condensed into 1, while any calls to save outside of the Options menu are unaffected.