Need help with a C# Save/Load script

Hello.

I am currently trying to learn how to implement a Save and Load function into my game.
A lot of tutorials mention player health, score, etc, but other than a virtual inventory script for door keys, it doesn’t have anything like that.
I really only need a way to save the player’s current position and any triggers / buttons that have been activated. Kind of like when you press “Pause” when play testing your game and then resuming afterwards.

So far I have made a script for a Main Menu, which handles the start of the game and quitting the game.

I searched around some tutorials and Frankenstein’d a code which I THOUGHT would work in some sort of way, but, of course, I am surely missing something, so I need help.

This is currently the code I use for my Main Menu: (There is a Canvas Panel with buttons that have been assigned each of the function, like: “Play”, “Save”, “Load” and “Quit”)

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

public class MenuManager : MonoBehaviour {

    public GameObject player;
    public GameObject MenuCamera;
    public GameObject MenuPanel;
    public Text button;
    public List<Game> savedGames = new List<Game>();

    void Start()
    {
        player.SetActive(false);
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Escape))
        {
            MenuCamera.SetActive(true);
            MenuPanel.SetActive(true);
            button.text = "Resume";
            player.SetActive(false);
        }
    }

    public void StartGame()
    {
        MenuCamera.SetActive(false);
        MenuPanel.SetActive(false);
        player.SetActive(true);
    }

    public void QuitGame()
    {
        Application.Quit();
    }

    public void Save()
    {
        SaveLoad.savedGames.Add(Game.current);
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "/savedGames.gd");
        bf.Serialize(file, SaveLoad.savedGames);
        file.Close();
        Application.LoadLevel(1);
    }

    public void Load()
    {
        if (File.Exists(Application.persistentDataPath + "/savedGames.gd"))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open);
            SaveLoad.savedGames = (List<Game>)bf.Deserialize(file);
            file.Close();
            Application.LoadLevel(1);
        }
    }

}

And these are some of the additional scripts that the tutorial told me to create:

(Called “Game”)

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Game
{

    public static Game current;

    public Game()
    {

    }

}

(Called “Character”)

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Character
{

    public string name;

    public Character()
    {
        this.name = "";
    }
}

Originally, the “Game” code looked like this:

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Game
{

    public static Game current;
    public Character knight;
    public Character rogue;
    public Character wizard;

    public Game()
    {
        knight = new Character();
        rogue = new Character();
        wizard = new Character();
    }

}

But I don’t actually need the RPG type classes, as it is not that type of game, so I deleted them. Not sure what else I could turn those into.

Also, the Save and Load functions in the Main Menu script were on their own in a seperate script and marked as “Static”, to be able to access them from everywhere, or something. I thought it might’ve worked if I just simply added them into the already existing Main Menu script.

So anyway, this mish mash of scripts I created doesn’t seem to do anything, so I am not sure what exactly is the issue, as it is not showing any errors either.
Can anyone, please, look into this and explain what I might be missing?

is empty. So when it serializes that class, it serializes empty space. And deserializes empty space. It needs to contain data you want to save.
So for example Game might look like

using UnityEngine;
using System;
//This struct is workaround for serializing Vector3
//Sorry for one-line-code here, but I feel better this way.
[Serializable]
public struct Vector3Serializer
{
    public float x,y,z;
    public Vector3Serializer(float x,float y,float z){this.x=x;this.y=y;this.z=z;}
//Following lines allow to cast Vector3 and Vector3Serializer into each other freely
    public static implicit operator Vector3(Vector3Serializer ser){return new Vector3(ser.x,ser.y,ser.z);}
    public static implicit operator Vector3Serializer(Vector3 vec){return new Vector3Serializer(vec.x,vec.y,vec.z);}
}

public class Game
{
    public static Game current;//This doesn't do anything other than let other code access current game through Game.current

    public Vector3Serializer playerPosition;//Note: Vector3 is not serializable, so using a serializable struct/class instead

    public int someValue; //This is serialized by default

    public Game()
    {
        //Fill out initial values here. An example:
        someValue = 5;
        playerPosition = new Vector3(2,3,5);
    }
}

Another way is to use PlayerPrefs - it’s good for simple small things, but it’s bad to save a lot with it. Remembering Vector3 will be something like

using UnityEngine;

public static class Loader
{
    public static Vector3 LoadVector3(string name)
    {
        float x,y,z;
        x = PlayerPrefs.GetFloat(name+"X");
        y = PlayerPrefs.GetFloat(name+"Y");
        z = PlayerPrefs.GetFloat(name+"Z");
    }
    public static void SaveVector3(string name, Vector3 value)
    {
        PlayerPrefs.SetFloat(name+"X", value.x);
        PlayerPrefs.SetFloat(name+"Y", value.y);
        PlayerPrefs.SetFloat(name+"Z",value.z);
    }
}

and then use them like that:

using UnityEngine;
public class SomeClass : MonoBehaviour
{
    public Vector3 value
    public int saveGameNumber = 0;
    void Start()
    {
        if(PlayerPrefs.HasKey(saveGameNumber+"PositionX")
            value = Loader.LoadVector3(saveGameNumber+"Position");
    }
    void OnApplicationQuit()
    {
        Loader.SaveVector3(saveGameNumber+"Position", value);
    }
}

Why don’t you make Character class with Vector3Serializable as position, and serialize that? You can call it a Pot or Dog or whatever else. Names don’t matter. You can call your player Character knight; - the line you removed - in code and noone will notice.

I recently learned and implemented writing and loading of save files into my game.
In my case, I’m saving what levels I’ve completed and the high scores on each level.

I write one instance of this class to the save file. The class has an array of serializable classes that holds the information for that level.

[Serializable]
class LevelStateData {
    public LevelInfoHolder[] LevelStatusArray {get; set;} 
}
using System.Collections;
using System;

[Serializable]
public class LevelInfoHolder {

    public bool Completed { get; set;}
    public int HighestPointAmount { get; set;}

    public LevelInfoHolder (){
        Completed = false;
        HighestPointAmount = 0;
    }
       
    public LevelInfoHolder (bool complete , int points){
        Completed = complete;
        HighestPointAmount = 0;
    }
}

Thank you for the answers.
It seems I have gotten the player position pretty much down, but I am still having some problems understanding how to save the status of, let’s say, trigger scripts.

For example, I have a simple sound trigger that activates when the player enters the collision box, and by using a bool, I make sure it only plays once on trigger enter.

This is the script:

using UnityEngine;
using System.Collections;

public class SoundTrigger : MonoBehaviour {
    public AudioSource source;
    public AudioClip clip;
    private bool isPlayed;

    public void Awake()
    {
        source = GetComponent<AudioSource>();
        isPlayed = false;
    }

    public void OnTriggerEnter(Collider other)
    {
        if (!isPlayed)
        {
            source.Play();
            isPlayed = true;
        }

    }

}

Now, as it doesn’t get saved, it would play again whenever he enters it. So how would I go about saving things like these?

You need to save isPlayed variable and restore it when you load game. You could place it in Game instead. Or you could make a serializable class both SoundTrigger and Game references which will be saved/loaded and contain it. In second way you would still have to restore reference to it in SoundTrigger when loading game.
Oh, and if you only have small amount of sound triggers, PlayerPrefs is still a valid way.

If I were to place a bool in Game, then how would I link this to the trigger script?

I am also guessing I would have to use Int for this? Like, if bool is false, then Int 0, and if true, then Int 1?

And I guess I could use PlayerPrefs, especially for the game I am making right now, which is just a demo version for practice, but I do plan on making something bigger along the lines, so I think that learning this would be more useful.

Game.current.isPlaying

Why else do you have public static Game current; there? Don’t forget to set Game.current to Game that’s currently happening.

If using PlayerPrefs, yes. Otherwise, no. bool is serializable type.

Okay, thank you. It seems I am getting the hang of it now.

But this made me wonder… Correct me if I’m wrong, but by using this method, it seems I would need to write an extra thing of code for every thing that should be saved in the scene. Like, if there are 5 single use triggers, I would have to write a code for each of them in the save system.

Isn’t there a more all encompassing method of saving? Like “Save Scene” instead, or something like that?
Because all the things that are changeable are on my scene. There is no player health, exp, money or anything like that.

There’s no default “Save Scene” as far as I know. You need to make sure all saveable objects are contained within your own serializable “Scene” - that’s exactly what Game class is.

Way 1:
Make some proxy between MonoBehaviour and your scripts.
Quite large block of pseudocode

Something like (pseudocode as I don’t feel writing it completely: TODOs are very large and not a 5-minute work)

[Serialiazable]
class SerializableMonobehaviourHelper : ISerializable
{
    MonoBehaviour ourMonoBehaviour;
    public SerializableMonobehaviourHelper(MonoBehaviour beh)
    {
        ourMonoBehaviour = beh;
        Game.current.AddSerializable(this); //Needs to be implemented in container class which I called it Game here
    }
    public SerializableMonobehaviourHelper(SerializationInfo info, StreamingContext context)
    {
        //TODO: recreate MonoBehaviour here from state you've written in GetObjectData and assign it to ourMonoBehaviour.
        //I'm assuming reflection to find variable and restore its value.
    }
    GetObjectData(SerializationInfo info, StreamingContext, context)
    {
        //TODO: write Monobehaviour data you would need to restore it here
        //I'm assuming reflection to find list of all variables present in MonoBehaviour and saving their name+value pairs here.
    }
}
class SerializableMonobehaviour : MonoBehaviour
{
    [SerializableAttribute]//Want to allow that variable to be saved through Unity hot code reload.
    SerializableMonobehaviourHelper serializationhelper;
    public Awake()//Note: if inheriting from this MonoBehaviour and overriding Awake, be sure to call base.Awake() there.
    {
        serializationhelper = new SerializableMonobehaviourHelper(serializationhelper);
    }
}

Then you’d just inherit scripts you need to be remembered from SerializableMonobehaviourHelper.

Way 2:
Separate script that will remember hierarchy of GameObjects and all components on GameObject it’s attached to and will remember/restore them. Either a lot of scripts that saves GameObject it’s attached to or one script that saves whole hierarchy of GameObjects/Components.

Way 3:
Write custom serializer/deserializer for MonoBehaviours which will remember their position in GameObject tree and restore them and just pass MonoBehaviours to it.

//Note: ways 1-3 aren’t that much different. I assume in all of them you’d use reflection to automatically find variables and their values. Otherwise they’ll be like Way 4.

Way 4:
Copy data from Monobehaviours somewhere serializable. It’s almost same as what you’ve been doing in Game - just try to automate it as much as possible (will still be mostly manual).
As alternative, you could make them implement interface(e.g. ISaveable) that’ll have Save and Load methods and then GetComponents(…) and call Save/Load on them (Save and Load methods will still need to be manually filled).

Way 5:
And, of course, you could just find some asset store solution for serialization which will do this for you. Or someone might’ve posted some of above methods as opensource somewhere.

Okay, thank you for the info. You’ve been great help!

For now I will probably keep working with what I got. At least to practice.

You should have a look at UnitySerializer, initial version is largely deprecated but there’s a new version someone did for u5. Haven’t tested it much as it didn’t work like the legacy one for the project i’m working on but should be fine for new projects that don’t have hundreds of prefabs already setup.

You can find it here for free: TheSniperFan / unityserializer-ng · GitLab