Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

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

Discussion in 'Scripting' started by mikeycodes, Apr 27, 2020.

  1. mikeycodes

    mikeycodes

    Joined:
    Apr 10, 2017
    Posts:
    3
    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:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6.  
    7. public class SettingsManager : MonoBehaviour
    8. {
    9.     // VARIABLES
    10.     private const string VOLUME_PREF = "volume";
    11.     private const string SENSITIVITY_PREF = "sensitivity";
    12.  
    13.  
    14.     public Slider volume;
    15.     public Slider sensitivity;
    16.    
    17.  
    18.     // Start is called before the first frame update
    19.     void Awake()
    20.     {
    21.         //volume.value = PlayerPrefs.GetFloat(VOLUME_PREF, 1);
    22.         //sensitivity.value = PlayerPrefs.GetFloat(SENSITIVITY_PREF, 1);
    23.  
    24.         volume.value = PlayerPrefs.GetFloat("volume");
    25.         sensitivity.value = PlayerPrefs.GetFloat("sensitivity");
    26.         DontDestroyOnLoad(gameObject);
    27.     }
    28.  
    29.     // Update is called once per frame
    30.     void Update()
    31.     {
    32.        
    33.     }
    34.  
    35.     private void SetPref(string key, float value)
    36.     {
    37.         PlayerPrefs.SetFloat(key, value);
    38.         PlayerPrefs.Save();
    39.     }
    40.  
    41.  
    42.  
    43.     public void OnChangeVolume(Single value)
    44.     {
    45.         SetPref(VOLUME_PREF, value);
    46.     }
    47.  
    48.     public void OnChangeSensitivity(Single value)
    49.     {
    50.         SetPref(SENSITIVITY_PREF, value * 300f);
    51.     }
    52. }
    53.  

    And here's the pause menu, in case the "quit" is what's causing it.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5.  
    6. public class PauseMenu : MonoBehaviour
    7. {
    8.     // VARIABLES
    9.     public static bool isGamePaused = false; // true or false if game is paused
    10.  
    11.     public GameObject pauseUI; // canvas ui gameobject
    12.  
    13.  
    14.     private void Awake()
    15.     {
    16.         pauseUI.SetActive(false);
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update()
    21.     {
    22.         if(Input.GetKeyDown(KeyCode.P) || Input.GetKeyDown(KeyCode.Escape))
    23.         {
    24.             if (isGamePaused)
    25.             {
    26.                 Resume();
    27.             }
    28.             else
    29.             {
    30.                 Pause();
    31.             }
    32.         }
    33.     }
    34.  
    35.     public void Resume()
    36.     {
    37.         pauseUI.SetActive(false);
    38.         Time.timeScale = 1f;
    39.         isGamePaused = false;
    40.        
    41.         // lock and hide the mouse cursor
    42.         Cursor.lockState = CursorLockMode.Locked;
    43.         Cursor.visible = false;
    44.     }
    45.  
    46.     void Pause()
    47.     {
    48.         pauseUI.SetActive(true);
    49.         Time.timeScale = 0f;
    50.         isGamePaused = true;
    51.  
    52.         // unlock and show the mouse cursor
    53.         Cursor.lockState = CursorLockMode.None;
    54.         Cursor.visible = true;
    55.     }
    56.  
    57.     public void Options()
    58.     {
    59.         Debug.Log("options");
    60.  
    61.     }
    62.  
    63.     public void QuitToMenu()
    64.     {
    65.         Debug.Log("quit");
    66.         SceneManager.LoadScene(0);
    67.     }
    68. }
    69.  

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

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class MainMenu : MonoBehaviour
    8. {
    9.     //public Slider sensitivitySlider;
    10.     //public Slider volumeSlider;
    11.  
    12.     private void Start()
    13.     {
    14.  
    15.     }
    16.  
    17.     // start game loop
    18.     public void PlayGame()
    19.     {
    20.         //volumeSlider.value = PlayerPrefs.GetFloat("volume");
    21.         //sensitivitySlider.value = PlayerPrefs.GetFloat("sensitivity");
    22.         SceneManager.LoadScene(1); // loads the test scene (index value '1' in the build settings)
    23.     }
    24.  
    25.     // options screen
    26.     public void Options()
    27.     {
    28.  
    29.     }
    30.    
    31.     // quit program
    32.     public void QuitGame()
    33.     {
    34.         Debug.Log("Program closing...");
    35.         Application.Quit();
    36.     }
    37. }
    38.  

    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!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    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.
     
    mikeycodes likes this.
  3. mikeycodes

    mikeycodes

    Joined:
    Apr 10, 2017
    Posts:
    3
    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.
     
    Kurt-Dekker likes this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    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.
     
    mikeycodes likes this.
  5. mikeycodes

    mikeycodes

    Joined:
    Apr 10, 2017
    Posts:
    3
    I'll try that when I get time later, thank you for your time and the great explanations!
     
    Kurt-Dekker likes this.
  6. RyanDavidson417

    RyanDavidson417

    Joined:
    Sep 15, 2022
    Posts:
    4
    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
     
  7. patrick_Drake

    patrick_Drake

    Joined:
    Jul 2, 2023
    Posts:
    16
    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.
     
  8. RyanDavidson417

    RyanDavidson417

    Joined:
    Sep 15, 2022
    Posts:
    4
    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.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    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.