Skip to content

Instantly share code, notes, and snippets.

@malj
Last active October 20, 2020 12:57
Show Gist options
  • Save malj/7780952d33f8641aec907f0828664eaf to your computer and use it in GitHub Desktop.
Save malj/7780952d33f8641aec907f0828664eaf to your computer and use it in GitHub Desktop.
Unity singleton
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
/*
using UnityEngine;
// Instantiated automatically each scene
[Autoload]
public class MyAutoloadingManager : Singleton {}
// Instantiated only when referenced
public class MyManager : Singleton {}
public class MyComponent : MonoBehaviour
{
MyManager myManager;
void Awake()
{
myManager = Singleton.GetInstance<MyManager>();
}
}
*/
/// <summary>
/// Singleton class attribute marking instances as required each scene,
/// regardless if any scene component has instantiated them.
/// </summary>
public class Autoload : Attribute { }
/// <summary>
/// Generic singleton mono behaviour. It handles initialization internally
/// by loading all singleton resources.
/// </summary>
[DisallowMultipleComponent]
public abstract class Singleton : MonoBehaviour
{
private static readonly Dictionary<Type, Provider> cache = new Dictionary<Type, Provider>();
/// <summary>
/// Lazy loads the current scene instance of the requested singleton type.
/// The singleton prefab must exist in the resources folder.
/// </summary>
public static T GetInstance<T>() where T : Singleton
{
var type = typeof(T);
if (!cache.TryGetValue(type, out var singleton))
{
throw new InvalidOperationException($"{type} prefab not found in the resources folder.");
}
return singleton.Value as T;
}
/// <summary>
/// Initialization method. Finds and caches all singletons
/// from the resources folder and sets up singleton autoloading.
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void OnLoad()
{
var autoproviders = new List<Provider>();
foreach (var singleton in Resources.LoadAll<Singleton>(string.Empty))
{
var type = singleton.GetType();
if (cache.ContainsKey(type))
{
throw new InvalidOperationException($"Duplicate {type} prefabs found in the resources folder.");
}
var provider = cache[type] = new Provider(singleton);
if (Attribute.IsDefined(type, typeof(Autoload)))
{
autoproviders.Add(provider);
}
}
// All singletons marked with the "Autoload" attribute are instantiated each scene
SceneManager.sceneLoaded += (scene, mode) =>
{
if (mode != LoadSceneMode.Additive)
{
foreach (var provider in autoproviders)
{
// Creates the instance if it doesn't exist yet, and discards the reference
_ = provider.Value;
}
}
};
}
/// <summary>
/// Internal helper class used for instantiating singletons.
/// </summary>
private class Provider
{
private Singleton instance;
private readonly Singleton prefab;
public Provider(Singleton prefab)
{
this.prefab = prefab;
}
/// <summary>
/// Invoking this property returns a reference to a singleton game object,
/// instantiating it in the process if it doesn't exist yet.
/// This usually means that the first call will create the singleton and
/// cache it, while all subsequent calls will return the cached existing instance.
/// </summary>
public Singleton Value
{
get
{
if (instance == null)
{
// Disable prefab temporarily when instantiating to prevent
// infinite loops when there are mutual singleton references.
// Disabled game objects don't invoke their components' "Awake" methods.
var active = prefab.gameObject.activeSelf;
prefab.gameObject.SetActive(false);
instance = Instantiate(prefab);
prefab.gameObject.SetActive(active);
// Remove "(Clone)" suffix
instance.name = prefab.name;
// Restore the singleton to its prefab's active state
instance.gameObject.SetActive(active);
}
return instance;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment