using UnityEngine; namespace Crosstales.Common.Util { /// Base-class for all singletons. [DisallowMultipleComponent] public abstract class Singleton : MonoBehaviour where T : Singleton { #region Variables [Tooltip("Don't destroy gameobject during scene switches (default: false)."), SerializeField] private bool dontDestroy = true; /// Fully qualified prefab path. public static string PrefabPath; /// Name of the gameobject in the scene. public static string GameObjectName = typeof(T).Name; protected static T instance; private static readonly object LOCK_OBJ = new object(); #endregion #region Properties /// Returns the singleton instance of this class. /// Singleton instance of this class. public static T Instance { get { //Debug.Log("Instance: " + SingletonHelper.isQuitting + " - " + BaseHelper.isEditorMode + " - " + BaseHelper.ApplicationIsPlaying); if (SingletonHelper.isQuitting && Crosstales.Common.Util.BaseHelper.isEditorMode) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"[Singleton] Instance '{typeof(T)}' already destroyed. Returning null."); return instance; } if (instance == null) CreateInstance(); //if (Util.BaseConstants.DEV_DEBUG) // Debug.LogWarning($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' GET: {instance.GetInstanceID()}"); return instance; } protected set { lock (LOCK_OBJ) { //if (Util.BaseConstants.DEV_DEBUG) // Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' SET: {value.GetInstanceID()}"); instance = value; } } } /// Don't destroy gameobject during scene switches. public bool DontDestroy { get => dontDestroy; set => dontDestroy = value; } #endregion #region MonoBehaviour methods protected virtual void Awake() { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' AWAKE"); //: {instance.GetInstanceID()}"); //#if !UNITY_2020_1_OR_NEWER Crosstales.Common.Util.BaseHelper.ApplicationIsPlaying = Application.isPlaying; //needed to enforce the right mode //#endif //isQuitting = false; if (instance == null) { Instance = GetComponent(); if (!Crosstales.Common.Util.BaseHelper.isEditorMode && dontDestroy) DontDestroyOnLoad(transform.root.gameObject); } else { if (!Crosstales.Common.Util.BaseHelper.isEditorMode && dontDestroy && instance != this) { Debug.LogWarning($"Only one active instance of '{typeof(T).Name}' allowed in all scenes!{System.Environment.NewLine}This object will now be destroyed.", this); Destroy(gameObject, 0.1f); } } } protected virtual void OnDestroy() { if (instance == this) { //SingletonHelper.isQuitting = !BaseHelper.isEditorMode; //SingletonHelper.isQuitting = true; if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' ONDESTROY: {instance.GetInstanceID()}"); if (!dontDestroy) Instance = null; //DeleteInstance(); } } protected virtual void OnApplicationQuit() { SingletonHelper.isQuitting = true; if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log("OnApplicationQuit", this); Crosstales.Common.Util.BaseHelper.ApplicationIsPlaying = false; } #endregion /// Creates an instance of this object. /// Search for existing GameObjects of this object (default: true, optional) /// Delete existing instance of this object (default: false, optional) public static void CreateInstance(bool searchExistingGameObject = true, bool deleteExistingInstance = false) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"[Singleton] Instance '{typeof(T)}' will be newly created."); if (deleteExistingInstance) DeleteInstance(); if (instance == null) { // Search for existing instance. if (searchExistingGameObject) #if UNITY_2023_1_OR_NEWER Instance = FindFirstObjectByType(); #else Instance = (T)FindObjectOfType(typeof(T)); #endif // Create new instance if one doesn't already exist. if (instance == null) { if (!string.IsNullOrEmpty(PrefabPath)) { T prefab = Resources.Load(PrefabPath); if (prefab == null) { Debug.LogWarning("Singleton prefab missing: " + PrefabPath); } else { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' CREATE Prefab"); //: {instance.GetInstanceID()}"); Instance = Instantiate(prefab); Instance.name = GameObjectName; } } if (instance == null) { if (Crosstales.Common.Util.BaseHelper.isEditorMode) { #if UNITY_EDITOR if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' CREATE Editor: {Crosstales.Common.Util.BaseHelper.isEditorMode}"); //instanceEditor = UnityEditor.EditorUtility.CreateGameObjectWithHideFlags($"{typeof(T).Name} (Hidden Singleton)", HideFlags.DontSaveInBuild | HideFlags.HideInHierarchy).AddComponent(); Instance = new GameObject(GameObjectName).AddComponent(); #endif } else { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup}-[Singleton] Instance '{typeof(T)}' CREATE Play: {Crosstales.Common.Util.BaseHelper.isEditorMode}"); Instance = new GameObject(GameObjectName).AddComponent(); } } } } } /// Deletes the instance of this object. public static void DeleteInstance() { if (instance != null) { T _instance = instance; Instance = null; Destroy(_instance.gameObject); } } } /// Helper-class for singletons. #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif public class SingletonHelper { #if UNITY_EDITOR //&& !UNITY_2020_1_OR_NEWER private const string key = "CT_SINGLETON_ISQUITTING"; private static bool quitting; private static bool isQuittingSet; public static bool isQuitting { get { //if (BaseHelper.isEditorMode) // return false; if (!isQuittingSet) { isQuittingSet = true; quitting = UnityEditor.BuildPipeline.isBuildingPlayer || Crosstales.Common.Util.CTPlayerPrefs.GetBool(key); } return quitting; } set { if (value != quitting) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log("SET isQuitting: " + value); quitting = value; Crosstales.Common.Util.CTPlayerPrefs.SetBool(key, value); Crosstales.Common.Util.CTPlayerPrefs.Save(); } } } #else public static bool isQuitting { get; set; } = false; #endif private static bool isInitialized; #region Constructor static SingletonHelper() { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - Constructor!"); initialize(); } #endregion [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void initialize() { if (isInitialized) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - already initialized!"); } else { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - initialize: {isQuitting}"); isInitialized = true; Application.quitting += onQuitting; //Application.wantsToQuit += onWantsToQuit; UnityEngine.SceneManagement.SceneManager.sceneLoaded += onSceneLoaded; //UnityEngine.SceneManagement.SceneManager.sceneUnloaded += onSceneUnloaded; #if UNITY_EDITOR UnityEditor.EditorApplication.playModeStateChanged += onPlayModeStateChanged; UnityEditor.SceneManagement.EditorSceneManager.sceneClosing += onSceneClosing; //UnityEditor.SceneManagement.EditorSceneManager.sceneOpening += onSceneOpening; UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += onSceneOpened; #endif } } private static void onSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onSceneLoaded"); isQuitting = false; Crosstales.Common.Util.BaseHelper.ApplicationIsPlaying = true; } /* private static void onSceneUnloaded(UnityEngine.SceneManagement.Scene scene) { if (Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onSceneUnloaded"); //if (Util.BaseHelper.ApplicationIsPlaying) //{ isQuitting = true; Util.BaseHelper.ApplicationIsPlaying = false; //} } */ #if UNITY_EDITOR private static void onPlayModeStateChanged(UnityEditor.PlayModeStateChange obj) { //isQuitting = obj == UnityEditor.PlayModeStateChange.ExitingEditMode || obj == UnityEditor.PlayModeStateChange.ExitingPlayMode; isQuitting = obj == UnityEditor.PlayModeStateChange.ExitingPlayMode; if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onPlayModeStateChanged: {obj} - {isQuitting}"); } private static void onSceneClosing(UnityEngine.SceneManagement.Scene scene, bool removingscene) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onSceneClosing"); isQuitting = true; } /* private static void onSceneOpening(string path, UnityEditor.SceneManagement.OpenSceneMode mode) { Debug.Log($"{Time.realtimeSinceStartup} - onSceneOpening"); isQuitting = false; } */ private static void onSceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode) { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onSceneOpened"); isQuitting = false; } #endif /* private static bool onWantsToQuit() { Debug.Log($"{Time.realtimeSinceStartup} - onWantsToQuit"); isQuitting = true; return true; } */ private static void onQuitting() { if (Crosstales.Common.Util.BaseConstants.DEV_DEBUG) Debug.Log($"{Time.realtimeSinceStartup} - onQuitting"); isQuitting = true; } } } // © 2020-2023 crosstales LLC (https://www.crosstales.com)