Persist player data with singleton class without MonoBehaviour

I need global variables for different game related data such as player score, game difficulty, elapsed time…

I was thinking of using the following class with a singleton pattern:

public sealed class PlayerSettings
    {
        static PlayerSettings _instance = null;
        static readonly object _padlock = new object();

        public PlayerSettings()
        {
        }

        public static PlayerSettings Instance
        {
            get
            {
                lock (_padlock)
                {
                    if (_instance == null)
                    {
                        _instance = new PlayerSettings();
                    }

                    return _instance;
                }
            }
        }

        public int Score { get; set; }
        public int Difficulty { get; set; }
    }

And use it like this in my scripts:

PlayerSettings.Instance.Score

I’ve seen quite a few times the singleton inherit MonoBehaviour

http://wiki.unity3d.com/index.php?title=Singleton

I was wondering what was the advantage, as you would need to add the singleton script into every scene ?

Cheers

might be of interest too

skip to 23:30 for basic overview and 46:36 for example

1 Like

First of all, for my own curiosity and my lack of knowledge, why do you want to avoid using Monobehaviour?

I use this to save data. While it may not be exactly what you’re looking for, it gets the job done for me.

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

//this is SaveInfoNewer.cs
//this script saves and loads all the info we want
public class SaveInfoNewer : MonoBehaviour
{
    public SlotLoader SlotLoader;
    //data is what is finally saved
    public Dictionary<string, int> data;

    void Awake()
    {
        //LoadUpgradeData();
        LoadData();
        //WARNING! data.Clear() deletes EVERYTHING
        //data.Clear();
        //SaveData();
    }

    public void LoadData()
    {
        //this loads the data
        data = SaveInfoNewer.DeserializeData<Dictionary<string, int>>("PleaseWork.save");
    }

    public void SaveData()
    {
        //this saves the data
        SaveInfoNewer.SerializeData(data, "PleaseWork.save");
    }
    public static void SerializeData<T>(T data, string path)
    {
        //this is just magic to save data.
        //if you're interested read up on serialization
        FileStream fs = new FileStream(path, FileMode.OpenOrCreate);
        BinaryFormatter formatter = new BinaryFormatter();
        try
        {
            formatter.Serialize(fs, data);
            Debug.Log("Data written to " + path + " @ " + DateTime.Now.ToShortTimeString());
        }
        catch (SerializationException e)
        {
            Debug.LogError(e.Message);
        }
        finally
        {
            fs.Close();
        }
    }

    public static T DeserializeData<T>(string path)
    {
        //this is the magic that deserializes the data so we can load it
        T data = default(T);

        if (File.Exists(path))
        {
            FileStream fs = new FileStream(path, FileMode.Open);
            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                data = (T)formatter.Deserialize(fs);
                Debug.Log("Data read from " + path);
            }
            catch (SerializationException e)
            {
                Debug.LogError(e.Message);
            }
            finally
            {
                fs.Close();
            }
        }
        return data;
    }
}

It does use Monobehaviour, so this makes me even more curious as to why you want to avoid Monobehaviour.

If you don’t have to use MonoBehaviour (ie - hook any of the special methods it exposes) then there’s absolutely no reason to. We have Singletons that load data off the disk and cache it that are just plain objects. A different MonoBehaviour actually tells the cache to load data so the cache code is more reusable and is Unity-agnostic (also - caches shouldn’t necessarily know when to load; they should just load and cache and retrieve when they’re told to).

To OP’s point - you wouldn’t add it to every scene, you’d use DontDestroyOnLoad so it persisted between scenes. Indeed, the one big downfall of MonoBehaviour derived singletons is that nothing stops designers from manually placing multiple instances in a scene or different instances in different scenes. You can code around this by throwing exceptions or destroying redundant instances but it could be seen as cluttering the implementation.

Ultimately, there’s a time and place for both. One could absolutely make the case for player-controlled objects in single player environments to be implemented as a Singleton. It’s entirely dependent on your use case.

2 Likes

Thank you for enlightening me :slight_smile:

For persisting game data such as score etc, I’m guessing I won’t be needing to inherit from MonoBehaviour.

You definitely don’t need to inherit from MonoBehaviour!

You also don’t need the instance. This could be a static class just fine. The lock’s also unnecessary unless you’re accessing this from a different thread.

So unless you’re using a serialization library to load the object, or if you need threaded access, this achieves the same thing:

public static class PlayerSettings {

    public int Score { get; set; }
    public int Difficulty { get; set; }

}
1 Like

This doesnt work?
Cannot declare instance members in a static class

I was sloppy four years ago. Stuff in a static modifier in front of those bad boys.