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
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question DataPersistence not saving an array variable when changing scene

Discussion in 'Scripting' started by naimuhair1999, Feb 17, 2023.

  1. naimuhair1999

    naimuhair1999

    Joined:
    Feb 17, 2023
    Posts:
    4
    Running the script on a single scene works fine but once I tried changing scenes, it just stops working.
    But it does save other variables from a different script but not the array

    Code (CSharp):
    1.     public void LoadData(GameData data)
    2.     {
    3.         for (int i = 0; i < _globalKanaUnlock.Length; i++)
    4.         {
    5.             data.globUnlocked.TryGetValue(i, out _globalKanaUnlock[i]);
    6.             data.collectibleCount.TryGetValue(i, out _item[i]);
    7.         }
    8.     }
    9.  
    10.     public void SaveData(GameData data)
    11.     {
    12.         for (int i = 0; i < _globalKanaUnlock.Length; i++)
    13.         {
    14.             if (data.globUnlocked.ContainsKey(i))
    15.             {
    16.                 data.globUnlocked.Remove(i);
    17.                 data.collectibleCount.Remove(i);
    18.                
    19.                
    20.             }
    21.                 data.globUnlocked.Add(i, _globalKanaUnlock[i]);
    22.                 data.collectibleCount.Add(i, _item[i]);
    23.            
    24.         }
    25.     }
    the load function works fine across level, its just the saving function doesn't seem to work
     
  2. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    This really is far too little info to work with.
    For example:

    - What script is this code on?
    - Is that script attached to a GO set to DontDestroyOnLoad?
    - What type of object is 'GameData'?
    - How are you changing scenes? (Additive Load? Destructive Load?)
    - What types are 'globUnlocked' and 'collectibleCount'? (Lists?)
     
  3. naimuhair1999

    naimuhair1999

    Joined:
    Feb 17, 2023
    Posts:
    4
    The script was put in a gamemanager object which has DontDestroyOnLoad
    GameData was a serializable script used to store the data


    Code (CSharp):
    1. [System.Serializable]
    2.  
    3. public class GameData
    4. {
    5.     public string SceneName;
    6.     public Vector3 playerPosition;
    7.     public Quaternion playerRotation;
    8.     public SerializableDictionary<string, bool> itemCollected;
    9.     public SerializableDictionary<int, bool> globUnlocked;
    10.     public SerializableDictionary<int, int> collectibleCount;
    11.  
    12.     public GameData()
    13.     {
    14.         SceneName = string.Empty;
    15.         playerPosition = Vector3.zero;
    16.         playerRotation = Quaternion.Euler(0, 0, 0);
    17.         itemCollected = new SerializableDictionary<string, bool>();
    18.         globUnlocked = new SerializableDictionary<int, bool>();
    19.         collectibleCount = new SerializableDictionary<int, int>();
    20.     }
    21. }
    I'm changing scene using
    Code (CSharp):
    1. AsyncOperation operation = SceneManager.LoadSceneAsync(_levelName);
    so that I can display a loading screen while waiting for the scene to load

    as for the globUnlocked and collectibleCount, it was a serializable dictionary stated in the GameData, it was supposed to store the data from the inventory script which is an array, bool[] and int[]
     
  4. naimuhair1999

    naimuhair1999

    Joined:
    Feb 17, 2023
    Posts:
    4
    The scripts I used as reference are from Trever Mock
    Specifically this video
     
  5. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    My first guess would be that you're somehow referencing a different object during your load as compared to your save.
    But since you claim that "it does save other variables from a different script but not the array", that would not be likely.

    My second guess is looking at
    out _globalKanaUnlock[i]

    I personally haven't used array-elements as out-params before. I'm not 100% sure if that works.
    Have you tried seperating that out?
    i.e.
    Code (CSharp):
    1. if (data.globUnlocked.TryGetValue(i, out var val))
    2.     _globalKanaUnlock[i] = val;
     
  6. naimuhair1999

    naimuhair1999

    Joined:
    Feb 17, 2023
    Posts:
    4
    I'm not sure what's wrong either, when putting and running the game from the game level, the save and load functions works perfectly but only on one level, then it just loads the old data without overwriting the globUnlocked and collectibleCount in the save file.

    Outputting the data is fine, it can load and store the data in the array but somehow it can't/didn't save the array values to the save file.
    The other variable such as playerPosition, playerRotation and itemCollected works just fine.
    As for itemCollected, it collects GO and stores it's data in array so it works I guess.
    Not sure why storing an array itself doesn't work across level other than the first level the scripts run on.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,946
    Nothing magic here... it just sounds like you have a bug. Go investigate it and fix it!

    Here is how to begin your debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    When in doubt, print it out!(tm)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
  8. Thatan0s

    Thatan0s

    Joined:
    Nov 8, 2021
    Posts:
    1
    It's me again, I'm using my own account this time, after debugging for... a whole day...
    I tried deleting every instance of game manager across the level and now it seems to be working.
    Just to be safe, I try to delete the game manager everytime when I would go to main menu.

    and if you're wondering, I did put DontDestroyOnLoad and delete if there's duplicating instances
    Code (CSharp):
    1.     private void Awake()
    2.     {
    3.         DontDestroyOnLoad(this);
    4.  
    5.         if (_inv == null)
    6.         {
    7.             _inv = this;
    8.         }
    9.         else
    10.         {
    11.             Destroy(gameObject);
    12.         }
    13.     }
    probably the issue lies when there's two script using those same statement in one gameObject making one of them obsolete?
     
  9. Kurt-Dekker

    Kurt-Dekker

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

    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.