PlayerPrefs: Saved value of slider resets when re-entering the main menu scene

I have a settings manager script that handles the saving and assigning of slider input at the main menu’s “options” screen. There are two sliders; volume and sensitivity. The volume slider seems fine, although I haven’t actually tested it with audio, but it stays at the same place as intended.
The sensitivity slider applies when I enter my test level (I can tell because the sensitivity shows in debug mode and because the in-game camera is clearly affected each time you pick a different sensitivity).
However, when I press the “quit” button in my pause menu to get back to the main menu, the sensitivity slider is reset to default, even though the volume slider is fine. Here’s the code of the settings manager:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SettingsManager : MonoBehaviour
{
    // VARIABLES
    private const string VOLUME_PREF = "volume";
    private const string SENSITIVITY_PREF = "sensitivity";


    public Slider volume;
    public Slider sensitivity;
   

    // Start is called before the first frame update
    void Awake()
    {
        //volume.value = PlayerPrefs.GetFloat(VOLUME_PREF, 1);
        //sensitivity.value = PlayerPrefs.GetFloat(SENSITIVITY_PREF, 1);

        volume.value = PlayerPrefs.GetFloat("volume");
        sensitivity.value = PlayerPrefs.GetFloat("sensitivity");
        DontDestroyOnLoad(gameObject);
    }

    // Update is called once per frame
    void Update()
    {
       
    }

    private void SetPref(string key, float value)
    {
        PlayerPrefs.SetFloat(key, value);
        PlayerPrefs.Save();
    }



    public void OnChangeVolume(Single value)
    {
        SetPref(VOLUME_PREF, value);
    }

    public void OnChangeSensitivity(Single value)
    {
        SetPref(SENSITIVITY_PREF, value * 300f);
    }
}

And here’s the pause menu, in case the “quit” is what’s causing it.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class PauseMenu : MonoBehaviour
{
    // VARIABLES
    public static bool isGamePaused = false; // true or false if game is paused

    public GameObject pauseUI; // canvas ui gameobject


    private void Awake()
    {
        pauseUI.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetKeyDown(KeyCode.P) || Input.GetKeyDown(KeyCode.Escape))
        {
            if (isGamePaused)
            {
                Resume();
            }
            else
            {
                Pause();
            }
        }
    }

    public void Resume()
    {
        pauseUI.SetActive(false);
        Time.timeScale = 1f;
        isGamePaused = false;
       
        // lock and hide the mouse cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    void Pause()
    {
        pauseUI.SetActive(true);
        Time.timeScale = 0f;
        isGamePaused = true;

        // unlock and show the mouse cursor
        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;
    }

    public void Options()
    {
        Debug.Log("options");

    }

    public void QuitToMenu()
    {
        Debug.Log("quit");
        SceneManager.LoadScene(0);
    }
}

And just for good measure, here’s the main menu.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour
{
    //public Slider sensitivitySlider;
    //public Slider volumeSlider;

    private void Start()
    {

    }

    // start game loop
    public void PlayGame()
    {
        //volumeSlider.value = PlayerPrefs.GetFloat("volume");
        //sensitivitySlider.value = PlayerPrefs.GetFloat("sensitivity");
        SceneManager.LoadScene(1); // loads the test scene (index value '1' in the build settings)
    }

    // options screen
    public void Options()
    {

    }
   
    // quit program
    public void QuitGame()
    {
        Debug.Log("Program closing...");
        Application.Quit();
    }
}

I’m very new to this, so I would appreciate some help, and I apologize sincerely if it’s an easy fix. Thanks for your time!

I see your DontDestroyOnLoad in the settings manager… are you making sure that isn’t making a fresh copy each time you load that scene?

Also, you probably DO want lines 21,22 in the settings manager instead of the hand-typed strings because:

  1. hand-typed strings are prone to error, which is why we have constants
  2. the default value will be zero, if the key does not exist (second optional argument to the get)

Beyond that, do some Debug.Log() printing of values going to and from playerprefs (sets and gets) and it should quickly become apparently where things are going wrong.

1 Like

I entered this because people on other threads suggested it as a means of keeping one consistent value for the player prefs.

I removed them because they were not properly assigning the sensitivity or volume values, they had no impact in the test level. I’ll do the testing you suggested and see what I can find, thanks for the tips.

1 Like

They are partially correct. DDOL is a means only of keeping an instance of a script around, which is one way of persisting things. You are already persisting things to the PlayerPrefs, so it’s sorta belt-and-suspenders.

Check out discussions on “unity singletons” for the notion of lifetime of your objects, and how to keep from multiple objects accumulating when you DDOL them.

For something like a SettingsManager, unless it needs to be called every frame on Update() (unlikely), you might as well just make the entire class and all its methods static, then a) there is only ever one of them, and b) it is always present.

1 Like

I’ll try that when I get time later, thank you for your time and the great explanations!

1 Like

Did you ever figure this out? I’m having the same issue. Two sliders - one gets reset when changing scenes (or when reloading the game), one is perfectly fine. I’ve tried a bit of print debugging but haven’t been able to narrow it down much.
Would be curious if you found anything that might provide added context

I can’t answer for OP but I have a feeling his problem was related to him multiplying his sensitivity value by 300 in the first script. That’s the only difference between the two there. He probably only wanted to multiply it by 300 after loading it to then apply the setting because his slider was set to 1 maximum. But he was multiplying it when saving as well, meaning the setting would probably set itself correctly in game but the slider would get set to 1 at every reload.

I can’t guarantee this is the same issue OP had, though I did find my issue.

I handled my OnChange delegates by writing the new value to my in-code variables and the values used elsewhere in the editor (the Mixergroup.audioMixer values). then I called a separate Save() method which would store both values to playerPrefs. Usually this wouldn’t be an issue, since it read the values directly from the slider, and the player would only drag one slider at a time. However when I loaded a new scene, or re-started the game, both sliders start at zero, then later be set to the value then I’d use a Load() method (called in Awake()) to set the values to what I had previously stored. The OnChange delegate would be triggered by this Load() method, but only one at a time. And as I mentioned, both OnChange delegates triggered my Save() method, writing the current values of the sliders to PlayerPrefs. And since the first delegate activated before the second slider had it’s stored value changed back to what was stored, playerPrefs ended up storing the unset value from the not-yet-initialized second slider.

I’m not sure if I’m making sense but tl;dr if someone happens across this thread in the future, check when exactly you’re storing your values. you might be accidentally storing them when another slider is initializing, but before the broken slider has re-initialized with the correct value. Meaning you’re reading from and storing a value that hasn’t been re-initialized.

Looking at this now three years later, I now notice (in OPs original question) the use of Awake() to manipulate properties that are NOT part of that script instance.

Absolutely avoid doing ANYTHING in Awake or OnEnable that has to do with ANYTHING outside of you.

In the code snippets of the initial post, ALL of these things should be done in Start(). Otherwise, how you would know that the Slider instances have had their Awake() calls? They might not be open for business yet!

Give everybody a chance to wake up and have their coffee before you start banging on their public properties.

These should all be in Start():

and

Here is some timing diagram help:

https://docs.unity3d.com/Manual/ExecutionOrder.html

Beyond that, try to avoid splattering playerpref strings all over your codebase. You’ll likely one day regret it.

Keep it tidy:

Here’s an example of simple persistent loading/saving values using PlayerPrefs:

https://gist.github.com/kurtdekker/01da815d2dfd336a925ae38019c3a163

Useful for a relatively small number of simple values.