using System; using System.IO; using System.Threading.Tasks; using UnityEngine; #pragma warning disable IDE1006 #pragma warning disable CS1998 namespace DA_Assets.Singleton { /// /// An analogue of the /// ScriptableSingleton<T> class, but works both in Playmode and in the Editor. /// The asset is searched in the folder by sequentially traversing the subfolders /// specified in the parameters. If the attribute is defined as /// (""), the search occurs in the root of the folder, /// and each subsequent parameter indicates the corresponding subfolder. /// Inherits from . /// public class SingletonScriptableObject : ScriptableObject where T : ScriptableObject { private static T _instance; /// /// Called upon the first access to the object instance after script recompilation and when entering Playmode. /// protected virtual void OnCreateInstance() { } public virtual async Task OnCreateInstanceAsync() { } /// /// Called after exiting PlayMode. /// protected virtual void OnExitPlayMode() { } private static SingletonScriptableObject _genericInstance => _instance as SingletonScriptableObject; /// /// Gets the instance of the Singleton. Unity creates the Singleton instance when this property is accessed for the first time. /// public static T Instance { get { CreateInstance(); return _instance; } } public static async Task GetInstanceAsync() { await InitializeAsync(); return _instance; } public static void CreateInstance() { if (_instance == null) { _instance = LoadInstance(); SetupPlayModeExitEvent(); _genericInstance?.OnCreateInstance(); } } public static async Task InitializeAsync() { if (_instance == null) { _instance = await LoadInstanceAsync(); SetupPlayModeExitEvent(); await _genericInstance?.OnCreateInstanceAsync(); } } private static string GetAssetPath() { object[] attributes = typeof(T).GetCustomAttributes(typeof(ResourcePathAttribute), true); if (attributes != null && attributes.Length > 0) { ResourcePathAttribute foldersAttribute = attributes[0] as ResourcePathAttribute; string path = Path.Combine(foldersAttribute.Path); path = Path.Combine(path, typeof(T).Name); return path; } return typeof(T).Name; } private static T LoadInstance() { string assetPath = GetAssetPath(); T instance = Resources.Load(assetPath); if (instance == null) { throw new NullReferenceException(_objectNotFoundErrorStr); } return instance; } private static async Task LoadInstanceAsync() { string assetPath = GetAssetPath(); ResourceRequest request = Resources.LoadAsync(assetPath); while (!request.isDone) await Task.Yield(); T instance = request.asset as T; if (instance == null) { throw new NullReferenceException(_objectNotFoundErrorStr); } return instance; } private static void SetupPlayModeExitEvent() { #if UNITY_EDITOR UnityEditor.EditorApplication.playModeStateChanged -= PlayModeExitEvent; UnityEditor.EditorApplication.playModeStateChanged += PlayModeExitEvent; #endif } #if UNITY_EDITOR private static void PlayModeExitEvent(UnityEditor.PlayModeStateChange change) { if (change == UnityEditor.PlayModeStateChange.EnteredEditMode) { _genericInstance?.OnExitPlayMode(); } } #endif private static string _missingAttributeErrorStr => $"Missing {nameof(ResourcePathAttribute)} in {typeof(T).Name}"; private static string _objectNotFoundErrorStr => $"{nameof(ScriptableObject)} '{typeof(T).Name}' not found in the project."; } [AttributeUsage(AttributeTargets.Class)] public class ResourcePathAttribute : Attribute { public string[] Path { get; private set; } public ResourcePathAttribute(params string[] path) { Path = path; } } }