How to load multiple entries from one Binary file?

Hello all,

In my game I save and load data using Binary file.
The save and load of one item was successful for me, however my goal is to save each passed level and once the game is over, show the player their gaming summary of all passed levels and the score for each level.
This is the code so far (based in this tutorial):

using System;

[Serializable]
public class Player_Tutorial
{
    private string _itemName;
    private int _itemNumber;
    private float _score;

    public Player_Tutorial() { }

    public Player_Tutorial(string name, int number, float score)
    {
        _itemName = name;
        _itemNumber = number;
        _score = score;
    }

    public string ItemName { get { return _itemName;} set {  _itemName = value;}} 
 //similar get/set

Data code:

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

public class SavingData_Tutorial : MonoBehaviour
{
    private Player_Tutorial myPlayer;
    private string DATA_PATH = "/MyGame.dat";
    private string path; 

    private void Start()
    {
        path = Application.persistentDataPath + DATA_PATH;
        //SaveData();
        LoadData();
        if (myPlayer!=null)
        {
            print("Player name: " + myPlayer.ItemName + " Player itemNumber: " + myPlayer.ItemNumber + " Player nw: " + myPlayer.Score);
        }
    }

    //this will called once a level is completed
    void SaveData()
    {
        FileStream file = null;
        try
        {
            BinaryFormatter bf = new BinaryFormatter();
            file = new FileStream(path, FileMode.Append);
            Player_Tutorial p = new Player_Tutorial("The wood", 1, 30);//for testing

            bf.Serialize(file, p); //store this data at this file location
        }

        catch (Exception e)
        {
            if (e !=null)
            {
                Debug.Log("File not found: " + e.Message);
            }
        }
        finally
        {
            if (file != null) file.Close();
        }
    }

    void LoadData()
    {
        FileStream file = null;
        try
        {
            BinaryFormatter bf = new BinaryFormatter();
            file = File.Open(path, FileMode.Open);
            myPlayer = bf.Deserialize(file) as Player_Tutorial;
            
        }
        catch(Exception e)
        {
            Debug.Log("Could not read from file: " + e.Message);
        }

        finally
        {
            if (file != null) file.Close();
        }
    }
}

The goal is to show the summary of each entry in a UI canvas at the end of the game, for example:

“The wood”, level 1 - score: 30

“The ocean”, level 2 - score:10

“The moon”, level 3 - score: 50

Is it a good idea to use binary files for that?

Thank you

So the premise is simple. Your problem is that you are trying to write multiple blobs of data into a binary file with their headers.


You dont need to do this: What you should do instead is write a collection/ array into the file instead (or use composition to create a SaveState object that contains all the data you want in its fields/ members. )


On save: You dont need to append to the file you just need to wipe it and save the whole blob of data in the file again.

On load: you just need to deserialise the file into the same collection/ array type and it will all work as demonstrated below.


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

public class FileSaver<TSaveFile>
{
    public List<TSaveFile> Data = new List<TSaveFile>();
    private string _path;

    public FileSaver(string path)
    {
        if(typeof(TSaveFile).GetCustomAttributes(typeof(SerializableAttribute), false).Length == 0)
            throw new NotSupportedException($"{nameof(TSaveFile)} must be be decorated with a SerializableAttribute");
            
        _path = path;
        LoadData();
    }

    public void SaveData(params TSaveFile[] data)
     {
         Data.AddRange(data);

         using(FileStream stream = File.Create(_path))
         {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, Data);
         }
     }
 
     public IEnumerable<TSaveFile> LoadData()
     {
         if(!File.Exists(_path)) SaveData();

         using(FileStream stream = File.OpenRead(_path))
         {
             if(stream.Length != 0)
             {
                BinaryFormatter formatter = new BinaryFormatter();
                Data = formatter.Deserialize(stream) as List<TSaveFile>;
             }
         }

         return Data;
     }
}


public class SaveManager : MonoBehaviour
{
    FileSaver<PlayerTutorial> PlayerTutorialSaver = new FileSaver<PlayerTutorial>("playerTutorials.save");   

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Q))
        {
            PlayerTutorialSaver.SaveData(new PlayerTutorial{
                SceneName = "Wooter"
            });
        }   
        else if(Input.GetKeyDown(KeyCode.W))
        {
            var data = PlayerTutorialSaver.LoadData();
            // NOTE: Use data here
            Debug.Log(string.Join(", ", data.Select(d => d.SceneName)));
        }
    }
}