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. Dismiss Notice

Resolved String wiping on scene change even when using DontDestroyOnLoad

Discussion in 'Editor & General Support' started by Tombulator, Sep 3, 2023.

  1. Tombulator

    Tombulator

    Joined:
    Nov 11, 2021
    Posts:
    1
    I'm attempting to complete the 3rd mission in the Junior Programmer pathway but I'm running into a bug where my username string is being wiped as the scene changes. As mentioned in the title I am using the DontDestroyOnLoad method and I've looked over previous tutorials numerous times but can't figure out where I've gone wrong. My username variable is declared in a script named MenuManager, which is attatched to a MenuManager gameObject in the scene. Another variable, a bool called hasUsername, that is in the same script retains it's value between scenes which just confuses me even more, as I can't work out why it's only my string that is being wiped.

    I have attached the script below.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using TMPro;
    4. using UnityEngine;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class MenuManager : MonoBehaviour
    8. {
    9.     public static MenuManager instance;
    10.  
    11.     public bool hasUsername = false;
    12.  
    13.     public TMP_InputField usernameInput;
    14.     public TMP_Text needUsername;
    15.  
    16.     public string username;
    17.  
    18.     private void Awake()
    19.     {
    20.         // start of new code
    21.         if (instance != null)
    22.         {
    23.             Destroy(gameObject);
    24.             return;
    25.         }
    26.         // end of new code
    27.  
    28.         instance = this;
    29.         DontDestroyOnLoad(gameObject);
    30.     }
    31.  
    32.     public void StartGame()
    33.     {
    34.         SetUsername();
    35.  
    36.         Debug.Log("Username: " + username);
    37.  
    38.         if(hasUsername)
    39.         {
    40.             SceneManager.LoadScene(1);
    41.         }
    42.     }
    43.  
    44.     public void SetUsername()
    45.     {
    46.         username = usernameInput.text;
    47.         if (username != "")
    48.         {
    49.             hasUsername = true;
    50.             needUsername.gameObject.SetActive(false);
    51.         }
    52.         else
    53.         {
    54.             needUsername.gameObject.SetActive(true);
    55.         }
    56.     }
    57. }
    58.  
     
  2. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,051
    Debug your script using
    Debug.Log
    and/or attaching a debugger.

    There's only one place where
    username
    is being set (assuming no other script is setting that field), so I'd start by adding a log after it's set, including the value it is set to, e.g:
    Code (CSharp):
    1. Debug.Log($"SetUsername username = '{username}'", this);
    I notice that you only ever set
    hasUsername
    to
    true
    . If
    SetUsername
    is called again when the input field is empty,
    username
    will be set to an empty string but
    hasUsername
    will remain
    true
    .
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    While I appreciate that you're following a given tutorial, this type of "Unity singleton" construct:

    ... is the source of gallons of programmer tears and torn-out gray hair. It comes so close to being useful but falls short and has so many hidden minefields like the one you're standing in right now.

    If you need ANY ( and I do mean ANY) kind of longer-than-one-scene-lived object, follow one of these patterns, and never NEVER NEVER NEVER place the long-lived item in any scene.

    ULTRA-simple static solution to a GameManager:

    https://forum.unity.com/threads/i-need-to-save-the-score-when-the-scene-resets.1168766/#post-7488068

    https://gist.github.com/kurtdekker/50faa0d78cd978375b2fe465d55b282b

    OR for a more-complex "lives as a MonoBehaviour or ScriptableObject" solution...

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance

    Alternately you could start one up with a
    RuntimeInitializeOnLoad
    attribute.

    The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.
     
    Tombulator likes this.