Acces a mainclass from another classes using more constructors

Viewed 51

I'm working in Unity to make a game and I'm having some problems with a Save System.

What I want is to save variables from more than 1 script.

  1. In [GameManager.cs] i'm saving things like Level, Currency and Scores, all of that in one file.
  2. In [MenuManager.cs] I want to save the user name IN ANOTHER file (in case he didn't did it yet) using the same saving system.
  3. Acces those variables in other scripts. Example: I want to Log the variables saved in point 1) (GameManager.cs) in point 2) (MenuManager.cs). Or ... is possible to load(READ them from the saved file) them from [MenuManager.cs] using the same script from [GameManager.cs]? (I managed to load them only from [GameManager.cs] using the LoadPlayer() function).

Here is how 1st application run look like: https://imgur.com/a/IqoQjpX and the second: https://imgur.com/a/QpienFQ

The question is why in second photo it outputs blank? Why the save system works perfectly fine for [GameManager.cs] and for [MenuGame.cs] it doesn't?

Here s how the script looks:

[PlayerData]

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

[System.Serializable]
public class PlayerData
{
    public int Score;
    public int BestScore;
    public string UserName;

    // 1st Constructor
    public PlayerData(GameManager Player)
    {
        BestScore = Player.BestScore;
    }
    
    // 2nd Constructor
    public PlayerData(MenuManager User)
    {
        UserName = User.UserName;
    }
}

[SaveSystem.cs]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

public class SaveSystem
{
    public static void SavePlayer(GameManager Player)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        string Path = Application.persistentDataPath + "/player.xd";
        FileStream Stream = new FileStream(Path, FileMode.Create);

        PlayerData Data = new PlayerData(Player);
        formatter.Serialize(Stream, Data);
        Stream.Close();
    }
    
    public static void SavePlayer(MenuManager Player)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        string Path = Application.persistentDataPath + "/name.xd";
        FileStream Stream = new FileStream(Path, FileMode.Create);

        PlayerData Data = new PlayerData(Player);
        formatter.Serialize(Stream, Data);
        Stream.Close();
    }

    public static PlayerData LoadPlayer()
    {
        string Path = Application.persistentDataPath + "/player.xd";

        if (File.Exists(Path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream Stream = new FileStream(Path, FileMode.Open);
            PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
            Stream.Close();
            return Data;
        }
        else
        {
            return null;
        }
    }
    
    public static PlayerData LoadName()
    {
        string Path = Application.persistentDataPath + "/name.xd";

        if (File.Exists(Path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream Stream = new FileStream(Path, FileMode.Open);
            PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
            Stream.Close();
            return Data;
        }
        else
        {
            return null;
        }
    }
}

[MenuManager.cs]

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

public class MenuManager : MonoBehaviour
{
    public string UserName = "whereIsTheLogic";
    public InputField input;

    public void Start()
    {
        // Here I checked if the user saved his name
        PlayerData Data = SaveSystem.LoadName();
        if (Data == null)
        {
            Debug.Log("User didn't saved his name");

            /* so ... We jump to SaveButton() */
        }
        else
        {
            Debug.Log("User saved his name");

            /* The file exists ... so we assume this isn't his first application use, right? */
            /* Now what I want is to simply log the name ... just log already 4 god sake!! */

            UserName = Data.UserName; 
            Debug.Log("User saved name is: " + UserName);
            /* BUT it outputs blank NOT null or the inserted name) */
        }
    }

    public void SaveButton()
    {
        string userName = input.text;
        SaveSystem.SavePlayer(this);
        Debug.Log("The user name: " + userName); /* yes ... it outputs the correct user answer */
    }

    public void PlayGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }

}

[GameManager.cs] (only things that matter ...)

public class GameManager : MonoBehaviour
{
    public int Score = 0;
    public int BestScore = 0;

    void Start()
    {
        /* Here I load saved variables (Score/Best Score)
           and it loads them correctly
        */
        LoadPlayer(); 
    }

    public void SavePlayer()
    {
        // Here I save the above mentioned variables and ... yes it works correctly
        SaveSystem.SavePlayer(this);
        Debug.Log("Salvat");
    }

    public void LoadPlayer()
    {
        PlayerData Data = SaveSystem.LoadPlayer();
        Debug.Log("Loaded");
        BestScore = Data.BestScore;
    }
}
2 Answers

So the main issue comes from

string userName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + userName);

You make your Log dependend on the local variable userName, but you never update any field value of this (= MenuManager).

What you probably rather wanted would be updating the existing field UserName:

UserName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + UserName);

In general it is quite confusing to me that you have two separate files name.xd and player.xd, both storing serialized data for the same class PlayerData. And then each file only actually uses very specific fields of that class.

You should rather either have the two files but serialize actually the data you need or - what I would do - have one file serializing everything like

public class SaveSystem
{
    private PlayerData _data = new PlayerData();

    private string _path = Path.Combine(Application.persistentDataPath, "player.xd");

    public static void SavePlayer(GameManager Player)
    {
        using(var stream = new File.Open(_path, FileMode.Create))
        {
            var formatter = new BinaryFormatter();

            // instead of creating it new only update the field in the existing data
            _data.BestScore = Player.BestScore;

            formatter.Serialize(Stream, Data);
        }
    }
    
    public static void SavePlayer(MenuManager Player)
    {
        using(var stream = new File.Open(_path, FileMode.Create))
        {
            var formatter = new BinaryFormatter();

            // instead of creating it new only update the field in the existing data
            _data.UserName = User.UserName;

            formatter.Serialize(Stream, Data);
        }
    }

    
    public static bool LoadPlayer(out PlayerData playerData)
    {
        playerData = default;

        // Since you seem to anyway load only exactly ONCE per app start
        // You could simply call this only ONCE
        if(_data != null)
        {
            playerData = _data;
            return true;
        }

        if (!File.Exists(path))
        {
            return false;
        }

        using(var stream = File.Open(path, FileMode.Open))
        {
            var formatter = new BinaryFormatter();
            
            _data = formatter.Deserialize(Stream) as PlayerData;
        }

        playerData = _data; 
        return true;
    }
}

and don't need the constructors so only

[System.Serializable]
public class PlayerData
{
    public int Score;
    public int BestScore;
    public string UserName;
}

And you now would rather use e.g.

public class MenuManager : MonoBehaviour
{
    public string UserName = "whereIsTheLogic";
    public InputField input;

    private void Start()
    {
        if(!SaveSystem.LoadPlayer(out var playerData))
        {
            Debug.Log("User didn't saved his name");

            /* so ... We jump to SaveButton() */
        }
        else
        {
            Debug.Log("User saved his name");

            UserName = playerData.UserName; 
            Debug.Log("User saved name is: " + UserName);
        }
    }

    public void SaveButton()
    {
        UserName = input.text;
        SaveSystem.SavePlayer(this);
        Debug.Log("The user name: " + UserName);
    }

    public void PlayGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }
}

and accordingly

public class GameManager : MonoBehaviour
{
    public int Score = 0;
    public int BestScore = 0;

    void Start()
    {
        LoadPlayer(); 
    }

    public void SavePlayer()
    {
        SaveSystem.SavePlayer(this);
        Debug.Log("Salvat");
    }

    public void LoadPlayer()
    {
        if(SaveSystem.LoadPlayer(out var playerData))
        {
            Debug.Log("Loaded");
            BestScore = playerData.BestScore;
        }
    }
}

You are right about the local variable :) Looked in your script a while, I managed to fix the errors (probably cause we have different compilers) but the saving system doesn't work with your method. Thanks for your time and help but I think your script is a bit way too much for me (at least for now). The things I had to change to your code to be able to compile the program: https://pastebin.com/umHpLJmL

I managed to save both score and name in one single file but with a non religious method. Even if my game isn't about optimizing or things like that, I still want to get better results and to improve my knowledge.

So, down below I made the following modifications:

[SaveSystem.cs]

public class SaveSystem
{
    static string Path = Application.persistentDataPath + "/Save";

    public static void SavePlayer(GameManager Player)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream Stream = new FileStream(Path, FileMode.Create);

        // FileStream Stream = File.Open(Path, FileMode.Open);


        PlayerData Data = new PlayerData(Player);
        formatter.Serialize(Stream, Data);
        Stream.Close();
    }

    public static void SavePlayer(MenuManager Player)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream Stream = new FileStream(Path, FileMode.Create);

        // FileStream Stream = File.Open(Path, FileMode.Open);

        PlayerData Data = new PlayerData(Player);
        formatter.Serialize(Stream, Data);
        Stream.Close();
    }

    public static PlayerData LoadPlayer()
    {
        if (File.Exists(Path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream Stream = new FileStream(Path, FileMode.Open);
            PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
            Stream.Close();
            return Data;
        }
        else
        {
            return null;
        }
    }
}

[GameManager.cs]

And here's the thing: in order not to lose any variables while saving the score, I made a load just before the new saving and then I saved the username in a global GameManager variable. So now I can easily save the game and after restarting it both name and score are saved.

public void SavePlayer()
{
    PlayerData Data = SaveSystem.LoadPlayer();
    UserName = Data.UserName; 
    SaveSystem.SavePlayer(this);
    Debug.Log("Salvat " + Data.UserName);
}
    
public void LoadPlayer()
{
    PlayerData Data = SaveSystem.LoadPlayer();
    Debug.Log("Loaded");
    BestScore = Data.BestScore;
}

[PlayerData.cs]

[System.Serializable]
public class PlayerData
{
    public int Score;
    public int BestScore;
    public string UserName;

    public PlayerData(GameManager Player)
    {
        BestScore = Player.BestScore;
        UserName = Player.UserName;
    }

    public PlayerData(MenuManager Player)
    {
        UserName = Player.UserName;
    }
}

So my question is, how can I do it JUST better? I mean, isn't there an option to save things in file in a specific section and not to modify the other things which are in the file?

Example:

[Level="18"]
[Name="Andrei"]

And to be able to modify only the parameter I choose to.

Another solution which came in my mind is (don't know if is possible to implement, just asking):

public static void SavePlayer(GameManager obj1, MenuManager obj2)
{
    // Loading both global scene variables
    PlayerData _data1 = new PlayerData(obj1);
    PlayerData _data2 = new PlayerData(obj2);
    
    // Copying the variables into global SaveSystem variables
    Score = _data1.Score;
    UserName = _data2.UserName;
    
    // Saving global SaveSystem variables into document
    formatter.Serialize(Stream, this);
}

There are two problems:

  1. Calling the function: SaveSystem.SavePlayer(GameManager, MenuManager); I used to make the calls with this. With what should I replace it? Isn't it a refference to current class name?
  2. formatter.Serialize(Stream, this); What I want in point 2) is to save the global class variables into document. But how? Using this doesn't make the refference to the class.
Related