I have heard a lot of mixed views on creating a Singleton-based GameState manager for games made in Unity.
One person says it’s the best way to handle persistent data across levels, another says to just use “DontDestroyOnLoad” between scene changes. Another person says that “Singletons are evil”, and another says that while they’re a great way to handle data, they’re a “slippery slope”, and once you get started, “you’ll end up on the path of no return”.
So my question is, what do you, the Unity community, think? I realize this is probably a very opinion-based discussion, but I’d like to hear what everyone has to say. I, personally, think that a Singleton GameState manager is a great way to handle persistent data across levels, and this weekend I’ll be updating my project to use one (I have to learn a bit more about them first!). “DontDestroyOnLoad” is pretty terrible because it ends up creating multiple instances of the same objects, or at least that’s been my experience.
I’d also like to know if there are other ways to store persistent data across levels. I haven’t heard of any, but one of you may surprise me!
A lot of people have pretty strong opinions, but honestly there’s a time and place for everything. It’s not evil to use Static classes and it’s not evil to use Singletons… just don’t get too carried away with it as it can make things harder to debug. You may have a call on your singleton that throws an exception but you might not know what GameObject made that call so it can be hard to track down how the error occurred. Realistically though, it’s just about coding smart and making sure you can figure out that information if you need.
Many times I use a mix. If it’s something that absolutely needs to persist, I tend to use a Static class as the actual “Manager” and it creates a Singleton Instance of the object I need to access. You could make it all static, but if you need to persist state between sessions, you may want to be able to serialize and deserialize so it should be instanceable. You can either allow the manager or the Singleton handle the deserialization process to restore the settings.
Ah, that must be why some people have said they’re evil - harder debugging. I’m still very new to the concept of them, I can certainly see that occurring though!
To me, they just seem like the easiest way to store data across levels/sessions. I should probably do a bit more studying on scripting with C# before I tackle one though, eh?
I prefer a game manager with DontDestroyOnLoad. To avoid any multible instances just create a launcher scene. I use to make my launcher scene after my splash, but now I even carry around a scene camera as a child of my GM that get activated for cut scenes, credit scene, splash and all menus.
The only thing you have to watch out for is concurrency if you are doing anything multithreaded… In this case it actually would make sense to use a lock, but thread locks are a bit on the expensive side, so you avoid locking unnecessarily by using two condition checks… The first checks to see if the instance is null… now it’s possible that two threads could get through this check before one gets the instance created, so if it’s null you want to lock it to make sure only one thread gets in at once, the
public sealed class MySingleton
{
private static object _lockObj = new object();
private static MySingleton instance;
public static MySingleton Instance
{
get
{
//only request the access lock if it's null to begin with
if (instance == null)
{
//obtain the access lock to ensure only one thread gets in at a time
lock(_lockObj)
{
//check again to see if it's null so if a previous thread created it, don't do it again
if(instance == null)
{
instance = new MySingleton();
}
}
}
return instance;
}
}
private MySingleton() { }
}
Now, in the above you see that if there’s an instance, it skips right down and returns it. If not, then it obtains a lock… then checks again. This way if a thread is waiting on the lock and gets inside of it, the instance will have already been created by a previous thread. If it’s still null, it creates it. And finally it cascades to the return. This will make the creation of your singleton thread safe.
I’m fairly sure Unity’s update system is mono-threaded. Yes, you can start multiple sub-threads (on some platform), but they must never interact directly with the main thread.
So in case that singleton derives from MonoBehaviour or ScriptableObject, that thread collision can never happens - you can’t instantiate them outside the main thread anyway.
I guess it could happen with something deriving from System.Object, starting background working thread that would request that instance, while you didn’t make sure it was initialized first.
public void DoSomething()
{
var instance = MySingleton.Instance;
}
public void CallDoSomethingInThreads()
{
var t1 = new Thread(new ThreadStart(DoSomething));
var t2 = new Thread(new ThreadStart(DoSomething));
t1.Start();
t2.Start();
}
Even in this case it’s unlikely but you get the point.
I know what you mean; in your example the dispatcher could decide to be an ass, delay their execution one cycle and start both at the same time. I could argue that starting background working thread without making sure the data they need in the first place is initialized is a good way to seek trouble too.
Doing;
public void CallDoSomethingInThreads()
{
object instance = MySingleton.Instance;
var t1 = new Thread(new ThreadStart(DoSomething));
var t2 = new Thread(new ThreadStart(DoSomething));
t1.Start();
t2.Start();
}
Would prevent the need for a lock and the instance would be initialized before any thread start going around. Frankly, I never encounter that specific issue in Unity, and I’m not sure which design I would prefer.
Completely agree… the example was just for simplicity to understand how it could happen. You’re right, you should make sure it’s instantiated prior to accessing from background worker threads but better safe than sorry. Also, it wouldn’t even need to be a dispatcher delay… if the singleton takes some time to instantiate because it’s doing something in the constructor, that would be sufficient to correlate the execution between the two threads.
I’ve finished quite a few apps and games, and I’m still not wholly happy with how I do things, so it’s an evolving thing. Something that might work for someone may change in a few years. Where are you, and what gets you results? That’s more important than trying to find the best pencil to work with.
I would agree if the Singleton I wrote derived from MonoBeaviour - but it doesn’t (and for the record - I despise those implementations) It’s certainly less of concern inside the context of Unity.
Lots of good information to think on. Thanks for the responses everyone!
I think for now, since I can’t find much on writing Singletons, I’ll just revamp some of my scripts in my current project to handle things a bit better, instead of trying to rewrite everything into something as huge as a Singleton Gamestate manager (plus, it’s a small game, so it would be kind of overkill!).