Singleton “Global Instance” Pattern in Unity

Introduction to Unity Concepts

Unity follows the entity-component-system (ECS) model where its entities (GameObject) have components (sets of values), which talk to systems (e.g. Rendering System, Physics System, etc.).

Generic ECS Model

Generic ECS to Unity Terms:

  • Entity: GameObject. Examples include Camera, Sprite, Text, and AudioSource. Anything that can be added to a Scene using the Hierarchy View is an entity.
  • Component: Component. Examples include Transform, Renderer, and Script. Anything that can be added to a GameObject using the Inspector View is a component.
  • System: System. Examples include Physics System, Rendering System, and Input System. Systems communicate with components using messages send to specially-named methods.
ECS to Unity Term Mapping

Unity uses the terms Scene (collection of GameObject), Asset (data used by but not an explicit part of a GameObject), and GameObject (collection of components). Running a project in Unity means running a scene.

Unity Concept Collections

SampleScene Scene

Hierarchy View

When a new project is created, Unity also makes a new Scene called “Sample Scene” and shows all of its current GameObjects in the Hierarchy View (list of entities). Through clicking on any particular GameObject, its current components are shown in the Inspector View.

Inspector View

Script Components and C# Methods

Adding a Script Component to a GameObject adds a Script Component to the Game Object viewable as part of its component listing in the Inspector View.

Script Component Example

It also creates a new C# file in the Project View.

Project View

Unity Messages as Class Methods

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Double-clicking the file in the Project View opens it for editing. (Unless changed, this opens the file in Visual Studio for editing)

The default code created through adding a Script Component to a GameObject creates a class in C# with two pre-defined methods: Start() and Update().

// Start is called before the first frame update
void Start()
{
        
}

The comment above the Start() method starts to explain how the Script Component receives information from systems. The use of the word “frame” is a clue to understanding its connection to systems in Unity. Before anything is drawn (rendered), this method will be called, explains the comment.

// Update is called once per frame
void Update()
{
        
}

The Update() also has a comment. This explains the method will be called once per frame. (The use of the word “frame” again signals its connection to the Rendering System.)

Order of Execution

The running of a scene triggers a large number of messages sent out to any GameObjects listening for them (via their components). These are based on when different systems happen, which is always the same for every scene.

Scene Systems

  • Initialization
  • Physics
  • Input
  • Game Logic
  • Rendering
  • Decommissioning

When a Scene starts, it sends out messages to anything listening for Initialization System messages. This is connected to the Awake() and Start() methods. Next is the Physics System, calling methods such as FixedUpdate(), OnTriggerX(), and OnCollision(). Then input is checked and the Game Logic System with calls to Update() and ending with a call to LateUpdate(). Finally, the Rendering System runs and Unity checks if any GameObjects are being destroyed and any use of the Decommissioning System methods.

When a scene starts, all GameObjects undergo the following systems:

  • Initialization
  • Physics
  • Input
  • Game Logic
  • Rendering

After all of the GameObjects in a scene have undergone their Rendering System methods, Unity then runs the “main loop” of the scene, re-running its cycle of the following systems:

  • Physics
  • Input
  • Game Logic
  • Rendering

If a GameObject is destroyed in the scene, Unity waits until after the Rendering System runs and then calls its Decommissioning methods.

By default, any created Script Component will “listen” with its Start() (as part of the Initialization System) and Update() (as part of the Rendering System) methods. This means any Scripting Component will have its Start() called once when the Scene starts and then have its Update() called every time the scene itself re-draws everything each frame.

Relationship Between GameObjects and Scripting Components

A Script Component, despite the code being written in C#, is a the GameObject. As defined by Unity, a GameObject is an Entity, it is a collection of components. This means a C# class is not a GameObject. However, it does have access to the GameObject it is a part of through the property gameObject.

When a Script Component is run, Unity passes certain information to the C# code. One of the details is what GameObject it is a part of through the gameObject property.

Unity operates on GameObjects. Each Script Component added to a GameObject is merely that, another component. It is some value as part of a collection included in the entity that is the GameObject.

Singleton Pattern

In programming terminology, the singleton pattern takes its name from mathematics for a set containing a single element. In programming, a singleton is a collection of values of which only one exists. It is a single during the life of the application.

The singleton pattern is often used to create a place to save and access data stored across other objects or functions. It serves as a “single location” for data not saved anywhere else.

In object-oriented programming, the singleton pattern should rarely be used because of the nature of using objects, how they are created and communicate via methods. It breaks the concept of encapsulation where objects contains their data. However, Unity does not follow the object-oriented programming model. It is based on the entity-component-system model. This means the singleton pattern can be used with Unity, but with an understanding of how it can be misused.

Creating a GlobalInstance Game Object

Create an empty GameObject and change its name to “GlobalInstance”. This will be a “global instance” of a GameObject that will live across scenes.

Add a new Scripting Component. Name the file and class GlobalInstance. (While technically the class and GameObjectc an have different names, it is recommended to match them for easier understand of what they do at a glance.)

Open the created GlobalInstance.cs through double-clicking on it in the Project View.

static fields and Preventing Destruction

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

By default, like any other class created by adding a Script Component, the new GlobalInstance class with have the Start() and Update() methods. Select and remove them.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{

}

This class will not work with the Rendering System. Instead, it will listen for the very first message sent out as part of the Initialization System: Awake().

In the same pattern of the removed methods, add an Awake() method returning void.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    private void Awake()
    {
        

    }
}

static Instance

A singleton needs to make sure it is the only copy of itself running. This can be handled in C# and Unity through saving a reference to itself, its this, and then making sure it is never destroyed and any other copies are destroyed. It also needs to be accessed by other classes and code.

To start to solve these problems, a field can be added to the class called Instance. This should be the same data type as the class itself. In this case, GlobalInstance. It also needs to be public, to be accessed by other code, and static, meaning it is part of the type and only one. It is “static,” unchanging.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    public static GlobalInstance Instance;

    private void Awake()
    {
        

    }
}

First Time, All Other Times

With the Awake() method, the GlobalInstance class will listen for the very beginning of the Initialization System. This means, when it is first created, the Instance field can be updated. However, there are two issues: (1) When will Instance be set to this and (2) How will other attempts to make a copy be handled?

Since Instance was set without a value, its initial value will be null. This can then be tested against. Knowing it is null, a conditional statement can be written to look for this case.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    public static GlobalInstance Instance;

    private void Awake()
    {
        // First time run
        if(Instance == null)
        {
            // Save a reference to 'this'
            Instance = this;
        }

    }
}

So far, the first instance of GlobalInstance is saved within itself. When it is first run, when the first Awake() happens, it will be null, the condition will be triggered, and it will become this. This solves the problem in C#.

Unity needs to be told something important. The method DontDestoryOnLoad() needs to be called. However, it accepts an Object, so the existing gameObject property can be used. This prevent the GameObject the Script Component is a part of from being destroyed between scene reloads, which means the Script Component itself will be maintained across scenes.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    public static GlobalInstance Instance;

    private void Awake()
    {
        // First time run
        if(Instance == null)
        {
            // Tell Unity not to destory the
            //  the GameObject this script component
            //  is attached to (thus keeping it alive
            //  as well).
            DontDestroyOnLoad(gameObject);
            
            // Save a reference to 'this'
            Instance = this;
        }

    }
}

One more things needs to be added to the code. Since the code now handles the “first time” the class GlobalInstance is run, it now has to account for all the other possible times. What happens if this Script Component is used as part of another GameObject? It should protect itself from other copies through using the Destroy() method.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    public static GlobalInstance Instance;

    private void Awake()
    {
        // First time run
        if(Instance == null)
        {
            // Tell Unity not to destory the
            //  the GameObject this script component
            //  is attached to (thus keeping it alive
            //  as well).
            DontDestroyOnLoad(gameObject);
            
            // Save a reference to 'this'
            Instance = this;
        } 
        else if(Instance != this)
        {
            // If Instance is ever not its first 'this',
            //  destroy it.
            Destroy(gameObject);
        }

    }
}

In the updated code, if any other GlobalInstance copies are made after the first one, they are destroyed by Unity through its Destroy() method, acting on the GameObject the Script Component is attached to and running from in the scene.

To clean up the code some, the two separate conditional tests can be combined into a single test.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    public static GlobalInstance Instance;

    private void Awake()
    {
        // If Instance is not null (any time after the first time)
        // AND
        // If Instance is not 'this' (after the first time)
        if (Instance != null && Instance != this)
        {
            // ...then destroy the game object this script component is attached to.
            Destroy(gameObject);
        }
        else
        {
            // Tell Unity not to destory the GameObject this
            //  is attached to between scenes.
            DontDestroyOnLoad(gameObject);
            // Save an internal reference to the first instance of this class
            Instance = this;
        }

    }
}

Using GlobalInstance Instance

A singleton, like any other class in C#, needs to be created first to be used. This means it should be part of whatever scene first requires it and then used again in future scenes. Any GameObjects fitting this description “being used multiple times” or “across scenes” should become a Prefab.

Unity uses the term Prefab to describe the process of a GameObject becoming an Asset. When a GameObject is dragged and dropped from the Hierarchy View to the Project View, its components are saved and it becomes a “prefabricated” object.

GlobalInstance Prefab

With GlobalInstance (the GameObject) and GlobalInstance (the class) as a Prefab, it can be added to any scene as a GameObject with its Script Component already created and setup. It is “prefabricated” already.

Changing global static fields

For whatever first scene the GlobalInstance GameObject (as a Prefab) appears in, that will set its initial values. However, once created, its Instance field can be accessed by any other code across any other scene because its Instance is static in C#. In other words, through adding more public fields to the GlobalInstance class, they can be accessed anywhere through its singleton Instance.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GlobalInstance : MonoBehaviour
{
    // "Global" instance
    public static GlobalInstance Instance;

    // Current number of lives
    public int Lives = 3;

    private void Awake()
    {
        // If Instance is not null (any time after the first time)
        // AND
        // If Instance is not 'this' (after the first time)
        if (Instance != null && Instance != this)
        {
            // ...then destroy the game object this script component is attached to.
            Destroy(gameObject);
        }
        else
        {
            // Tell Unity not to destory the GameObject this
            //  is attached to between scenes.
            DontDestroyOnLoad(gameObject);
            // Save an internal reference to the first instance of this class
            Instance = this;
        }

    }
}

In the above code, the field Lives could be accessed anywhere through using GlobalInstance.Instance.Lives to change its value. As long as it is public (and not itself static), any other code anywhere in any scene could access and change.

Don’t Abuse the Singleton Pattern

In Unity, the Singleton Pattern can be very useful for data such as lives, levels, currency, or other data existing across scenes. However, it is not fool-proof. It requires the GameObject it is attached to being created before it is used anywhere else. (Generally, it is a good idea to create the singleton as part of a main menu or other “early” in-game scene.)

It also allows any code to change its values. The Singleton Pattern is not “under” the rules of objects where the keywords public and private can protect values. In order for it to be used, all fields that are part of the singleton must be public. This also means other code can change them at any time.

Finally, because the DontDestroyOnLoad() method is used, the GameObject (and Script Component) is never destroyed after being created. This can invite some major problems when attempting to store large amount of data that will simply “remain” in memory and take up space when it is not being used in a scene. The use of the DontDestroyOnLoad() method is also a signal to Unity the author will take care of their object. It is up to the author to use or remove it, not Unity. Once it is told to never destroy it, Unity never will.