-1

I have an int variable called "Stash". When I beat the level, the game will save. When it saves, I want Stash goes up by however much my score was that level. Currently, my code isn't saving my stash value, and it just sets it to "0(stash default) + Score(whatever my score was that level)." How can I write within this code to make my goal happen?

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

[RequireComponent(typeof(GameData))]
public class SaveScript : MonoBehaviour
{
    private GameData gameData;
    private string savePath;

    void Start()
    {
        gameData = GetComponent<GameData>();
        savePath = Application.persistentDataPath + "/gamesave.save";
    }

    public void SaveData()
    {
        var save = new Save()
        {
            SavedStash = gameData.Stash + gameData.Score      //<------ (This code)
        };

        var binaryFormatter = new BinaryFormatter();
        using (var fileStream = File.Create(savePath))
        {
            binaryFormatter.Serialize(fileStream, save);
        }

        Debug.Log("Data Saved");
    }

    public void LoadData()
    {
        if (File.Exists(savePath))
        {
            Save save;

            var binaryFormatter = new BinaryFormatter();
            using (var fileStream = File.Open(savePath, FileMode.Open))
            {
                save = (Save)binaryFormatter.Deserialize(fileStream);
            }
            gameData.Stash = save.SavedStash;              //<---------- (and this code)
            gameData.ShowData();

            Debug.Log("Data Loaded");
        }
        else
        {
            Debug.LogWarning("Save file doesn't exist");
        }
    }
}

Also relevant stuff in my GameData file:

    public int Stash { get; set; }
    public int StashNew { get; set; }
    public int Score { get; set; }

The StashNew was just an idea I had to get the whole thing working.

1 Answers1

1

First of all: Never simply use + "/" for system file paths!

Different target devices might have different file systems with different path separators.

Rather use Path.Combine which automatically inserts the correct path separators depending on the OS it is running on.

savePath = Path.combine(Application.persistentDataPath, "gamesave.save");

Then to the main issue:

Properties like

public int Stash { get; set; }
public int StashNew { get; set; }
public int Score { get; set; }

are not de-/serialized by the BinaryFormatter!

Therefore when you do

binaryFormatter.Serialize(fileStream, save);

the numbers are not even written to the output file!

Then when reading in the same thing doesn't work and your properties simply keep the int default value of 0.


Simply remove these { get; set; } in order to convert your properties into serializable Fields and you should be fine.

public int Stash;
public int StashNew;
public int Score;

Just for the demo I used this code

public class MonoBehaviourForTests : MonoBehaviour
{
    [SerializeField] private GameData In;
    [SerializeField] private GameData Out;

    [ContextMenu("Test")]
    private void Test()
    {
        var formatter = new BinaryFormatter();

        using (var fs = File.Open(Path.Combine(Application.streamingAssetsPath, "Example.txt"), FileMode.OpenOrCreate, FileAccess.Write))
        {
            formatter.Serialize(fs, In);
        }

        using (var fs = File.Open(Path.Combine(Application.streamingAssetsPath, "Example.txt"), FileMode.Open, FileAccess.Read))
        {
            Out = (GameData)formatter.Deserialize(fs);
        }
    }

    [Serializable]
    private class GameData
    {
        public int Stash;
        public int StashNew;
        public int Score;
    }
}

As you can see this has also the side-efect that now the fields actually appear in the Inspector and can be edited. I write values only to the In instance and after hitting Test you can see them loaded into the Out instance via the file.

enter image description here


However in general I wouldn't recommend to use the BinaryFormatter here at all ... it adds a lot of overhead garbage you don't need at all. Simply go for e.g. JSON or a comparable simple file format like XML or CSV which only store the relevant data.

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Thank you for the tips, I will definitely use them, but my question remains unanswered. How can I add the score to the stash when I save? – Journey Thouin Apr 01 '20 at 14:29
  • well the answer is you already do this here `gameData.Stash + gameData.Score` but your problem was that the int value was never stored ... now it is serialized and therefore now it should get stored and correctly loaded => added to the existing loaded score – derHugo Apr 01 '20 at 14:30
  • ah! I understand what you meant by that now. Thanks. – Journey Thouin Apr 01 '20 at 14:36
  • I took this video to help explain my problem. https://imgur.com/a/eBysUoc When the level ends and I click either button, the game will save. This should add the score to the stash number you see on the left side of the level select. However it just returns it, which I believe is because it isn't saving the stash value properly. I followed your tips, but to no avail. – Journey Thouin Apr 01 '20 at 15:05
  • well you seem to save the stash value before loading in the existing one so overwriting the existing file. In you code I can't see where and when your methods are called ... you should set breakpoints and find out why it writes the values before it loads them. You seem to load only via the button right? – derHugo Apr 01 '20 at 15:48
  • That was the issue! I changed it to save it when you touch the flag, and load it when you press one of the buttons at the end of the game. I’m still a bit confused as to why you have to load the data in the level, as well as outside of it, but other than that it’s all good. – Journey Thouin Apr 01 '20 at 18:09
  • @JourneyThouin because I guess inside the levels you load another scene so whatever manager handles the data in this scene at the beginning starts with the default values .... you wouldn't have to do this if you either make your manager a **singleton** and `DontDestroyOnLoad`, use a static class to store the data or use additive scene loading instead – derHugo Apr 02 '20 at 05:23