Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Bug Not Understanding Reason For Error

Discussion in 'Scripting' started by mstewart52178, Nov 23, 2022.

  1. mstewart52178

    mstewart52178

    Joined:
    Feb 8, 2022
    Posts:
    18
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using TMPro;
    6. using UnityEngine.SceneManagement;
    7. using System.IO;
    8. using System.Runtime.Serialization.Formatters.Binary;
    9.  
    10. public class GameManager : MonoBehaviour
    11. {
    12.     public static GameManager instance = null;
    13.     [SerializeField] private float maxTime;
    14.     private float timeLeft;
    15.     private int sceneIndex;
    16.     private TextMeshProUGUI collectedText;
    17.     private TextMeshProUGUI timerText;
    18.     private RubyController player;
    19.  
    20.     public GameData data;
    21.     string dataFilePath;
    22.     BinaryFormatter bf;
    23.  
    24.     private int callTest;
    25.  
    26.     private void Awake()
    27.     {
    28.         sceneIndex = SceneManager.GetActiveScene().buildIndex;
    29.         if(instance == null)
    30.         {
    31.             instance = this;
    32.             DontDestroyOnLoad(gameObject);
    33.         }
    34.         else
    35.         {
    36.             Destroy(gameObject);
    37.         }
    38.  
    39.         bf = new BinaryFormatter();
    40.         dataFilePath = Application.persistentDataPath + "/game.dat";    //use persistentDataPath for mobile
    41.         //Debug.Log(dataFilePath);
    42.     }
    43.  
    44.     private void Start()
    45.     {
    46.         timeLeft = maxTime;
    47.         //Debug.Log("Start");
    48.        // Debug.Log(bf);
    49.         //Debug.Log(data);
    50.     }
    51.  
    52.     private void Update()
    53.     {
    54.         if (sceneIndex != 0 && timeLeft > 0)
    55.         {
    56.             UpdateTimer();
    57.         }
    58.     }
    59.  
    60.     private void OnLevelWasLoaded(int level)
    61.     {
    62.         //sceneIndex = level;
    63.         if(level > 0)
    64.         {
    65.             collectedText = GameObject.FindGameObjectWithTag("FruitText").GetComponent<TextMeshProUGUI>();
    66.             timerText = GameObject.FindGameObjectWithTag("TimerText").GetComponent<TextMeshProUGUI>();
    67.             player = GameObject.FindGameObjectWithTag("Player").GetComponent<RubyController>();
    68.             data.fruitCount = 0;
    69.             timeLeft = maxTime;
    70.             FruitUI(0);
    71.  
    72.             callTest++;
    73.             Debug.Log("Call: " + callTest);
    74.         }
    75.         else
    76.         {
    77.             callTest = 0;
    78.             LoadData();
    79.         }
    80.         /*Debug.Log("Level Load");
    81.         Debug.Log(bf);
    82.         Debug.Log(data);
    83.         SaveData();*/
    84.     }
    85.  
    86.     private void OnEnable()
    87.     {
    88.         //Debug.Log("Data Loaded");
    89.         LoadData();
    90.     }
    91.  
    92.     private void OnDisable()
    93.     {
    94.         //Debug.Log("Data Saved");
    95.         Debug.Log("OnDisable: ");
    96.         SaveData();
    97.     }
    98.  
    99.     public void SaveData()
    100.     {
    101.         Debug.Log("Save: " + dataFilePath);
    102.         Debug.Log("Save: " + bf);
    103.         Debug.Log("Save: " + data);
    104.         FileStream fs = new FileStream(dataFilePath, FileMode.Create);
    105.         bf.Serialize(fs, data);
    106.         fs.Close();
    107.         //Debug.Log("Data Saved");
    108.     }
    109.  
    110.     public void LoadData()
    111.     {
    112.         if (File.Exists(dataFilePath))
    113.         {
    114.             FileStream fs = new FileStream(dataFilePath, FileMode.Open);
    115.             data = (GameData)bf.Deserialize(fs);
    116.             //Debug.Log("# of berries = " + data.fruitCount);
    117.             fs.Close();
    118.         }
    119.     }
    120.  
    121.     private void UpdateTimer()
    122.     {
    123.         timeLeft -= Time.deltaTime;
    124.         timerText.text = Mathf.RoundToInt(timeLeft).ToString();
    125.         if(timeLeft <= 0)
    126.         {
    127.             timerText.text = 0.ToString();
    128.             Start();
    129.             player.Respawn();
    130.         }
    131.     }
    132.  
    133.     public void FruitUI(int collected)
    134.     {
    135.         data.fruitCount += collected;
    136.         collectedText.text = data.fruitCount.ToString();
    137.     }
    138. }
    139.  
    I am recieving this error:
    ArgumentNullException: Value cannot be null.
    Parameter name: path
    System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) (at <695d1cc93cca45069c528c15c9fdd749>:0)
    System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode) (at <695d1cc93cca45069c528c15c9fdd749>:0)
    (wrapper remoting-invoke-with-check) System.IO.FileStream..ctor(string,System.IO.FileMode)
    GameManager.SaveData () (at Assets/Scripts/GameManager.cs:135)
    GameManager.OnDisable () (at Assets/Scripts/GameManager.cs:105)
    UnityEngine.Object:Destroy(Object)
    GameManager:Awake() (at Assets/Scripts/GameManager.cs:42)

    Entering play mode in scene 1 throws no error. I hit play button and enter scene 2 with no errors. I hit the back button which calls SaveData and return to scene 1. Scene 1 then gives the error provided. After returning to scene 1, I found the Debug.Log for dataFilePath and bf in SaveData are empty, but I am not sure why.

    Also; I was wondering if binary saving is acceptable. I was reading some webpages that were saying that it was easily exploitable and not the ideal way of saving data. What alternatives would be better suited for a save system if binary is not the way to go?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,519
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,607
    I'm guessing your data is null.

    Also, you should not be using binary formatter; it's unsafe and out of date.
     
    Kurt-Dekker likes this.
  4. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,260
    When you call Destroy, OnDisable is called immediately. It doesn't execute the rest of the code in Awake that sets the data path or even initialize that evil BinaryFormatter. You need to not use OnDisable to save, something like OnApplicationQuit would be better.

    Test it with this to see.
    Code (CSharp):
    1. public class DestroyTest : MonoBehaviour
    2. {
    3.     private void Awake()
    4.     {
    5.         Destroy(gameObject);
    6.         Debug.Log("Awake!");
    7.     }
    8.  
    9.     private void OnDisable()
    10.     {
    11.         Debug.Log("Disabled!");
    12.     }
    13.  
    14.     private void OnDestroy()
    15.     {
    16.         Debug.Log("Destroyed!");
    17.     }
    18. }
    It will tell you the order in which it happens.
     
    mstewart52178 likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,519
    Keep it simple and human readable: JSON.

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
    mstewart52178 likes this.
  6. mstewart52178

    mstewart52178

    Joined:
    Feb 8, 2022
    Posts:
    18
    I figured it might have something to do with the order of function calls. I'll definitely give this a try. Thank you.

    Thank you for the links. I've seen posts here and there of people saying JSON is a good choice, so I will reference these for sure.