Last active
May 28, 2020 19:55
-
-
Save nonathaj/90d7f3894ae0718c8ab1 to your computer and use it in GitHub Desktop.
Generic UnitySingleton class for creating different types of singletons in Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Collections; | |
/// <summary> | |
/// Class for holding singleton component instances in Unity. | |
/// </summary> | |
/// <example> | |
/// [UnitySingleton(UnitySingletonAttribute.Type.LoadedFromResources, false, "test")] | |
/// public class MyClass : UnitySingleton<MyClass> { } | |
/// </example> | |
/// <example> | |
/// [UnitySingleton(UnitySingletonAttribute.Type.CreateOnNewGameObject)] | |
/// public class MyOtherClass : UnitySingleton<MyOtherClass> { } | |
/// </example> | |
/// <typeparam name="T">The type of the singleton</typeparam> | |
public abstract class UnitySingleton<T> : MonoBehaviour where T : MonoBehaviour | |
{ | |
/// <summary> | |
/// Is there an instance active of this singleton? | |
/// </summary> | |
public static bool InstanceExists { get { return instance != null; } } | |
private static T instance = null; | |
/// <summary> | |
/// Returns an instance of this singleton (if it does not exist, generates one based on T's UnitySingleton Attribute settings) | |
/// </summary> | |
public static T Instance { | |
get { | |
TouchInstance(); | |
return instance; | |
} | |
set { | |
UnitySingletonAttribute attribute = Attribute.GetCustomAttribute(typeof(T), typeof(UnitySingletonAttribute)) as UnitySingletonAttribute; | |
if (attribute == null) | |
Debug.LogError("Cannot find UnitySingleton attribute on " + typeof(T).Name); | |
if (attribute.allowSetInstance) | |
instance = value; | |
else | |
Debug.LogError(typeof(T).Name + " is not allowed to set instances. Please set the allowSetInstace flag to true to enable this feature."); | |
} | |
} | |
/// <summary> | |
/// Destroy the current static instance of this singleton | |
/// </summary> | |
/// <param name="destroyGameObject">Should we destroy the gameobject of the instance too?</param> | |
public static void DestroyInstance(bool destroyGameObject = true) | |
{ | |
if (InstanceExists) | |
{ | |
if (destroyGameObject) | |
Destroy(instance.gameObject); | |
else | |
Destroy(instance); | |
instance = null; | |
} | |
} | |
/// <summary> | |
/// Called when this object is created. Children should call this base method when overriding. | |
/// </summary> | |
protected virtual void Awake() | |
{ | |
if (InstanceExists && instance != this) | |
Destroy(gameObject); | |
} | |
/// <summary> | |
/// Ensures that an instance of this singleton is generated | |
/// </summary> | |
public static void TouchInstance() | |
{ | |
if (!InstanceExists) | |
Generate(); | |
} | |
/// <summary> | |
/// Generates this singleton | |
/// </summary> | |
private static void Generate() | |
{ | |
UnitySingletonAttribute attribute = Attribute.GetCustomAttribute(typeof(T), typeof(UnitySingletonAttribute)) as UnitySingletonAttribute; | |
if (attribute == null) | |
{ | |
Debug.LogError("Cannot find UnitySingleton attribute on " + typeof(T).Name); | |
return; | |
} | |
for (int x = 0; x < attribute.singletonTypePriority.Length; x++) | |
{ | |
if (TryGenerateInstance(attribute.singletonTypePriority[x], attribute.destroyOnLoad, attribute.resourcesLoadPath, x == attribute.singletonTypePriority.Length - 1)) | |
break; | |
} | |
} | |
/// <summary> | |
/// Attempts to generate a singleton with the given parameters | |
/// </summary> | |
/// <param name="type"></param> | |
/// <paramparam name="resourcesLoadPath"></param> | |
/// <param name="warn"></param> | |
/// <returns></returns> | |
private static bool TryGenerateInstance(UnitySingletonAttribute.Type type, bool destroyOnLoad, string resourcesLoadPath, bool warn) | |
{ | |
if (type == UnitySingletonAttribute.Type.ExistsInScene) | |
{ | |
instance = GameObject.FindObjectOfType<T>(); | |
if (instance == null) | |
{ | |
if(warn) | |
Debug.LogError("Cannot find an object with a " + typeof(T).Name + " . Please add one to the scene."); | |
return false; | |
} | |
} | |
else if (type == UnitySingletonAttribute.Type.LoadedFromResources) | |
{ | |
if (string.IsNullOrEmpty(resourcesLoadPath)) | |
{ | |
if(warn) | |
Debug.LogError("UnitySingletonAttribute.resourcesLoadPath is not a valid Resources location in " + typeof(T).Name); | |
return false; | |
} | |
T pref = Resources.Load<T>(resourcesLoadPath); | |
if (pref == null) | |
{ | |
if(warn) | |
Debug.LogError("Failed to load prefab with " + typeof(T).Name + " component attached to it from folder Resources/" + resourcesLoadPath + ". Please add a prefab with the component to that location, or update the location."); | |
return false; | |
} | |
instance = Instantiate<T>(pref); | |
if (instance == null) | |
{ | |
if (warn) | |
Debug.LogError("Failed to create instance of prefab " + pref + " with component " + typeof(T).Name + ". Please check your memory constraints"); | |
return false; | |
} | |
} | |
else if (type == UnitySingletonAttribute.Type.CreateOnNewGameObject) | |
{ | |
GameObject go = new GameObject(typeof(T).Name + " Singleton"); | |
if (go == null) | |
{ | |
if (warn) | |
Debug.LogError("Failed to create gameobject for instance of " + typeof(T).Name + ". Please check your memory constraints."); | |
return false; | |
} | |
instance = go.AddComponent<T>(); | |
if (instance == null) | |
{ | |
if (warn) | |
Debug.LogError("Failed to add component of " + typeof(T).Name + "to new gameobject. Please check your memory constraints."); | |
Destroy(go); | |
return false; | |
} | |
} | |
if (!destroyOnLoad) | |
DontDestroyOnLoad(instance.gameObject); | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
[AttributeUsage(AttributeTargets.Class, Inherited = true)] | |
public class UnitySingletonAttribute : Attribute | |
{ | |
/// <summary> | |
/// What kind of singleton is this and how should it be generated? | |
/// </summary> | |
public enum Type | |
{ | |
ExistsInScene, ///already exists in the scene, just look for it | |
LoadedFromResources, ///load from the Resources folder, at the given path | |
CreateOnNewGameObject, ///Create a new gameobject and create this singleton on it | |
} | |
public readonly Type[] singletonTypePriority; | |
public readonly bool destroyOnLoad; | |
public readonly string resourcesLoadPath; | |
public readonly bool allowSetInstance; | |
public UnitySingletonAttribute(Type singletonCreateType, bool destroyInstanceOnLevelLoad = true, string resourcesPath = "", bool allowSet = false) | |
{ | |
singletonTypePriority = new Type[] { singletonCreateType }; | |
destroyOnLoad = destroyInstanceOnLevelLoad; | |
resourcesLoadPath = resourcesPath; | |
allowSetInstance = allowSet; | |
} | |
public UnitySingletonAttribute(Type[] singletonCreateTypePriority, bool destroyInstanceOnLevelLoad = true, string resourcesPath = "", bool allowSet = false) | |
{ | |
singletonTypePriority = singletonCreateTypePriority; | |
destroyOnLoad = destroyInstanceOnLevelLoad; | |
resourcesLoadPath = resourcesPath; | |
allowSetInstance = allowSet; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment