using System; using System.Linq; using UnityEngine; using Object = UnityEngine.Object; #if UNITY_EDITOR using System.IO; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; #endif namespace Coffee.UIParticleInternal { public abstract class PreloadedProjectSettings : ScriptableObject #if UNITY_EDITOR { [Tooltip("When enabled, this settings asset will be added to PlayerSettings.preloadedAssets in build.\n\n" + "When disable, you should load this settings via Resources, AssetBundles or Addressables to use.")] [SerializeField] [Header("Advanced")] [HideInInspector] private bool m_PreLoadSettingsInBuild = true; protected static bool s_BuildingPlayer; private class EditorEvents : AssetPostprocessor, IPreprocessBuildWithReport, IPostprocessBuildWithReport { int IOrderedCallback.callbackOrder => 0; private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____) { Initialize(); } void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report) { AssetDatabase.Refresh(); Initialize(); s_BuildingPlayer = true; foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>))) { var settings = GetDefaultSettings(t); if (settings == null || settings.m_PreLoadSettingsInBuild) continue; PlayerSettings.SetPreloadedAssets( PlayerSettings.GetPreloadedAssets() .Where(x => x != null && x.GetType() != t) .ToArray()); Debug.Log($"[PreloadedProjectSettings] Build started: removed '{settings.name}' " + $"({t.Name}) from PreloadedAssets. " + $"It will be restored after build completes."); } } void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report) { s_BuildingPlayer = false; Initialize(); } #if UNITY_2019_3_OR_NEWER [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void OnDomainReload() { foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>))) { var defaultSettings = GetDefaultSettings(t); if (defaultSettings != null) { defaultSettings.OnDomainReload(); } } } #endif } private static void Initialize() { foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>))) { var defaultSettings = GetDefaultSettings(t); if (defaultSettings == null) { if (!s_BuildingPlayer) { // When create a new instance, automatically set it as default settings. defaultSettings = CreateInstance(t) as PreloadedProjectSettings; SetDefaultSettings(defaultSettings); } } else if (GetPreloadedSettings(t).Length != 1) { if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings); } if (defaultSettings != null) { defaultSettings.OnInitialize(); } } } protected static string GetDefaultName(Type type, bool nicify) { var typeName = type.Name; return nicify ? ObjectNames.NicifyVariableName(typeName) : typeName; } private static Object[] GetPreloadedSettings(Type type) { return PlayerSettings.GetPreloadedAssets() .Where(x => x != null && x.GetType() == type) .ToArray(); } protected static PreloadedProjectSettings GetDefaultSettings(Type type) { return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings ?? AssetDatabase.FindAssets($"t:{type.Name}") .Select(AssetDatabase.GUIDToAssetPath) .Select(AssetDatabase.LoadAssetAtPath) .FirstOrDefault(x => x != null && x.GetType() == type); } protected static void SetDefaultSettings(PreloadedProjectSettings asset) { if (asset == null) return; var type = asset.GetType(); if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset))) { if (!AssetDatabase.IsValidFolder("Assets/ProjectSettings")) { AssetDatabase.CreateFolder("Assets", "ProjectSettings"); } var assetPath = $"Assets/ProjectSettings/{GetDefaultName(type, false)}.asset"; assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); if (!File.Exists(assetPath)) { AssetDatabase.CreateAsset(asset, assetPath); asset.OnCreateAsset(); } } var preloadedAssets = PlayerSettings.GetPreloadedAssets(); var projectSettings = GetPreloadedSettings(type); PlayerSettings.SetPreloadedAssets(preloadedAssets .Where(x => x != null) .Except(projectSettings.Except(new[] { asset })) .Append(asset) .Distinct() .ToArray()); AssetDatabase.Refresh(); } protected virtual void OnCreateAsset() { } protected virtual void OnInitialize() { } protected virtual void OnDomainReload() { } } internal abstract class PreloadedProjectSettingsEditor : Editor { private SerializedProperty _preLoadSettingsInBuild; protected virtual void OnEnable() { _preLoadSettingsInBuild = serializedObject.FindProperty("m_PreLoadSettingsInBuild"); } protected void DrawPreLoadSettingsInBuild(string packageName) { EditorGUILayout.PropertyField(_preLoadSettingsInBuild); if (!_preLoadSettingsInBuild.boolValue) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.HelpBox( $"{target.GetType().Name} asset will not be built in.\n" + $"please load manually from Resources, AssetBundle, or Addressables before using {packageName}.", MessageType.Warning); if (GUILayout.Button("Ping")) { EditorGUIUtility.PingObject(target); } EditorGUILayout.EndHorizontal(); } } } #else { } #endif public abstract class PreloadedProjectSettings : PreloadedProjectSettings where T : PreloadedProjectSettings { private static T s_Instance; #if UNITY_EDITOR private string _jsonText; public static bool hasInstance => s_Instance != null; public static T instance { get { if (s_Instance != null) return s_Instance; s_Instance = GetDefaultSettings(typeof(T)) as T; if (s_Instance != null) return s_Instance; s_Instance = CreateInstance(); if (s_Instance == null) { s_Instance = null; return s_Instance; } if (!s_BuildingPlayer) SetDefaultSettings(s_Instance); return s_Instance; } } private void OnPlayModeStateChanged(PlayModeStateChange state) { if (this == null) return; switch (state) { case PlayModeStateChange.ExitingEditMode: _jsonText = EditorJsonUtility.ToJson(this); break; case PlayModeStateChange.ExitingPlayMode: if (_jsonText != null) { EditorJsonUtility.FromJsonOverwrite(_jsonText, this); _jsonText = null; } break; } } protected override void OnDomainReload() { s_Instance = null; } #else public static T instance => s_Instance != null ? s_Instance : s_Instance = CreateInstance(); #endif /// /// This function is called when the object becomes enabled and active. /// protected virtual void OnEnable() { #if UNITY_EDITOR var isDefaultSettings = s_Instance == null || s_Instance == this || GetDefaultSettings(typeof(T)) == this; if (!isDefaultSettings) { DestroyImmediate(this, true); return; } EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; #else if (s_Instance != null && s_Instance != this) { Destroy(s_Instance); } #endif s_Instance = this as T; } /// /// This function is called when the behaviour becomes disabled. /// protected virtual void OnDisable() { #if UNITY_EDITOR EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; #endif if (s_Instance != this) return; s_Instance = null; } #if UNITY_EDITOR protected sealed class PreloadedProjectSettingsProvider : SettingsProvider { private Editor _editor; private PreloadedProjectSettings _target; public PreloadedProjectSettingsProvider(string path) : base(path, SettingsScope.Project) { } public override void OnGUI(string searchContext) { if (_target == null) { if (_editor != null) { DestroyImmediate(_editor); _editor = null; } _target = instance; _editor = Editor.CreateEditor(_target); } _editor.OnInspectorGUI(); } } #endif } }