Saving Player Data for Multiple Scenes/Levels

I am attempting to modify the [Unity Trash Cat Walkthrough][1] so that it will save information for multiple levels instead of just one general leaderboard for the entire game.

The issue is when I am trying to read the fastest times entries for each level from the file that was previously created. I am receiving the error: EndOfStreamException: Failed to read past end of stream.

This is happening at these lines:

entry.name = r.ReadString();
entry.time = r.ReadSingle();
entry.score = r.ReadInt32();
entry.sceneNum = r.ReadInt32();
entry.bonusCount = r.ReadInt32();

I, however, am not sure if this is happening bc I am writing to the file incorrectly or reading… or BOTH. Is this even a good way to go about this? Am I looking at this wrong?

Here is my code:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

public struct FastestTimeEntry : System.IComparable<FastestTimeEntry>
{
	public int sceneNum;
	public string name;
	public float time;
	public int score;
	public int bonusCount;

	public int CompareTo(FastestTimeEntry other)
	{
		// We want to sort from  lowest to highest time.
		return time.CompareTo(other.time);
	}
}



public class PlayerProgress{

	static protected PlayerProgress m_Instance;
    static public PlayerProgress instance { get { return m_Instance; } }
    protected string saveFile = "";
	static int s_Version = 1; 
	public int tokens;
    public int premium;
	public int unlockedSceneNum;
 	public bool licenseAccepted;

	 public string previousName = "Game Champ";

	public List<FastestTimeEntry>[] fastestTimes = new List<FastestTimeEntry>[GameControllerMain.sceneNames.Length];

    public int ftueLevel = 0;
    public int rank = 0;


	public int totalBonusCount = 0;
	public int totalScore;
	
	// Fastest Time management

	public int GetTimePlace(float time, int sceneNum)
	{
		FastestTimeEntry entry = new FastestTimeEntry();
		entry.time = time;
		entry.sceneNum = sceneNum;


		int index = fastestTimes[sceneNum].FindIndex(c => c.time == time);

		Debug.Log("GetTimePlace Index:"+ index);

		return index < 0 ? (~index) : index;
	}




	public void InsertTime(float time, int score, int sceneNum, int bonusCount)
	{
		Debug.Log("Inserting time...");
		FastestTimeEntry entry = new FastestTimeEntry();
		entry.time = time;
		entry.score = score;
		entry.sceneNum = sceneNum;
		entry.bonusCount = bonusCount;

		fastestTimes[sceneNum].Insert(GetTimePlace(time, sceneNum), entry);

        // Keep only the 10 best scores.
        while (fastestTimes[sceneNum].Count > 10)
            fastestTimes[sceneNum].RemoveAt(fastestTimes[sceneNum].Count - 1);
	}




	// File management

    static public void Create()
    {
		if (m_Instance == null)
		{
			m_Instance = new PlayerProgress();

        }

        m_Instance.saveFile = Application.persistentDataPath + "/save.bin";

        if (File.Exists(m_Instance.saveFile))
        {
            // If we have a save, we read it.
            m_Instance.Read();
        }
        else
        {
            // If not we create one with default data.
			NewSave();
        }
    }

	static public void NewSave()
	{
		Debug.Log("This is a new save!");

        m_Instance.tokens = 0;
        m_Instance.premium = 0;

		m_Instance.totalBonusCount = 0;
		m_Instance.totalScore = 0;

        m_Instance.ftueLevel = 0;
        m_Instance.rank = 0;

		for (int i = 0; i < GameControllerMain.sceneNames.Length; ++i){
			m_Instance.fastestTimes *= new List<FastestTimeEntry>();*
  •  }*
    
  •  m_Instance.Save();*
    
  • }*

public void Read()
{
BinaryReader r = new BinaryReader(new FileStream(saveFile, FileMode.Open));

int ver = r.ReadInt32();

tokens = r.ReadInt32();

// Save contains the version they were written with. If data are added bump the version & test for that version before loading that data.
if(ver >= 1)
{
premium = r.ReadInt32();

  •  	for (int i = 0; i < GameControllerMain.sceneNames.Length; ++i)*
    
  •  	{*
    

_ fastestTimes = new List();_
_ fastestTimes*.Clear();
int count = r.ReadInt32();
for (int j = 0; j < count; ++j)
{
Debug.Log(“Reading a fastest time entry”);
FastestTimeEntry entry = new FastestTimeEntry();*_

* entry.name = r.ReadString();*
* entry.time = r.ReadSingle();*
* entry.score = r.ReadInt32();*
* entry.sceneNum = r.ReadInt32();*
* entry.bonusCount = r.ReadInt32();*

_ fastestTimes*.Add(entry);
}*_

* }*

* previousName = r.ReadString();*
* licenseAccepted = r.ReadBoolean();*
* ftueLevel = r.ReadInt32();*
rank = r.ReadInt32();

* totalBonusCount = r.ReadInt32();*
* totalScore = r.ReadInt32();*
}

r.Close();
}

public void Save()
{
BinaryWriter w = new BinaryWriter(new FileStream(saveFile, FileMode.OpenOrCreate));

w.Write(s_Version);
w.Write(tokens);
w.Write(premium);

* // Write fastestTimes*
* for (int i = 0; i < GameControllerMain.sceneNames.Length; ++i){*
_ w.Write(fastestTimes*.Count);
for(int j = 0; j < fastestTimes.Count; ++i)
{
//w.Write(fastestTimes.name);
w.Write(fastestTimes[j].time);
w.Write(fastestTimes[j].sceneNum);
w.Write(fastestTimes[j].score);
w.Write(fastestTimes[j].bonusCount);
}
}*_

* // Write name.*
* w.Write(previousName);*

w.Write(licenseAccepted);

w.Write(ftueLevel);
w.Write(rank);

* w.Write(totalBonusCount);*
* w.Write(totalScore);*

w.Close();
}

}
_*[1]: Learn game development w/ Unity | Courses & tutorials in game design, VR, AR, & Real-time 3D | Unity Learn

what I did for my saving/loading, is to make a class, and store all savable/loadable variables inside it. Then just save this class (serialize) into a binary file. and read a file similarly (deserialize), and restore each variable from file (class)…

So, after finding out it was the empty/null list tripping me up above I have decided to go this route which is working:

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



[Serializable]
public struct FastestTimeEntry : System.IComparable<FastestTimeEntry>
{
	public int sceneNum;
	public string name;
	public float time;
	public int score;
	public int bonusCount;

	public int CompareTo(FastestTimeEntry other)
	{
		// We want to sort from  lowest to highest time.
		return time.CompareTo(other.time);
	}
}


public class PlayerProgress{

	static protected PlayerProgress m_Instance;
    static public PlayerProgress instance { get { return m_Instance; } }
    protected string saveFile = "";
	static int s_Version = 1; 
	public int coins;
        public int premium;
	public int unlockedSceneNum;
 	public bool licenseAccepted;

	 public string previousName = "Best Bud";

	public List<FastestTimeEntry>[] fastestTimes = new List<FastestTimeEntry>[GameControllerMain.sceneNames.Length];


    public int ftueLevel = 0;
    public int rank = 0;


	public int totalBonusCount = 0;
	public int totalScore;

	
	// Fastest Time management

	public int GetTimePlace(float time, int sceneNum)
	{
		Debug.Log("Getting time...");
		FastestTimeEntry entry = new FastestTimeEntry();
		entry.time = time;
		entry.sceneNum = sceneNum;

		int index = fastestTimes[sceneNum-1].BinarySearch(entry);

		Debug.Log("GetTimePlace Index:"+ index);

		return index < 0 ? (~index) : index;
	}




	public void InsertTime(float time, int score, int sceneNum, int bonusCount)
	{
		Debug.Log("Inserting time...");
		FastestTimeEntry entry = new FastestTimeEntry();
		entry.time = time;
		//entry.name = name;
		entry.score = score;
		entry.sceneNum = sceneNum;
		entry.bonusCount = bonusCount;

		int timePlace = GetTimePlace(time, sceneNum);
		fastestTimes[sceneNum-1].Insert(timePlace, entry);

        // Keep only the 10 best scores.
        while (fastestTimes[sceneNum-1].Count > 10)
            fastestTimes[sceneNum-1].RemoveAt(fastestTimes[sceneNum-1].Count - 1);
	}



    // File management
    static public void Create()
    {
		if (m_Instance == null)
		{
			m_Instance = new PlayerProgress();

        }

        m_Instance.saveFile = Application.persistentDataPath + "/save.bin";

        if (File.Exists(m_Instance.saveFile))
        {
            m_Instance.Read();
        }
        else
        {
			NewSave();
        }

    }

	static public void NewSave()
	{
		

        m_Instance.coins = 0;
        m_Instance.premium = 0;

		m_Instance.totalBonusCount = 0;
		m_Instance.totalScore = 0;

        m_Instance.ftueLevel = 0;
        m_Instance.rank = 0;

		for (int i = 0; i < GameControllerMain.sceneNames.Length; ++i){
			m_Instance.fastestTimes *= new List<FastestTimeEntry>();*
  •  }*
    
  •  m_Instance.Save();*
    
  • }*

public void Read()
{

  •  IFormatter formatter = new BinaryFormatter();*
    

using (Stream stream = new FileStream( saveFile, FileMode.Open, FileAccess.Read, FileShare.Read))
{
m_Instance = (PlayerProgress)formatter.Deserialize(stream);
}
}

public void Save()
{
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream(saveFile, FileMode.Create, FileAccess.Write, FileShare.None))
{

formatter.Serialize(stream, m_Instance);
}
}

}