[Unity] About Singleton Practice

What is a Singleton?

A Singleton is a design pattern that restricts the instantiation of a class to one single instance. This is useful when exactly one object is needed to coordinate actions across the system. The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.

Characteristics of Singleton:

  1. Single Instance: Ensures that only one instance of the class is created.
  2. Global Access Point: Provides a global point of access to the instance.
  3. Lazy Initialization: Often used to create the instance when it is first needed.

 Implementing a Singleton in C#

Steps to Implement a Singleton:

  1. Private Static Instance: A private static variable to hold the single instance of the class.
  2. Public Static Property: A public static property to provide global access to the instance.
  3. Private Constructor: A private constructor to prevent direct instantiation from outside the class.
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    // Step 1: Private static instance
    private static ObjectPool instance;

    // Step 2: Public static property
    public static ObjectPool Instance
    {
        get
        {
            // Lazy initialization
            if (instance == null)
            {
                // Attempt to find an existing instance in the scene
                instance = FindObjectOfType<ObjectPool>();

                // If no instance exists, create a new GameObject and attach ObjectPool component
                if (instance == null)
                {
                    GameObject obj = new GameObject(“ObjectPool”);
                    instance = obj.AddComponent<ObjectPool>();
                }

                // Make sure the instance persists across scene loads
                DontDestroyOnLoad(instance.gameObject);
            }
            return instance;
        }
    }

    // Private dictionary to hold multiple queues of pooled objects, one for each prefab
    private Dictionary<GameObject, Queue<GameObject>> poolDictionary = new Dictionary<GameObject, Queue<GameObject>>();

    // Step 3: Private constructor (in this case, the MonoBehaviour constructor is private by default)
    private void Awake()
    {
        // Ensure only one instance of ObjectPool exists
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
    }

    public void InitializePool(GameObject prefab, int initialSize)
    {
        if (!poolDictionary.ContainsKey(prefab))
        {
            poolDictionary[prefab] = new Queue<GameObject>();

            for (int i = 0; i < initialSize; i++)
            {
                GameObject obj = Instantiate(prefab);
                obj.SetActive(false);
                poolDictionary[prefab].Enqueue(obj);
            }
        }
    }

    public GameObject GetObject(GameObject prefab, Vector3 position, Quaternion rotation)
    {
        if (poolDictionary.ContainsKey(prefab) && poolDictionary[prefaf].Count > 0)
        {
            GameObject obj = poolDictionary[prefab].Dequeue();
            obj.transform.position = position;
            obj.transform.rotation = rotation;
            obj.SetActive(true);
            return obj;
        }
        else
        {
            GameObject obj = Instantiate(prefab, position, rotation);
            return obj;
        }
    }

    public void ReturnObject(GameObject prefab, GameObject obj)
    {
        obj.SetActive(false);
        if (!poolDictionary.ContainsKey(prefab))
        {
            poolDictionary[prefab] = new Queue<GameObject>();
        }
        poolDictionary[prefab].Enqueue(obj);
    }
}


Summary

  • Singleton Pattern: Ensures that only one instance of the ObjectPool exists and provides a global access point.
  • Centralized Object Pool: All EnemySpawner instances use the shared ObjectPool to manage enemy objects, promoting efficient resource management.
  • Initialization: The Initialize method in ObjectPool ensures the pool is only set up once per prefab type.
  • Object Reuse: The pooling mechanism reuses enemy objects, reducing the overhead of frequent instantiation and destruction.

Lazy Initialization in Singleton Pattern

In the context of a Singleton pattern, lazy initialization ensures that the single instance of the class is not created until it is first accessed. This is particularly useful if the creation of the instance is expensive or if the instance might not be needed during the entire lifetime of the application.

Scroll to Top