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.
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; }
}