Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Replacements for My Singletons? (And Am I Even Using Them?)

Discussion in 'Scripting' started by Megalogue1, Jun 22, 2021.

  1. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    I'm fairly new to Unity--I started about 6 months ago--and I'm at the point where I can make working versions of most of my game ideas without too much hassle. Now that I can make stuff work, I'm fascinated with making it work well. Code architecture, especially.

    When I started using singletons (or what I thought were singletons), I was thrilled by how convenient they were. Then I started coming across more and more forum and blog posts discouraging the use of singletons. I'm not interested in continuing that debate; I just want to learn a variety of clean, solid strategies to accomplish what I'm already accomplishing. And, of course, whether I actually am using singletons in the first place.

    Here's how I would start out what I call a "singleton" in my work:
    Code (CSharp):
    1. public class SoundHandler : MonoBehaviour
    2. {
    3.     private static SoundHandler _i;
    4.  
    5.     public static SoundHandler i
    6.     {
    7.         get
    8.         {
    9.             if (_i == null)
    10.                 _i = GameObject.Find("SoundHandler").GetComponent<SoundHandler>();
    11.             return _i;
    12.         }
    13.     }
    14.  
    15.     //code that plays sound
    16. }
    In this case, "SoundHandler" would be a single GameObject in my scene with a single responsibility: to play a sound whenever a sound is needed. If I want my projectile to play a sound when it hits an object, the projectile script can simply call:
    SoundHandler.i.PlaySound(Sound.ProjHit); 

    I can use a similar call if a character makes a sound when jumping, or if the UI makes a sound when the health bar changes, and so on. The important thing is that playing a sound is extremely simple, and I can do it from any script, without needing to explicitly give every single one of those scripts a reference to the SoundHandler. This is the convenience I mentioned.

    I've been using this same pattern for things like "FXHandler" (playing particle effects from any script), "UIHandler" (making UI changes from any script), and so on. So, I have three main questions right now:
    1. Am I actually using singletons?
    2. Do you consider singletons a valid pattern to use for the above use cases?
    3. For the above use cases, what are some other patterns I could consider? (Again, not a debate about singletons. I just want more tools in my belt.)
    P.S. Yes, I have researched this already, and I'm aware there's already a lot of info out there about singletons. But many people still say that you can use them sometimes. My issue, as a new developer, is knowing when those times are, and whether my use cases qualify.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    I consider all patterns that solve your problem to be valid patterns.

    Make sure you understand your problem.

    Make sure you understand the lifecycle of your data.

    Singletons come in a variety of flavors when in Unity: ones that last forever, ones that last for the scene, ones that last for the game session or network connection, etc.

    As long as you understand the duration and lifecycle of the various components of your game, you can clearly reason about the tradeoffs of tighter or looser coupling via constructs such as singleton and static classes.

    At the end of the day, CODE DOES NOT MATTER. Code is merely a way to transform data.

    "The purpose of all parts of all programs is to transform data." - Mike Acton

    tl;dr: try lots of things, be sure to understand them, compare and contrast how well they work for you.
     
    stain2319 likes this.
  3. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    272
    First of all, of you want to clean architecture, don't use GameObject.Find, I mean never, forget it. Use FindObjectsOfType<MyManager>, check length to see if there's only one, and assign it.

    Singletons are completely ok, if you don't assign them in the awake/start, making initialization very messy and unreliable. In your case singleton is kind of lazy, which is good.

    Like for all static stuff (Physics, Time, Input, and almost everything provided by Unity), it's completly impossible to write unit-tests without any additional effort. So if you are writing tests, singletons isn't a good idea.

    If you want clean architecture in your project, without implicit dependencies (generally what singletons are), then you can think about learning Zenject or any other Di container. They let you get rid of singletons completley, and inject usual classes, which can be wrapped in interfaces.
    If you want to work in big professional teams, on some interesting popular projects, then I would recommend learning right design. You will always be able to write singletons if you need it, and you won't write tests in every single project, but understanding of these advanced techniques won't be useless.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Agreed. It's kinda like Fight Club actually.

    Rule Number One of GameObject.Find() is, "Do not use GameObject.Find();"
     
  5. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Hey, thanks for your fast reply. :)

    Maybe I shouldn't have used the word "valid." Do the "singletons," if that's what my creations can be called, work? Yes. In that way, one could call them valid. It also "works" to throw lots of random methods (for playing sound, playing particle effects, updating the UI, etc.) into one big MasterClass.cs and use that for most of your game's logic. But that gets messy fast, and can be really hard to work with.

    I'm less concerned about whether I should use singletons in a given context, and more concerned with what other patterns I could use to achieve the same thing. Like I said, more tools in my belt. Dependency injection, I've heard, is one option; I'll look into that. The service-locator pattern is another, which I'll also look into.

    You say I should "try lots of things," and that's what I want to do. But as a new developer, I wouldn't presume to already be aware of all the "things" I could try, to accomplish my above use cases. So I'd like people to throw as many at me as possible. :)
     
  6. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,590
    Architectural questions without context are harder to answer than one might think. There are certain best practices, and you should definitely take a look at programming patterns (as those are basically best practices).
    However, besides that architecture always depends on the purpose, at least to some degree. Singletons are great.. sometimes. But only using Singletons is a bad idea! Unless it is not ;)

    Architecture is basically a way of keeping code open to most changes, but deciding on one way of doing things will inevitably make it harder to implement some features down the line.
    While this is not really a satisfactory answer, deciding which approaches may cripple development at later stages does come with experience to a degree. It also helps to have a clear picture of the feature that will be required and plan ahead tho. There is a major difference between starting from scratch and patchworking one feature after the other into a game, or starting from scratch with a clear plan of all the features that will be needed in the future. Taking "all" with a grain of salt, obviously, since demands will change as you go along.

    While this does not help with macro architecture, a few advices on keeping code clean and easy to maintain:
    • As others mentioned, dont use Find. It is heavy on performance and never needs to be used. See it as a prototyping tool, or rather strike it out of your mind completely, knowing that there is always a better, proper way.
    • I would strongly advice against naming variables something like 'i'. For one, 'i' is usually used as a loop counter. Other than that, code should be readable, and short does not necessarily make it easier to read. Nothing speaks against calling it _instance.
    • Absolutely use the brackets {} when using if statements, even if the compiler allows to skip them for single-line statements. This simply makes code inconsistant and even harder to maintain, since if you wanna add a line to the if-statement in the future, you need to remember then to re-add the brackets, and forgetting to do so will cause some seriously hard to debug bugs if you are unlucky.
    Before worrying about the big-picture architecture, try to keep your in-script code as clean as possible. But not to the point where you get obsessed over it, as in the end code that works does its job :)

    Edit: To answer your main questions:
    1. Yes
    2. Yes, but
    3. Singletons have the habit of making things less modular. Imagine you have each object handle its own sounds and particles. You can now place this object into another scene, or even game, and it would work right away, without needing any other things. Singletons prevent this, and will be required in any scene or game for those objects to do what they are supposed to do. They make things convenient and appear more flexible, but in reality they make things less flexible architecture wise. As a rule of thumb i would always keep things as modular as possible. This way, if you ever want to switch out one type of feature for another one, you can do so without touching anything but the feature in question. Imagine if you wanted to rewrite, say, the CharacterController. Good architecture would allow you to simply replace it with some other module doing the same thing (moving the character). If you have to touch the WeatherController to do so, things will get messy real quickly^^
     
    Last edited: Jun 22, 2021
    Megalogue1 and VolodymyrBS like this.
  7. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    No offense, but how much context do you need? I want to be able to do certain common tasks, like play sounds and make UI changes, from within any script and with minimal effort. Assume the project is small-to-medium-scale, if those are even meaningful descriptions.

    In your answer to #3, I like the idea of making things as modular as possible. But suppose you're making a bullet hell game and you want each bullet to be able to play a particle effect on impact. If each bullet is completely self-contained, you would need hundreds of clones of the same bullet impact particle system--one for each bullet. This has always seemed inefficient to me, which is why I started using a singleton FXHandler that pools a small number of particle systems, then moves them and plays them whenever they're needed.

    Not trying to shoot down your ideas, just understand them better. :)
     
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,134
    Here's the one I use which avoids the need to search for an existing instance:
    Code (csharp):
    1. public class SomeSingleton : MonoBehaviour
    2. {
    3.     private static SomeSingleton m_instance;
    4.     public static SomeSingleton Instance => m_instance; // creates a read-only property
    5.  
    6.     private void Awake()
    7.     {
    8.         if (m_instance != null)
    9.         {
    10.             DestroyImmediate(gameObject);
    11.         }
    12.         else
    13.         {
    14.             m_instance = this;
    15.             transform.SetParent(null);
    16.             DontDestroyOnLoad(gameObject);
    17.         }
    18.     }
    19. }
    Mine start life in the scene that handles splash screens and other initializations.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    I get you, but man, I do NOT like having to sprinkle debris like this all over scenes. It's just ... messy to me. I know it's the Unity way, but I definitely prefer using one of these two methods:

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
     
  10. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,134
    Yeah I know the feeling. I'm primarily using this approach because the lead programmer prefers it this way but my own preferred method is to store all the managers within their own scene under a root object and then load the scene in at the start of the program. It's still not as clean as your method but it's far cleaner than the one I posted.
     
    Kurt-Dekker likes this.
  11. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,590
    Thats the thing tho.. in small projects architecture is less relevant by definition, since the whole point is to allow building on top of it. But using the big-boy architectural decisions from something like an MMORPG will likely be counter-productive and pose a huge overhead and maintenance effort to smaller projects.
    Imagine it like actual buildings architecture. Anybody can build a small hut from sticks and leaves in the wood, by patchworking things together. If it works, it works. Thats great and all. You wont get away with this approach when building a house. One might argue that best-practices from building a house would carry over and be helpful to building a hut from sticks and leaves in the forest. And that is true. However, when building a skyscraper you need material science and steel beams and even have to consider in which neighborhoods the shadow falls, or if you are using a convex glass front, you have to make sure it wont focus light and melt cars (which, yes, actually happened).
    These may be very specific architectural design choices to the type of skyscraper you are building, and trying to apply any of them to building a stick hut in the woods simply makes no sense.

    Coming back to games, smaller ones get away with pretty much anything as long as it runs. Best practices will make your life easier in general. But larger projects can require specific optimizations or other architectural design choices, that are considered good or even required for these project, but that make no sense to implement in smaller projects. A rhythm game will have widely different criterias for its sound handling than, say, an open world MMORPG.

    So yes, if the criteria you have is "being able to access the same sound handler from any script you have", then Singletons - and the way you use them, more on that later - are absolutely viable. And that is a viable criteria to have, if you are working on a small project and just want as much convenience as possible.
    However, this assumption is not a given and for most projects, you would likely want to keep things more modular. Which is why i attempted to offer insight into how different criteria may affect the approach you choose, and mentioned why general best practices may argue against the use of Signletons.

    I actually never worked with the Unity particle system, so take my answer with a grain of salt. First and foremost, if the particles are purely visual i'd use the VFX graph or a custom shader. In these cases i'd likely have the projectiles register the point and direction of particles caused in some shared list and use that as input. Which, yes, is probably similar to what you described. And in case of a bullethell game with usually hundreds or more bullets, this may be an optimization that is even required.
    However, bullets and how they act would still be one module to me. You would (likely) not want to put bullets into a scene, without them causing particle effects or sound. So all of this would, to me, be one module. Say, the BulletHandler. The BulletHandler may then be put into any scene that needs these bullets, and you would request bullets directly from it. It would act like an Object Pool and assign the returned Bullets references to the shared systems (FX, Sound, ..). The BulletHandler in this case is a blackbox. Any object that needs to spawn bullets would reference this BulletHandler, and then call handler.SpawnBullet(...), or something along those lines. If you ever wanted to replace the entire BulletHandler, you could easily do so as long as the new BulletHandler shared the same interface, ie the SpawnBullet(...) method. Other than that, it may do arbitrarily different things. It would be modular.

    The Singleton pattern by itself only makes sure that only one instance of this object exists. That by itself is not a problem at all. However, using a static function to internally receive and work with this instance, is.

    A module is supposed to be a black box. It offers a defined interface (of public variables, methods, properties, ..) to the outside world. The provided functions have a description, taking an input and returning an output. The implementation of this black box is comletely irrelevant, hence the name 'black box'. When we talk about replacing a module with another one, we usually mean replacing one blackbox with some sum of functions with a different blackbox with the same sum of functions, but a different implementation.
    As an arbitrary example, let's say Unity originally implemented Transform.Rotate(x,y,z) using Euler Angles. Later they realise that Euler Angles are horrible for several reasons and want to use Quaternions instead. If they simply removed or changed the previously communicated interface (Rotate(x,y,z)), they would upset a lot of people since their code now breaks.
    Instead, they would keep the same interface and simply switch out the implementation. The implementation may be completely different, but everything still works, according to the previously communicated interface the users have been using for years.

    Let's use an example.

    Class A:
    Code (CSharp):
    1. public class A: MonoBehaviour {
    2.    void SomeFunction() {
    3.         SoundHandler.GetInstance().Handle();
    4.     }
    5. }
    Class B:
    Code (CSharp):
    1. public class B: MonoBehaviour {
    2.     public SoundHandler handler; // assigned through inspector
    3.  
    4.     void SomeFunction() {
    5.          // possibly with if handler != null
    6.          handler.Handle();
    7.      }
    8. }
    Class A is a 'module' with an internal dependency. Treating class A like a black box, noone knows how it works internally (and noone should have to!). Putting class A into a different environment however, or even just changing or removing an entirely different object (SoundHandler), breaks class A for no apparant reason. This is bad design.

    Class B on the other hand is a module with clear public dependencies. If you remove the SoundHandler from the scene, or forget to remove it, class B aswell stops working (properly), but at least it is clear why, according to its definition. This is better design, as you dont have to know what class B does internally, but you do know that it required some SoundHandler class to work, which you have to provide if you want to use class B.

    I hope the difference became somewhat clear. Since this got pretty long, thanks for reading :D
     
    Last edited: Jun 23, 2021
    VolodymyrBS and Megalogue1 like this.
  12. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    The stick hut/skyscraper analogy actually helped a lot, as well as the two class examples. Thanks for this!

    In the bullet example, you mention giving the bullets references to the "shared systems." Are you suggesting that each would not have its own audio source (as I inferred in my naive interpretation), but would instead make use of a single SoundHandler script--similar to what I've been doing, but without the static reference? That doesn't sound too bad at all to me--it wouldn't require many changes to my existing code at all, which is always nice. :)
     
  13. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    In the literal sense of the word what you're using are not singletons, since it is entirely possible to create more than a single instance of those classes. Literal singletons usually have a private constructor making it impossible for other classes to create instances of it manually.

    If you want better flexibility than you get with singletons look into patterns that allow for inversion of control. Usually this would be easy to achieve with constructor injection, but when dealing with MonoBehaviours this unfortunately isn't possible.

    The service locator pattern is a pretty good substitute in my opinion (although some consider it an anti-pattern). Even if the service locator itself is a singleton or a static class, at least all the services that are retrieved using it can quite easily be swapped with other implementations. Unit testing isn't as simple with the service locator pattern as it would be with simple constructor injection, but at least it's manageable (unlike with singletons).

    Another option to get something very similar to pure constructor injection is to use a static "factory" method that accepts arguments, creates the MonoBehaviour instance internally, passes the arguments to the instance and then returns the instance. If an instance is created without using the proper static method it can throw an exception in the constructor, ensuring that instances are not created without the required dependencies.

    And yeah, then there are the full-blown dependency injection frameworks like Zenject if you want to go that route.
     
    Yoreki likes this.
  14. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,590
    This is right. The BulletHandler in that exmaple contains some way of handling sound. Either by itself referencing some external SoundHandler, which takes care of the sound, or by providing the functionality to do so itself. To keep the bullets all contained as one self-sufficient feature (module), i would probably go with the latter here.
    Anyways, when the BulletHandler returns a new bullet, it would simply assign that bullet a reference to this sound handling object. Each bullet can access the same instance that way.

    Now, since i did not play around with sound handling specifically too much, i cant tell you whether having one single SoundHandler for all objects is a good idea, or if having one per module (ie, the BulletHandler, the Player, the Boss, .. would handle their own sounds for example) would be a better idea. You can probably read up on some best practices for Unity there.
    But getting rid of internal dependencies which cause an object to break if something else changes, is certainly a good idea.

    Edit: Yeah technically @SisusCo is right there - which is the best kind of being right. It is not a true Singleton. While you would only assign _i once and thus should always return the same instance, nothing stops you from creating as many such instances afterwards as you liked.
    Singletons in Unity are a bit weird anyways, since Unity handles the real constructor for you (in MonoBehaviour objects at least, which is why @Kurt-Dekker posted a non-MonoBehaviour example). So what youd do to guarantee only one instance existing, is have the object check on creation if _i is null. If it is not, and not 'itself', then the object must self-destruct to get rid of the additional instance. But even then, there is technically two of the same object for a moment, so one may argue that a Singleton like that is not possibly in Unity MonoBehaviours.
     
    Last edited: Jun 23, 2021
    Megalogue1 and SisusCo like this.
  15. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,114
    Yeah, while you can't make the constructors of a MonoBehaviour private, I would still definitely consider a class that does not allow more than one instance to survive past the initialization event functions a singleton. Like this:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public sealed class SoundHandler : MonoBehaviour
    5. {
    6.     private static SoundHandler instance;
    7.  
    8.     public static SoundHandler Instance
    9.     {
    10.         get
    11.         {
    12.             if(instance == null)
    13.             {
    14.                 CreateInstance();
    15.             }
    16.             return instance;
    17.         }
    18.     }
    19.  
    20.     public SoundHandler()
    21.     {
    22.         if(instance != null)
    23.         {
    24.             throw new InvalidOperationException($"Can't create more than one instance of {nameof(SoundHandler)}.");
    25.         }
    26.     }
    27.  
    28.     private void Reset()
    29.     {
    30.         if(instance != null)
    31.         {
    32.             DestroyImmediate(this);
    33.         }
    34.         instance = this;
    35.     }
    36.  
    37.     private void Awake()
    38.     {
    39.         if(instance != null)
    40.         {
    41.             DestroyImmediate(this);
    42.         }
    43.         instance = this;
    44.     }
    45.  
    46.     private static void CreateInstance()
    47.     {
    48.         new GameObject(nameof(SoundHandler)).AddComponent<SoundHandler>();
    49.     }
    50. }
    51.  
     
    Last edited: Jun 24, 2021
  16. thorham3011

    thorham3011

    Joined:
    Sep 7, 2017
    Posts:
    25
    It's more the opposite: Code is the most important thing. Without it you have nothing.
     
    koirat likes this.
  17. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Well, you (and everyone else) have convinced me. I can finally see the disadvantages of singletons, and putting in a few [SerializeField]s here and there to reference the services I need really isn't that big a deal.

    Also, Yoreki, I just want to thank you again for that nice long post. Somehow I hadn't come across the idea of modules as black boxes yet. Since yesterday, I've been heavily refactoring some of my existing projects, focusing on making each script/feature as self-contained and low-maintenance as possible, and...it kind of feels like magic. :) Now I can just enable/disable my components at will, without causing like twenty different errors across three different scripts! It's a huge deal for iterating my game designs, and for reusing code across projects.

    This thread has led to a huge productivity boost for me, so thanks again everyone!
     
    Yoreki likes this.