From fc7269fc8c50e931adcc58ed747c8d765781ee15 Mon Sep 17 00:00:00 2001 From: Ali Sharoz Date: Tue, 8 Oct 2024 12:30:42 +0500 Subject: [PATCH] Level selection buttons update --- .../Core/GameStateMachine/GameStateMachine.cs | 72 +- .../Databases/GameProgressionDatabase.cs | 1 + .../D2D_Scripts/UI/SetActivityOnGameState.cs | 1 + .../Resources/ES3/ES3Defaults.asset | 1 + Assets/LevelButton.cs | 52 + Assets/LevelButton.cs.meta | 11 + ...itypackage.meta => PlayerPrefsEditor.meta} | 3 +- Assets/PlayerPrefsEditor/CHANGELOG.md | 51 + Assets/PlayerPrefsEditor/CHANGELOG.md.meta | 7 + .../Documentation.meta} | 3 +- .../Documentation/Images.meta | 8 + .../Images/bgtools_ppe_manual_filterModes.png | Bin 0 -> 7296 bytes .../bgtools_ppe_manual_filterModes.png.meta | 92 + .../Images/bgtools_ppe_manual_layout.png | Bin 0 -> 40947 bytes .../Images/bgtools_ppe_manual_layout.png.meta | 92 + .../Documentation/MANUAL.html | 474 ++++ .../Documentation/MANUAL.html.meta | 7 + .../PlayerPrefsEditor/Editor Resources.meta | 9 + .../Editor Resources/ImageManager.cs | 224 ++ .../Editor Resources/ImageManager.cs.meta | 12 + ...y.PlayerPrefsEditor.EditorResources.asmdef | 14 + ...yerPrefsEditor.EditorResources.asmdef.meta | 7 + .../Editor Resources/exclamation.png | Bin 0 -> 448 bytes .../Editor Resources/exclamation.png.meta | 99 + .../Editor Resources/info.png | Bin 0 -> 487 bytes .../Editor Resources/info.png.meta | 99 + .../Editor Resources/not_watching.png | Bin 0 -> 597 bytes .../Editor Resources/not_watching.png.meta | 86 + .../Editor Resources/os_linux_icon.png | Bin 0 -> 682 bytes .../Editor Resources/os_linux_icon.png.meta | 100 + .../Editor Resources/os_mac_icon.png | Bin 0 -> 400 bytes .../Editor Resources/os_mac_icon.png.meta | 100 + .../Editor Resources/os_win_icon.png | Bin 0 -> 299 bytes .../Editor Resources/os_win_icon.png.meta | 100 + .../Editor Resources/refresh.png | Bin 0 -> 531 bytes .../Editor Resources/refresh.png.meta | 100 + .../Editor Resources/sort.png | Bin 0 -> 1175 bytes .../Editor Resources/sort.png.meta | 122 + .../Editor Resources/sort_asc.png | Bin 0 -> 1191 bytes .../Editor Resources/sort_asc.png.meta | 108 + .../Editor Resources/sort_desc.png | Bin 0 -> 1164 bytes .../Editor Resources/sort_desc.png.meta | 108 + .../Editor Resources/trash.png | Bin 0 -> 368 bytes .../Editor Resources/trash.png.meta | 106 + .../Editor Resources/watching.png | Bin 0 -> 547 bytes .../Editor Resources/watching.png.meta | 86 + Assets/PlayerPrefsEditor/Editor.meta | 9 + Assets/PlayerPrefsEditor/Editor/Dialogs.meta | 9 + .../Editor/Dialogs/TextFieldDialog.cs | 139 + .../Editor/Dialogs/TextFieldDialog.cs.meta | 12 + .../Editor/Dialogs/TextValidator.cs | 64 + .../Editor/Dialogs/TextValidator.cs.meta | 11 + .../PlayerPrefsEditor/Editor/Extensions.meta | 9 + .../Editor/Extensions/CenterOnWindow.cs | 90 + .../Editor/Extensions/CenterOnWindow.cs.meta | 12 + .../Editor/PreferencesEditor.meta | 9 + .../PreferencesEditor/PreferenceEntry.cs | 36 + .../PreferencesEditor/PreferenceEntry.cs.meta | 12 + .../PreferenceEntryHolder.cs | 29 + .../PreferenceEntryHolder.cs.meta | 12 + .../PreferenceStorageAccessor.cs | 272 ++ .../PreferenceStorageAccessor.cs.meta | 11 + .../PreferencesEditorWindow.cs | 700 +++++ .../PreferencesEditorWindow.cs.meta | 12 + .../PreferencesEditor/RegistryMonitor.cs | 364 +++ .../PreferencesEditor/RegistryMonitor.cs.meta | 11 + Assets/PlayerPrefsEditor/Editor/Styles.cs | 109 + .../PlayerPrefsEditor/Editor/Styles.cs.meta | 12 + .../Unity.PlayerPrefsEditor.Editor.asmdef | 16 + ...Unity.PlayerPrefsEditor.Editor.asmdef.meta | 7 + Assets/PlayerPrefsEditor/README.md | 71 + Assets/PlayerPrefsEditor/README.md.meta | 7 + Assets/PlayerPrefsEditor/Samples.meta | 8 + .../Samples/SampleScene.meta | 8 + .../SampleScene/PlayerPrefsController.cs | 53 + .../SampleScene/PlayerPrefsController.cs.meta | 11 + .../Samples/SampleScene/SampleScene.unity | 2314 +++++++++++++++++ .../SampleScene/SampleScene.unity.meta} | 2 +- ...ayerPrefsEditor.Samples.SampleScene.asmdef | 12 + ...refsEditor.Samples.SampleScene.asmdef.meta | 7 + Assets/Plugins/AssetUsageDetector.meta | 9 + Assets/Plugins/AssetUsageDetector/Editor.meta | 9 + .../Editor/AssetUsageDetector.Editor.asmdef | 29 + .../AssetUsageDetector.Editor.asmdef.meta | 7 + .../Editor/AssetUsageDetector.cs | 1398 ++++++++++ .../Editor/AssetUsageDetector.cs.meta | 12 + .../Editor/AssetUsageDetectorCache.cs | 207 ++ .../Editor/AssetUsageDetectorCache.cs.meta | 11 + .../AssetUsageDetectorSearchFunctions.cs | 1962 ++++++++++++++ .../AssetUsageDetectorSearchFunctions.cs.meta | 11 + .../Editor/AssetUsageDetectorSettings.cs | 298 +++ .../Editor/AssetUsageDetectorSettings.cs.meta | 12 + .../Editor/AssetUsageDetectorWindow.cs | 812 ++++++ .../Editor/AssetUsageDetectorWindow.cs.meta | 12 + .../AssetUsageDetector/Editor/Enumerators.cs | 130 + .../Editor/Enumerators.cs.meta | 12 + .../AssetUsageDetector/Editor/ListDrawer.cs | 252 ++ .../Editor/ListDrawer.cs.meta | 12 + .../Editor/ObjectToSearch.cs | 120 + .../Editor/ObjectToSearch.cs.meta | 11 + .../Editor/SearchRefactoring.cs | 382 +++ .../Editor/SearchRefactoring.cs.meta | 12 + .../AssetUsageDetector/Editor/SearchResult.cs | 1395 ++++++++++ .../Editor/SearchResult.cs.meta | 11 + .../Editor/SearchResultTooltip.cs | 91 + .../Editor/SearchResultTooltip.cs.meta | 12 + .../Editor/SearchResultTreeView.cs | 1353 ++++++++++ .../Editor/SearchResultTreeView.cs.meta | 12 + .../AssetUsageDetector/Editor/Utilities.cs | 565 ++++ .../Editor/Utilities.cs.meta | 11 + .../Editor/VariableGetter.cs | 83 + .../Editor/VariableGetter.cs.meta | 11 + Assets/Plugins/AssetUsageDetector/README.txt | 4 + .../AssetUsageDetector/README.txt.meta | 8 + Assets/Scenes/MainMenu.unity | 440 +++- Assets/Scripts/DataManager.cs | 370 +++ Assets/Scripts/DataManager.cs.meta | 11 + Assets/Scripts/Dev/GameManager.cs | 9 +- Assets/Scripts/Utils/LevelsSwitcher.cs | Bin 3493 -> 3602 bytes Assets/Source/UI/Prefabs/LevelButton.prefab | 47 +- ProjectSettings/EditorBuildSettings.asset | 3 + ProjectSettings/EditorSettings.asset | 10 +- 122 files changed, 16907 insertions(+), 74 deletions(-) create mode 100644 Assets/LevelButton.cs create mode 100644 Assets/LevelButton.cs.meta rename Assets/{Epic Toon FX/Upgrade/ETFX 2019.3 URP Fix.unitypackage.meta => PlayerPrefsEditor.meta} (67%) create mode 100644 Assets/PlayerPrefsEditor/CHANGELOG.md create mode 100644 Assets/PlayerPrefsEditor/CHANGELOG.md.meta rename Assets/{Epic Toon FX/Upgrade/ETFX Standard Materials.unitypackage.meta => PlayerPrefsEditor/Documentation.meta} (67%) create mode 100644 Assets/PlayerPrefsEditor/Documentation/Images.meta create mode 100644 Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png create mode 100644 Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png.meta create mode 100644 Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png create mode 100644 Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png.meta create mode 100644 Assets/PlayerPrefsEditor/Documentation/MANUAL.html create mode 100644 Assets/PlayerPrefsEditor/Documentation/MANUAL.html.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/exclamation.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/exclamation.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/info.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/info.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/not_watching.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/not_watching.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/refresh.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/refresh.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/trash.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/trash.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/watching.png create mode 100644 Assets/PlayerPrefsEditor/Editor Resources/watching.png.meta create mode 100644 Assets/PlayerPrefsEditor/Editor.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Dialogs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Extensions.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Styles.cs create mode 100644 Assets/PlayerPrefsEditor/Editor/Styles.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef create mode 100644 Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef.meta create mode 100644 Assets/PlayerPrefsEditor/README.md create mode 100644 Assets/PlayerPrefsEditor/README.md.meta create mode 100644 Assets/PlayerPrefsEditor/Samples.meta create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene.meta create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs.meta create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity rename Assets/{Epic Toon FX/Upgrade/ETFX 2019.2.3f1 LWRP Upgrade.unitypackage.meta => PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity.meta} (74%) create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef create mode 100644 Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef.meta create mode 100644 Assets/Plugins/AssetUsageDetector.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs create mode 100644 Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs.meta create mode 100644 Assets/Plugins/AssetUsageDetector/README.txt create mode 100644 Assets/Plugins/AssetUsageDetector/README.txt.meta create mode 100644 Assets/Scripts/DataManager.cs create mode 100644 Assets/Scripts/DataManager.cs.meta diff --git a/Assets/3rd/D2D_Scripts/Core/GameStateMachine/GameStateMachine.cs b/Assets/3rd/D2D_Scripts/Core/GameStateMachine/GameStateMachine.cs index e50c5d0a..19e1d953 100644 --- a/Assets/3rd/D2D_Scripts/Core/GameStateMachine/GameStateMachine.cs +++ b/Assets/3rd/D2D_Scripts/Core/GameStateMachine/GameStateMachine.cs @@ -14,11 +14,11 @@ namespace D2D.Core { public enum CommonGameState { - None, + None, Running, Menu } - + /// /// Owner and its action. Used to unsub zero actions with already destroyed owners /// @@ -27,7 +27,7 @@ namespace D2D.Core public GameObject owner; public Action action; } - + /// /// Powerful and safe game state machine which extensible (use classes instead of fixed enums) /// and allows to check is next state possible, count happened states @@ -37,29 +37,29 @@ namespace D2D.Core public class GameStateMachine : MonoBehaviour, ILazy { [SerializeField] private CommonGameState _startState; - + public bool IsEmpty => _states.Count == 0; - + public bool IsGameFinished => WasAny(); public bool WasWin => Was(); - + public bool WasLose => Was(); - + public GameState Last { get; private set; } - + // Should be used only by editor public IEnumerable StatesForEditor => _states; - + private List _states = new List(); /// /// For each game state we have a list of subscribers' actions /// - private Dictionary> _subscribersMap = + private Dictionary> _subscribersMap = new Dictionary>(); - - private Dictionary _happenedStates = + + private Dictionary _happenedStates = new Dictionary(); private void Start() @@ -82,32 +82,32 @@ namespace D2D.Core // Init dictionary if needed if (!_subscribersMap.ContainsKey(typeof(TState))) _subscribersMap[typeof(TState)] = new List(); - + // Add new subscriber var newSubscriber = new Subscriber { owner = owner, action = action, }; - + _subscribersMap[typeof(TState)].Add(newSubscriber); } - + /// /// On with 2 game state binding. /// - public void On(Action action, GameObject owner = null) + public void On(Action action, GameObject owner = null) where TState1 : GameState where TState2 : GameState { On(action, owner); On(action, owner); } - + /// /// On with 3 game state binding. /// - public void On(Action action, GameObject owner = null) + public void On(Action action, GameObject owner = null) where TState1 : GameState where TState2 : GameState where TState3 : GameState @@ -126,22 +126,32 @@ namespace D2D.Core { if (!safely) Debug.LogError($"{newState} can't go after {Last}"); - + return; } _happenedStates[newState.GetType()] = newState; _states.Add(newState); Last = newState; - + newState.Action(); - + NotifySubscribers(newState.GetType()); - + + //if() + + //DataManager.Instance.MAXLEVELINDEX = DataManager.Instance.MAXLEVELINDEX == DataManager.Instance.CURRENTLEVELINDEX ? DataManager.Instance.MAXLEVELINDEX + 1: DataManager.Instance.MAXLEVELINDEX; + DataManager.Instance.MAXLEVELINDEX = + (DataManager.Instance.MAXLEVELINDEX == DataManager.Instance.CURRENTLEVELINDEX) + ? DataManager.Instance.MAXLEVELINDEX + 1 + : DataManager.Instance.MAXLEVELINDEX; + + DataManager.Instance.CURRENTLEVELINDEX++; + // For subs who are listening for every state // Emit(typeof(GameState)); } - + public void Push(bool safely = false) where TState : GameState { _happenedStates.TryGetValue(typeof(TState), out GameState g); @@ -162,14 +172,14 @@ namespace D2D.Core // State should be different if (!newState.CanRepeat && Last.GetType() == newState.GetType()) return false; - + // Any new state after last scene loading??? Nee, it is not possible if (Last.Is()) return false; if (Last.Is() && newState.Is()) return false; - + if (Last.Is() && newState.Is()) return false; @@ -183,10 +193,10 @@ namespace D2D.Core { if (!_subscribersMap.TryGetValue(t, out var subs)) return; - + if (subs.IsNullOrEmpty()) return; - + // For here cuz we will use remove at for (int i = 0; i < subs.Count; i++) { @@ -230,11 +240,11 @@ namespace D2D.Core if (removingOwner == null) return; - + foreach (var typeSubscribersPair in _subscribersMap) { var subs = typeSubscribersPair.Value; - + if (subs.IsNullOrEmpty()) continue; @@ -263,12 +273,12 @@ namespace D2D.Core /// /// Was this state emitted? /// - public bool Was() + public bool Was() where TState : GameState { return _happenedStates.ContainsKey(typeof(TState)); } - + /// /// Was any of these states emitted? /// diff --git a/Assets/3rd/D2D_Scripts/Databases/GameProgressionDatabase.cs b/Assets/3rd/D2D_Scripts/Databases/GameProgressionDatabase.cs index 6726c919..021e5d92 100644 --- a/Assets/3rd/D2D_Scripts/Databases/GameProgressionDatabase.cs +++ b/Assets/3rd/D2D_Scripts/Databases/GameProgressionDatabase.cs @@ -126,6 +126,7 @@ namespace D2D.Databases protected override void OnGameWin() { + Debug.Log("PassedLevels: " + PassedLevels.Value); CompletedLevelsPerSession.Value++; PassedLevels.Value++; } diff --git a/Assets/3rd/D2D_Scripts/UI/SetActivityOnGameState.cs b/Assets/3rd/D2D_Scripts/UI/SetActivityOnGameState.cs index 697b267f..41851aaa 100644 --- a/Assets/3rd/D2D_Scripts/UI/SetActivityOnGameState.cs +++ b/Assets/3rd/D2D_Scripts/UI/SetActivityOnGameState.cs @@ -26,6 +26,7 @@ namespace D2D private void Show(GameObject[] parent) { + Debug.Log("CheckAli"); if (parent.IsNullOrEmpty()) return; diff --git a/Assets/3rd/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset b/Assets/3rd/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset index 8b8aafb5..34b94a49 100644 --- a/Assets/3rd/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset +++ b/Assets/3rd/Plugins/Easy Save 3/Resources/ES3/ES3Defaults.asset @@ -54,6 +54,7 @@ MonoBehaviour: - StompyRobot.SRDebugger.Editor - ToonyColorsPro.Runtime - MoreMountains.Feedbacks.URP + - AssetUsageDetector.Editor - NaughtyAttributes.Test - MoreMountains.Feedbacks.MMTools - Lofelt.NiceVibrations diff --git a/Assets/LevelButton.cs b/Assets/LevelButton.cs new file mode 100644 index 00000000..14f8f17e --- /dev/null +++ b/Assets/LevelButton.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +public enum LevelState { Locked,Completed,Upcoming} +public class LevelButton : MonoBehaviour +{ + public LevelState levelState; + public List levelStateImages; + bool[] buttonState = { false, true, true }; + public Button button; + public int id; + //private void OnValidate() + //{ + // Func(); + //} + //public void Func() + //{ + // if(id==0) + // id = transform.GetSiblingIndex(); + //} + private void Start() + { + CheckState(); + StateEnabler(); + } + public void CheckState() + { + if (DataManager.Instance.MAXLEVELINDEX < id) + { + levelState = LevelState.Locked; + } + else if (DataManager.Instance.MAXLEVELINDEX == id) + { + levelState = LevelState.Upcoming; + } + else + { + levelState = LevelState.Completed; + } + } + public void StateEnabler() + { + levelStateImages[(int)levelState].SetActive(true); + button.enabled = buttonState[(int)levelState]; + } + public void currentLevelIndexSet() + { + DataManager.Instance.CURRENTLEVELINDEX = id; + } +} diff --git a/Assets/LevelButton.cs.meta b/Assets/LevelButton.cs.meta new file mode 100644 index 00000000..1c266619 --- /dev/null +++ b/Assets/LevelButton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b00c509790002f4eb548c0faadfc2fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Epic Toon FX/Upgrade/ETFX 2019.3 URP Fix.unitypackage.meta b/Assets/PlayerPrefsEditor.meta similarity index 67% rename from Assets/Epic Toon FX/Upgrade/ETFX 2019.3 URP Fix.unitypackage.meta rename to Assets/PlayerPrefsEditor.meta index 7e6d3ff1..a00f0f64 100644 --- a/Assets/Epic Toon FX/Upgrade/ETFX 2019.3 URP Fix.unitypackage.meta +++ b/Assets/PlayerPrefsEditor.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: 2a7477119085c0543ab993533aa56fad +guid: 86da4588b8b8cfa4a8ed40aa77329d3e +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/Assets/PlayerPrefsEditor/CHANGELOG.md b/Assets/PlayerPrefsEditor/CHANGELOG.md new file mode 100644 index 00000000..237f0e64 --- /dev/null +++ b/Assets/PlayerPrefsEditor/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.2] - 2023-01-10 +- Fix icon offset influence on other inspector entries + +## [1.2.1] - 2022-04-24 +- Fix detection for PlayerPrefs where the key contains '_h' +- Use unicode for windows registry lookups to support none ASCII chars in projects names + +## [1.2.0] - 2022-01-01 +### Added +- Enhanced search field to filter player preferences by key or value +- Add sorting functionality for Pref entries (none, ascending, descending) + +### Removed +- Remove Unity 2017 support +- Remove Unity 2018 support + +## [1.1.2] - 2021-07-01 +- Fixed ImageManger icon detection + +## [1.1.1] - 2021-05-23 +- Add utf8 key encryption support for windows + +## [1.1.0] - 2021-05-17 +- Improve key validation with more characters +- Async output reading for MAC plist process +- Performance optimizations + +## [1.0.4] - 2020-09-20 +- Add handling for special characters in product/company name +- Improvement of plist read call on MAC + +## [1.0.3] - 2020-09-20 +- Fix text color on professional skin + +## [1.0.2] - 2020-08-11 +- Switch package author to 'BG Tools' +- Fix UPM documentation image path + +## [1.0.1] - 2020-06-01 +- Resizable column width for table layout +- Multiple UX improvements +- Add manual + +## [1.0.0] - 2020-05-26 +This is the first release of PlayerPrefs Editor \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/CHANGELOG.md.meta b/Assets/PlayerPrefsEditor/CHANGELOG.md.meta new file mode 100644 index 00000000..77b9ed2d --- /dev/null +++ b/Assets/PlayerPrefsEditor/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 53e2fe1165389a84c8c415eed555029d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Epic Toon FX/Upgrade/ETFX Standard Materials.unitypackage.meta b/Assets/PlayerPrefsEditor/Documentation.meta similarity index 67% rename from Assets/Epic Toon FX/Upgrade/ETFX Standard Materials.unitypackage.meta rename to Assets/PlayerPrefsEditor/Documentation.meta index 5c84c897..2c20605f 100644 --- a/Assets/Epic Toon FX/Upgrade/ETFX Standard Materials.unitypackage.meta +++ b/Assets/PlayerPrefsEditor/Documentation.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: 474d6b0a9f4e12747bf9c8ee63d2f12a +guid: ac7740c4463611344b22ca368af84da1 +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/Assets/PlayerPrefsEditor/Documentation/Images.meta b/Assets/PlayerPrefsEditor/Documentation/Images.meta new file mode 100644 index 00000000..2667eee3 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Documentation/Images.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fa4d09c10dec18841a67065c4ef628bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png new file mode 100644 index 0000000000000000000000000000000000000000..158512126547f8a2dc35acac21e9df4e698d78a5 GIT binary patch literal 7296 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 literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png.meta b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png.meta new file mode 100644 index 00000000..4b31669e --- /dev/null +++ b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: 418213a0ca74fec47960ee53fbae1cad +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png new file mode 100644 index 0000000000000000000000000000000000000000..be2432c9db20b4eef43d6c20cbe7084d97bed50c GIT binary patch literal 40947 zcmbTeWmr{R+b&FZr}Uybr8^c4(jnd5-3^ig(w!pGEhSxol!BzvAs``03*X?rpXdGd zkN4O=HU}y$*PLt4ImWoIvxaf%FXS=NNYP+mU@#RGpqel+@DLan*Z~wc@JcqUa1r=} z#7kD+OUu>9%h%k)8b->>)xsL0=xlClt!Zs;LPfHJbH!piv7s%5Y%`IHLy~Jq1mHvAR&Tguz|8rs&&;L9q@W?p*%-uM-Ik-5T zouBUOpLctDXHw*ar8w2Yt-zmTj9xNkvjE+MFZAP*m}jKFgl z8JhnbYxV!~%s9cta6V0r|6{WJ_a!g`pMLzGsSp10Ka0oO1*{kkur!h>^fqB&@@W;J zQrdpYM+L~*+VW&EdXfQIED%|Bb=)D7eiMsiIZN5HM#f7!Jj6w`1m-cNuP@!M|o*! zX-f+)BO{}TNLO)jSVvY?7BMk#b!Fw^^76sKfrmtY0g5m`KheUquCg-vBjS8XRh5H< zg@u#T=MKos_4PHQS|Nn3r=L_Bfe%(ZJv|+An?xocr(%w6TMI=)M}Ho@4tFM-c+Jmc z2LFbDd0L$2Y(vPR|9eVC1|caa8r7maU42^{zi+6HzP>&L7y9}9>`W@gg9;N9^X-ah zAP(;H=g$dPrVvqMHwg#{hY5wCy%i+kru`lSsI;`SI!Pwv<*p*D6xzLnn>@{3kzS@ zK#`Uf7u~LCAp{Mvu_$qB!f0fHenM;OGJbx34h}4OEjXQig#*KLa|CVFkT^A}rlX%< z-=P#_V5SD@&z%lYAD=4SdX82p5iH-|2NCdVXAescsY-VXYH4t#)S^Up!Ai(~M5YoX zb5qk9&Va-klnxlYR6>b>Jbg#6Z)|8VH(-$qO4>`|Ab@Xbft{TlaI>K99hKp|MfvAd zD7%KP?zh=lZXO<*aM^#}SE4XCH@CPvI?g<>7Qx0O`vV0j7t_@7l@%Sw!7_}NmKHuf{ynj~ z?MZllVrO$WBKt(a7(|~&9_NUUfso~|Ys%+{y+AV|FMqCSF z9UI5H86O%MnHrSg3i4A3y5Aa2?pTmMyw!1`r+@pc*6@Sfs(yoQ^7;^}lvfLc0qNM_ z+cyJ#eg;a)slvhydirEau?U13+BArJ04po{!>uyp@^wL6bu~;VC8gTLM3Jcfo5I36 zeKHdyc63V0ti+@a-*{_*$d5~^aEFul8Pw`+8boUhVNX}Jf|zNx#MA%h5U(e)EGx_G z;a>NAuR4(eT`DavZ^C&`L!PG^EHNxBET4BTP<3nZtZaLKQ&V$*=4T9r57XUn?JUw6zbUXSlq) zy;eO@n=V@Tv%k#RH-^uc|G`P)cxGd&Km@FI?<^io zCU$mqHa4rDeNHLgVMzM}2HW+k=$P{u>FHDZXuuO#a10{0%NkFpVUtpyQNF4*G@L3r zsV;?6Evu@kVuNldmHMCW`ba!5Rn=Wwu)N!T=5&F=alW^<-2ASlM#b2;z%j4t`&8$P zxxI8|_SaOL9^QY>&n*uZ%auQ(6LYwAxha=}(lf=!UDDnz$jONv7gla2q}hziwmE6x zV#3_OL;>dZ4{qr6!D18rz!$vNq@<*o_V#et$yw)KftQPa4t5R?4K*~v+sJGE*wyTC zN@Np=uu4kQJ?a&z*$v}IUaKl7NESC#<;0;Holer5t_=-ALDdtKo^gw)uhZ{n@)&=! zv8B}^m{<=dCiWc;e$d;yxv8nRaZy22hg7+ovyUw`l^22F2ekKh2Pz=oj|?yfX#wzl?F z61fn;7titZ(B4QS)gT3cUVR8W8!NGjlRbZ{_E zk62D9f=^=5&_=7T5d7dnaMWTDO~=iHJ%0A%W8kUVk8b~4CmVw{I2QL;7(d&rZEU2& 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 {QT8F=o|kwlP(w)ZTO0P4xWy-QFs+7_+w@)7;M zFAv1T9%Csn(*SD0$k@^hO$eD({02FJCba0DA(}>aWGA7B=L_xwLA+tl4Y~eR&@K(H zQM{ub$4N?Mu<#P{wW{jm@KOv`BMk zh>@?q*Wtrwug@1Q1+Syom*2O%2kD|4J1K9BUp+ouD6|F@uo$|mj7XF;g zd!2y>hBy9(d`&A{TAu)@v zJiovcNq9$5klOFsLz!mkwkO3qlOjM~9}(935WYb#&FmRAUSq8+AKGBEr-X@ zNJXEp6nb2OaxBFQ<8cG~V|U=v7@!#&v$mUHyg_}QB)kGSDcI~3>H|~BItcy)f<|^V zP`T^|ehtV2x24W^h<6D7dN}qrj;1T5#J7K{wEa0CZ6pDy9Z0tn^r)r|ggyc}m$^nI z;{|W`umfACPDL=^(#3H0D^)X!4#hY;kgq!9Js7*@9ZPTK2&lcRYytQ~hc$YfWA@id zD>-;*L9I;#ssV*4tne!IbRF{6D&~I}KIGXgbuAqdJ!o;+qA1*TgZ-t97xbBGi)m2D zPl4xyCP#CEmrz77#j5Jx8{)-s!KxVxiF#6rO9V}XP;a%T+tgDt;EC5tgERjf+Ycr; zWYwMpE|Hvo8R~_cf$*F0KCA9ir!@%)xXOMMn?)s+;XXXwIBeMkgdv&I5MErM+GJ}) zXbnvX|I$oSRCzjXiIa#5G4TkGq+f7yR>ji!bWeJ~O#3+(aD!o>l~=Ts+ZWkcr^ds* z_}O#%h0;ZB(8dtk?%KXxdtyW5r?9;TiwLT;BnFU{WkOeGb!H0x&W*!nt^@l>Ao5zj zZH~5x!DI>y2W@KzuNLhm?<^rX}y?&ZcUTRuIx{AnCmUFdHovaWvj zx!n>@gLrbbKCe=z+rN#BxGn#gvHhs6tqm8`I-73}Ei;7J@I5gBWW22bAlC+9nrrEH z9hMgX7}SQ~J`j5+ z8Y*=lMBAh(1U*O+CCXfx%I@qsoFPKNY&r3-yo@iF6`iG-#gJ?0@57LcBOBAW?)Nk zO|-LPwZ8CG$^hKGVJFc5Z3ni}y~euwdUR(Ax8IiW!JK1v#mPZEN@M39GOzV5LJ2G$ znMz3&0^nH?=f=s@X$%f{msIjkCpg&dGcuNifqoF+==Bcu>uw7J2n*8FnMJaV7``h0 zD_?~>qZDI9b9)kEyJR6J^)7C+l0z@K13bOk@Bd`LLZ*Qo@+KsEIA=-P^LE5`E`u=x zeGJ8*uz^_&M)T+(LlN4bv%sj#W(^kT=A{#{SjeiA>TzI46z3=un!?ixlA1MW7{d%O zH1S6Ncp))V-Pr@mh5jspxV~Hw+tT@}&A;tOZV`o@E#<@X&g974M)@p60#3GRN&MuB zTKOZ1B-5NImN#2TG9MM%eJkp@^ssBopM + + + PlayerPrefsEditor-Manual + + + + + +

PlayerPrefs Editor for Unity 3D

+

Tool extension for the Unity Editor that enables easy access to the player preferences over a simple UI. Allows to view, add, remove and modify entries on the development machine.

+

Support

+

GitHub | Website | Mail | Discord

+

Features

+
    +
  • Add, remove and edit PlayerPrefs
  • +
  • Intuitive visual editor
  • +
  • Works with standard Unity PlayerPrefs
  • +
  • Monitors changes from code
  • +
  • Supports all editors (Windows, Linux, MacOS)
  • +
  • Lightweight dockable for full integration in your workflow
  • +
  • Supports both skins (Personal, Professional)
  • +
+

Usage

+

The PlayerPrefs Editor is located in the top menu at Tools/BG Tools/PlayerPrefs Editor. It's a standard dockable window, so place it wherever it helps to be productive.

+

PlayerPrefs editor window layout

+

The PlayerPrefs Editor window displays:

+
    +
  • (A) Filter field
  • +
  • (B) Sort mode
  • +
  • (C) Toggle 'System changes monitoring' behavior
  • +
  • (D) Refresh data
  • +
  • (E) Delete all data
  • +
  • (F) Operating system and path to PlayerPrefs data
  • +
  • (G) PlayerPrefs data list (Key, Type, Value)
  • +
  • (H) Add/Remove a PlayerPrefs entry
  • +
  • (I) Toggle visibility of system defined PlayerPrefs
  • +
+

Modify Entries

+

The PlayerPrefs Editor allow to add, remove and edit PlayerPrefs data.

+

Add a new entry

+

Press the '+' button and select the type of the new PlayerPref entry. It's not possible to modify this type later. Add the key for the new entry in the upcoming dialog field. Additionally the dialog will provide feedback when it detected unintended overrides or invalid characters.

+

Remove a existing entry

+

Select the entry from the list that you want to delete. Press the '-' button to remove this entry. Confirm the warning dialog to finish the operation.

+

Modify a existing entry

+

To change a value of a existing entry do this directly in the value field in the PlayerPref list.

+

Sort & Filter

+

Sorting

+

Circle trought the sorting funtions by pressing the (B) button in the toolbar.

+

Following sorting function are aviliable for the PlayerPref entries:

+
    +
  • None
  • +
  • Ascending
  • +
  • Descending
  • +
+

Filtering

+

Enter a text into the the search field (A) in the toolbar to filter the PlayerPrefs data list (G).

+

PlayerPrefs editor searchfield modes

+

Additionally select the mode for filtering by pressing on the magnifying glass icon in the search field. Choose between filtering the existing PlayPrefs by key or value. The current search target will be shown in the searchfield if no search string is present.

+

Monitoring system changes

+

The plugin can monitor changes at runtime automatically and keep the view up-to-date. This detection is active by default, but it can be turned off over the (C) button in the toolbar.

+

Samples

+

This package includes two samples for testing purposes:

+

Test Value Menu

+
+

Adds new entries into the top menu Tools/BG Tools/PlayerPrefs Test Values. This allows easily to add text values to the PlayerPrefs of the current project.

+
+

Sample Scene

+
+

Simple UI that manipulates PlayerPrefs entries on runtime.

+
+

Technical details

+

Requirements

+

This version of PlayerPrefs Editor is compatible with the following versions of the Unity Editor:

+
+

2019.4 and later (recommended)
+Windows, MacOS, Linux

+
+

Limitations MacOS

+

Due to technical on MacOS it take time to update the persistent file that stores the PlayerPrefs. To avoid inconsistent data the plugin will show a loading screen until the data can be fully refreshed. Sorry for the inconvenience.

+

Keep in mind that it's possible to deactivate the automatic refresh in the settings.

+ + + + \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Documentation/MANUAL.html.meta b/Assets/PlayerPrefsEditor/Documentation/MANUAL.html.meta new file mode 100644 index 00000000..44e19270 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Documentation/MANUAL.html.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 907d7ca800984c64d9b2116fdaf6681e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources.meta b/Assets/PlayerPrefsEditor/Editor Resources.meta new file mode 100644 index 00000000..026e7001 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 31c463d7ccf40cf4cab8c990a851231d +folderAsset: yes +timeCreated: 1500321077 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs b/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs new file mode 100644 index 00000000..50e383b1 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs @@ -0,0 +1,224 @@ +#if UNITY_EDITOR +using System; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace BgTools.Utils +{ + public class ImageManager + { + // Keep this ID unique + private static readonly string ID = "[PlayerPrefsEditor] com.bgtools.playerprefseditor"; + + private static string imageManagerPath; + private static string GetAssetDir() + { + if (imageManagerPath != null) + { + return imageManagerPath; + } + + foreach (string assetGuid in AssetDatabase.FindAssets("ImageManager")) + { + string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); + string fileName = Path.GetFileName(assetPath); + + if (fileName.Equals("ImageManager.cs")) + { + // Check ID if it's the correct ImageManager + if (File.ReadLines(Path.GetFullPath(assetPath)).Any(line => line.Contains(ID))) + { + imageManagerPath = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar; + return imageManagerPath; + } + } + } + throw new Exception("Cannot find ImageManager.cs in the project. Are sure all the files in place?"); + } + + public static Texture2D GetOsIcon() + { +#if UNITY_EDITOR_WIN + return OsWinIcon; +#elif UNITY_EDITOR_OSX + return OsMacIcon; +#elif UNITY_EDITOR_LINUX + return OsLinuxIcon; +#endif + } + + private static Texture2D osLinuxIcon; + public static Texture2D OsLinuxIcon + { + get + { + if (osLinuxIcon == null) + { + osLinuxIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_linux_icon.png", typeof(Texture2D)); + } + return osLinuxIcon; + } + } + + private static Texture2D osWinIcon; + public static Texture2D OsWinIcon + { + get + { + if (osWinIcon == null) + { + osWinIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_win_icon.png", typeof(Texture2D)); + } + return osWinIcon; + } + } + + private static Texture2D osMacIcon; + public static Texture2D OsMacIcon + { + get + { + if (osMacIcon == null) + { + osMacIcon = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "os_mac_icon.png", typeof(Texture2D)); + } + return osMacIcon; + } + } + + private static GUIContent[] spinWheelIcons; + public static GUIContent[] SpinWheelIcons + { + get + { + if(spinWheelIcons == null) + { + spinWheelIcons = new GUIContent[12]; + for (int i = 0; i < 12; i++) + spinWheelIcons[i] = EditorGUIUtility.IconContent("WaitSpin" + i.ToString("00")); + } + return spinWheelIcons; + } + } + + private static Texture2D refresh; + public static Texture2D Refresh + { + get + { + if (refresh == null) + { + refresh = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "refresh.png", typeof(Texture2D)); + } + return refresh; + } + } + + private static Texture2D trash; + public static Texture2D Trash + { + get + { + if (trash == null) + { + trash = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "trash.png", typeof(Texture2D)); + } + return trash; + } + } + + private static Texture2D exclamation; + public static Texture2D Exclamation + { + get + { + if(exclamation == null) + { + exclamation = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "exclamation.png", typeof(Texture2D)); + } + return exclamation; + } + } + + private static Texture2D info; + public static Texture2D Info + { + get + { + if (info == null) + { + info = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "info.png", typeof(Texture2D)); + } + return info; + } + } + + private static Texture2D watching; + public static Texture2D Watching + { + get + { + if(watching == null) + { + watching = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "watching.png", typeof(Texture2D)); + } + return watching; + } + } + + private static Texture2D notWatching; + public static Texture2D NotWatching + { + get + { + if (notWatching == null) + { + notWatching = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "not_watching.png", typeof(Texture2D)); + } + return notWatching; + } + } + + private static Texture2D sortDisabled; + public static Texture2D SortDisabled + { + get + { + if (sortDisabled == null) + { + sortDisabled = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort.png", typeof(Texture2D)); + } + return sortDisabled; + } + } + + private static Texture2D sortAsscending; + public static Texture2D SortAsscending + { + get + { + if (sortAsscending == null) + { + sortAsscending = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort_asc.png", typeof(Texture2D)); + } + return sortAsscending; + } + } + + private static Texture2D sortDescending; + public static Texture2D SortDescending + { + get + { + if (sortDescending == null) + { + sortDescending = (Texture2D)AssetDatabase.LoadAssetAtPath(GetAssetDir() + "sort_desc.png", typeof(Texture2D)); + } + return sortDescending; + } + } + } +} +#endif diff --git a/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs.meta b/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs.meta new file mode 100644 index 00000000..d5444821 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/ImageManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ee68545419352384a950cc488e731084 +timeCreated: 1500324006 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef b/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef new file mode 100644 index 00000000..6183550a --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Unity.PlayerPrefsEditor.EditorResources", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef.meta b/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef.meta new file mode 100644 index 00000000..5237db41 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/Unity.PlayerPrefsEditor.EditorResources.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bf6f54031c06d954889037da1389c752 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png b/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png new file mode 100644 index 0000000000000000000000000000000000000000..69f9ae38e84ab33d010ad2afd3d53dae25bce7c1 GIT binary patch literal 448 zcmV;x0YCnUP)`EQ-iMet}4&h~FUyx=0|D zG6;!|Gzs!MMD`2#L6RaR5(N^Cizp_$RDWRGX#F9t5pbm$RJN}v?%$SdQ~8Btv+2upcjyh z)Niqkg7w~g$(!mpAX*P%>%sb1ZRYVr1scadBlxZd{&L_qfY0+*zpdV-`rQiR&r`8q zYGrxIQPFLE~T zs)_$HCua-T2hOG<=8uGV@y`eeTXYCK%c%@t035_ND9GCY-lwadH4=<3KgsXT$o(@y zk`sIao)lCyFCGIQ0U{^Q{B4O%Ep=1f1X?q*;how49#%|#3!GQXIgQ`yVn5@gj&LCW0000b5%ePmI+qb#NEruDV+zJW&tJ=cMM_Q6IM@@2fu&6)#0GO9}sXwO8kR zP_G>L2+Sq4FEZ~Ho&q1#nXCZtHbV~3Qy0_)iiW2!54=ly)WyWH?}}l?+le$*GgXa% zPc>T~c$tX@cmsTntM8Z|Mb)*$XU)0e)pgcJZz|FQaqyUQzg(e!$Wmhot;pm z8w)hy1+W1;pHRS9;CRC1HaLz2z5u`eBKa8jdM!|H#l1}MImM`+MYgljcAm-E3n5(I zu&9H~$gStfRM#^(i&e~ZlS2smz;1%4Y3HYWJ0XOF>Z4P0zNVO(`N%eFv42vikM0Oq zX($NPo;teaPfg)aebz`GiOg7BB5m56B?+S=C91nxS=Vuz4WIjc1yQ39Z_8R dm-(#!gez{JGe%7sPayyR002ovPDHLkV1n_M*9ias literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/info.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/info.png.meta new file mode 100644 index 00000000..dd3f5103 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/info.png.meta @@ -0,0 +1,99 @@ +fileFormatVersion: 2 +guid: babe6cc749d43c2469c6cb12a7a9d344 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 9 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - serializedVersion: 2 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png b/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png new file mode 100644 index 0000000000000000000000000000000000000000..683501e9fe9fd0503c2e70d15ae0687b42f779dd GIT binary patch literal 597 zcmV-b0;>IqP)}#)1-2GT0zRh6eeOQnO%&@v(UC%k}iWW~kZ7**woZ=llJhbI(2Z zM*O$oG&B#V_#AvfC#KcI6@!ROjEF4}u{t6aM8x=r7>J115iuho7ANSAq9{85q`KKS zjXs0hT{H|;xB&;!)^mG-U6_f(rPzjyzZJE+f+k!@q~|!B{-0n97U2lGGx3WVe|^=5 zrn2~GTuXrS7>g@uy&22#xm0;S=4Toca5cql{78ZV=ThKgb&GhHL`JGiO4*L(tx4ZT zm9wkvqzLRyj!PJkk$Uk3+wis|{t=V2);`R5jkun0ZGj%V!K6~#?$@ZgY6ml12kw-( zQ&Ukrfv(hwl_k)jI^x|JoeA|~e~G(HWyHGzt5Sk)*^NZAUniz&J2T7)Jj0mGH!bn{ zu%ha0OXNqGnt>PMWhQ>x&mmZ>az9!!{B%4>*nQP!sr6C&85GGU@XzDT0 ziAao$jCEij9x*df6KN0;CI|*iS_4Td9T+s4GC(|Ak5=n5IJYSMnIBl_w#P^A6_)H3orNBdA82ANr z0FPsA*GveE13kc9;3@DEI0sAsRqK#VAus}L2F?SMz&_wOAh0k6zOtz&)y4wfj#z^U zbzUWWnjL%tE~;lqYLElQfVV(5uoLJi2M*QP)!V=WpaJ*@JOb_j&wzD6Kd?E3@H@rN z7Lo5)Z^rc&bx~fdIq%w@`5*G->PPipnRr%xAD>Be`@afYRY%o^3OPsB!8kq!)!Avu z>(z1fdX>D{>R0t)JgT00N4wx0bYg>ex(rT1qMj^B?m0a50+}QVc-_+zwz<6uuHd=q}9p1HI&_lqtbn QUjP6A07*qoM6N<$g2$LC(*OVf literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png.meta new file mode 100644 index 00000000..65284f25 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png.meta @@ -0,0 +1,100 @@ +fileFormatVersion: 2 +guid: c78f517c9f87bdf4e814d0d96e740793 +timeCreated: 1500327620 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Standalone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: iPhone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Android + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: WebGL + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png b/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4b9c295781568a8458ee3567ae3ea975a0a90f GIT binary patch literal 400 zcmV;B0dM|^P)eDE=# zLzrJ+vTVUAac;_nj{t8G{1aFyTd)+t-+`Sx?*u8JRY@n3)+F^MJxJ@RUQC+ilqZoeeiVy9Kx>J^$FSrIKZI~J_`&C@arUQ#d+RP%16(c z%Phf+0WO6d<`q1MwCrE5`-8cwapvkD<{MTndv@=DaK);IYP_rVgvc)j|i0()latmG*q} t+OyMl-!taQ5Q{It%q2l-kI&TAu^pP?z_+ekstV{=22WQ%mvv4FO#rCQc!~f3 literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png.meta new file mode 100644 index 00000000..7792f8df --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png.meta @@ -0,0 +1,100 @@ +fileFormatVersion: 2 +guid: d40b23b11d3b85145af46d2c1316e057 +timeCreated: 1500327620 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Standalone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: iPhone + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Android + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: WebGL + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/refresh.png b/Assets/PlayerPrefsEditor/Editor Resources/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..ef52cc74a4f9f789b9d0c5e358dd69b1f2b4dfa3 GIT binary patch literal 531 zcmV+u0_^>XP)?-}~NsGdu6WKSpRmQpbU7 zz(wE~*aJR>5ROk3UA?2Os)zoGeRWBlFZi_8zS^o2o>JFJM9c6~eWFgN9d(%77pugl z)!l~TF+QkoqIC~oydXXayaX;s<|AMYSOIo{5ikSX1s(!ta^BS@XTDO%YD1l?;m@dV zD=PZE0`&%PFY@04ZiNsIYjXOWSD1$SIEwy5olWrD>Y(OMdNFxSJ%~1!8zj`PzjGHL zdT8ox;B3C!2qEkx0UK@QDr}nlNu{MO=A?ny z(CDbo&4x)6ELnF~YRjy*vc-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`kBtHuNWFoz#!AFNG#Ad)HBe}%?0@jth%@)C>7xhtg4GcDhpEegHnt0 zON)|$@sXws(+mtd{1$-}0$pR}Uz7=ql*AmD{N&Qy)VvZ;7h5Huj9yA+ij}LYqqC)} zo4J{pnWd|dnWM9tk(;@Jp)<@3Y`gLD2*8txIZAW?5>ATTyF!Nq_$Tw`bO66lU&a zD2tgiv6(yvX3vk;XGpB@IzhT_v1|rhjo&49E@bxHbgm1W6fyW z%wTA&=k2kLq5GkMX@sugNhX`N$6H!5xEpRruM%Xr!I3aS_1=<*4gc+5CfJ*8mMB#5 z=2W<*Xy_ly_()u$l4~~W4kIbWhO=uLHMJV_7DfqNw3)=NHd85e%X^cP85yoe%V&v* zR_H8@OKd+Z^h)e{gpT6MLyDT}lY>)@_FVdQ&MBb@ E0HIfTNdN!< literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/sort.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/sort.png.meta new file mode 100644 index 00000000..1206ad87 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/sort.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: 5d3ebd901d622c14a9653af004d172a0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png b/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png new file mode 100644 index 0000000000000000000000000000000000000000..19357741b6ac7e763274c66720d30ec0c8baf0b4 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nA0*tB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`kBtHuNWFoz#!AFNG#Ad)HBe}%?0@jth%@)C>7xhtg4GcDhpEegHnt0 zON)|$@sXws(+mtd{1$-}0$pR}Uz7=ql*AmD{N&Qy)VvZ;7h5Huj9yA+ij}LYqqC)} zo4J{pnWd|dnWM9tk(;@Jp)<@3YHGxhOTUB)=#mKR*YS0s=DfOY(~| z@(UE4gUu8)!ZY(y^2>`gLD2*8txIZAW?5>ATTybj?kV@SoVE7Q*M9x~u*eav8O z;NqXtXt5!p{1w|Ai6&mJohc4_>Qs13&k#$qhVTb>efLtK+e6 z2V)l8>ohy{$}Ay9$8SsIN{0zQN>@fS9LYNB*LX$uI>RHYf|!{xMJbo=-`ASQ#KWK< WRw2DdX3us|(c$Up=d#Wzp$PysU5%>% literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png.meta new file mode 100644 index 00000000..18800134 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 3e86075fa207f0041b6111dbfaa3d66e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png b/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..b146ae8b6c082b4f0adb654c22c2b99d20950c0f GIT binary patch literal 1164 zcmbVMU1$_n6dq}`X*2}|QG>#Df+fZ7{AG70GiHptGqcTtyN1oeE-K<=<|Y}EnLC-e z$xh;n=t?WJrGK|_`+O$X{KKRlKN~=C8K2)Eg4~mGQcam)MAt*S^%>9}3 zecw6fo-@aL3ZFMOwKP!_)tu|ri)7pwKaH!%`%YDTN`_6?sNi1IkE>LIN*3HJvV)c29B-L zBt7sa%e6|xe? zYjuw0utK{gw5O6CqAUqKZ)mcRk#t$(r4B>ukTU5yHs^)d^ekA{b%^dFR(dN|(E?~< z6qFDeu2(?MAi^jdL_W}ZMbK7uJR4QRPvhfRh*pPzGY0K!fLt)2U&UEQ|J`vfVD0zG z8B3gD667$^FWVuOh8)W>|N7?^u8dFK9FYVAzllzaJE+xdH6 z99n&H-;>$XxBhDAupR2;!?woX8*U!!+7h#x{sk&pz3__0&o( znqKwew$$D$Q(vo(t(U*GB(|M++<52ja_M>N&^PC@7p*^>ZO r+CNR-YuUAXL*dc(9}c`MUufj0!Y|j>fAwPI+xY*GGYa}t=HQW6m=Aup literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png.meta new file mode 100644 index 00000000..43fbc2f2 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 73059e24567e2e647bb1d8280f28bf43 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor Resources/trash.png b/Assets/PlayerPrefsEditor/Editor Resources/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..098dad505859f0e7de9ac6e24aba9b2894750849 GIT binary patch literal 368 zcmV-$0gwKPP)PgM~I)2o{Qv!bT7T3lV;s5zb|^cbC&b$PWuQ^Uu!z&n@i2AH%8?N!kP2LHgCq zt{OX#G}dg4%l0QHkhBfl0GGg7Kpz78z>b;S&g=fvoem=0z`2uD4=Zdq0FDEG184z{ zfFgUlzy~l5xFa(=2Uw1Xt^q4SuA4B~0yh2o9e7UEf}}igsAb5*eoIL;cmYPhgU1tJ zcy8o1)e%gPdv54)-yi^Lp1i<^V>dH0^Sk`Erdo%!aQ+1;6i z9sKu5WlnV;a2YrYi~@t<`UWfkuYuW=()WMLsy5X7>Za9b1-J1x!Zr0)58*HB^+~}Jv3K#0qej4;Ar9AA6K?rf9|o+Tt^*O+jhERIICjN zPJ32iS>2z-9S+;JI^H!-1$j{&$;e~sYKi!&I+pQA)TQuE{kA7NJ>CsEm)Q@g_ta)4 z+zi`L#yg)CbGPR~s*`qFvu$Vmk7T}@Ivii#Z4GrgVz$)DT=S2|PCW@s1E+vC@Cuj% z-UAyEa}YQITm&uvHQ+69Bc=4NXZ3|7Nxh=JjLYhWZFNq)Y7A%*rO$OFs{?0%<3M*- ln!rb3A*HlYS@90a@C(YAJZa{y04@Lk002ovPDHLkV1jJ0^)Ubd literal 0 HcmV?d00001 diff --git a/Assets/PlayerPrefsEditor/Editor Resources/watching.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/watching.png.meta new file mode 100644 index 00000000..d1062b12 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor Resources/watching.png.meta @@ -0,0 +1,86 @@ +fileFormatVersion: 2 +guid: ed8c91adb8fc54f41aaff0986b083281 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor.meta b/Assets/PlayerPrefsEditor/Editor.meta new file mode 100644 index 00000000..911b1088 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7afdecdeaea3efc42b92ba335397568c +folderAsset: yes +timeCreated: 1496263422 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Dialogs.meta b/Assets/PlayerPrefsEditor/Editor/Dialogs.meta new file mode 100644 index 00000000..9fde6e24 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Dialogs.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2c61139fc58134242bb4b9e6d9fabdc0 +folderAsset: yes +timeCreated: 1502815237 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs new file mode 100644 index 00000000..8225bb48 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs @@ -0,0 +1,139 @@ +using BgTools.Extensions; +using BgTools.Utils; +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace BgTools.Dialogs +{ + public class TextFieldDialog : EditorWindow + { + [NonSerialized] + private string resultString = string.Empty; + + [NonSerialized] + private Action callback; + + [NonSerialized] + private string description; + + [NonSerialized] + private List validatorList = new List(); + + [NonSerialized] + private TextValidator errorValidator = null; + + public static void OpenDialog(string title, string description, List validatorList, Action callback, EditorWindow targetWin = null) + { + TextFieldDialog window = ScriptableObject.CreateInstance(); + + window.name = "TextFieldDialog '" + title + "'"; + window.titleContent = new GUIContent (title); + window.description = description; + window.callback = callback; + window.validatorList = validatorList; + window.position = new Rect(0, 0, 350, 140); + + window.ShowUtility(); + + window.CenterOnWindow(targetWin); + window.Focus(); + EditorWindow.FocusWindowIfItsOpen(); + } + + void OnGUI() + { + errorValidator = null; + + Color defaultColor = GUI.contentColor; + + GUILayout.Space(20); + EditorGUILayout.LabelField(description); + GUILayout.Space(20); + + GUI.SetNextControlName(name+"_textInput"); + resultString = EditorGUILayout.TextField(resultString, GUILayout.ExpandWidth(true)); +// GUILayout.Space(20); + GUILayout.FlexibleSpace(); + + + foreach(TextValidator val in validatorList) + { + if (!val.Validate(resultString)) + { + errorValidator = val; + break; + } + } + bool lockOkButton = !(errorValidator != null && errorValidator.m_errorType == TextValidator.ErrorType.Error); + + GUILayout.BeginHorizontal(); + + if(errorValidator != null) + { + switch (errorValidator.m_errorType) + { + case TextValidator.ErrorType.Info: + GUI.contentColor = Styles.Colors.Blue; + GUILayout.Box(new GUIContent(ImageManager.Info, errorValidator.m_failureMsg), Styles.icon); + break; + case TextValidator.ErrorType.Warning: + GUI.contentColor = Styles.Colors.Yellow; + GUILayout.Box(new GUIContent(ImageManager.Exclamation, errorValidator.m_failureMsg), Styles.icon); + break; + case TextValidator.ErrorType.Error: + GUI.contentColor = Styles.Colors.Red; + GUILayout.Box(new GUIContent(ImageManager.Exclamation, errorValidator.m_failureMsg), Styles.icon); + break; + } + GUI.contentColor = defaultColor; + } + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Cancel", GUILayout.Width(75.0f))) + this.Close(); + + GUI.enabled = lockOkButton; + + if (GUILayout.Button("OK", GUILayout.Width(75.0f))) + { + callback(resultString); + Close(); + } + + GUI.enabled = true; + + GUILayout.EndHorizontal(); + + GUILayout.Space(20); + + // set focus only if element exist + try + { + EditorGUI.FocusTextInControl(name+"_textInput"); + } + catch (MissingReferenceException) + { } + + if (Event.current != null && Event.current.isKey) + { + switch (Event.current.keyCode) + { + case KeyCode.Return: + if (lockOkButton) + { + callback(resultString); + Close(); + } + break; + case KeyCode.Escape: + Close(); + break; + } + + } + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs.meta b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs.meta new file mode 100644 index 00000000..b050888f --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextFieldDialog.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 380be3677d2e95144863ee00c051c1f2 +timeCreated: 1500849296 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs new file mode 100644 index 00000000..6628aa12 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs @@ -0,0 +1,64 @@ +using System; +using System.Text.RegularExpressions; + +namespace BgTools.Dialogs +{ + public class TextValidator + { + public enum ErrorType + { + Invalid = -1, + Info = 0, + Warning = 1, + Error = 2 + } + + [NonSerialized] + public ErrorType m_errorType = ErrorType.Invalid; + + [NonSerialized] + private string m_regEx = string.Empty; + + [NonSerialized] + private Func m_validationFunction; + + [NonSerialized] + public string m_failureMsg = string.Empty; + + /// + /// Validator for TextFieldDialog based on regex. + /// + /// Categorie of the error. + /// Message that described the reason why the validation fail. + /// String with regular expression. It need to describe the valid state. + public TextValidator(ErrorType errorType, string failureMsg, string regEx) + { + m_errorType = errorType; + m_failureMsg = failureMsg; + m_regEx = regEx; + } + + /// + /// Validator for TextFieldDialog based on regex. + /// + /// Categorie of the error. + /// Message that described the reason why the validation fail. + /// Function that validate the input. Get the current input as string and need to return a bool. Nedd to return 'false' if the validation fails. + public TextValidator(ErrorType errorType, string failureMsg, Func validationFunction) + { + m_errorType = errorType; + m_failureMsg = failureMsg; + m_validationFunction = validationFunction; + } + + public bool Validate(string srcString) + { + if (m_regEx != string.Empty) + return Regex.IsMatch(srcString, m_regEx); + else if (m_validationFunction != null) + return m_validationFunction(srcString); + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs.meta b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs.meta new file mode 100644 index 00000000..9a715f6e --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Dialogs/TextValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4202eaaf18e2e43438f2f3632b252393 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Extensions.meta b/Assets/PlayerPrefsEditor/Editor/Extensions.meta new file mode 100644 index 00000000..49f9dbb8 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Extensions.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5c7bb3ee5362c0a40a707ade01e79972 +folderAsset: yes +timeCreated: 1502876479 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs b/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs new file mode 100644 index 00000000..fa22b8f7 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace BgTools.Extensions +{ + + public static class Extensions + { + private static Type[] GetAllDerivedTypes(this AppDomain aAppDomain, Type aType) + { + var result = new List(); + var assemblies = aAppDomain.GetAssemblies(); + + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes(); + foreach (Type type in types) + { + if (type.IsSubclassOf(aType)) + result.Add(type); + } + } + return result.ToArray(); + } + + public static Rect GetEditorMainWindowPos(EditorWindow relatedWin = null) + { + var containerWinType = AppDomain.CurrentDomain.GetAllDerivedTypes(typeof(ScriptableObject)).Where(t => t.Name == "ContainerWindow").FirstOrDefault(); + + if (containerWinType == null) + throw new MissingMemberException("Can't find internal type ContainerWindow. Maybe something has changed inside Unity"); + + var showModeField = containerWinType.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var positionProperty = containerWinType.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + + if (showModeField == null || positionProperty == null) + throw new MissingFieldException("Can't find internal fields 'm_ShowMode' or 'position'. Maybe something has changed inside Unity"); + + var windows = Resources.FindObjectsOfTypeAll(containerWinType); + foreach (var win in windows) + { + var showmode = (int)showModeField.GetValue(win); + + // Given window + //if (relatedWin != null && relatedWin.GetInstanceID() == win.GetInstanceID()) + //{ + // var pos = (Rect)positionProperty.GetValue(win, null); + // return pos; + //} + + // Main window + if (showmode == 4) + { + var pos = (Rect)positionProperty.GetValue(win, null); + return pos; + } + } + throw new NotSupportedException("Can't find internal main window. Maybe something has changed inside Unity"); + } + + /// + /// Center the EditorWindow in front of the MainUnityWindow (support multi screens). + /// Kept the currend window sizes. + /// + public static void CenterOnMainWindow(this EditorWindow window) + { + CenterOnWindow(window, null); + } + + /// + /// Center the EditorWindow in front of the given EditorWindow (support multi screens). + /// Kept the currend window sizes. + /// + /// Referance window for the positioning. + public static void CenterOnWindow(this EditorWindow window, EditorWindow relatedWin) + { + var main = GetEditorMainWindowPos(relatedWin); + + var pos = window.position; + float w = (main.width - pos.width) * 0.5f; + float h = (main.height - pos.height) * 0.5f; + pos.x = main.x + w; + pos.y = main.y + h; + window.position = pos; + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs.meta b/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs.meta new file mode 100644 index 00000000..6334001d --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Extensions/CenterOnWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 274db1862ad1a1b4c80a2ed6558e05ec +timeCreated: 1502876542 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor.meta new file mode 100644 index 00000000..d2caa327 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e93ef5c4e798b034bb024596113459cb +folderAsset: yes +timeCreated: 1505565882 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs new file mode 100644 index 00000000..191d819c --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs @@ -0,0 +1,36 @@ +namespace BgTools.PlayerPrefsEditor +{ + [System.Serializable] + public class PreferenceEntry + { + public enum PrefTypes + { + String = 0, + Int = 1, + Float = 2 + } + + public PrefTypes m_typeSelection; + public string m_key; + + // Need diffrend ones for auto type selection of serilizedProerty + public string m_strValue; + public int m_intValue; + public float m_floatValue; + + public string ValueAsString() + { + switch(m_typeSelection) + { + case PrefTypes.String: + return m_strValue; + case PrefTypes.Int: + return m_intValue.ToString(); + case PrefTypes.Float: + return m_floatValue.ToString(); + default: + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs.meta new file mode 100644 index 00000000..e630a8d7 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntry.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2ae9239fbddf12b4099b3cacc5301271 +timeCreated: 1496684286 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs new file mode 100644 index 00000000..60be7f31 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace BgTools.PlayerPrefsEditor +{ + [System.Serializable] + public class PreferenceEntryHolder : ScriptableObject + { + public List userDefList; + public List unityDefList; + + private void OnEnable() + { + hideFlags = HideFlags.DontSave; + if (userDefList == null) + userDefList = new List(); + if (unityDefList == null) + unityDefList = new List(); + } + + public void ClearLists() + { + if (userDefList != null) + userDefList.Clear(); + if (unityDefList != null) + unityDefList.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs.meta new file mode 100644 index 00000000..165060ac --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceEntryHolder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d5d94a5263d6af0478dde8fb08a3dcb7 +timeCreated: 1500316993 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs new file mode 100644 index 00000000..d04fa036 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs @@ -0,0 +1,272 @@ +using System; +using System.Linq; + +#if UNITY_EDITOR_WIN +using Microsoft.Win32; +using System.Text; +#elif UNITY_EDITOR_OSX +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +#elif UNITY_EDITOR_LINUX +using System.IO; +using System.Xml; +using System.Xml.Linq; +#endif + +namespace BgTools.PlayerPrefsEditor +{ + public abstract class PreferanceStorageAccessor + { + protected string prefPath; + protected string[] cachedData = new string[0]; + + protected abstract void FetchKeysFromSystem(); + + protected PreferanceStorageAccessor(string pathToPrefs) + { + prefPath = pathToPrefs; + } + + public string[] GetKeys(bool reloadData = true) + { + if (reloadData || cachedData.Length == 0) + { + FetchKeysFromSystem(); + } + + return cachedData; + } + + public Action PrefEntryChangedDelegate; + protected bool ignoreNextChange = false; + + public void IgnoreNextChange() + { + ignoreNextChange = true; + } + + protected virtual void OnPrefEntryChanged() + { + if (ignoreNextChange) + { + ignoreNextChange = false; + return; + } + + PrefEntryChangedDelegate(); + } + + public Action StartLoadingDelegate; + public Action StopLoadingDelegate; + + public abstract void StartMonitoring(); + public abstract void StopMonitoring(); + public abstract bool IsMonitoring(); + } + +#if UNITY_EDITOR_WIN + + public class WindowsPrefStorage : PreferanceStorageAccessor + { + RegistryMonitor monitor; + + public WindowsPrefStorage(string pathToPrefs) : base(pathToPrefs) + { + monitor = new RegistryMonitor(RegistryHive.CurrentUser, prefPath); + monitor.RegChanged += new EventHandler(OnRegChanged); + } + + private void OnRegChanged(object sender, EventArgs e) + { + OnPrefEntryChanged(); + } + + protected override void FetchKeysFromSystem() + { + cachedData = new string[0]; + + using (RegistryKey rootKey = Registry.CurrentUser.OpenSubKey(prefPath)) + { + if (rootKey != null) + { + cachedData = rootKey.GetValueNames(); + rootKey.Close(); + } + } + + // Clean _h3320113488 nameing + cachedData = cachedData.Select((key) => { return key.Substring(0, key.LastIndexOf("_h", StringComparison.Ordinal)); }).ToArray(); + + EncodeAnsiInPlace(); + } + + public override void StartMonitoring() + { + monitor.Start(); + } + + public override void StopMonitoring() + { + monitor.Stop(); + } + + public override bool IsMonitoring() + { + return monitor.IsMonitoring; + } + + private void EncodeAnsiInPlace() + { + Encoding utf8 = Encoding.UTF8; + Encoding ansi = Encoding.GetEncoding(1252); + + for (int i = 0; i < cachedData.Length; i++) + { + cachedData[i] = utf8.GetString(ansi.GetBytes(cachedData[i])); + } + } + } + +#elif UNITY_EDITOR_LINUX + + public class LinuxPrefStorage : PreferanceStorageAccessor + { + FileSystemWatcher fileWatcher; + + public LinuxPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs)) + { + fileWatcher = new FileSystemWatcher(); + fileWatcher.Path = Path.GetDirectoryName(prefPath); + fileWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite; + fileWatcher.Filter = "prefs"; + + fileWatcher.Changed += OnWatchedFileChanged; + } + + protected override void FetchKeysFromSystem() + { + cachedData = new string[0]; + + if (File.Exists(prefPath)) + { + XmlReaderSettings settings = new XmlReaderSettings(); + XmlReader reader = XmlReader.Create(prefPath, settings); + + XDocument doc = XDocument.Load(reader); + + cachedData = doc.Element("unity_prefs").Elements().Select((e) => e.Attribute("name").Value).ToArray(); + } + } + + public override void StartMonitoring() + { + fileWatcher.EnableRaisingEvents = true; + } + + public override void StopMonitoring() + { + fileWatcher.EnableRaisingEvents = false; + } + + public override bool IsMonitoring() + { + return fileWatcher.EnableRaisingEvents; + } + + private void OnWatchedFileChanged(object source, FileSystemEventArgs e) + { + OnPrefEntryChanged(); + } + } + +#elif UNITY_EDITOR_OSX + + public class MacPrefStorage : PreferanceStorageAccessor + { + private FileSystemWatcher fileWatcher; + private DirectoryInfo prefsDirInfo; + private String prefsFileNameWithoutExtension; + + public MacPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs)) + { + prefsDirInfo = new DirectoryInfo(Path.GetDirectoryName(prefPath)); + prefsFileNameWithoutExtension = Path.GetFileNameWithoutExtension(prefPath); + + fileWatcher = new FileSystemWatcher(); + fileWatcher.Path = Path.GetDirectoryName(prefPath); + fileWatcher.NotifyFilter = NotifyFilters.LastWrite; + fileWatcher.Filter = Path.GetFileName(prefPath); + + // MAC delete the old and create a new file instead of updating + fileWatcher.Created += OnWatchedFileChanged; + } + + protected override void FetchKeysFromSystem() + { + // Workaround to avoid incomplete tmp phase from MAC OS + foreach (FileInfo info in prefsDirInfo.GetFiles()) + { + // Check if tmp PlayerPrefs file exist + if (info.FullName.Contains(prefsFileNameWithoutExtension) && !info.FullName.EndsWith(".plist")) + { + StartLoadingDelegate(); + return; + } + } + StopLoadingDelegate(); + + cachedData = new string[0]; + + if (File.Exists(prefPath)) + { + string fixedPrefsPath = prefPath.Replace("\"", "\\\"").Replace("'", "\\'").Replace("`", "\\`"); + var cmdStr = string.Format(@"-p '{0}'", fixedPrefsPath); + + string stdOut = String.Empty; + string errOut = String.Empty; + + var process = new System.Diagnostics.Process(); + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "plutil"; + process.StartInfo.Arguments = cmdStr; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.OutputDataReceived += new DataReceivedEventHandler((sender, evt) => { stdOut += evt.Data + "\n"; }); + process.ErrorDataReceived += new DataReceivedEventHandler((sender, evt) => { errOut += evt.Data + "\n"; }); + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(); + + MatchCollection matches = Regex.Matches(stdOut, @"(?: "")(.*)(?:"" =>.*)"); + cachedData = matches.Cast().Select((e) => e.Groups[1].Value).ToArray(); + } + } + + public override void StartMonitoring() + { + fileWatcher.EnableRaisingEvents = true; + } + + public override void StopMonitoring() + { + fileWatcher.EnableRaisingEvents = false; + } + + public override bool IsMonitoring() + { + return fileWatcher.EnableRaisingEvents; + } + + private void OnWatchedFileChanged(object source, FileSystemEventArgs e) + { + OnPrefEntryChanged(); + } + + } +#endif +} diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs.meta new file mode 100644 index 00000000..6c1d495d --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferenceStorageAccessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f54241e622579a145a495df929a9330a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs new file mode 100644 index 00000000..8c91c5bc --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs @@ -0,0 +1,700 @@ +using UnityEngine; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEditorInternal; +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using BgTools.Utils; +using BgTools.Dialogs; + +#if (UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX) +using System.Text; +using System.Globalization; +#endif + +namespace BgTools.PlayerPrefsEditor +{ + public class PreferencesEditorWindow : EditorWindow + { +#region ErrorValues + private readonly int ERROR_VALUE_INT = int.MinValue; + private readonly string ERROR_VALUE_STR = ""; + #endregion //ErrorValues + + private enum PreferencesEntrySortOrder + { + None = 0, + Asscending = 1, + Descending = 2 + } + + private static string pathToPrefs = String.Empty; + private static string platformPathPrefix = @"~"; + + private string[] userDef; + private string[] unityDef; + private bool showSystemGroup = false; + + private PreferencesEntrySortOrder sortOrder = PreferencesEntrySortOrder.None; + + private SerializedObject serializedObject; + private ReorderableList userDefList; + private ReorderableList unityDefList; + + private SerializedProperty[] userDefListCache = new SerializedProperty[0]; + + private PreferenceEntryHolder prefEntryHolder; + + private Vector2 scrollPos; + private float relSpliterPos; + private bool moveSplitterPos = false; + + private PreferanceStorageAccessor entryAccessor; + + private MySearchField searchfield; + private string searchTxt; + private int loadingSpinnerFrame; + + private bool updateView = false; + private bool monitoring = false; + private bool showLoadingIndicatorOverlay = false; + + private readonly List prefKeyValidatorList = new List() + { + new TextValidator(TextValidator.ErrorType.Error, @"Invalid character detected. Only letters, numbers, space and ,.;:<>_|!§$%&/()=?*+~#-]+$ are allowed", @"(^$)|(^[a-zA-Z0-9 ,.;:<>_|!§$%&/()=?*+~#-]+$)"), + new TextValidator(TextValidator.ErrorType.Warning, @"The given key already exist. The existing entry would be overwritten!", (key) => { return !PlayerPrefs.HasKey(key); }) + }; + +#if UNITY_EDITOR_LINUX + private readonly char[] invalidFilenameChars = { '"', '\\', '*', '/', ':', '<', '>', '?', '|' }; +#elif UNITY_EDITOR_OSX + private readonly char[] invalidFilenameChars = { '$', '%', '&', '\\', '/', ':', '<', '>', '|', '~' }; +#endif + [MenuItem("Tools/BG Tools/PlayerPrefs Editor", false, 1)] + static void ShowWindow() + { + PreferencesEditorWindow window = EditorWindow.GetWindow(false, "Prefs Editor"); + window.minSize = new Vector2(270.0f, 300.0f); + window.name = "Prefs Editor"; + + //window.titleContent = EditorGUIUtility.IconContent("SettingsIcon"); // Icon + + window.Show(); + } + + private void OnEnable() + { +#if UNITY_EDITOR_WIN + pathToPrefs = @"SOFTWARE\Unity\UnityEditor\" + PlayerSettings.companyName + @"\" + PlayerSettings.productName; + platformPathPrefix = @""; + entryAccessor = new WindowsPrefStorage(pathToPrefs); +#elif UNITY_EDITOR_OSX + pathToPrefs = @"Library/Preferences/unity." + MakeValidFileName(PlayerSettings.companyName) + "." + MakeValidFileName(PlayerSettings.productName) + ".plist"; + entryAccessor = new MacPrefStorage(pathToPrefs); + entryAccessor.StartLoadingDelegate = () => { showLoadingIndicatorOverlay = true; }; + entryAccessor.StopLoadingDelegate = () => { showLoadingIndicatorOverlay = false; }; +#elif UNITY_EDITOR_LINUX + pathToPrefs = @".config/unity3d/" + MakeValidFileName(PlayerSettings.companyName) + "/" + MakeValidFileName(PlayerSettings.productName) + "/prefs"; + entryAccessor = new LinuxPrefStorage(pathToPrefs); +#endif + entryAccessor.PrefEntryChangedDelegate = () => { updateView = true; }; + + monitoring = EditorPrefs.GetBool("BGTools.PlayerPrefsEditor.WatchingForChanges", true); + if(monitoring) + entryAccessor.StartMonitoring(); + + sortOrder = (PreferencesEntrySortOrder) EditorPrefs.GetInt("BGTools.PlayerPrefsEditor.SortOrder", 0); + searchfield = new MySearchField(); + searchfield.DropdownSelectionDelegate = () => { PrepareData(); }; + + // Fix for serialisation issue of static fields + if (userDefList == null) + { + InitReorderedList(); + PrepareData(); + } + } + + // Handel view updates for monitored changes + // Necessary to avoid main thread access issue + private void Update() + { + if (showLoadingIndicatorOverlay) + { + loadingSpinnerFrame = (int)Mathf.Repeat(Time.realtimeSinceStartup * 10, 11.99f); + PrepareData(); + Repaint(); + } + + if (updateView) + { + updateView = false; + PrepareData(); + Repaint(); + } + } + + private void OnDisable() + { + entryAccessor.StopMonitoring(); + } + + private void InitReorderedList() + { + if (prefEntryHolder == null) + { + var tmp = Resources.FindObjectsOfTypeAll(); + if (tmp.Length > 0) + { + prefEntryHolder = tmp[0]; + } + else + { + prefEntryHolder = ScriptableObject.CreateInstance(); + } + } + + if (serializedObject == null) + { + serializedObject = new SerializedObject(prefEntryHolder); + } + + userDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("userDefList"), false, true, true, true); + unityDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("unityDefList"), false, true, false, false); + + relSpliterPos = EditorPrefs.GetFloat("BGTools.PlayerPrefsEditor.RelativeSpliterPosition", 100 / position.width); + + userDefList.drawHeaderCallback = (Rect rect) => + { + EditorGUI.LabelField(rect, "User defined"); + }; + userDefList.drawElementBackgroundCallback = OnDrawElementBackgroundCallback; + userDefList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => + { + SerializedProperty element = GetUserDefListElementAtIndex(index, userDefList.serializedProperty); + + SerializedProperty key = element.FindPropertyRelative("m_key"); + SerializedProperty type = element.FindPropertyRelative("m_typeSelection"); + + SerializedProperty value; + + // Load only necessary type + switch ((PreferenceEntry.PrefTypes)type.enumValueIndex) + { + case PreferenceEntry.PrefTypes.Float: + value = element.FindPropertyRelative("m_floatValue"); + break; + case PreferenceEntry.PrefTypes.Int: + value = element.FindPropertyRelative("m_intValue"); + break; + case PreferenceEntry.PrefTypes.String: + value = element.FindPropertyRelative("m_strValue"); + break; + default: + value = element.FindPropertyRelative("This should never happen"); + break; + } + + float spliterPos = relSpliterPos * rect.width; + rect.y += 2; + + EditorGUI.BeginChangeCheck(); + string prefKeyName = key.stringValue; + EditorGUI.LabelField(new Rect(rect.x, rect.y, spliterPos - 1, EditorGUIUtility.singleLineHeight), new GUIContent(prefKeyName, prefKeyName)); + GUI.enabled = false; + EditorGUI.EnumPopup(new Rect(rect.x + spliterPos + 1, rect.y, 60, EditorGUIUtility.singleLineHeight), (PreferenceEntry.PrefTypes)type.enumValueIndex); + GUI.enabled = !showLoadingIndicatorOverlay; + switch ((PreferenceEntry.PrefTypes)type.enumValueIndex) + { + case PreferenceEntry.PrefTypes.Float: + EditorGUI.DelayedFloatField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + case PreferenceEntry.PrefTypes.Int: + EditorGUI.DelayedIntField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + case PreferenceEntry.PrefTypes.String: + EditorGUI.DelayedTextField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + } + if (EditorGUI.EndChangeCheck()) + { + entryAccessor.IgnoreNextChange(); + + switch ((PreferenceEntry.PrefTypes)type.enumValueIndex) + { + case PreferenceEntry.PrefTypes.Float: + PlayerPrefs.SetFloat(key.stringValue, value.floatValue); + break; + case PreferenceEntry.PrefTypes.Int: + PlayerPrefs.SetInt(key.stringValue, value.intValue); + break; + case PreferenceEntry.PrefTypes.String: + PlayerPrefs.SetString(key.stringValue, value.stringValue); + break; + } + + PlayerPrefs.Save(); + } + }; + userDefList.onRemoveCallback = (ReorderableList l) => + { + userDefList.ReleaseKeyboardFocus(); + unityDefList.ReleaseKeyboardFocus(); + + string prefKey = l.serializedProperty.GetArrayElementAtIndex(l.index).FindPropertyRelative("m_key").stringValue; + if (EditorUtility.DisplayDialog("Warning!", $"Are you sure you want to delete this entry from PlayerPrefs?\n\nEntry: {prefKey}", "Yes", "No")) + { + entryAccessor.IgnoreNextChange(); + + PlayerPrefs.DeleteKey(prefKey); + PlayerPrefs.Save(); + + ReorderableList.defaultBehaviours.DoRemoveButton(l); + PrepareData(); + GUIUtility.ExitGUI(); + } + }; + userDefList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => + { + var menu = new GenericMenu(); + foreach (PreferenceEntry.PrefTypes type in Enum.GetValues(typeof(PreferenceEntry.PrefTypes))) + { + menu.AddItem(new GUIContent(type.ToString()), false, () => + { + TextFieldDialog.OpenDialog("Create new property", "Key for the new property:", prefKeyValidatorList, (key) => { + + entryAccessor.IgnoreNextChange(); + + switch (type) + { + case PreferenceEntry.PrefTypes.Float: + PlayerPrefs.SetFloat(key, 0.0f); + + break; + case PreferenceEntry.PrefTypes.Int: + PlayerPrefs.SetInt(key, 0); + + break; + case PreferenceEntry.PrefTypes.String: + PlayerPrefs.SetString(key, string.Empty); + + break; + } + PlayerPrefs.Save(); + + PrepareData(); + + Focus(); + }, this); + + }); + } + menu.ShowAsContext(); + }; + + unityDefList.drawElementBackgroundCallback = OnDrawElementBackgroundCallback; + unityDefList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => + { + var element = unityDefList.serializedProperty.GetArrayElementAtIndex(index); + SerializedProperty key = element.FindPropertyRelative("m_key"); + SerializedProperty type = element.FindPropertyRelative("m_typeSelection"); + + SerializedProperty value; + + // Load only necessary type + switch ((PreferenceEntry.PrefTypes)type.enumValueIndex) + { + case PreferenceEntry.PrefTypes.Float: + value = element.FindPropertyRelative("m_floatValue"); + break; + case PreferenceEntry.PrefTypes.Int: + value = element.FindPropertyRelative("m_intValue"); + break; + case PreferenceEntry.PrefTypes.String: + value = element.FindPropertyRelative("m_strValue"); + break; + default: + value = element.FindPropertyRelative("This should never happen"); + break; + } + + float spliterPos = relSpliterPos * rect.width; + rect.y += 2; + + GUI.enabled = false; + string prefKeyName = key.stringValue; + EditorGUI.LabelField(new Rect(rect.x, rect.y, spliterPos - 1, EditorGUIUtility.singleLineHeight), new GUIContent(prefKeyName, prefKeyName)); + EditorGUI.EnumPopup(new Rect(rect.x + spliterPos + 1, rect.y, 60, EditorGUIUtility.singleLineHeight), (PreferenceEntry.PrefTypes)type.enumValueIndex); + + switch ((PreferenceEntry.PrefTypes)type.enumValueIndex) + { + case PreferenceEntry.PrefTypes.Float: + EditorGUI.DelayedFloatField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + case PreferenceEntry.PrefTypes.Int: + EditorGUI.DelayedIntField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + case PreferenceEntry.PrefTypes.String: + EditorGUI.DelayedTextField(new Rect(rect.x + spliterPos + 62, rect.y, rect.width - spliterPos - 60, EditorGUIUtility.singleLineHeight), value, GUIContent.none); + break; + } + GUI.enabled = !showLoadingIndicatorOverlay; + }; + unityDefList.drawHeaderCallback = (Rect rect) => + { + EditorGUI.LabelField(rect, "Unity defined"); + }; + } + + private void OnDrawElementBackgroundCallback(Rect rect, int index, bool isActive, bool isFocused) + { + if (Event.current.type == EventType.Repaint) + { + ReorderableList.defaultBehaviours.elementBackground.Draw(rect, false, isActive, isActive, isFocused); + } + + Rect spliterRect = new Rect(rect.x + relSpliterPos * rect.width, rect.y, 2, rect.height); + EditorGUIUtility.AddCursorRect(spliterRect, MouseCursor.ResizeHorizontal); + if (Event.current.type == EventType.MouseDown && spliterRect.Contains(Event.current.mousePosition)) + { + moveSplitterPos = true; + } + if(moveSplitterPos) + { + if (Event.current.mousePosition.x > 100 && Event.current.mousePosition.x= Enum.GetValues(typeof(PreferencesEntrySortOrder)).Length) + { + sortOrder = 0; + } + EditorPrefs.SetInt("BGTools.PlayerPrefsEditor.SortOrder", (int) sortOrder); + PrepareData(false); + } + + GUIContent watcherContent = (entryAccessor.IsMonitoring()) ? new GUIContent(ImageManager.Watching, "Watching changes") : new GUIContent(ImageManager.NotWatching, "Not watching changes"); + if (GUILayout.Button(watcherContent, EditorStyles.toolbarButton)) + { + monitoring = !monitoring; + + EditorPrefs.SetBool("BGTools.PlayerPrefsEditor.WatchingForChanges", monitoring); + + if (monitoring) + entryAccessor.StartMonitoring(); + else + entryAccessor.StopMonitoring(); + + Repaint(); + } + if (GUILayout.Button(new GUIContent(ImageManager.Refresh, "Refresh"), EditorStyles.toolbarButton)) + { + PlayerPrefs.Save(); + PrepareData(); + } + if (GUILayout.Button(new GUIContent(ImageManager.Trash, "Delete all"), EditorStyles.toolbarButton)) + { + if (EditorUtility.DisplayDialog("Warning!", "Are you sure you want to delete ALL entries from PlayerPrefs?\n\nUse with caution! Unity defined keys are affected too.", "Yes", "No")) + { + PlayerPrefs.DeleteAll(); + PrepareData(); + GUIUtility.ExitGUI(); + } + } + EditorGUIUtility.SetIconSize(new Vector2(0.0f, 0.0f)); + + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + + GUILayout.Box(ImageManager.GetOsIcon(), Styles.icon); + GUILayout.TextField(platformPathPrefix + Path.DirectorySeparatorChar + pathToPrefs, GUILayout.MinWidth(200)); + + GUILayout.EndHorizontal(); + + scrollPos = GUILayout.BeginScrollView(scrollPos); + serializedObject.Update(); + userDefList.DoLayoutList(); + serializedObject.ApplyModifiedProperties(); + + GUILayout.FlexibleSpace(); + + showSystemGroup = EditorGUILayout.Foldout(showSystemGroup, new GUIContent("Show System")); + if (showSystemGroup) + { + unityDefList.DoLayoutList(); + } + GUILayout.EndScrollView(); + GUILayout.EndVertical(); + + GUI.enabled = true; + + if (showLoadingIndicatorOverlay) + { + GUILayout.BeginArea(new Rect(position.size.x * 0.5f - 30, position.size.y * 0.5f - 25, 60, 50), GUI.skin.box); + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Box(ImageManager.SpinWheelIcons[loadingSpinnerFrame], Styles.icon); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label("Loading"); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + GUILayout.EndArea(); + } + + GUI.contentColor = defaultColor; + } + catch (InvalidOperationException) + { } + } + + private void PrepareData(bool reloadKeys = true) + { + prefEntryHolder.ClearLists(); + + LoadKeys(out userDef, out unityDef, reloadKeys); + + CreatePrefEntries(userDef, ref prefEntryHolder.userDefList); + CreatePrefEntries(unityDef, ref prefEntryHolder.unityDefList); + + // Clear cache + userDefListCache = new SerializedProperty[prefEntryHolder.userDefList.Count]; + } + + private void CreatePrefEntries(string[] keySource, ref List listDest) + { + if (!string.IsNullOrEmpty(searchTxt) && searchfield.SearchMode == MySearchField.SearchModePreferencesEditorWindow.Key) + { + keySource = keySource.Where((keyEntry) => keyEntry.ToLower().Contains(searchTxt.ToLower())).ToArray(); + } + + foreach (string key in keySource) + { + var entry = new PreferenceEntry(); + entry.m_key = key; + + string s = PlayerPrefs.GetString(key, ERROR_VALUE_STR); + + if (s != ERROR_VALUE_STR) + { + entry.m_strValue = s; + entry.m_typeSelection = PreferenceEntry.PrefTypes.String; + listDest.Add(entry); + continue; + } + + float f = PlayerPrefs.GetFloat(key, float.NaN); + if (!float.IsNaN(f)) + { + entry.m_floatValue = f; + entry.m_typeSelection = PreferenceEntry.PrefTypes.Float; + listDest.Add(entry); + continue; + } + + int i = PlayerPrefs.GetInt(key, ERROR_VALUE_INT); + if (i != ERROR_VALUE_INT) + { + entry.m_intValue = i; + entry.m_typeSelection = PreferenceEntry.PrefTypes.Int; + listDest.Add(entry); + continue; + } + } + + if (!string.IsNullOrEmpty(searchTxt) && searchfield.SearchMode == MySearchField.SearchModePreferencesEditorWindow.Value) + { + listDest = listDest.Where((preferenceEntry) => preferenceEntry.ValueAsString().ToLower().Contains(searchTxt.ToLower())).ToList(); + } + + switch(sortOrder) + { + case PreferencesEntrySortOrder.Asscending: + listDest.Sort((PreferenceEntry x, PreferenceEntry y) => { return x.m_key.CompareTo(y.m_key); }); + break; + case PreferencesEntrySortOrder.Descending: + listDest.Sort((PreferenceEntry x, PreferenceEntry y) => { return y.m_key.CompareTo(x.m_key); }); + break; + } + } + + private void LoadKeys(out string[] userDef, out string[] unityDef, bool reloadKeys) + { + string[] keys = entryAccessor.GetKeys(reloadKeys); + + //keys.ToList().ForEach( e => { Debug.Log(e); } ); + + // Seperate keys int unity defined and user defined + Dictionary> groups = keys + .GroupBy( (key) => key.StartsWith("unity.") || key.StartsWith("UnityGraphicsQuality") ) + .ToDictionary( (g) => g.Key, (g) => g.ToList() ); + + unityDef = (groups.ContainsKey(true)) ? groups[true].ToArray() : new string[0]; + userDef = (groups.ContainsKey(false)) ? groups[false].ToArray() : new string[0]; + } + + private SerializedProperty GetUserDefListElementAtIndex(int index, SerializedProperty ListProperty) + { + UnityEngine.Assertions.Assert.IsTrue(ListProperty.isArray, "Given 'ListProperts' is not type of array"); + + if (userDefListCache[index] == null) + { + userDefListCache[index] = ListProperty.GetArrayElementAtIndex(index); + } + return userDefListCache[index]; + } + +#if (UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX) + private string MakeValidFileName(string unsafeFileName) + { + string normalizedFileName = unsafeFileName.Trim().Normalize(NormalizationForm.FormD); + StringBuilder stringBuilder = new StringBuilder(); + + // We need to use a TextElementEmumerator in order to support UTF16 characters that may take up more than one char(case 1169358) + TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(normalizedFileName); + while (charEnum.MoveNext()) + { + string c = charEnum.GetTextElement(); + if (c.Length == 1 && invalidFilenameChars.Contains(c[0])) + { + stringBuilder.Append('_'); + continue; + } + UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c, 0); + if (unicodeCategory != UnicodeCategory.NonSpacingMark) + stringBuilder.Append(c); + } + return stringBuilder.ToString().Normalize(NormalizationForm.FormC); + } +#endif + } +} + +public class MySearchField : SearchField +{ + public enum SearchModePreferencesEditorWindow { Key, Value } + + public SearchModePreferencesEditorWindow SearchMode { get; private set; } + + public Action DropdownSelectionDelegate; + + public new string OnGUI( + Rect rect, + string text, + GUIStyle style, + GUIStyle cancelButtonStyle, + GUIStyle emptyCancelButtonStyle) + { + style.padding.left = 17; + Rect ContextMenuRect = new Rect(rect.x, rect.y, 10, rect.height); + + // Add interactive area + EditorGUIUtility.AddCursorRect(ContextMenuRect, MouseCursor.Text); + if (Event.current.type == EventType.MouseDown && ContextMenuRect.Contains(Event.current.mousePosition)) + { + void OnDropdownSelection(object parameter) + { + SearchMode = (SearchModePreferencesEditorWindow) Enum.Parse(typeof(SearchModePreferencesEditorWindow), parameter.ToString()); + DropdownSelectionDelegate(); + } + + GenericMenu menu = new GenericMenu(); + foreach(SearchModePreferencesEditorWindow EnumIt in Enum.GetValues(typeof(SearchModePreferencesEditorWindow))) + { + String EnumName = Enum.GetName(typeof(SearchModePreferencesEditorWindow), EnumIt); + menu.AddItem(new GUIContent(EnumName), SearchMode == EnumIt, OnDropdownSelection, EnumName); + } + + menu.DropDown(rect); + } + + // Render original search field + String result = base.OnGUI(rect, text, style, cancelButtonStyle, emptyCancelButtonStyle); + + // Render additional images + GUIStyle ContexMenuOverlayStyle = GUIStyle.none; + ContexMenuOverlayStyle.contentOffset = new Vector2(9, 5); + GUI.Box(new Rect(rect.x, rect.y, 5, 5), EditorGUIUtility.IconContent("d_ProfilerTimelineDigDownArrow@2x"), ContexMenuOverlayStyle); + + if (!HasFocus() && String.IsNullOrEmpty(text)) + { + GUI.enabled = false; + GUI.Label(new Rect(rect.x + 14, rect.y, 40, rect.height), Enum.GetName(typeof(SearchModePreferencesEditorWindow), SearchMode)); + GUI.enabled = true; + } + ContexMenuOverlayStyle.contentOffset = new Vector2(); + return result; + } + + public new string OnToolbarGUI(string text, params GUILayoutOption[] options) => this.OnToolbarGUI(GUILayoutUtility.GetRect(29f, 200f, 18f, 18f, EditorStyles.toolbarSearchField, options), text); + public new string OnToolbarGUI(Rect rect, string text) => this.OnGUI(rect, text, EditorStyles.toolbarSearchField, EditorStyles.toolbarButton, EditorStyles.toolbarButton); +} diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs.meta new file mode 100644 index 00000000..9e69c699 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/PreferencesEditorWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 13c94fa190e7e6f4690cadc347a312aa +timeCreated: 1496263475 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs new file mode 100644 index 00000000..6d99c316 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs @@ -0,0 +1,364 @@ +/* + * Thanks to gr0ss for the inspiration. + * + * https://github.com/gr0ss/RegistryMonitor + * + * 11/08/2019 + */ + +using System; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Runtime.InteropServices; +using Microsoft.Win32; + +namespace BgTools.PlayerPrefsEditor +{ + public class RegistryMonitor : IDisposable + { + #region P/Invoke + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern int RegCloseKey(IntPtr hKey); + + private const int KEY_QUERY_VALUE = 0x0001; + private const int KEY_NOTIFY = 0x0010; + private const int STANDARD_RIGHTS_READ = 0x00020000; + + private static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000)); + private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); + private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + private static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003)); + private static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004)); + private static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005)); + private static readonly IntPtr HKEY_DYN_DATA = new IntPtr(unchecked((int)0x80000006)); + + #endregion + + #region Event handling + + /// + /// Occurs when the specified registry key has changed. + /// + public event EventHandler RegChanged; + + /// + /// Raises the event. + /// + /// + ///

+ /// OnRegChanged is called when the specified registry key has changed. + ///

+ /// + /// When overriding in a derived class, be sure to call + /// the base class's method. + /// + ///
+ protected virtual void OnRegChanged() + { + EventHandler handler = RegChanged; + if (handler != null) + handler(this, null); + } + + /// + /// Occurs when the access to the registry fails. + /// + public event ErrorEventHandler Error; + + /// + /// Raises the event. + /// + /// The which occured while watching the registry. + /// + ///

+ /// OnError is called when an exception occurs while watching the registry. + ///

+ /// + /// When overriding in a derived class, be sure to call + /// the base class's method. + /// + ///
+ protected virtual void OnError(Exception e) + { + ErrorEventHandler handler = Error; + if (handler != null) + handler(this, new ErrorEventArgs(e)); + } + + #endregion + + #region Private member variables + + private IntPtr _registryHive; + private string _registrySubName; + private object _threadLock = new object(); + private Thread _thread; + private bool _disposed = false; + private ManualResetEvent _eventTerminate = new ManualResetEvent(false); + + private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute | RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security; + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// The registry key to monitor. + public RegistryMonitor(RegistryKey registryKey) + { + InitRegistryKey(registryKey.Name); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + public RegistryMonitor(string name) + { + if (name == null || name.Length == 0) + throw new ArgumentNullException("name"); + + InitRegistryKey(name); + } + + /// + /// Initializes a new instance of the class. + /// + /// The registry hive. + /// The sub key. + public RegistryMonitor(RegistryHive registryHive, string subKey) + { + InitRegistryKey(registryHive, subKey); + } + + /// + /// Disposes this object. + /// + public void Dispose() + { + Stop(); + _disposed = true; + GC.SuppressFinalize(this); + } + + /// + /// Gets or sets the RegChangeNotifyFilter. + /// + public RegChangeNotifyFilter RegChangeNotifyFilter + { + get { return _regFilter; } + set + { + lock (_threadLock) + { + if (IsMonitoring) + throw new InvalidOperationException("Monitoring thread is already running"); + + _regFilter = value; + } + } + } + + #region Initialization + + private void InitRegistryKey(RegistryHive hive, string name) + { + switch (hive) + { + case RegistryHive.ClassesRoot: + _registryHive = HKEY_CLASSES_ROOT; + break; + + case RegistryHive.CurrentConfig: + _registryHive = HKEY_CURRENT_CONFIG; + break; + + case RegistryHive.CurrentUser: + _registryHive = HKEY_CURRENT_USER; + break; + + case RegistryHive.DynData: + _registryHive = HKEY_DYN_DATA; + break; + + case RegistryHive.LocalMachine: + _registryHive = HKEY_LOCAL_MACHINE; + break; + + case RegistryHive.PerformanceData: + _registryHive = HKEY_PERFORMANCE_DATA; + break; + + case RegistryHive.Users: + _registryHive = HKEY_USERS; + break; + + default: + throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive)); + } + _registrySubName = name; + } + + private void InitRegistryKey(string name) + { + string[] nameParts = name.Split('\\'); + + switch (nameParts[0]) + { + case "HKEY_CLASSES_ROOT": + case "HKCR": + _registryHive = HKEY_CLASSES_ROOT; + break; + + case "HKEY_CURRENT_USER": + case "HKCU": + _registryHive = HKEY_CURRENT_USER; + break; + + case "HKEY_LOCAL_MACHINE": + case "HKLM": + _registryHive = HKEY_LOCAL_MACHINE; + break; + + case "HKEY_USERS": + _registryHive = HKEY_USERS; + break; + + case "HKEY_CURRENT_CONFIG": + _registryHive = HKEY_CURRENT_CONFIG; + break; + + default: + _registryHive = IntPtr.Zero; + throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", "value"); + } + + _registrySubName = String.Join("\\", nameParts, 1, nameParts.Length - 1); + } + + #endregion + + /// + /// true if this object is currently monitoring; + /// otherwise, false. + /// + public bool IsMonitoring + { + get { return _thread != null; } + } + + /// + /// Start monitoring. + /// + public void Start() + { + if (_disposed) + throw new ObjectDisposedException(null, "This instance is already disposed"); + + lock (_threadLock) + { + if (!IsMonitoring) + { + _eventTerminate.Reset(); + _thread = new Thread(new ThreadStart(MonitorThread)) { IsBackground = true }; + _thread.Start(); + } + } + } + + /// + /// Stops the monitoring thread. + /// + public void Stop() + { + if (_disposed) + throw new ObjectDisposedException(null, "This instance is already disposed"); + + lock (_threadLock) + { + Thread thread = _thread; + if (thread != null) + { + _eventTerminate.Set(); + thread.Join(); + } + } + } + + private void MonitorThread() + { + try + { + ThreadLoop(); + } + catch (Exception e) + { + OnError(e); + } + _thread = null; + } + + private void ThreadLoop() + { + IntPtr registryKey; + int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out registryKey); + if (result != 0) + { + throw new Win32Exception(result); + } + + try + { + AutoResetEvent _eventNotify = new AutoResetEvent(false); + WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate }; + while (!_eventTerminate.WaitOne(0, true)) + { + result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.SafeWaitHandle.DangerousGetHandle(), true); + if (result != 0) + { + throw new Win32Exception(result); + } + + if (WaitHandle.WaitAny(waitHandles) == 0) + { + OnRegChanged(); + } + } + } + finally + { + if (registryKey != IntPtr.Zero) + { + RegCloseKey(registryKey); + } + } + } + } + + /// + /// Filter for notifications reported by . + /// + [Flags] + public enum RegChangeNotifyFilter + { + /// Notify the caller if a subkey is added or deleted. + Key = 1, + /// Notify the caller of changes to the attributes of the key, + /// such as the security descriptor information. + Attribute = 2, + /// Notify the caller of changes to a value of the key. This can + /// include adding or deleting a value, or changing an existing value. + Value = 4, + /// Notify the caller of changes to the security descriptor + /// of the key. + Security = 8, + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs.meta b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs.meta new file mode 100644 index 00000000..b5f58d7b --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/PreferencesEditor/RegistryMonitor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c38f17e357d98d4296b689ae716240b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Styles.cs b/Assets/PlayerPrefsEditor/Editor/Styles.cs new file mode 100644 index 00000000..19dd74e6 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Styles.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace BgTools.Utils +{ + public class Styles + { + #region Colors + public class Colors { + public static Color DarkGray = new Color(0.09f, 0.09f, 0.09f); + public static Color LightGray = new Color(0.65f, 0.65f, 0.65f); + public static Color Red = new Color(1.00f, 0.00f, 0.00f); + public static Color Yellow = new Color(1.00f, 1.00f, 0.00f); + public static Color Blue = new Color(0.00f, 0.63f, 0.99f); + } + #endregion // Colors + + #region Texture manager + static Dictionary mTextures = new Dictionary(); + + public static Texture2D GetTexture(long pColorRGBA) + { + if (mTextures.ContainsKey(pColorRGBA) && mTextures[pColorRGBA] != null) + return mTextures[pColorRGBA]; + + Color32 c = GetColor(pColorRGBA); + + var texture = new Texture2D(4, 4); + for (int x = 0; x < 4; x++) + for (int y = 0; y < 4; y++) + texture.SetPixel(x, y, c); + texture.Apply(); + texture.Compress(true); + + mTextures[pColorRGBA] = texture; + + return texture; + } + + private static Color32 GetColor(long pColorRGBA) + { + byte r = (byte)((pColorRGBA & 0xff000000) >> 24); + byte g = (byte)((pColorRGBA & 0xff0000) >> 16); + byte b = (byte)((pColorRGBA & 0xff00) >> 8); + byte a = (byte)((pColorRGBA & 0xff)); + + Color32 c = new Color32(r, g, b, a); + return c; + } + #endregion Texture manager + + static GUIStyle mHSeparator; + private static GUIStyle hSeparator + { + get + { + if (mHSeparator == null) + { + mHSeparator = new GUIStyle(); + mHSeparator.alignment = TextAnchor.MiddleCenter; + mHSeparator.stretchWidth = true; + mHSeparator.fixedHeight = 1; + mHSeparator.margin = new RectOffset(20, 20, 5, 5); + mHSeparator.normal.background = (EditorGUIUtility.isProSkin) ? GetTexture(0xb5b5b5ff) : GetTexture(0x000000ff); + } + return mHSeparator; + } + } + + public static void HorizontalSeparator() + { + GUILayout.Label("", hSeparator); + } + + static GUIStyle Icon; + public static GUIStyle icon + { + get + { + if (Icon == null) + { + Icon = new GUIStyle(); + Icon.fixedWidth = 15.0f; + Icon.fixedHeight = 15.0f; + Icon.margin = new RectOffset(2, 2, 2, 2); + } + return Icon; + } + } + + static GUIStyle MiniButton; + public static GUIStyle miniButton + { + get + { + if (MiniButton == null) + { + MiniButton = new GUIStyle(GUI.skin.button); + MiniButton.fixedWidth = 15.0f; + MiniButton.fixedHeight = 15.0f; + MiniButton.margin = new RectOffset(2, 2, 2, 2); + MiniButton.padding = new RectOffset(2, 2, 2, 2); + } + return MiniButton; + } + } + } +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/Styles.cs.meta b/Assets/PlayerPrefsEditor/Editor/Styles.cs.meta new file mode 100644 index 00000000..ffcf7680 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Styles.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: be53f59c705f7434a9d6581d0746990f +timeCreated: 1496670894 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef b/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef new file mode 100644 index 00000000..1218f4db --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Unity.PlayerPrefsEditor.Editor", + "references": [ + "Unity.PlayerPrefsEditor.EditorResources" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef.meta b/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef.meta new file mode 100644 index 00000000..d9d4906e --- /dev/null +++ b/Assets/PlayerPrefsEditor/Editor/Unity.PlayerPrefsEditor.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 516df2812c38a7348b10d202b71bf483 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/README.md b/Assets/PlayerPrefsEditor/README.md new file mode 100644 index 00000000..5fd9d9a8 --- /dev/null +++ b/Assets/PlayerPrefsEditor/README.md @@ -0,0 +1,71 @@ +# PlayerPrefs Editor for Unity 3D + +[![Minimal unity editor version](https://img.shields.io/badge/UnityEditor-2019.4%20or%20later-blue.svg)](https://unity3d.com/de/get-unity/download/archive) +[![CI](https://github.com/Dysman/bgTools-playerPrefsEditor/workflows/CI/badge.svg)](https://github.com/Dysman/bgTools-playerPrefsEditor/actions)   +[![Release](https://img.shields.io/github/v/release/Dysman/bgTools-playerPrefsEditor?include_prereleases&label=Release)](https://github.com/Dysman/bgTools-playerPrefsEditor/releases) +[![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/dysman/bgTools-playerPrefsEditor/upm?label=GitURL-UPM)](https://github.com/Dysman/bgTools-playerPrefsEditor/tree/upm) +[![openupm](https://img.shields.io/npm/v/com.bgtools.playerprefseditor?label=OpenUPM®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.bgtools.playerprefseditor) +[![AssetStore](https://img.shields.io/badge/dynamic/xml?url=http://u3d.as/1RLa&label=UnityAssetStore&query=//*[contains(@class,%20%27product-version%27)]/div[2]&prefix=v)](http://u3d.as/1RLa) + +[![Flattr this git repo](https://img.shields.io/badge/_-Flattr-green?logo=flattr&style=flat)](https://flattr.com/@dysman)  +[![Buy me a coffee](https://img.shields.io/badge/-Buy%20Me%20A%20Coffee-yellow?logo=BuyMeACoffee&style=flat&logoColor=white)](https://www.buymeacoffee.com/dysman) +[](https://discord.gg/8rcPZrD) + +Tool extension for the Unity Editor that enables easy access to the player preferences over a simple UI. Allows to view, add, remove and modify entries on the development machine. + +![Preference editor window](https://www.bgranzow.de/downloads/PlayerPrefsEditorV1_2_0.png) + +## Features + +* Add, remove and edit PlayerPrefs +* Intuitive visual editor +* Works with standard Unity PlayerPrefs +* Monitors changes from code +* Supports all editors (Windows, Linux, MacOS) +* Lightweight dockable for full integration in your workflow +* Supports both skins (Personal, Professional) + +## Requirements + +Unity Version: 2019.4 (LTS) or higher + +Editor Version: Windows, MacOS, Linux + +## Installation + +The plugin provides *manual* and *UPM* installation. + + +Additionally it's available on the [Unity Asset Store](http://u3d.as/1RLa). + +### Manual +Place the PlayerPrefsEditor folder somewhere in your project. It's not relevant where it's located, the plugin will find all of its files by itself. + +### Unity Package Manager (UPM) + +**Via Git URL** + +Through the Unity Plugin Manager it's possible to install the plugin direct from this git repository. +The UPM need a specific structure what will be provided into the *upm* branch. + +Use following direct URL for the configuration: +``` +https://github.com/Dysman/bgTools-playerPrefsEditor.git#upm +``` +See official Unity documentation for more informations: [UI](https://docs.unity3d.com/Manual/upm-ui-giturl.html) or [manifest.json](https://docs.unity3d.com/Manual/upm-git.html) + +**Via OpenUPM** + +The package is available on the [openupm registry](https://openupm.com). It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli). + +``` +openupm add com.bgtools.playerprefseditor +``` + +## Usage + +The entry to open the _PlayerPrefs Editor_ is located in the top menu at Tools/BG Tools/PlayerPrefs Editor. It's a standard dockable window, so place it wherever it helps to be productive. +A more detailed manual can be fund in following locations: +* GitHub (Manual)- [Manual page](Packages/PlayerPrefsEditor/Documentation~/PlayerPrefsEditor.md) +* GitHub (UPM) - Press the _Documentation_ link on the UPM description. +* Unity Asset Store Package - [MANUAL.html](Documentation/MANUAL.html) diff --git a/Assets/PlayerPrefsEditor/README.md.meta b/Assets/PlayerPrefsEditor/README.md.meta new file mode 100644 index 00000000..e28d03e7 --- /dev/null +++ b/Assets/PlayerPrefsEditor/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b2e0e2a0041b58458afaba08099fba4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Samples.meta b/Assets/PlayerPrefsEditor/Samples.meta new file mode 100644 index 00000000..5d69064d --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 70d87455559ee7d4d9abd1153e42ed4f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene.meta b/Assets/PlayerPrefsEditor/Samples/SampleScene.meta new file mode 100644 index 00000000..46fcaddd --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16180e12a1ec47a4fb2cc753af98d39e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs b/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs new file mode 100644 index 00000000..3209b7e3 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +public class PlayerPrefsController : MonoBehaviour +{ + + #region Add + public void AddTestStrings() + { + PlayerPrefs.SetString("Runtime_String", "boing"); + PlayerPrefs.SetString("Runtime_String2", "foo"); + PlayerPrefs.Save(); + } + + public void AddTestInt() + { + PlayerPrefs.SetInt("Runtime_Int", 1234); + PlayerPrefs.Save(); + } + + public void AddTestFloat() + { + PlayerPrefs.SetFloat("Runtime_Float", 3.14f); + PlayerPrefs.Save(); + } + #endregion + + #region Remove + public void RemoveTestStrings() + { + PlayerPrefs.DeleteKey("Runtime_String"); + PlayerPrefs.DeleteKey("Runtime_String2"); + PlayerPrefs.Save(); + } + + public void RemoveTestInt() + { + PlayerPrefs.DeleteKey("Runtime_Int"); + PlayerPrefs.Save(); + } + + public void RemoveTestFloat() + { + PlayerPrefs.DeleteKey("Runtime_Float"); + PlayerPrefs.Save(); + } + + public void DeleteAll() + { + PlayerPrefs.DeleteAll(); + PlayerPrefs.Save(); + } + #endregion +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs.meta b/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs.meta new file mode 100644 index 00000000..bee2ec22 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/PlayerPrefsController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4004328c339a7cb4fb509e2e5f789688 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity b/Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity new file mode 100644 index 00000000..19d577b2 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity @@ -0,0 +1,2314 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 170076734} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 10 + m_Resolution: 2 + m_BakeResolution: 10 + m_AtlasSize: 512 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 256 + m_PVRBounces: 2 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringMode: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ShowResolutionOverlay: 1 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &138348546 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 138348547} + - component: {fileID: 138348550} + - component: {fileID: 138348549} + - component: {fileID: 138348548} + m_Layer: 5 + m_Name: Remove Float Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &138348547 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 138348546} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1451198900} + m_Father: {fileID: 590894633} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &138348548 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 138348546} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 138348549} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: RemoveTestFloat + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &138348549 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 138348546} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &138348550 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 138348546} + m_CullTransparentMesh: 0 +--- !u!1 &170076733 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 170076735} + - component: {fileID: 170076734} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &170076734 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 170076733} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &170076735 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 170076733} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &173658991 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 173658992} + - component: {fileID: 173658995} + - component: {fileID: 173658994} + - component: {fileID: 173658993} + m_Layer: 5 + m_Name: Del Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &173658992 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173658991} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1360994566} + m_Father: {fileID: 590894633} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &173658993 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173658991} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 173658994} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: DeleteAll + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &173658994 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173658991} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &173658995 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173658991} + m_CullTransparentMesh: 0 +--- !u!1 &268524658 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 268524659} + - component: {fileID: 268524661} + - component: {fileID: 268524660} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &268524659 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 268524658} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 801019244} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &268524660 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 268524658} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.4339623, g: 0.4339623, b: 0.4339623, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &268524661 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 268524658} + m_CullTransparentMesh: 0 +--- !u!1 &317393205 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 317393206} + - component: {fileID: 317393210} + - component: {fileID: 317393209} + - component: {fileID: 317393208} + - component: {fileID: 317393207} + m_Layer: 5 + m_Name: SettingsTxt + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &317393206 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 317393205} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 801019244} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.65, y: 0.1} + m_AnchorMax: {x: 0.98, y: 0.8} + m_AnchoredPosition: {x: 0, y: -10} + m_SizeDelta: {x: 0, y: -20} + m_Pivot: {x: 0, y: 0.5} +--- !u!114 &317393207 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 317393205} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1297475563, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 0 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 +--- !u!114 &317393208 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 317393205} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1573420865, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_EffectColor: {r: 0, g: 0, b: 0, a: 0.5} + m_EffectDistance: {x: 2, y: -2} + m_UseGraphicAlpha: 1 +--- !u!114 &317393209 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 317393205} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 70 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: "The small eye icon on the top right indicates the monitoring state.\r\n\r\nYou + can toggle the behavior of 'monitoring' with this button.\r\n" +--- !u!222 &317393210 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 317393205} + m_CullTransparentMesh: 0 +--- !u!1 &525475502 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 525475503} + - component: {fileID: 525475505} + - component: {fileID: 525475504} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &525475503 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 525475502} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1139655137} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &525475504 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 525475502} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Remove Int +--- !u!222 &525475505 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 525475502} + m_CullTransparentMesh: 0 +--- !u!1 &534669902 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 534669905} + - component: {fileID: 534669904} + - component: {fileID: 534669903} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &534669903 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 534669902} + m_Enabled: 1 +--- !u!20 &534669904 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 534669902} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &534669905 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 534669902} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &590894632 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 590894633} + - component: {fileID: 590894634} + m_Layer: 0 + m_Name: GridLayout + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &590894633 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 590894632} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1280115979} + - {fileID: 1412871434} + - {fileID: 741602743} + - {fileID: 1139655137} + - {fileID: 875359336} + - {fileID: 138348547} + - {fileID: 173658992} + m_Father: {fileID: 801019244} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &590894634 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 590894632} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -2095666955, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 4 + m_StartCorner: 0 + m_StartAxis: 0 + m_CellSize: {x: 100, y: 50} + m_Spacing: {x: 20, y: 20} + m_Constraint: 1 + m_ConstraintCount: 2 +--- !u!1 &638895137 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 638895138} + - component: {fileID: 638895140} + - component: {fileID: 638895139} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &638895138 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638895137} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 875359336} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &638895139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638895137} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Add Float +--- !u!222 &638895140 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638895137} + m_CullTransparentMesh: 0 +--- !u!1 &741602742 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 741602743} + - component: {fileID: 741602746} + - component: {fileID: 741602745} + - component: {fileID: 741602744} + m_Layer: 5 + m_Name: Add Int Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &741602743 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 741602742} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1066883286} + m_Father: {fileID: 590894633} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &741602744 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 741602742} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 741602745} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: AddTestInt + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &741602745 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 741602742} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &741602746 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 741602742} + m_CullTransparentMesh: 0 +--- !u!1 &801019239 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 801019244} + - component: {fileID: 801019243} + - component: {fileID: 801019242} + - component: {fileID: 801019241} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &801019241 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 801019239} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &801019242 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 801019239} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &801019243 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 801019239} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &801019244 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 801019239} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 268524659} + - {fileID: 899057308} + - {fileID: 1124054026} + - {fileID: 317393206} + - {fileID: 590894633} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &875359335 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 875359336} + - component: {fileID: 875359339} + - component: {fileID: 875359338} + - component: {fileID: 875359337} + m_Layer: 5 + m_Name: Add Float Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &875359336 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 875359335} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 638895138} + m_Father: {fileID: 590894633} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &875359337 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 875359335} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 875359338} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: AddTestFloat + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &875359338 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 875359335} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &875359339 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 875359335} + m_CullTransparentMesh: 0 +--- !u!1 &899057307 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 899057308} + - component: {fileID: 899057311} + - component: {fileID: 899057310} + - component: {fileID: 899057309} + m_Layer: 5 + m_Name: Title + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &899057308 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 899057307} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 801019244} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -20} + m_SizeDelta: {x: 0, y: 100} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &899057309 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 899057307} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1573420865, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_EffectColor: {r: 0, g: 0, b: 0, a: 0.5} + m_EffectDistance: {x: 5, y: -5} + m_UseGraphicAlpha: 1 +--- !u!114 &899057310 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 899057307} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 70 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 7 + m_MaxSize: 70 + m_Alignment: 1 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: PlayerPrefs Editor +--- !u!222 &899057311 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 899057307} + m_CullTransparentMesh: 0 +--- !u!1 &1066883285 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1066883286} + - component: {fileID: 1066883288} + - component: {fileID: 1066883287} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1066883286 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1066883285} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 741602743} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1066883287 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1066883285} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Add Int +--- !u!222 &1066883288 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1066883285} + m_CullTransparentMesh: 0 +--- !u!1 &1124054025 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1124054026} + - component: {fileID: 1124054029} + - component: {fileID: 1124054028} + - component: {fileID: 1124054027} + m_Layer: 5 + m_Name: TutorialTxt + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1124054026 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1124054025} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 801019244} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.02, y: 0.1} + m_AnchorMax: {x: 0.35, y: 0.8} + m_AnchoredPosition: {x: 0, y: -10} + m_SizeDelta: {x: 0, y: -20} + m_Pivot: {x: 0, y: 0.5} +--- !u!114 &1124054027 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1124054025} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1573420865, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_EffectColor: {r: 0, g: 0, b: 0, a: 0.5} + m_EffectDistance: {x: 2, y: -2} + m_UseGraphicAlpha: 1 +--- !u!114 &1124054028 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1124054025} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 70 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Thanks for using + + PlayerPrefs Editor + + from BG Tools. + + + You can open the Window Tools/BG Tools/PlayerPrefs Editor + + + Press the buttons to change PlayerPrefs and see how the tool lists it.' +--- !u!222 &1124054029 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1124054025} + m_CullTransparentMesh: 0 +--- !u!1 &1139655136 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1139655137} + - component: {fileID: 1139655140} + - component: {fileID: 1139655139} + - component: {fileID: 1139655138} + m_Layer: 5 + m_Name: Remove Int Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1139655137 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1139655136} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 525475503} + m_Father: {fileID: 590894633} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1139655138 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1139655136} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1139655139} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: RemoveTestInt + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &1139655139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1139655136} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &1139655140 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1139655136} + m_CullTransparentMesh: 0 +--- !u!1 &1280115978 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1280115979} + - component: {fileID: 1280115982} + - component: {fileID: 1280115981} + - component: {fileID: 1280115980} + m_Layer: 5 + m_Name: Add String Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1280115979 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1280115978} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 2075943591} + m_Father: {fileID: 590894633} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1280115980 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1280115978} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1280115981} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: AddTestStrings + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &1280115981 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1280115978} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &1280115982 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1280115978} + m_CullTransparentMesh: 0 +--- !u!1 &1360994565 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1360994566} + - component: {fileID: 1360994568} + - component: {fileID: 1360994567} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1360994566 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1360994565} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 173658992} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1360994567 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1360994565} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Delete All +--- !u!222 &1360994568 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1360994565} + m_CullTransparentMesh: 0 +--- !u!1 &1412871433 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1412871434} + - component: {fileID: 1412871437} + - component: {fileID: 1412871436} + - component: {fileID: 1412871435} + m_Layer: 5 + m_Name: Remove String Btn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1412871434 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1412871433} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1598968325} + m_Father: {fileID: 590894633} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1412871435 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1412871433} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1412871436} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1845372761} + m_MethodName: RemoveTestStrings + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &1412871436 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1412871433} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 +--- !u!222 &1412871437 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1412871433} + m_CullTransparentMesh: 0 +--- !u!1 &1451198899 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1451198900} + - component: {fileID: 1451198902} + - component: {fileID: 1451198901} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1451198900 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451198899} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 138348547} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1451198901 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451198899} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Remove Float +--- !u!222 &1451198902 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451198899} + m_CullTransparentMesh: 0 +--- !u!1 &1598968324 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1598968325} + - component: {fileID: 1598968327} + - component: {fileID: 1598968326} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1598968325 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1598968324} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1412871434} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1598968326 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1598968324} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Remove Strings +--- !u!222 &1598968327 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1598968324} + m_CullTransparentMesh: 0 +--- !u!1 &1845372757 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1845372760} + - component: {fileID: 1845372759} + - component: {fileID: 1845372758} + - component: {fileID: 1845372761} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1845372758 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1845372757} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1077351063, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1845372759 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1845372757} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -619905303, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1845372760 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1845372757} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1845372761 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1845372757} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4004328c339a7cb4fb509e2e5f789688, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &2075943590 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2075943591} + - component: {fileID: 2075943593} + - component: {fileID: 2075943592} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2075943591 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075943590} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1280115979} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2075943592 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075943590} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Add Strings +--- !u!222 &2075943593 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075943590} + m_CullTransparentMesh: 0 diff --git a/Assets/Epic Toon FX/Upgrade/ETFX 2019.2.3f1 LWRP Upgrade.unitypackage.meta b/Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity.meta similarity index 74% rename from Assets/Epic Toon FX/Upgrade/ETFX 2019.2.3f1 LWRP Upgrade.unitypackage.meta rename to Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity.meta index c3d5c56e..a30d2dd1 100644 --- a/Assets/Epic Toon FX/Upgrade/ETFX 2019.2.3f1 LWRP Upgrade.unitypackage.meta +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/SampleScene.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0a573fe589907b74fa928784d2c3aeca +guid: fba661fc32606eb498ae23fd271867b4 DefaultImporter: externalObjects: {} userData: diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef b/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef new file mode 100644 index 00000000..7c3e8ec5 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef @@ -0,0 +1,12 @@ +{ + "name": "Unity.PlayerPrefsEditor.Samples.SampleScene", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef.meta b/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef.meta new file mode 100644 index 00000000..08c24552 --- /dev/null +++ b/Assets/PlayerPrefsEditor/Samples/SampleScene/Unity.PlayerPrefsEditor.Samples.SampleScene.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7bb1abbf070c8e248939f8fd7910665f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector.meta b/Assets/Plugins/AssetUsageDetector.meta new file mode 100644 index 00000000..07f30443 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c1764d69117881843b761dc14ca276d4 +folderAsset: yes +timeCreated: 1561225368 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor.meta b/Assets/Plugins/AssetUsageDetector/Editor.meta new file mode 100644 index 00000000..3634eb53 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 19f677a9eb83d3942af6d4c5fa8dbeee +folderAsset: yes +timeCreated: 1520032274 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef new file mode 100644 index 00000000..f02732f0 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef @@ -0,0 +1,29 @@ +{ + "name": "AssetUsageDetector.Editor", + "rootNamespace": "", + "references": [ + "Unity.Addressables" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.addressables", + "expression": "0.0.0", + "define": "ASSET_USAGE_ADDRESSABLES" + }, + { + "name": "com.unity.visualeffectgraph", + "expression": "0.0.0", + "define": "ASSET_USAGE_VFX_GRAPH" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef.meta new file mode 100644 index 00000000..a29079c3 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8579ab42c9ab63d4bac5fb07bd390b46 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs new file mode 100644 index 00000000..b1a0a22e --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs @@ -0,0 +1,1398 @@ +// Asset Usage Detector - by Suleyman Yasir KULA (yasirkula@gmail.com) + +using UnityEngine; +using UnityEditor; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using System.Collections.Generic; +using System.Reflection; +using System; +using System.IO; +using System.Text; +using Object = UnityEngine.Object; +#if UNITY_2018_3_OR_NEWER && !UNITY_2021_2_OR_NEWER +using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; +#endif + +namespace AssetUsageDetectorNamespace +{ + [Flags] + public enum SceneSearchMode { None = 0, OpenScenes = 1, ScenesInBuildSettingsAll = 2, ScenesInBuildSettingsTickedOnly = 4, AllScenes = 8 }; + + public partial class AssetUsageDetector + { + #region Helper Classes + [Serializable] + public class Parameters + { + public Object[] objectsToSearch = null; + + public SceneSearchMode searchInScenes = SceneSearchMode.AllScenes; + public Object[] searchInScenesSubset = null; + public Object[] excludedScenesFromSearch = null; + public bool searchInSceneLightingSettings = true; + public bool searchInAssetsFolder = true; + public Object[] searchInAssetsSubset = null; + public Object[] excludedAssetsFromSearch = null; + public bool dontSearchInSourceAssets = true; + public bool searchInProjectSettings = true; + + public int searchDepthLimit = 4; + public BindingFlags fieldModifiers = BindingFlags.Public | BindingFlags.NonPublic; + public BindingFlags propertyModifiers = BindingFlags.Public | BindingFlags.NonPublic; + public bool searchNonSerializableVariables = true; + + public bool searchUnusedMaterialProperties = true; + + public SearchRefactoring searchRefactoring = null; + + public bool lazySceneSearch = true; +#if ASSET_USAGE_ADDRESSABLES + public bool addressablesSupport = false; +#endif + public bool calculateUnusedObjects = false; + public bool hideDuplicateRows = true; + public bool hideReduntantPrefabVariantLinks = true; + public bool noAssetDatabaseChanges = false; + public bool showDetailedProgressBar = true; + } + #endregion + + private Parameters searchParameters; + + // A set that contains the searched scene object(s), asset(s) and their sub-assets (if any) + private readonly HashSet objectsToSearchSet = new HashSet(); + // Scenes of scene object(s) in objectsToSearchSet + private readonly HashSet sceneObjectsToSearchScenesSet = new HashSet(); + // Project asset(s) in objectsToSearchSet + private readonly HashSet assetsToSearchSet = new HashSet(); + // assetsToSearchSet's path(s) + private readonly HashSet assetsToSearchPathsSet = new HashSet(); + // The root prefab objects in assetsToSearchSet that will be used to search for prefab references + private readonly List assetsToSearchRootPrefabs = new List( 4 ); + // Path(s) of the assets that should be excluded from the search + private readonly HashSet excludedAssetsPathsSet = new HashSet(); + // Extension(s) of assets that will always be searched in detail + private readonly HashSet alwaysSearchedExtensionsSet = new HashSet(); + + // Results for the currently searched scene + private SearchResultGroup currentSearchResultGroup; + + // An optimization to search an object only once (key is a hash of the searched object) + private readonly Dictionary searchedObjects = new Dictionary( 4096 ); + private readonly Dictionary searchedUnityObjects = new Dictionary( 32768 ); // Unity objects use their instanceIDs as key which is more performant + + // Stack of SearchObject function parameters to avoid infinite loops (which happens when same object is passed as parameter to function) + private readonly List callStack = new List( 64 ); + + private Object currentSearchedObject; + private int currentDepth; + + private bool searchingSourceAssets; + private bool isInPlayMode; + +#if UNITY_2018_3_OR_NEWER + private PrefabStage openPrefabStage; + private GameObject openPrefabStagePrefabAsset; +#if UNITY_2020_1_OR_NEWER + private GameObject openPrefabStageContextObject; +#endif +#endif + + private int searchedObjectsCount; // Number of searched objects + private double searchStartTime; + + private readonly List nodesPool = new List( 32 ); + + // Search for references! + public SearchResult Run( Parameters searchParameters ) + { + if( searchParameters == null ) + { + Debug.LogError( "'searchParameters' mustn't be null!" ); + return new SearchResult( false, null, null, null, this, searchParameters ); + } + + if( searchParameters.objectsToSearch == null ) + { + Debug.LogError( "'objectsToSearch' list (\"SEARCHED OBJECTS\") is empty!" ); + return new SearchResult( false, null, null, null, this, searchParameters ); + } + +#if UNITY_2018_3_OR_NEWER + openPrefabStagePrefabAsset = null; + string openPrefabStageAssetPath = null; + openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if( openPrefabStage != null ) + { + if( !openPrefabStage.stageHandle.IsValid() ) + openPrefabStage = null; + else + { + if( openPrefabStage.scene.isDirty ) + { + // Don't start the search if a prefab stage is currently open and dirty (not saved) + Debug.LogError( "Save open prefab first!" ); + return new SearchResult( false, null, null, null, this, searchParameters ); + } + +#if UNITY_2020_1_OR_NEWER + string prefabAssetPath = openPrefabStage.assetPath; +#else + string prefabAssetPath = openPrefabStage.prefabAssetPath; +#endif + openPrefabStagePrefabAsset = AssetDatabase.LoadAssetAtPath( prefabAssetPath ); + openPrefabStageAssetPath = prefabAssetPath; + +#if UNITY_2020_1_OR_NEWER + openPrefabStageContextObject = openPrefabStage.openedFromInstanceRoot; +#endif + } + } +#endif + + List searchResult = null; + isInPlayMode = EditorApplication.isPlaying; + + if( !isInPlayMode && !Utilities.AreScenesSaved() && !EditorUtility.DisplayDialog( "Asset Usage Detector", "Some scene(s) aren't saved. This may result in incorrect search results in those scene(s). Proceed?", "Yes", "Cancel" ) ) + { + Debug.LogError( "Save open scene(s) first!" ); + return new SearchResult( false, null, null, null, this, searchParameters ); + } + + // Get the scenes that are open right now + SceneSetup[] initialSceneSetup = !isInPlayMode ? EditorSceneManager.GetSceneManagerSetup() : null; + Scene activeScene = EditorSceneManager.GetActiveScene(); + + // Make sure that the AssetDatabase is up-to-date + AssetDatabase.SaveAssets(); + + try + { + this.searchParameters = searchParameters; + + // Initialize commonly used variables + searchResult = new List(); // Overall search results + + currentSearchedObject = null; + currentDepth = 0; + searchedObjectsCount = 0; + searchStartTime = EditorApplication.timeSinceStartup; + + searchedObjects.Clear(); + searchedUnityObjects.Clear(); + animationClipUniqueBindings.Clear(); + callStack.Clear(); + objectsToSearchSet.Clear(); + sceneObjectsToSearchScenesSet.Clear(); + assetsToSearchSet.Clear(); + assetsToSearchPathsSet.Clear(); + assetsToSearchRootPrefabs.Clear(); + excludedAssetsPathsSet.Clear(); + alwaysSearchedExtensionsSet.Clear(); + shaderIncludesToSearchSet.Clear(); +#if UNITY_2017_3_OR_NEWER + assemblyDefinitionFilesToSearch.Clear(); +#endif + + if( assetDependencyCache == null ) + { + LoadCache(); + searchStartTime = EditorApplication.timeSinceStartup; + } + else if( !searchParameters.noAssetDatabaseChanges ) + { + foreach( var cacheEntry in assetDependencyCache.Values ) + cacheEntry.verified = false; + } + + foreach( var cacheEntry in assetDependencyCache.Values ) + cacheEntry.searchResult = CacheEntry.Result.Unknown; + + lastRefreshedCacheEntry = null; + + // Store the searched objects(s) in HashSets + HashSet folderContentsSet = new HashSet(); + foreach( Object obj in searchParameters.objectsToSearch ) + { + if( obj == null || obj.Equals( null ) ) + continue; + + if( obj.IsFolder() ) + folderContentsSet.UnionWith( Utilities.EnumerateFolderContents( obj ) ); + else + AddSearchedObjectToFilteredSets( obj, true ); + } + + foreach( string filePath in folderContentsSet ) + { + // Skip scene assets + if( filePath.EndsWithFast( ".unity" ) ) + continue; + + Object[] assets = AssetDatabase.LoadAllAssetsAtPath( filePath ); + if( assets == null || assets.Length == 0 ) + continue; + + for( int i = 0; i < assets.Length; i++ ) + AddSearchedObjectToFilteredSets( assets[i], true ); + } + + // Find Project Settings to search for references. Don't search Project Settings if searched object(s) are all scene objects + // as Project Settings can't hold references to scene objects + string[] projectSettingsToSearch = new string[0]; + if( searchParameters.searchInProjectSettings && assetsToSearchSet.Count > 0 ) + { + string[] projectSettingsAssets = Directory.GetFiles( "ProjectSettings" ); + projectSettingsToSearch = new string[projectSettingsAssets.Length]; + for( int i = 0; i < projectSettingsAssets.Length; i++ ) + projectSettingsToSearch[i] = "ProjectSettings/" + Path.GetFileName( projectSettingsAssets[i] ); + + // AssetDatabase.GetDependencies doesn't work with Project Settings assets. By adding these assets to assetsToSearchPathsSet, + // we make sure that AssetHasAnyReference returns true for these assets and they don't get excluded from the search + assetsToSearchPathsSet.UnionWith( projectSettingsToSearch ); + } + + // Find the scenes to search for references + HashSet scenesToSearch = new HashSet(); + if( searchParameters.searchInScenesSubset != null && searchParameters.searchInScenesSubset.Length > 0 ) + { + foreach( Object obj in searchParameters.searchInScenesSubset ) + { + if( obj == null || obj.Equals( null ) ) + continue; + + if( !obj.IsAsset() ) + continue; + + if( obj.IsFolder() ) + { + string[] folderContents = AssetDatabase.FindAssets( "t:SceneAsset", new string[] { AssetDatabase.GetAssetPath( obj ) } ); + if( folderContents == null ) + continue; + + for( int i = 0; i < folderContents.Length; i++ ) + scenesToSearch.Add( AssetDatabase.GUIDToAssetPath( folderContents[i] ) ); + } + else if( obj is SceneAsset ) + scenesToSearch.Add( AssetDatabase.GetAssetPath( obj ) ); + } + } + else if( ( searchParameters.searchInScenes & SceneSearchMode.AllScenes ) == SceneSearchMode.AllScenes ) + { + // Get all scenes from the Assets folder + string[] sceneGuids = AssetDatabase.FindAssets( "t:SceneAsset" ); + for( int i = 0; i < sceneGuids.Length; i++ ) + scenesToSearch.Add( AssetDatabase.GUIDToAssetPath( sceneGuids[i] ) ); + } + else + { + if( ( searchParameters.searchInScenes & SceneSearchMode.OpenScenes ) == SceneSearchMode.OpenScenes ) + { + // Get all open (and loaded) scenes + for( int i = 0; i < SceneManager.sceneCount; i++ ) + { + Scene scene = SceneManager.GetSceneAt( i ); + if( scene.IsValid() && scene.isLoaded ) + scenesToSearch.Add( scene.path ); + } + } + + bool searchInScenesInBuildTickedAll = ( searchParameters.searchInScenes & SceneSearchMode.ScenesInBuildSettingsAll ) == SceneSearchMode.ScenesInBuildSettingsAll; + if( searchInScenesInBuildTickedAll || ( searchParameters.searchInScenes & SceneSearchMode.ScenesInBuildSettingsTickedOnly ) == SceneSearchMode.ScenesInBuildSettingsTickedOnly ) + { + // Get all scenes in build settings + EditorBuildSettingsScene[] scenesTemp = EditorBuildSettings.scenes; + for( int i = 0; i < scenesTemp.Length; i++ ) + { + if( ( searchInScenesInBuildTickedAll || scenesTemp[i].enabled ) ) + scenesToSearch.Add( scenesTemp[i].path ); + } + } + } + + // In Play mode, only open scenes can be searched + if( isInPlayMode ) + { + HashSet openScenes = new HashSet(); + for( int i = 0; i < SceneManager.sceneCount; i++ ) + { + Scene scene = SceneManager.GetSceneAt( i ); + if( scene.IsValid() && scene.isLoaded ) + openScenes.Add( scene.path ); + } + + List skippedScenes = new List( scenesToSearch.Count ); + scenesToSearch.RemoveWhere( ( path ) => + { + if( !openScenes.Contains( path ) ) + { + skippedScenes.Add( path ); + return true; + } + + return false; + } ); + + if( skippedScenes.Count > 0 ) + { + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + sb.Append( "Can't search unloaded scenes while in play mode, skipped " ).Append( skippedScenes.Count ).AppendLine( " scene(s):" ); + for( int i = 0; i < skippedScenes.Count; i++ ) + sb.Append( "- " ).AppendLine( skippedScenes[i] ); + + Debug.Log( sb.ToString() ); + } + } + + // Initialize data used by search functions + InitializeSearchFunctionsData( searchParameters ); + + // Initialize the nodes of searched asset(s) + foreach( Object obj in objectsToSearchSet ) + searchedUnityObjects.Add( obj.GetInstanceID(), PopReferenceNode( obj ) ); + + // Progressbar values + int searchProgress = 0; + int searchTotalProgress = scenesToSearch.Count; + if( isInPlayMode && searchParameters.searchInScenes != SceneSearchMode.None ) + searchTotalProgress++; // DontDestroyOnLoad scene + + if( searchParameters.showDetailedProgressBar ) + searchTotalProgress += projectSettingsToSearch.Length; + + // Don't search assets if searched object(s) are all scene objects as assets can't hold references to scene objects + if( searchParameters.searchInAssetsFolder && assetsToSearchSet.Count > 0 ) + { + currentSearchResultGroup = new SearchResultGroup( "Project Window (Assets)", SearchResultGroup.GroupType.Assets ); + + // Get the paths of all assets that are to be searched + IEnumerable assetPaths; + if( searchParameters.searchInAssetsSubset == null || searchParameters.searchInAssetsSubset.Length == 0 ) + { + string[] allAssetPaths = AssetDatabase.GetAllAssetPaths(); + assetPaths = allAssetPaths; + + if( searchParameters.showDetailedProgressBar ) + searchTotalProgress += allAssetPaths.Length; + } + else + { + folderContentsSet.Clear(); + + foreach( Object obj in searchParameters.searchInAssetsSubset ) + { + if( obj == null || obj.Equals( null ) ) + continue; + + if( !obj.IsAsset() ) + continue; + + if( obj.IsFolder() ) + folderContentsSet.UnionWith( Utilities.EnumerateFolderContents( obj ) ); + else + folderContentsSet.Add( AssetDatabase.GetAssetPath( obj ) ); + } + + assetPaths = folderContentsSet; + + if( searchParameters.showDetailedProgressBar ) + searchTotalProgress += folderContentsSet.Count; + } + + // Calculate the path(s) of the assets that won't be searched for references + if( searchParameters.excludedAssetsFromSearch != null ) + { + foreach( Object obj in searchParameters.excludedAssetsFromSearch ) + { + if( obj == null || obj.Equals( null ) ) + continue; + + if( !obj.IsAsset() ) + continue; + + if( obj.IsFolder() ) + excludedAssetsPathsSet.UnionWith( Utilities.EnumerateFolderContents( obj ) ); + else + excludedAssetsPathsSet.Add( AssetDatabase.GetAssetPath( obj ) ); + } + } + + if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching assets", 0f ) ) + throw new Exception( "Search aborted" ); + + foreach( string path in assetPaths ) + { + if( searchParameters.showDetailedProgressBar && ++searchProgress % 30 == 1 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching assets", (float) searchProgress / searchTotalProgress ) ) + throw new Exception( "Search aborted" ); + + if( excludedAssetsPathsSet.Contains( path ) ) + continue; + + // If asset resides inside the Assets directory and is not a scene asset + if( path.StartsWithFast( "Assets/" ) && !path.EndsWithFast( ".unity" ) ) + { + if( !AssetHasAnyReference( path ) ) + continue; + + Object[] assets = AssetDatabase.LoadAllAssetsAtPath( path ); + if( assets == null || assets.Length == 0 ) + continue; + + for( int i = 0; i < assets.Length; i++ ) + { + // Components are already searched while searching the GameObject + if( assets[i] is Component ) + continue; + + BeginSearchObject( assets[i] ); + } + } + } + + // If a reference is found in the Project view, save the results + if( currentSearchResultGroup.NumberOfReferences > 0 ) + searchResult.Add( currentSearchResultGroup ); + } + + // Search all assets inside the ProjectSettings folder + if( projectSettingsToSearch.Length > 0 ) + { + currentSearchResultGroup = new SearchResultGroup( "Project Settings", SearchResultGroup.GroupType.ProjectSettings ); + + if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching Project Settings", (float) searchProgress / searchTotalProgress ) ) + throw new Exception( "Search aborted" ); + + for( int i = 0; i < projectSettingsToSearch.Length; i++ ) + { + if( searchParameters.showDetailedProgressBar && ++searchProgress % 30 == 1 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching Project Settings", (float) searchProgress / searchTotalProgress ) ) + throw new Exception( "Search aborted" ); + + Object[] assets = AssetDatabase.LoadAllAssetsAtPath( projectSettingsToSearch[i] ); + if( assets != null && assets.Length > 0 ) + { + for( int j = 0; j < assets.Length; j++ ) + BeginSearchObject( assets[j] ); + } + } + + if( currentSearchResultGroup.NumberOfReferences > 0 ) + searchResult.Add( currentSearchResultGroup ); + } + + // Search non-serializable variables for references while searching a scene in play mode + if( isInPlayMode ) + searchSerializableVariablesOnly = false; + + if( scenesToSearch.Count > 0 ) + { + // Calculate the path(s) of the scenes that won't be searched for references + HashSet excludedScenesPathsSet = new HashSet(); + if( searchParameters.excludedScenesFromSearch != null ) + { + foreach( Object obj in searchParameters.excludedScenesFromSearch ) + { + if( obj == null || obj.Equals( null ) ) + continue; + + if( !obj.IsAsset() ) + continue; + + if( obj.IsFolder() ) + { + string[] folderContents = AssetDatabase.FindAssets( "t:SceneAsset", new string[] { AssetDatabase.GetAssetPath( obj ) } ); + if( folderContents == null ) + continue; + + for( int i = 0; i < folderContents.Length; i++ ) + excludedScenesPathsSet.Add( AssetDatabase.GUIDToAssetPath( folderContents[i] ) ); + } + else if( obj is SceneAsset ) + excludedScenesPathsSet.Add( AssetDatabase.GetAssetPath( obj ) ); + } + } + + // Search scenes for references + foreach( string scenePath in scenesToSearch ) + { + if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching scene: " + scenePath, (float) ++searchProgress / searchTotalProgress ) ) + throw new Exception( "Search aborted" ); + + if( string.IsNullOrEmpty( scenePath ) ) + continue; + + if( excludedScenesPathsSet.Contains( scenePath ) ) + continue; + +#if UNITY_2019_2_OR_NEWER + // Skip scenes in read-only packages (Issue #36) + // Credit: https://forum.unity.com/threads/check-if-asset-inside-package-is-readonly.900902/#post-5990822 + if( !scenePath.StartsWithFast( "Assets/" ) ) + { + var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath( scenePath ); + if( packageInfo != null && packageInfo.source != UnityEditor.PackageManager.PackageSource.Embedded && packageInfo.source != UnityEditor.PackageManager.PackageSource.Local ) + continue; + } +#endif + + SearchScene( scenePath, searchResult, searchParameters, initialSceneSetup ); + } + } + + // Search through all the GameObjects under the DontDestroyOnLoad scene (if exists) + if( isInPlayMode && searchParameters.searchInScenes != SceneSearchMode.None ) + { + if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching scene: DontDestroyOnLoad", 1f ) ) + throw new Exception( "Search aborted" ); + + currentSearchResultGroup = new SearchResultGroup( "DontDestroyOnLoad", SearchResultGroup.GroupType.DontDestroyOnLoad ); + + GameObject[] rootGameObjects = GetDontDestroyOnLoadObjects(); + for( int i = 0; i < rootGameObjects.Length; i++ ) + SearchGameObjectRecursively( rootGameObjects[i] ); + + if( currentSearchResultGroup.NumberOfReferences > 0 ) + searchResult.Add( currentSearchResultGroup ); + } + + // Searching source assets last prevents some references from being excluded due to callStack.ContainsFast + if( !searchParameters.dontSearchInSourceAssets ) + { + searchingSourceAssets = true; + + foreach( Object obj in objectsToSearchSet ) + { + currentSearchedObject = obj; + SearchObject( obj ); + } + + searchingSourceAssets = false; + } + + EditorUtility.DisplayProgressBar( "Please wait...", "Post-processing search results", 1f ); + + InitializeSearchResultNodes( searchResult ); + + HashSet usedObjects = null; + if( searchResult.Count > 0 && searchParameters.calculateUnusedObjects ) + CalculateUnusedObjects( searchResult, out usedObjects ); + + // Log some c00l stuff to console + Debug.Log( "Searched " + searchedObjectsCount + " objects in " + ( EditorApplication.timeSinceStartup - searchStartTime ).ToString( "F2" ) + " seconds" ); + + return new SearchResult( true, searchResult, usedObjects, initialSceneSetup, this, searchParameters ); + } + catch( Exception e ) + { + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + sb.EnsureCapacity( objectsToSearchSet.Count * 50 + callStack.Count * 50 + 500 ); + + sb.AppendLine( "AssetUsageDetector Error: The following Exception is thrown during the search. Details:" ); + + Object latestUnityObjectInCallStack = AppendCallStackToStringBuilder( sb ); + + sb.AppendLine( "Searching references of: " ); + foreach( Object obj in objectsToSearchSet ) + { + if( obj ) + sb.Append( obj.name ).Append( " (" ).Append( obj.GetType() ).AppendLine( ")" ); + } + + Debug.LogError( sb.ToString(), latestUnityObjectInCallStack ); + Debug.LogException( e, latestUnityObjectInCallStack ); + + try + { + InitializeSearchResultNodes( searchResult ); + } + catch + { } + + return new SearchResult( false, searchResult, null, initialSceneSetup, this, searchParameters ); + } + finally + { + currentSearchResultGroup = null; + currentSearchedObject = null; + + EditorUtility.ClearProgressBar(); + + // If the active scene was changed during search, reset it + if( EditorSceneManager.GetActiveScene() != activeScene ) + EditorSceneManager.SetActiveScene( activeScene ); + +#if UNITY_2018_3_OR_NEWER + // If a prefab stage was open when the search was triggered, try reopening the prefab stage after the search is completed + if( !string.IsNullOrEmpty( openPrefabStageAssetPath ) ) + { +#if UNITY_2020_1_OR_NEWER + bool shouldOpenPrefabStageWithoutContext = true; + if( openPrefabStageContextObject != null && !openPrefabStageContextObject.Equals( null ) ) + { + try + { + // Try to access this method: https://github.com/Unity-Technologies/UnityCsReference/blob/73925b1711847c067e607ec8371f8e9ffe7ab65d/Editor/Mono/SceneManagement/StageManager/PrefabStage/PrefabStageUtility.cs#L61-L65 + MethodInfo prefabStageOpenerWithContext = typeof( PrefabStageUtility ).GetMethod( "OpenPrefab", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[2] { typeof( string ), typeof( GameObject ) }, null ); + if( prefabStageOpenerWithContext != null ) + { + prefabStageOpenerWithContext.Invoke( null, new object[2] { openPrefabStageAssetPath, openPrefabStageContextObject } ); + shouldOpenPrefabStageWithoutContext = false; + } + } + catch { } + } + + if( shouldOpenPrefabStageWithoutContext ) +#endif + { + AssetDatabase.OpenAsset( AssetDatabase.LoadAssetAtPath( openPrefabStageAssetPath ) ); + } + } +#endif + } + } + + private void InitializeSearchResultNodes( List searchResult ) + { + for( int i = 0; i < searchResult.Count; i++ ) + { + searchResult[i].InitializeNodes( objectsToSearchSet ); + + // Remove empty search result groups + if( !searchResult[i].PendingSearch && searchResult[i].NumberOfReferences == 0 ) + searchResult.RemoveAt( i-- ); + } + } + + private void CalculateUnusedObjects( List searchResult, out HashSet usedObjectsSet ) + { + currentSearchResultGroup = new SearchResultGroup( "Unused Objects", SearchResultGroup.GroupType.UnusedObjects, false, false ); + + usedObjectsSet = new HashSet(); + HashSet usedObjectPathsSet = new HashSet(); // For assets: stores the filepaths, For scene objects: stores the topmost GameObject's instanceID + foreach( SearchResultGroup searchResultGroup in searchResult ) + { + for( int j = 0; j < searchResultGroup.NumberOfReferences; j++ ) + { + Object obj = searchResultGroup[j].UnityObject; + if( obj is Component ) + obj = ( (Component) obj ).gameObject; + + if( usedObjectsSet.Add( obj ) ) + { + string assetPath = AssetDatabase.GetAssetPath( obj ); + if( !string.IsNullOrEmpty( assetPath ) ) + usedObjectPathsSet.Add( assetPath ); + else + { + for( Transform parent = ( (GameObject) obj ).transform.parent; parent != null; parent = parent.parent ) + usedObjectPathsSet.Add( parent.gameObject.GetInstanceID().ToString() ); + } + } + } + } + + Dictionary unusedMainObjectNodes = new Dictionary( objectsToSearchSet.Count - usedObjectsSet.Count ); + Dictionary> unusedSubObjectNodes = new Dictionary>( objectsToSearchSet.Count - usedObjectsSet.Count ); + foreach( Object obj in objectsToSearchSet ) + { + // Omit components, their GameObjects are already included in search + if( obj is Component ) + continue; + + // Omit assets that are invisible in Hierarchy/Inspector + if( ( obj.hideFlags & ( HideFlags.HideInInspector | HideFlags.HideInHierarchy ) ) != HideFlags.None ) + continue; + + if( usedObjectsSet.Contains( obj ) ) + continue; + + string assetPath = AssetDatabase.GetAssetPath( obj ); + + // Omit unused sub-assets whose parent assets are used (configurable via Settings) + if( AssetUsageDetectorSettings.MarkUsedAssetsSubAssetsAsUsed && AssetDatabase.IsSubAsset( obj ) && usedObjectsSet.Contains( AssetDatabase.LoadMainAssetAtPath( assetPath ) ) ) + continue; + + // Omit meshes of an imported model asset + if( obj is Mesh && !string.IsNullOrEmpty( assetPath ) && AssetDatabase.GetMainAssetTypeAtPath( assetPath ) == typeof( GameObject ) && objectsToSearchSet.Contains( AssetDatabase.LoadMainAssetAtPath( assetPath ) ) ) + continue; + + // Omit MonoScripts whose types can't be determined + if( obj is MonoScript && ( (MonoScript) obj ).GetClass() == null ) + continue; + + GameObject searchedTopmostGameObject = null; + if( obj is GameObject ) + { + if( string.IsNullOrEmpty( assetPath ) ) + { + for( Transform parent = ( (GameObject) obj ).transform.parent; parent != null; parent = parent.parent ) + { + if( objectsToSearchSet.Contains( parent ) && !usedObjectsSet.Contains( parent.gameObject ) ) + searchedTopmostGameObject = parent.gameObject; + } + } + else + { + for( Transform parent = ( (GameObject) obj ).transform.parent; parent != null; parent = parent.parent ) + { + if( objectsToSearchSet.Contains( parent ) ) + { + searchedTopmostGameObject = parent.gameObject; + break; + } + } + } + + if( searchedTopmostGameObject && !string.IsNullOrEmpty( assetPath ) ) // Omit GameObject assets if their parent objects are already included in search + continue; + } + + // Use new ReferenceNodes in UnusedObjects search result group because we don't want these nodes to be linked to the actual ReferenceNodes in any way + // (i.e. we don't use actual ReferenceNodes of these objects (GetReferenceNode) because these may have links to other nodes in unknown circumstances) + ReferenceNode node = PopReferenceNode( obj ); + node.usedState = ReferenceNode.UsedState.Unused; + + if( string.IsNullOrEmpty( assetPath ) ) + { + if( !searchedTopmostGameObject ) + { + if( obj is GameObject ) + unusedMainObjectNodes[obj.GetInstanceID().ToString()] = node; + else + currentSearchResultGroup.AddReference( node ); + } + else // List child GameObject scene objects under their parent GameObject + { + string dictionaryKey = searchedTopmostGameObject.GetInstanceID().ToString(); + List unusedSubObjectNodesAtPath; + if( !unusedSubObjectNodes.TryGetValue( dictionaryKey, out unusedSubObjectNodesAtPath ) ) + unusedSubObjectNodes[dictionaryKey] = unusedSubObjectNodesAtPath = new List( 2 ); + + unusedSubObjectNodesAtPath.Add( node ); + } + } + else + { + if( AssetDatabase.IsMainAsset( obj ) ) + unusedMainObjectNodes[assetPath] = node; + else + { + List unusedSubObjectNodesAtPath; + if( !unusedSubObjectNodes.TryGetValue( assetPath, out unusedSubObjectNodesAtPath ) ) + unusedSubObjectNodes[assetPath] = unusedSubObjectNodesAtPath = new List( 2 ); + + unusedSubObjectNodesAtPath.Add( node ); + } + } + } + + foreach( KeyValuePair kvPair in unusedMainObjectNodes ) + { + List unusedSubAssetNodesAtPath; + if( unusedSubObjectNodes.TryGetValue( kvPair.Key, out unusedSubAssetNodesAtPath ) ) + { + currentSearchResultGroup.AddReference( kvPair.Value ); + for( int i = 0; i < unusedSubAssetNodesAtPath.Count; i++ ) + kvPair.Value.AddLinkTo( unusedSubAssetNodesAtPath[i] ); + + if( usedObjectPathsSet.Contains( kvPair.Key ) ) + kvPair.Value.usedState = ReferenceNode.UsedState.MixedCollapsed; + + unusedSubObjectNodes.Remove( kvPair.Key ); + } + else if( !usedObjectPathsSet.Contains( kvPair.Key ) ) // If a main asset has sub-assets and all of them are used, consider the main asset as used, as well (especially useful for Sprite assets) + currentSearchResultGroup.AddReference( kvPair.Value ); + else if( !AssetDatabase.Contains( (Object) kvPair.Value.nodeObject ) ) + { + currentSearchResultGroup.AddReference( kvPair.Value ); + kvPair.Value.usedState = ReferenceNode.UsedState.MixedCollapsed; + } + } + + foreach( KeyValuePair> kvPair in unusedSubObjectNodes ) // These aren't linked to any unusedMainObjectNodes, add them as root nodes to the search result group + { + foreach( ReferenceNode node in kvPair.Value ) + currentSearchResultGroup.AddReference( node ); + } + + if( currentSearchResultGroup.NumberOfReferences > 0 ) + { + for( int i = 0; i < currentSearchResultGroup.NumberOfReferences; i++ ) + currentSearchResultGroup[i].InitializeRecursively(); + + searchResult.Insert( 0, currentSearchResultGroup ); + } + } + + // Checks if object is asset or scene object and adds it to the corresponding HashSet(s) + private void AddSearchedObjectToFilteredSets( Object obj, bool expandGameObjects ) + { + if( obj == null || obj.Equals( null ) ) + return; + + objectsToSearchSet.Add( obj ); + +#if UNITY_2018_3_OR_NEWER + // When searching for references of a prefab stage object, try adding its corresponding prefab asset to the searched assets, as well + if( openPrefabStage != null && openPrefabStagePrefabAsset != null && obj is GameObject && openPrefabStage.IsPartOfPrefabContents( (GameObject) obj ) ) + { + GameObject prefabStageObjectSource = ( (GameObject) obj ).FollowSymmetricHierarchy( openPrefabStage.prefabContentsRoot, openPrefabStagePrefabAsset ); + if( prefabStageObjectSource != null ) + AddSearchedObjectToFilteredSets( prefabStageObjectSource, expandGameObjects ); + } +#endif + + bool isAsset = obj.IsAsset(); + if( isAsset ) + { + assetsToSearchSet.Add( obj ); + + string assetPath = AssetDatabase.GetAssetPath( obj ); + if( !string.IsNullOrEmpty( assetPath ) ) + { + assetsToSearchPathsSet.Add( assetPath ); + if( searchParameters.dontSearchInSourceAssets && AssetDatabase.IsMainAsset( obj ) ) + excludedAssetsPathsSet.Add( assetPath ); + } + + GameObject go = null; + if( obj is GameObject ) + go = (GameObject) obj; + else if( obj is Component ) + go = ( (Component) obj ).gameObject; + + if( go != null ) + { + Transform transform = go.transform; + bool shouldAddRootPrefabEntry = true; + for( int i = assetsToSearchRootPrefabs.Count - 1; i >= 0; i-- ) + { + Transform rootTransform = assetsToSearchRootPrefabs[i].transform; + if( transform.IsChildOf( rootTransform ) ) + { + shouldAddRootPrefabEntry = false; + break; + } + + if( rootTransform.IsChildOf( transform ) ) + assetsToSearchRootPrefabs.RemoveAt( i ); + } + + if( shouldAddRootPrefabEntry ) + assetsToSearchRootPrefabs.Add( go ); + } + } + else + { + if( obj is GameObject ) + sceneObjectsToSearchScenesSet.Add( ( (GameObject) obj ).scene.path ); + else if( obj is Component ) + sceneObjectsToSearchScenesSet.Add( ( (Component) obj ).gameObject.scene.path ); + } + + if( expandGameObjects && obj is GameObject ) + { + // If searched asset is a GameObject, include its components in the search + Component[] components = ( (GameObject) obj ).GetComponents(); + for( int i = 0; i < components.Length; i++ ) + { + if( components[i] == null || components[i].Equals( null ) ) + continue; + + objectsToSearchSet.Add( components[i] ); + + if( isAsset ) + assetsToSearchSet.Add( components[i] ); + } + } + else if( obj is Component ) + { + // Include searched components' GameObjects in the search, as well + AddSearchedObjectToFilteredSets( ( (Component) obj ).gameObject, false ); + } + } + + // Search a scene for references + private void SearchScene( string scenePath, List searchResult, Parameters searchParameters, SceneSetup[] initialSceneSetup ) + { + Scene scene = EditorSceneManager.GetSceneByPath( scenePath ); + if( isInPlayMode && !scene.isLoaded ) + return; + + bool canContainSceneObjectReference = scene.isLoaded && ( !EditorSceneManager.preventCrossSceneReferences || sceneObjectsToSearchScenesSet.Contains( scenePath ) ); + if( !canContainSceneObjectReference ) + { + bool canContainAssetReference = assetsToSearchSet.Count > 0 && ( isInPlayMode || AssetHasAnyReference( scenePath ) ); + if( !canContainAssetReference ) + return; + } + + if( !scene.isLoaded ) + { + if( searchParameters.lazySceneSearch ) + { + searchResult.Add( new SearchResultGroup( scenePath, SearchResultGroup.GroupType.Scene, true, true ) ); + return; + } + + scene = EditorSceneManager.OpenScene( scenePath, OpenSceneMode.Additive ); + } + + currentSearchResultGroup = new SearchResultGroup( scenePath, SearchResultGroup.GroupType.Scene ); + + // Search through all the GameObjects in the scene + GameObject[] rootGameObjects = scene.GetRootGameObjects(); + for( int i = 0; i < rootGameObjects.Length; i++ ) + SearchGameObjectRecursively( rootGameObjects[i] ); + + // Search through Lighting Settings (it requires changing the active scene but don't do that in play mode) + if( searchParameters.searchInSceneLightingSettings && ( !isInPlayMode || SceneManager.GetActiveScene() == scene ) ) + { + if( !isInPlayMode && EditorSceneManager.GetActiveScene() != scene ) + EditorSceneManager.SetActiveScene( scene ); + + BeginSearchObject( lightmapSettingsGetter() ); + BeginSearchObject( renderSettingsGetter() ); + } + + // If no references are found in the scene and if the scene is not part of the initial scene setup, close it + if( currentSearchResultGroup.NumberOfReferences == 0 ) + { + if( !isInPlayMode ) + { + bool sceneIsOneOfInitials = false; + for( int i = 0; i < initialSceneSetup.Length; i++ ) + { + if( initialSceneSetup[i].path == scenePath ) + { + if( !initialSceneSetup[i].isLoaded ) + EditorSceneManager.CloseScene( scene, false ); + + sceneIsOneOfInitials = true; + break; + } + } + + if( !sceneIsOneOfInitials ) + EditorSceneManager.CloseScene( scene, true ); + } + } + else + { + // Some references are found in this scene, save the results + searchResult.Add( currentSearchResultGroup ); + } + } + + // Search a GameObject and its children for references recursively + private void SearchGameObjectRecursively( GameObject go ) + { + BeginSearchObject( go ); + + Transform tr = go.transform; + for( int i = 0; i < tr.childCount; i++ ) + SearchGameObjectRecursively( tr.GetChild( i ).gameObject ); + } + + // Begin searching a root object (like a GameObject or an asset) + private void BeginSearchObject( Object obj ) + { + if( obj is SceneAsset ) + return; + + currentSearchedObject = obj; + + ReferenceNode searchResult = SearchObject( obj ); + if( searchResult != null ) + currentSearchResultGroup.AddReference( searchResult ); + } + + // Search an object for references + private ReferenceNode SearchObject( object obj ) + { + if( obj == null || obj.Equals( null ) ) + return null; + + // Avoid recursion (which leads to stackoverflow exception) using a stack (initially, I was using callStack.ContainsFast + // here but it returned false for objects that do exist in the call stack if VFX Graph window was open) + for( int i = callStack.Count - 1; i >= 0; i-- ) + { + if( callStack[i].Equals( obj ) ) + return null; + } + + bool searchingSourceAsset = searchingSourceAssets && ReferenceEquals( currentSearchedObject, obj ); + + // Hashing does not work well with structs all the time, don't cache search results for structs + if( !( obj is ValueType ) && !searchingSourceAsset ) + { + // If object was searched before, return the cached result + ReferenceNode cachedResult; + if( TryGetReferenceNode( obj, out cachedResult ) ) + return cachedResult; + } + + searchedObjectsCount++; + + ReferenceNode result; + Object unityObject = obj as Object; + if( unityObject != null ) + { + // If the Object is an asset, search it in detail only if its dependencies contain at least one of the searched asset(s) + string assetPath = null; + if( unityObject.IsAsset() ) + { + if( assetsToSearchSet.Count == 0 ) + { + searchedUnityObjects.Add( unityObject.GetInstanceID(), null ); + return null; + } + + assetPath = AssetDatabase.GetAssetPath( unityObject ); + if( excludedAssetsPathsSet.Contains( assetPath ) || !AssetHasAnyReference( assetPath ) ) + { + searchedUnityObjects.Add( unityObject.GetInstanceID(), null ); + return null; + } + } + + callStack.Add( unityObject ); + + // Search the Object in detail + Func searchFunction; + if( assetPath != null && extensionToSearchFunction.TryGetValue( Utilities.GetFileExtension( assetPath ), out searchFunction ) && AssetDatabase.IsMainAsset( unityObject ) ) + result = searchFunction( unityObject ); + else if( typeToSearchFunction.TryGetValue( unityObject.GetType(), out searchFunction ) ) + result = searchFunction( unityObject ); + else if( unityObject is Component ) + result = SearchComponent( unityObject ); + else + { + result = PopReferenceNode( unityObject ); + SearchVariablesWithSerializedObject( result ); + } + + // A prefab asset should have a link to its children because when a scene object uses a prefab and a child of that prefab uses + // a searched object, the scene object needs to appear in the search results. Since prefab assets aren't automatically linked to + // their children, we need to create that link manually + if( assetPath != null && unityObject is GameObject && AssetDatabase.IsMainAsset( unityObject ) ) + { + if( result == null ) + result = PopReferenceNode( unityObject ); + + GameObject prefabGameObject = (GameObject) unityObject; + Transform[] prefabChildren = prefabGameObject.GetComponentsInChildren( true ); + for( int i = 0; i < prefabChildren.Length; i++ ) + { + if( prefabChildren[i].gameObject != prefabGameObject ) + result.AddLinkTo( SearchObject( prefabChildren[i].gameObject ), isWeakLink: true ); + } + } + + callStack.RemoveAt( callStack.Count - 1 ); + } + else + { + // Comply with the recursive search limit + if( currentDepth >= searchParameters.searchDepthLimit ) + return null; + + callStack.Add( obj ); + currentDepth++; + + result = PopReferenceNode( obj ); + SearchVariablesWithReflection( result ); + + currentDepth--; + callStack.RemoveAt( callStack.Count - 1 ); + } + + if( result != null && result.NumberOfOutgoingLinks == 0 ) + { + PoolReferenceNode( result ); + result = null; + } + + // Cache the search result if we are skimming through a class (not a struct; i.e. objHash != null) + // and if the object is a UnityEngine.Object (if not, cache the result only if we have actually found something + // or we are at the root of the search; i.e. currentDepth == 0) + if( !( obj is ValueType ) && ( result != null || unityObject != null || currentDepth == 0 ) ) + { + if( !searchingSourceAsset ) + { + if( obj is Object ) + searchedUnityObjects.Add( unityObject.GetInstanceID(), result ); + else + searchedObjects.Add( GetNodeObjectHash( obj ), result ); + } + else if( result != null ) + { + result.CopyReferencesTo( searchedUnityObjects[unityObject.GetInstanceID()] ); + PoolReferenceNode( result ); + } + } + + return result; + } + + // Check if the asset at specified path depends on any of the references + private bool AssetHasAnyReference( string assetPath ) + { +#if ASSET_USAGE_ADDRESSABLES + if( searchParameters.addressablesSupport ) + return true; +#endif + + if( assetsToSearchPathsSet.Contains( assetPath ) ) + return true; + + if( alwaysSearchedExtensionsSet.Count > 0 && alwaysSearchedExtensionsSet.Contains( Utilities.GetFileExtension( assetPath ) ) ) + return true; + + return AssetHasAnyReferenceInternal( assetPath ); + } + + // Recursively check if the asset at specified path depends on any of the references + private bool AssetHasAnyReferenceInternal( string assetPath ) + { + CacheEntry cacheEntry; + if( !assetDependencyCache.TryGetValue( assetPath, out cacheEntry ) ) + { + cacheEntry = new CacheEntry( assetPath ); + assetDependencyCache[assetPath] = cacheEntry; + } + else if( !cacheEntry.verified ) + cacheEntry.Verify( assetPath ); + + if( cacheEntry.searchResult != CacheEntry.Result.Unknown ) + return cacheEntry.searchResult == CacheEntry.Result.Yes; + + cacheEntry.searchResult = CacheEntry.Result.No; + + string[] dependencies = cacheEntry.dependencies; + long[] fileSizes = cacheEntry.fileSizes; + for( int i = 0; i < dependencies.Length; i++ ) + { + // If a dependency was renamed (which doesn't affect the verified hash, unfortunately), + // force refresh the asset's dependencies and search it again + if( !Directory.Exists( dependencies[i] ) ) // Calling FileInfo.Length on a directory throws FileNotFoundException + { + FileInfo assetFile = new FileInfo( dependencies[i] ); + if( !assetFile.Exists || assetFile.Length != fileSizes[i] ) + { + // Although not reproduced, it is reported that this section caused StackOverflowException due to infinite loop, + // if that happens, log useful information to help reproduce the issue + if( lastRefreshedCacheEntry == cacheEntry ) + { + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + sb.EnsureCapacity( 1000 ); + + sb.AppendLine( "Infinite loop while refreshing a cache entry, please report it to the author." ).AppendLine(); + sb.Append( "Asset path: " ).AppendLine( assetPath ); + + for( int j = 0; j < 2; j++ ) + { + if( j == 1 ) + { + cacheEntry.Refresh( assetPath ); + dependencies = cacheEntry.dependencies; + fileSizes = cacheEntry.fileSizes; + } + + sb.AppendLine().AppendLine( j == 0 ? "Old Dependencies:" : "New Dependencies" ); + for( int k = 0; k < dependencies.Length; k++ ) + { + sb.Append( "- " ).Append( dependencies[k] ); + + if( Directory.Exists( dependencies[k] ) ) + { + sb.Append( " (Dir)" ); + if( fileSizes[k] != 0L ) + sb.Append( " WasCachedAsFile: " ).Append( fileSizes[k] ); + } + else + { + assetFile = new FileInfo( dependencies[k] ); + sb.Append( " (File) " ).Append( "CachedSize: " ).Append( fileSizes[k] ); + if( assetFile.Exists ) + sb.Append( " RealSize: " ).Append( assetFile.Length ); + else + sb.Append( " NoLongerExists" ); + } + + sb.AppendLine(); + } + } + + Debug.LogError( sb.ToString() ); + return false; + } + + cacheEntry.Refresh( assetPath ); + cacheEntry.searchResult = CacheEntry.Result.Unknown; + lastRefreshedCacheEntry = cacheEntry; + + return AssetHasAnyReferenceInternal( assetPath ); + } + } + + if( assetsToSearchPathsSet.Contains( dependencies[i] ) ) + { + cacheEntry.searchResult = CacheEntry.Result.Yes; + return true; + } + } + + for( int i = 0; i < dependencies.Length; i++ ) + { + if( AssetHasAnyReferenceInternal( dependencies[i] ) ) + { + cacheEntry.searchResult = CacheEntry.Result.Yes; + return true; + } + } + + return false; + } + + // If object was already searched, return its ReferenceNode + private bool TryGetReferenceNode( object nodeObject, out ReferenceNode referenceNode ) + { + if( nodeObject is Object ) + { + if( searchedUnityObjects.TryGetValue( ( (Object) nodeObject ).GetInstanceID(), out referenceNode ) ) + return true; + } + else if( searchedObjects.TryGetValue( GetNodeObjectHash( nodeObject ), out referenceNode ) ) + return true; + + referenceNode = null; + return false; + } + + // Get reference node for object + private ReferenceNode GetReferenceNode( object nodeObject ) + { + ReferenceNode result; + if( nodeObject is Object ) + { + int hash = ( (Object) nodeObject ).GetInstanceID(); + if( !searchedUnityObjects.TryGetValue( hash, out result ) || result == null ) + { + result = PopReferenceNode( nodeObject ); + searchedUnityObjects[hash] = result; + } + } + else + { + string hash = GetNodeObjectHash( nodeObject ); + if( !searchedObjects.TryGetValue( hash, out result ) || result == null ) + { + result = PopReferenceNode( nodeObject ); + searchedObjects[hash] = result; + } + } + + return result; + } + + // Fetch a reference node from pool + private ReferenceNode PopReferenceNode( object nodeObject ) + { + ReferenceNode node; + if( nodesPool.Count == 0 ) + node = new ReferenceNode(); + else + { + int index = nodesPool.Count - 1; + node = nodesPool[index]; + nodesPool.RemoveAt( index ); + } + + node.nodeObject = nodeObject; + return node; + } + + // Pool a reference node + private void PoolReferenceNode( ReferenceNode node ) + { + node.Clear(); + nodesPool.Add( node ); + } + + // Get a unique-ish string hash code for a plain C# object (i.e. non-UnityEngine.Object object) + private string GetNodeObjectHash( object nodeObject ) + { + return nodeObject.GetHashCode() + nodeObject.GetType().Name; + } + + // Retrieve the game objects listed under the DontDestroyOnLoad scene + private GameObject[] GetDontDestroyOnLoadObjects() + { + GameObject temp = null; + try + { + temp = new GameObject(); + Object.DontDestroyOnLoad( temp ); + Scene dontDestroyOnLoad = temp.scene; + Object.DestroyImmediate( temp ); + temp = null; + + return dontDestroyOnLoad.GetRootGameObjects(); + } + finally + { + if( temp != null ) + Object.DestroyImmediate( temp ); + } + } + + // Appends contents of callStack to StringBuilder and returns the most recent Unity object in callStack + private Object AppendCallStackToStringBuilder( StringBuilder sb ) + { + Object latestUnityObjectInCallStack = null; + if( callStack.Count > 0 ) + { + sb.AppendLine().AppendLine( "Stack contents: " ); + + for( int i = callStack.Count - 1; i >= 0; i-- ) + { + latestUnityObjectInCallStack = callStack[i] as Object; + if( latestUnityObjectInCallStack ) + { + if( !AssetDatabase.Contains( latestUnityObjectInCallStack ) ) + { + string scenePath = AssetDatabase.GetAssetOrScenePath( latestUnityObjectInCallStack ); + if( !string.IsNullOrEmpty( scenePath ) && SceneManager.GetSceneByPath( scenePath ).IsValid() ) + sb.Append( "Scene: " ).AppendLine( scenePath ); + } + + break; + } + } + + for( int i = callStack.Count - 1; i >= 0; i-- ) + { + sb.Append( i ).Append( ": " ); + + Object unityObject = callStack[i] as Object; + if( unityObject ) + sb.Append( unityObject.name ).Append( " (" ).Append( unityObject.GetType() ).AppendLine( ")" ); + else if( callStack[i] != null ) + sb.Append( callStack[i].GetType() ).AppendLine( " object" ); + else + sb.AppendLine( "<>" ); + } + + sb.AppendLine(); + } + + return latestUnityObjectInCallStack; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs.meta new file mode 100644 index 00000000..8d63a0c4 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2c0dea52dcdb16e4e9b13f8dacc1590f +timeCreated: 1520032279 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs new file mode 100644 index 00000000..4bb14034 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace AssetUsageDetectorNamespace +{ + public partial class AssetUsageDetector + { + #region Helper Classes + private class CacheEntry + { + public enum Result { Unknown = 0, No = 1, Yes = 2 }; + + public string hash; + public string[] dependencies; + public long[] fileSizes; + + public bool verified; + public Result searchResult; + + public CacheEntry( string path ) + { + Verify( path ); + } + + public CacheEntry( string hash, string[] dependencies, long[] fileSizes ) + { + this.hash = hash; + this.dependencies = dependencies; + this.fileSizes = fileSizes; + } + + public void Verify( string path ) + { + string hash = AssetDatabase.GetAssetDependencyHash( path ).ToString(); + if( this.hash != hash ) + { + this.hash = hash; + Refresh( path ); + } + + verified = true; + } + + public void Refresh( string path ) + { + dependencies = AssetDatabase.GetDependencies( path, false ); + if( fileSizes == null || fileSizes.Length != dependencies.Length ) + fileSizes = new long[dependencies.Length]; + + int length = dependencies.Length; + for( int i = 0; i < length; i++ ) + { + if( !string.IsNullOrEmpty( dependencies[i] ) ) + { + FileInfo assetFile = new FileInfo( dependencies[i] ); + fileSizes[i] = assetFile.Exists ? assetFile.Length : 0L; + } + else + { + // This dependency is empty which causes issues when passed to FileInfo constructor + // Find a non-empty dependency and move it to this index + for( int j = length - 1; j > i; j--, length-- ) + { + if( !string.IsNullOrEmpty( dependencies[j] ) ) + { + dependencies[i--] = dependencies[j]; + break; + } + } + + length--; + } + } + + if( length != fileSizes.Length ) + { + Array.Resize( ref dependencies, length ); + Array.Resize( ref fileSizes, length ); + } + } + } + #endregion + + // An optimization to fetch the dependencies of an asset only once (key is the path of the asset) + private Dictionary assetDependencyCache; + private CacheEntry lastRefreshedCacheEntry; + + private string CachePath { get { return Application.dataPath + "/../Library/AssetUsageDetector.cache"; } } // Path of the cache file + + public void SaveCache() + { + if( assetDependencyCache == null ) + return; + + try + { + using( FileStream stream = new FileStream( CachePath, FileMode.Create ) ) + using( BinaryWriter writer = new BinaryWriter( stream ) ) + { + writer.Write( assetDependencyCache.Count ); + + foreach( var keyValuePair in assetDependencyCache ) + { + CacheEntry cacheEntry = keyValuePair.Value; + string[] dependencies = cacheEntry.dependencies; + long[] fileSizes = cacheEntry.fileSizes; + + writer.Write( keyValuePair.Key ); + writer.Write( cacheEntry.hash ); + writer.Write( dependencies.Length ); + + for( int i = 0; i < dependencies.Length; i++ ) + { + writer.Write( dependencies[i] ); + writer.Write( fileSizes[i] ); + } + } + } + } + catch( Exception e ) + { + Debug.LogException( e ); + } + } + + private void LoadCache() + { + if( File.Exists( CachePath ) ) + { + using( FileStream stream = new FileStream( CachePath, FileMode.Open, FileAccess.Read ) ) + using( BinaryReader reader = new BinaryReader( stream ) ) + { + try + { + int cacheSize = reader.ReadInt32(); + assetDependencyCache = new Dictionary( cacheSize ); + + for( int i = 0; i < cacheSize; i++ ) + { + string assetPath = reader.ReadString(); + string hash = reader.ReadString(); + + int dependenciesLength = reader.ReadInt32(); + string[] dependencies = new string[dependenciesLength]; + long[] fileSizes = new long[dependenciesLength]; + for( int j = 0; j < dependenciesLength; j++ ) + { + dependencies[j] = reader.ReadString(); + fileSizes[j] = reader.ReadInt64(); + } + + assetDependencyCache[assetPath] = new CacheEntry( hash, dependencies, fileSizes ); + } + } + catch( Exception e ) + { + assetDependencyCache = null; + Debug.LogWarning( "Couldn't load cache (probably cache format has changed in an update), will regenerate cache.\n" + e.ToString() ); + } + } + } + + // Generate cache for all assets for the first time + if( assetDependencyCache == null ) + { + assetDependencyCache = new Dictionary( 1024 * 8 ); + + string[] allAssets = AssetDatabase.GetAllAssetPaths(); + if( allAssets.Length > 0 ) + { + double startTime = EditorApplication.timeSinceStartup; + + try + { + for( int i = 0; i < allAssets.Length; i++ ) + { + if( i % 30 == 0 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Generating cache for the first time (optional)", (float) i / allAssets.Length ) ) + { + EditorUtility.ClearProgressBar(); + Debug.LogWarning( "Initial cache generation cancelled, cache will be generated on the fly as more and more assets are searched." ); + break; + } + + assetDependencyCache[allAssets[i]] = new CacheEntry( allAssets[i] ); + } + + EditorUtility.ClearProgressBar(); + + Debug.Log( "Cache generated in " + ( EditorApplication.timeSinceStartup - startTime ).ToString( "F2" ) + " seconds" ); + Debug.Log( "You can always reset the cache by deleting " + Path.GetFullPath( CachePath ) ); + + SaveCache(); + } + catch( Exception e ) + { + EditorUtility.ClearProgressBar(); + Debug.LogException( e ); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs.meta new file mode 100644 index 00000000..3d8d8965 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 71ea9a3fd0b82594d8130d882dbfc844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs new file mode 100644 index 00000000..e978d589 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs @@ -0,0 +1,1962 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.IO; +using System.Reflection; +using System.Text; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using UnityEngine.UI; +#if UNITY_2017_1_OR_NEWER +using UnityEngine.U2D; +using UnityEngine.Playables; +#endif +#if UNITY_2018_2_OR_NEWER +using UnityEditor.U2D; +#endif +#if UNITY_2017_3_OR_NEWER +using UnityEditor.Compilation; +#endif +#if UNITY_2017_2_OR_NEWER +using UnityEngine.Tilemaps; +#endif +#if ASSET_USAGE_ADDRESSABLES +using UnityEngine.AddressableAssets; +#endif +using Object = UnityEngine.Object; + +namespace AssetUsageDetectorNamespace +{ + public partial class AssetUsageDetector + { + #region Helper Classes +#if UNITY_2017_3_OR_NEWER +#pragma warning disable 0649 // The fields' values are assigned via JsonUtility + [Serializable] + private struct AssemblyDefinitionReferences + { + public string reference; // Used by AssemblyDefinitionReferenceAssets + public List references; // Used by AssemblyDefinitionAssets + } +#pragma warning restore 0649 +#endif + +#if UNITY_2018_1_OR_NEWER +#pragma warning disable 0649 // The fields' values are assigned via JsonUtility + [Serializable] + private struct ShaderGraphReferences // Used by old Shader Graph serialization format + { + [Serializable] + public struct JSONHolder + { + public string JSONnodeData; + } + + [Serializable] + public class TextureHolder + { + public string m_SerializedTexture; + public string m_SerializedCubemap; + public string m_Guid; + + public string GetTexturePath() + { + string guid = ExtractGUIDFromString( !string.IsNullOrEmpty( m_SerializedTexture ) ? m_SerializedTexture : m_SerializedCubemap ); + if( string.IsNullOrEmpty( guid ) ) + guid = m_Guid; + + return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid ); + } + } + + [Serializable] + public struct PropertyData + { + public string m_Name; + public string m_DefaultReferenceName; + public string m_OverrideReferenceName; + public TextureHolder m_Value; + + public string GetName() + { + if( !string.IsNullOrEmpty( m_OverrideReferenceName ) ) + return m_OverrideReferenceName; + if( !string.IsNullOrEmpty( m_DefaultReferenceName ) ) + return m_DefaultReferenceName; + if( !string.IsNullOrEmpty( m_Name ) ) + return m_Name; + + return "Property"; + } + } + + [Serializable] + public struct NodeData + { + public string m_Name; + public string m_FunctionSource; // Custom Function node's Source field + public string m_SerializedSubGraph; // Sub-graph node + public List m_SerializableSlots; + + public string GetSubGraphPath() + { + string guid = ExtractGUIDFromString( m_SerializedSubGraph ); + return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid ); + } + } + + [Serializable] + public struct NodeSlotData + { + public TextureHolder m_Texture; + public TextureHolder m_TextureArray; + public TextureHolder m_Cubemap; + + public string GetTexturePath() + { + if( m_Texture != null ) + return m_Texture.GetTexturePath(); + if( m_Cubemap != null ) + return m_Cubemap.GetTexturePath(); + if( m_TextureArray != null ) + return m_TextureArray.GetTexturePath(); + + return null; + } + } + + public List m_SerializedProperties; + public List m_SerializableNodes; + + // String can be in one of the following formats: + // "guid":"GUID_VALUE" + // "guid": "GUID_VALUE" + // "guid" : "GUID_VALUE" + private static string ExtractGUIDFromString( string str ) + { + if( !string.IsNullOrEmpty( str ) ) + { + int guidStartIndex = str.IndexOf( "\"guid\"" ); + if( guidStartIndex >= 0 ) + { + guidStartIndex += 6; + guidStartIndex = str.IndexOf( '"', guidStartIndex ); + if( guidStartIndex > 0 ) + { + guidStartIndex++; + + int guidEndIndex = str.IndexOf( '"', guidStartIndex ); + if( guidEndIndex > 0 ) + return str.Substring( guidStartIndex, guidEndIndex - guidStartIndex ); + } + } + } + + return null; + } + } +#pragma warning restore 0649 +#endif + #endregion + + // Dictionary to quickly find the function to search a specific type with + private Dictionary> typeToSearchFunction; + // Dictionary to associate special file extensions with their search functions + private Dictionary> extensionToSearchFunction; + + // An optimization to fetch & filter fields and properties of a class only once + private readonly Dictionary typeToVariables = new Dictionary( 4096 ); + private readonly List validVariables = new List( 32 ); + + // All MonoScripts in objectsToSearchSet + private readonly List monoScriptsToSearch = new List(); + private readonly List monoScriptsToSearchTypes = new List(); + + // Path(s) of .cginc, .cg, .hlsl and .glslinc assets in assetsToSearchSet + private readonly HashSet shaderIncludesToSearchSet = new HashSet(); + +#if UNITY_2017_3_OR_NEWER + // Path(s) of the Assembly Definition Files in objectsToSearchSet (Value: files themselves) + private readonly Dictionary assemblyDefinitionFilesToSearch = new Dictionary( 8 ); +#endif + + // An optimization to fetch an animation clip's curve bindings only once + private readonly Dictionary animationClipUniqueBindings = new Dictionary( 256 ); + + private bool searchPrefabConnections; + private bool searchMonoBehavioursForScript; + private bool searchTextureReferences; +#if UNITY_2018_1_OR_NEWER + private bool searchShaderGraphsForSubGraphs; +#endif + + private bool searchSerializableVariablesOnly; + private bool prevSearchSerializableVariablesOnly; + + private BindingFlags fieldModifiers, propertyModifiers; + private BindingFlags prevFieldModifiers, prevPropertyModifiers; + + // Unity's internal function that returns a SerializedProperty's corresponding FieldInfo + private delegate FieldInfo FieldInfoGetter( SerializedProperty p, out Type t ); +#if UNITY_2019_3_OR_NEWER + private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); +#else + private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); +#endif + + private readonly Func lightmapSettingsGetter = (Func) Delegate.CreateDelegate( typeof( Func ), typeof( LightmapEditorSettings ).GetMethod( "GetLightmapSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); + private readonly Func renderSettingsGetter = (Func) Delegate.CreateDelegate( typeof( Func ), typeof( RenderSettings ).GetMethod( "GetRenderSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); +#if UNITY_2021_2_OR_NEWER + private readonly Func defaultReflectionProbeGetter = (Func) Delegate.CreateDelegate( typeof( Func ), typeof( RenderSettings ).GetProperty( "defaultReflection", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).GetGetMethod( true ) ); +#endif + +#if ASSET_USAGE_ADDRESSABLES + private readonly Func spriteAtlasPackedSpritesGetter = (Func) Delegate.CreateDelegate( typeof( Func ), typeof( SpriteAtlasExtensions ).GetMethod( "GetPackedSprites", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); + private readonly PropertyInfo assetReferenceSubObjectTypeGetter = typeof( AssetReference ).GetProperty( "SubOjbectType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); +#endif + +#if ASSET_USAGE_VFX_GRAPH + private static Type vfxResourceType => typeof( Editor ).Assembly.GetType( "UnityEditor.VFX.VisualEffectResource" ) ?? Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "UnityEditor.VFXModule" ).GetType( "UnityEditor.VFX.VisualEffectResource" ); + private readonly Func vfxResourceGetter = (Func) Delegate.CreateDelegate( typeof( Func ), vfxResourceType.GetMethod( "GetResourceAtPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) ); + private readonly MethodInfo vfxResourceContentsGetter = vfxResourceType.GetMethod( "GetContents", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); + private readonly MethodInfo vfxSerializableObjectValueGetter = Array.Find( Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "Unity.VisualEffectGraph.Editor" ).GetType( "UnityEditor.VFX.VFXSerializableObject" ).GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ), ( methodInfo ) => methodInfo.Name == "Get" && !methodInfo.IsGenericMethod ); +#endif + + private void InitializeSearchFunctionsData( Parameters searchParameters ) + { + if( typeToSearchFunction == null ) + { + typeToSearchFunction = new Dictionary>() + { + { typeof( GameObject ), SearchGameObject }, + { typeof( Material ), SearchMaterial }, + { typeof( Shader ), SearchShader }, + { typeof( MonoScript ), SearchMonoScript }, + { typeof( RuntimeAnimatorController ), SearchAnimatorController }, + { typeof( AnimatorOverrideController ), SearchAnimatorController }, + { typeof( AnimatorController ), SearchAnimatorController }, + { typeof( AnimatorStateMachine ), SearchAnimatorStateMachine }, + { typeof( AnimatorState ), SearchAnimatorState }, + { typeof( AnimatorStateTransition ), SearchAnimatorStateTransition }, + { typeof( BlendTree ), SearchBlendTree }, + { typeof( AnimationClip ), SearchAnimationClip }, + { typeof( TerrainData ), SearchTerrainData }, + { typeof( LightmapSettings ), SearchLightmapSettings }, + { typeof( RenderSettings ), SearchRenderSettings }, +#if UNITY_2017_1_OR_NEWER + { typeof( SpriteAtlas ), SearchSpriteAtlas }, +#endif + }; + } + + if( extensionToSearchFunction == null ) + { + extensionToSearchFunction = new Dictionary>() + { + { "compute", SearchShaderSecondaryAsset }, + { "cginc", SearchShaderSecondaryAsset }, + { "cg", SearchShaderSecondaryAsset }, + { "glslinc", SearchShaderSecondaryAsset }, + { "hlsl", SearchShaderSecondaryAsset }, +#if UNITY_2017_3_OR_NEWER + { "asmdef", SearchAssemblyDefinitionFile }, +#endif +#if UNITY_2019_2_OR_NEWER + { "asmref", SearchAssemblyDefinitionFile }, +#endif +#if UNITY_2018_1_OR_NEWER + { "shadergraph", SearchShaderGraph }, + { "shadersubgraph", SearchShaderGraph }, +#endif +#if ASSET_USAGE_VFX_GRAPH + { "vfx", SearchVFXGraphAsset }, + { "vfxoperator", SearchVFXGraphAsset }, + { "vfxblock", SearchVFXGraphAsset }, +#endif + }; + } + + fieldModifiers = searchParameters.fieldModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly; + propertyModifiers = searchParameters.propertyModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly; + searchSerializableVariablesOnly = !searchParameters.searchNonSerializableVariables; + + if( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly ) + typeToVariables.Clear(); + + prevFieldModifiers = fieldModifiers; + prevPropertyModifiers = propertyModifiers; + prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly; + + searchPrefabConnections = false; + searchMonoBehavioursForScript = false; + searchTextureReferences = false; +#if UNITY_2018_1_OR_NEWER + searchShaderGraphsForSubGraphs = false; +#endif +#if ASSET_USAGE_VFX_GRAPH + bool searchVFXGraphs = false; +#endif + + foreach( Object obj in objectsToSearchSet ) + { + if( obj is Texture || obj is Sprite ) + searchTextureReferences = true; + else if( obj is MonoScript ) + { + searchMonoBehavioursForScript = true; + + Type monoScriptType = ( (MonoScript) obj ).GetClass(); + if( monoScriptType != null && !monoScriptType.IsSealed ) + { + monoScriptsToSearch.Add( (MonoScript) obj ); + monoScriptsToSearchTypes.Add( monoScriptType ); + } + } + else if( obj is GameObject ) + searchPrefabConnections = true; +#if UNITY_2017_3_OR_NEWER + else if( obj is UnityEditorInternal.AssemblyDefinitionAsset ) + assemblyDefinitionFilesToSearch[AssetDatabase.GetAssetPath( obj )] = obj; +#endif +#if ASSET_USAGE_VFX_GRAPH + else if( !searchVFXGraphs && ( obj is Shader || obj is Mesh || obj.GetType().Name.StartsWithFast( "PointCache" ) || obj.GetType().Name == "ShaderGraphVfxAsset" ) ) + searchVFXGraphs = true; +#endif + } + + // We need to search for class/interface inheritance references manually because AssetDatabase.GetDependencies doesn't take that into account + if( monoScriptsToSearch.Count > 0 ) + { + alwaysSearchedExtensionsSet.Add( "cs" ); + alwaysSearchedExtensionsSet.Add( "dll" ); + } + + foreach( string path in assetsToSearchPathsSet ) + { + string extension = Utilities.GetFileExtension( path ); + if( extension == "hlsl" || extension == "cginc" || extension == "cg" || extension == "glslinc" ) + shaderIncludesToSearchSet.Add( path ); +#if UNITY_2018_1_OR_NEWER + else if( extension == "shadersubgraph" ) + searchShaderGraphsForSubGraphs = true; +#endif + } + + // AssetDatabase.GetDependencies doesn't take #include lines in shader source codes into consideration. If we are searching for references + // of a potential #include target (shaderIncludesToSearchSet), we must search all shader assets and check their #include lines manually + if( shaderIncludesToSearchSet.Count > 0 ) + { + alwaysSearchedExtensionsSet.Add( "shader" ); + alwaysSearchedExtensionsSet.Add( "compute" ); + alwaysSearchedExtensionsSet.Add( "cginc" ); + alwaysSearchedExtensionsSet.Add( "cg" ); + alwaysSearchedExtensionsSet.Add( "glslinc" ); + alwaysSearchedExtensionsSet.Add( "hlsl" ); + } + +#if UNITY_2017_3_OR_NEWER + // AssetDatabase.GetDependencies doesn't return references from Assembly Definition Files to their Assembly Definition References, + // so if we are searching for an Assembly Definition File's usages, we must search all Assembly Definition Files' references manually. + if( assemblyDefinitionFilesToSearch.Count > 0 ) + { + alwaysSearchedExtensionsSet.Add( "asmdef" ); +#if UNITY_2019_2_OR_NEWER + alwaysSearchedExtensionsSet.Add( "asmref" ); +#endif + } +#endif + +#if UNITY_2018_1_OR_NEWER + // AssetDatabase.GetDependencies doesn't work with Shader Graph assets. We must search all Shader Graph assets in the following cases: + // searchTextureReferences: to find Texture references used in various nodes and properties + // searchShaderGraphsForSubGraphs: to find Shader Sub-graph references in other Shader Graph assets + // shaderIncludesToSearchSet: to find .cginc, .cg, .glslinc and .hlsl references used in Custom Function nodes + if( searchTextureReferences || searchShaderGraphsForSubGraphs || shaderIncludesToSearchSet.Count > 0 ) + { + alwaysSearchedExtensionsSet.Add( "shadergraph" ); + alwaysSearchedExtensionsSet.Add( "shadersubgraph" ); + } +#endif + +#if ASSET_USAGE_VFX_GRAPH + if( searchTextureReferences || searchVFXGraphs ) + { + alwaysSearchedExtensionsSet.Add( "vfx" ); + alwaysSearchedExtensionsSet.Add( "vfxoperator" ); + alwaysSearchedExtensionsSet.Add( "vfxblock" ); + } +#endif + } + + private ReferenceNode SearchGameObject( object obj ) + { + GameObject go = (GameObject) obj; + ReferenceNode referenceNode = PopReferenceNode( go ); + + // Check if this GameObject's prefab is one of the selected assets + if( searchPrefabConnections ) + { +#if UNITY_2018_3_OR_NEWER + Object prefab = go; + while( prefab = PrefabUtility.GetCorrespondingObjectFromSource( prefab ) ) +#else + Object prefab = PrefabUtility.GetPrefabParent( go ); + if( prefab ) +#endif + { + if( objectsToSearchSet.Contains( prefab ) && assetsToSearchRootPrefabs.ContainsFast( prefab as GameObject ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( prefab ), "Prefab object" ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new PrefabMatch( go, prefab ) ); + } + } + } + + // Search through all the components of the object + Component[] components = go.GetComponents(); + for( int i = 0; i < components.Length; i++ ) + referenceNode.AddLinkTo( SearchObject( components[i] ), isWeakLink: true ); + + return referenceNode; + } + + private ReferenceNode SearchComponent( object obj ) + { + Component component = (Component) obj; + + // Ignore Transform component (no object field to search for) + if( component is Transform ) + return null; + + ReferenceNode referenceNode = PopReferenceNode( component ); + + if( searchMonoBehavioursForScript && component is MonoBehaviour ) + { + // If a searched asset is script, check if this component is an instance of it + // Although SearchVariablesWithSerializedObject can detect these references with SerializedObject, it isn't possible when reflection is used in Play mode + MonoScript script = MonoScript.FromMonoBehaviour( (MonoBehaviour) component ); + if( objectsToSearchSet.Contains( script ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( script ) ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new BehaviourUsageMatch( component.gameObject, script, component ) ); + } + } + + if( component is Animation ) + { + // Search animation clips for references + if( searchParameters.searchRefactoring == null ) + { + foreach( AnimationState anim in (Animation) component ) + referenceNode.AddLinkTo( SearchObject( anim.clip ) ); + } + else + { + AnimationClip[] clips = AnimationUtility.GetAnimationClips( component.gameObject ); + bool modifiedClips = false; + for( int i = 0; i < clips.Length; i++ ) + { + referenceNode.AddLinkTo( SearchObject( clips[i] ) ); + + if( objectsToSearchSet.Contains( clips[i] ) ) + { + searchParameters.searchRefactoring( new AnimationSystemMatch( component, clips[i], ( newValue ) => + { + clips[i] = (AnimationClip) newValue; + modifiedClips = true; + } ) ); + } + } + + if( modifiedClips ) + AnimationUtility.SetAnimationClips( (Animation) component, clips ); + } + + // Search the objects that are animated by this Animation component for references + SearchAnimatedObjects( referenceNode ); + } + else if( component is Animator ) + { + // Search animation clips for references (via AnimatorController) + RuntimeAnimatorController animatorController = ( (Animator) component ).runtimeAnimatorController; + referenceNode.AddLinkTo( SearchObject( animatorController ) ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( animatorController ) ) + searchParameters.searchRefactoring( new AnimationSystemMatch( component, animatorController, ( newValue ) => ( (Animator) component ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) ); + + // Search the objects that are animated by this Animator component for references + SearchAnimatedObjects( referenceNode ); + } +#if UNITY_2017_2_OR_NEWER + else if( component is Tilemap ) + { + // Search the tiles for references + TileBase[] tiles = new TileBase[( (Tilemap) component ).GetUsedTilesCount()]; + ( (Tilemap) component ).GetUsedTilesNonAlloc( tiles ); + + if( tiles != null ) + { + for( int i = 0; i < tiles.Length; i++ ) + { + referenceNode.AddLinkTo( SearchObject( tiles[i] ), "Tile" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( tiles[i] ) ) + searchParameters.searchRefactoring( new OtherSearchMatch( component, tiles[i], ( newValue ) => ( (Tilemap) component ).SwapTile( tiles[i], (TileBase) newValue ) ) ); + } + } + } +#endif +#if UNITY_2017_1_OR_NEWER + else if( component is PlayableDirector ) + { + // Search the PlayableAsset's scene bindings for references + PlayableAsset playableAsset = ( (PlayableDirector) component ).playableAsset; + if( playableAsset != null && !playableAsset.Equals( null ) ) + { + foreach( PlayableBinding binding in playableAsset.outputs ) + { + Object bindingValue = ( (PlayableDirector) component ).GetGenericBinding( binding.sourceObject ); + referenceNode.AddLinkTo( SearchObject( bindingValue ), "Binding: " + binding.streamName ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( bindingValue ) ) + searchParameters.searchRefactoring( new AnimationSystemMatch( component, bindingValue, ( newValue ) => ( (PlayableDirector) component ).SetGenericBinding( binding.sourceObject, newValue ) ) ); + } + } + } +#endif + else if( component is ParticleSystemRenderer ) + { + // Search ParticleSystemRenderer's custom meshes for references (at runtime, they can't be searched with reflection, unfortunately) + if( isInPlayMode && !AssetDatabase.Contains( component ) ) + { + Mesh[] meshes = new Mesh[( (ParticleSystemRenderer) component ).meshCount]; + int meshCount = ( (ParticleSystemRenderer) component ).GetMeshes( meshes ); + bool modifiedMeshes = false; + for( int i = 0; i < meshCount; i++ ) + { + referenceNode.AddLinkTo( SearchObject( meshes[i] ), "Renderer Module: Mesh" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( meshes[i] ) ) + { + searchParameters.searchRefactoring( new OtherSearchMatch( component, meshes[i], ( newValue ) => + { + meshes[i] = (Mesh) newValue; + modifiedMeshes = true; + } ) ); + } + } + + if( modifiedMeshes ) + ( (ParticleSystemRenderer) component ).SetMeshes( meshes, meshCount ); + } + } + else if( component is ParticleSystem ) + { + // At runtime, some ParticleSystem properties can't be searched with reflection, search them manually here + if( isInPlayMode && !AssetDatabase.Contains( component ) ) + { + ParticleSystem particleSystem = (ParticleSystem) component; + + try + { + ParticleSystem.CollisionModule collisionModule = particleSystem.collision; +#if UNITY_2020_2_OR_NEWER + for( int i = 0, j = collisionModule.planeCount; i < j; i++ ) +#else + for( int i = 0, j = collisionModule.maxPlaneCount; i < j; i++ ) +#endif + { + Transform plane = collisionModule.GetPlane( i ); + referenceNode.AddLinkTo( SearchObject( plane ), "Collision Module: Plane" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( plane ) ) + searchParameters.searchRefactoring( new OtherSearchMatch( collisionModule, plane, component, ( newValue ) => collisionModule.SetPlane( i, (Transform) newValue ) ) ); + } + } + catch { } + + try + { + ParticleSystem.TriggerModule triggerModule = particleSystem.trigger; +#if UNITY_2020_2_OR_NEWER + for( int i = 0, j = triggerModule.colliderCount; i < j; i++ ) +#else + for( int i = 0, j = triggerModule.maxColliderCount; i < j; i++ ) +#endif + { + Component collider = triggerModule.GetCollider( i ); + referenceNode.AddLinkTo( SearchObject( collider ), "Trigger Module: Collider" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( collider ) ) + searchParameters.searchRefactoring( new OtherSearchMatch( triggerModule, collider, component, ( newValue ) => triggerModule.SetCollider( i, (Component) newValue ) ) ); + } + } + catch { } + +#if UNITY_2017_1_OR_NEWER + try + { + ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule = particleSystem.textureSheetAnimation; + for( int i = 0, j = textureSheetAnimationModule.spriteCount; i < j; i++ ) + { + Sprite sprite = textureSheetAnimationModule.GetSprite( i ); + referenceNode.AddLinkTo( SearchObject( sprite ), "Texture Sheet Animation Module: Sprite" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( sprite ) ) + searchParameters.searchRefactoring( new OtherSearchMatch( textureSheetAnimationModule, sprite, component, ( newValue ) => textureSheetAnimationModule.SetSprite( i, (Sprite) newValue ) ) ); + } + } + catch { } +#endif + +#if UNITY_5_5_OR_NEWER + try + { + ParticleSystem.SubEmittersModule subEmittersModule = particleSystem.subEmitters; + for( int i = 0, j = subEmittersModule.subEmittersCount; i < j; i++ ) + { + ParticleSystem subEmitterSystem = subEmittersModule.GetSubEmitterSystem( i ); + referenceNode.AddLinkTo( SearchObject( subEmitterSystem ), "Sub Emitters Module: ParticleSystem" ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( subEmitterSystem ) ) + searchParameters.searchRefactoring( new OtherSearchMatch( subEmittersModule, subEmitterSystem, component, ( newValue ) => subEmittersModule.SetSubEmitterSystem( i, (ParticleSystem) newValue ) ) ); + } + } + catch { } +#endif + } + } + + SearchVariablesWithSerializedObject( referenceNode ); + return referenceNode; + } + + private ReferenceNode SearchMaterial( object obj ) + { + const string TEXTURE_PROPERTY_PREFIX = "m_SavedProperties.m_TexEnvs["; + + Material material = (Material) obj; + ReferenceNode referenceNode = PopReferenceNode( material ); + + // We used to search only the shader and the Texture properties in this function but it has changed for 2 major reasons: + // 1) Materials can store more than these references now. For example, HDRP materials can have references to other HDRP materials + // 2) It wasn't possible to search Texture properties that were no longer used by the shader + // Thus, we are searching every property of the material using SerializedObject + SearchVariablesWithSerializedObject( referenceNode ); + + // Post-process the found results and convert links that start with TEXTURE_PROPERTY_PREFIX to their readable names + SerializedObject materialSO = null; + for( int i = referenceNode.NumberOfOutgoingLinks - 1; i >= 0; i-- ) + { + List linkDescriptions = referenceNode[i].descriptions; + for( int j = linkDescriptions.Count - 1; j >= 0; j-- ) + { + int texturePropertyPrefixIndex = linkDescriptions[j].IndexOf( TEXTURE_PROPERTY_PREFIX ); + if( texturePropertyPrefixIndex >= 0 ) + { + texturePropertyPrefixIndex += TEXTURE_PROPERTY_PREFIX.Length; + int texturePropertyEndIndex = linkDescriptions[j].IndexOf( ']', texturePropertyPrefixIndex ); + if( texturePropertyEndIndex > texturePropertyPrefixIndex ) + { + int texturePropertyIndex; + if( int.TryParse( linkDescriptions[j].Substring( texturePropertyPrefixIndex, texturePropertyEndIndex - texturePropertyPrefixIndex ), out texturePropertyIndex ) ) + { + if( materialSO == null ) + materialSO = new SerializedObject( material ); + + string propertyName = materialSO.FindProperty( "m_SavedProperties.m_TexEnvs.Array.data[" + texturePropertyIndex + "].first" ).stringValue; + if( material.HasProperty( propertyName ) ) + linkDescriptions[j] = "[Property: " + propertyName + "]"; + else if( searchParameters.searchUnusedMaterialProperties ) + { + // Move unused references to the end of the list so that used references come first + linkDescriptions.Add( "[Property (UNUSED): " + propertyName + "]" ); + linkDescriptions.RemoveAt( j ); + } + else + linkDescriptions.RemoveAt( j ); + } + } + } + } + + if( linkDescriptions.Count == 0 ) // All shader properties were unused and we weren't searching for unused material properties + referenceNode.RemoveLink( i ); + } + + // At runtime, Textures assigned to clone materials can't be searched with reflection, search them manually here + if( searchTextureReferences && isInPlayMode && !AssetDatabase.Contains( material ) ) + { + Shader shader = material.shader; + int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader ); + for( int i = 0; i < shaderPropertyCount; i++ ) + { + if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) + { + string propertyName = ShaderUtil.GetPropertyName( shader, i ); + Texture assignedTexture = material.GetTexture( propertyName ); + if( objectsToSearchSet.Contains( assignedTexture ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( assignedTexture ), "Shader property: " + propertyName ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new OtherSearchMatch( material, assignedTexture, ( newValue ) => material.SetTexture( propertyName, (Texture) newValue ) ) ); + } + } + } + } + + return referenceNode; + } + + // Searches default Texture values assigned to shader properties, as well as #include references in shader source code + private ReferenceNode SearchShader( object obj ) + { + Shader shader = (Shader) obj; + ReferenceNode referenceNode = PopReferenceNode( shader ); + + if( searchTextureReferences ) + { + ShaderImporter shaderImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( shader ) ) as ShaderImporter; + if( shaderImporter != null ) + { + int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader ); + for( int i = 0; i < shaderPropertyCount; i++ ) + { + if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) + { + string propertyName = ShaderUtil.GetPropertyName( shader, i ); + Texture defaultTexture = shaderImporter.GetDefaultTexture( propertyName ); +#if UNITY_2018_1_OR_NEWER + if( !defaultTexture ) + defaultTexture = shaderImporter.GetNonModifiableTexture( propertyName ); +#endif + + if( objectsToSearchSet.Contains( defaultTexture ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( defaultTexture ), "Default Texture: " + propertyName ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( shaderImporter, defaultTexture, propertyName, null ) ); + } + } + } + } + } + + // Search shader source code for #include references + if( shaderIncludesToSearchSet.Count > 0 ) + SearchShaderSourceCodeForCGIncludes( referenceNode ); + + return referenceNode; + } + + // Searches .compute, .cginc, .cg, .hlsl and .glslinc assets for #include references + private ReferenceNode SearchShaderSecondaryAsset( object obj ) + { + if( shaderIncludesToSearchSet.Count == 0 ) + return null; + + ReferenceNode referenceNode = PopReferenceNode( obj ); + SearchShaderSourceCodeForCGIncludes( referenceNode ); + return referenceNode; + } + + // Searches class/interface inheritances and default UnityEngine.Object values assigned to script variables + private ReferenceNode SearchMonoScript( object obj ) + { + MonoScript script = (MonoScript) obj; + Type scriptType = script.GetClass(); + if( scriptType == null || ( !scriptType.IsSubclassOf( typeof( MonoBehaviour ) ) && !scriptType.IsSubclassOf( typeof( ScriptableObject ) ) ) ) + return null; + + ReferenceNode referenceNode = PopReferenceNode( script ); + + // Check for class/interface inheritance references + for( int i = monoScriptsToSearch.Count - 1; i >= 0; i-- ) + { + if( monoScriptsToSearchTypes[i] != scriptType && monoScriptsToSearchTypes[i].IsAssignableFrom( scriptType ) ) + referenceNode.AddLinkTo( GetReferenceNode( monoScriptsToSearch[i] ), monoScriptsToSearchTypes[i].IsInterface ? "Implements interface" : "Extends class" ); + } + + MonoImporter scriptImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( script ) ) as MonoImporter; + if( scriptImporter != null ) + { + VariableGetterHolder[] variables = GetFilteredVariablesForType( scriptType ); + for( int i = 0; i < variables.Length; i++ ) + { + if( variables[i].isSerializable && !variables[i].IsProperty ) + { + Object defaultValue = scriptImporter.GetDefaultReference( variables[i].Name ); + if( objectsToSearchSet.Contains( defaultValue ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( defaultValue ), "Default variable value: " + variables[i].Name ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( scriptImporter, defaultValue, variables[i].Name, variables ) ); + } + } + } + } + + return referenceNode; + } + + private ReferenceNode SearchAnimatorController( object obj ) + { + RuntimeAnimatorController controller = (RuntimeAnimatorController) obj; + ReferenceNode referenceNode = PopReferenceNode( controller ); + + if( controller is AnimatorController ) + { + AnimatorControllerLayer[] layers = ( (AnimatorController) controller ).layers; + for( int i = 0; i < layers.Length; i++ ) + { + if( objectsToSearchSet.Contains( layers[i].avatarMask ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( layers[i].avatarMask ), layers[i].name + " Mask" ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new AnimationSystemMatch( layers[i], layers[i].avatarMask, controller, ( newValue ) => layers[i].avatarMask = (AvatarMask) newValue ) ); + } + + referenceNode.AddLinkTo( SearchObject( layers[i].stateMachine ) ); + } + } + else + { + if( controller is AnimatorOverrideController ) + { + RuntimeAnimatorController parentController = ( (AnimatorOverrideController) controller ).runtimeAnimatorController; + if( objectsToSearchSet.Contains( parentController ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( parentController ) ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new AnimationSystemMatch( controller, parentController, ( newValue ) => ( (AnimatorOverrideController) controller ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) ); + } + + if( searchParameters.searchRefactoring != null ) + { + List> overrideClips = new List>( ( (AnimatorOverrideController) controller ).overridesCount ); + ( (AnimatorOverrideController) controller ).GetOverrides( overrideClips ); + bool modifiedOverrideClips = false; + for( int i = overrideClips.Count - 1; i >= 0; i-- ) + { + if( objectsToSearchSet.Contains( overrideClips[i].Value ) ) + { + searchParameters.searchRefactoring( new AnimationSystemMatch( controller, overrideClips[i].Value, ( newValue ) => + { + overrideClips[i] = new KeyValuePair( overrideClips[i].Key, (AnimationClip) newValue ); + modifiedOverrideClips = true; + } ) ); + } + } + + if( modifiedOverrideClips ) + ( (AnimatorOverrideController) controller ).ApplyOverrides( overrideClips ); + } + } + + AnimationClip[] animClips = controller.animationClips; + for( int i = 0; i < animClips.Length; i++ ) + referenceNode.AddLinkTo( SearchObject( animClips[i] ) ); + } + + return referenceNode; + } + + private ReferenceNode SearchAnimatorStateMachine( object obj ) + { + AnimatorStateMachine animatorStateMachine = (AnimatorStateMachine) obj; + ReferenceNode referenceNode = PopReferenceNode( animatorStateMachine ); + + ChildAnimatorStateMachine[] stateMachines = animatorStateMachine.stateMachines; + for( int i = 0; i < stateMachines.Length; i++ ) + referenceNode.AddLinkTo( SearchObject( stateMachines[i].stateMachine ), "Child State Machine" ); + + ChildAnimatorState[] states = animatorStateMachine.states; + for( int i = 0; i < states.Length; i++ ) + referenceNode.AddLinkTo( SearchObject( states[i].state ) ); + + if( searchMonoBehavioursForScript ) + { + StateMachineBehaviour[] behaviours = animatorStateMachine.behaviours; + for( int i = 0; i < behaviours.Length; i++ ) + { + MonoScript script = MonoScript.FromScriptableObject( behaviours[i] ); + if( objectsToSearchSet.Contains( script ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( script ) ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorStateMachine, script, behaviours[i] ) ); + } + } + } + + return referenceNode; + } + + private ReferenceNode SearchAnimatorState( object obj ) + { + AnimatorState animatorState = (AnimatorState) obj; + ReferenceNode referenceNode = PopReferenceNode( animatorState ); + + referenceNode.AddLinkTo( SearchObject( animatorState.motion ), "Motion" ); + + if( searchParameters.searchRefactoring != null && animatorState.motion as AnimationClip && objectsToSearchSet.Contains( animatorState.motion ) ) + searchParameters.searchRefactoring( new AnimationSystemMatch( animatorState, animatorState.motion, ( newValue ) => animatorState.motion = (Motion) newValue ) ); + + if( searchMonoBehavioursForScript ) + { + StateMachineBehaviour[] behaviours = animatorState.behaviours; + for( int i = 0; i < behaviours.Length; i++ ) + { + MonoScript script = MonoScript.FromScriptableObject( behaviours[i] ); + if( objectsToSearchSet.Contains( script ) ) + { + referenceNode.AddLinkTo( GetReferenceNode( script ) ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorState, script, behaviours[i] ) ); + } + } + } + + return referenceNode; + } + + private ReferenceNode SearchAnimatorStateTransition( object obj ) + { + // Don't search AnimatorStateTransition objects, it will just return duplicate results of SearchAnimatorStateMachine + return PopReferenceNode( obj ); + } + + private ReferenceNode SearchBlendTree( object obj ) + { + BlendTree blendTree = (BlendTree) obj; + ReferenceNode referenceNode = PopReferenceNode( blendTree ); + + ChildMotion[] children = blendTree.children; + for( int i = 0; i < children.Length; i++ ) + { + referenceNode.AddLinkTo( SearchObject( children[i].motion ), "Motion" ); + + if( searchParameters.searchRefactoring != null && children[i].motion as AnimationClip && objectsToSearchSet.Contains( children[i].motion ) ) + searchParameters.searchRefactoring( new AnimationSystemMatch( blendTree, children[i].motion, ( newValue ) => children[i].motion = (Motion) newValue ) ); + } + + return referenceNode; + } + + private ReferenceNode SearchAnimationClip( object obj ) + { + AnimationClip clip = (AnimationClip) obj; + ReferenceNode referenceNode = PopReferenceNode( clip ); + + // Get all curves from animation clip + EditorCurveBinding[] objectCurves = AnimationUtility.GetObjectReferenceCurveBindings( clip ); + for( int i = 0; i < objectCurves.Length; i++ ) + { + // Search through all the keyframes in this curve + ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve( clip, objectCurves[i] ); + bool modifiedKeyframes = false; + for( int j = 0; j < keyframes.Length; j++ ) + { + referenceNode.AddLinkTo( SearchObject( keyframes[j].value ), "Keyframe: " + keyframes[j].time ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( keyframes[j].value ) ) + { + searchParameters.searchRefactoring( new AnimationSystemMatch( clip, keyframes[j].value, ( newValue ) => + { + keyframes[j].value = newValue; + modifiedKeyframes = true; + } ) ); + } + } + + if( modifiedKeyframes ) + AnimationUtility.SetObjectReferenceCurve( clip, objectCurves[i], keyframes ); + } + + // Get all events from animation clip + AnimationEvent[] events = AnimationUtility.GetAnimationEvents( clip ); + bool modifiedEvents = false; + for( int i = 0; i < events.Length; i++ ) + { + referenceNode.AddLinkTo( SearchObject( events[i].objectReferenceParameter ), "AnimationEvent: " + events[i].time ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( events[i].objectReferenceParameter ) ) + { + searchParameters.searchRefactoring( new AnimationSystemMatch( clip, events[i].objectReferenceParameter, ( newValue ) => + { + events[i].objectReferenceParameter = newValue; + modifiedEvents = true; + } ) ); + } + } + + if( modifiedEvents ) + AnimationUtility.SetAnimationEvents( clip, events ); + + return referenceNode; + } + + // TerrainData's properties like tree/detail/layer definitions aren't exposed to SerializedObject so use reflection instead + private ReferenceNode SearchTerrainData( object obj ) + { + ReferenceNode referenceNode = PopReferenceNode( obj ); + SearchVariablesWithReflection( referenceNode ); + return referenceNode; + } + + private ReferenceNode SearchLightmapSettings( object obj ) + { + ReferenceNode referenceNode = PopReferenceNode( obj ); + + referenceNode.AddLinkTo( SearchObject( LightmapSettings.lightProbes ), "Light Probes" ); + + LightmapData[] lightmaps = LightmapSettings.lightmaps; + if( lightmaps != null ) + { + for( int i = 0; i < lightmaps.Length; i++ ) + referenceNode.AddLinkTo( SearchObject( lightmaps[i] ), "Lightmap" ); + } + + SearchVariablesWithSerializedObject( referenceNode, true ); + return referenceNode; + } + + private ReferenceNode SearchRenderSettings( object obj ) + { + ReferenceNode referenceNode = PopReferenceNode( obj ); + +#if UNITY_2021_2_OR_NEWER + referenceNode.AddLinkTo( SearchObject( defaultReflectionProbeGetter() ), "Default Reflection Probe" ); +#else + referenceNode.AddLinkTo( SearchObject( ReflectionProbe.defaultTexture ), "Default Reflection Probe" ); +#endif + SearchVariablesWithSerializedObject( referenceNode, true ); + return referenceNode; + } + +#if UNITY_2017_1_OR_NEWER + private ReferenceNode SearchSpriteAtlas( object obj ) + { + SpriteAtlas spriteAtlas = (SpriteAtlas) obj; + ReferenceNode referenceNode = PopReferenceNode( spriteAtlas ); + + SerializedObject spriteAtlasSO = new SerializedObject( spriteAtlas ); + if( spriteAtlas.isVariant ) + { + SerializedProperty masterAtlasProperty = spriteAtlasSO.FindProperty( "m_MasterAtlas" ); + Object masterAtlas = masterAtlasProperty.objectReferenceValue; + if( objectsToSearchSet.Contains( masterAtlas ) ) + { + referenceNode.AddLinkTo( SearchObject( masterAtlas ), "Master Atlas" ); + + if( searchParameters.searchRefactoring != null ) + searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, masterAtlas, masterAtlasProperty ) ); + } + } + + SerializedProperty packables = spriteAtlasSO.FindProperty( "m_EditorData.packables" ); + if( packables != null ) + { + for( int i = 0, length = packables.arraySize; i < length; i++ ) + { + SerializedProperty packedSpriteProperty = packables.GetArrayElementAtIndex( i ); + Object packedSprite = packedSpriteProperty.objectReferenceValue; + SearchSpriteAtlas( referenceNode, packedSprite ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( packedSprite ) ) + searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, packedSprite, packedSpriteProperty ) ); + } + } +#if UNITY_2018_2_OR_NEWER + else + { + Object[] _packables = spriteAtlas.GetPackables(); + if( _packables != null ) + { + for( int i = 0; i < _packables.Length; i++ ) + SearchSpriteAtlas( referenceNode, _packables[i] ); + } + } +#endif + + return referenceNode; + } + + private void SearchSpriteAtlas( ReferenceNode referenceNode, Object packedAsset ) + { + if( packedAsset == null || packedAsset.Equals( null ) ) + return; + + referenceNode.AddLinkTo( SearchObject( packedAsset ), "Packed Texture" ); + + if( packedAsset is Texture ) + { + // Search the Texture's sprites if the Texture asset isn't included in the "SEARCHED OBJECTS" list (i.e. user has + // added only a Sprite sub-asset of the Texture to the list, not the Texture asset itself). Otherwise, references to + // both the Texture and its sprites will be found which can be considered as duplicate references + if( AssetDatabase.IsMainAsset( packedAsset ) && !assetsToSearchSet.Contains( packedAsset ) ) + { + Object[] textureSubAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( packedAsset ) ); + for( int i = 0; i < textureSubAssets.Length; i++ ) + { + if( textureSubAssets[i] is Sprite ) + referenceNode.AddLinkTo( SearchObject( textureSubAssets[i] ), "Packed Texture" ); + } + } + } + else if( packedAsset.IsFolder() ) + { + // Search all Sprites in the folder + string[] texturesInFolder = AssetDatabase.FindAssets( "t:Texture2D", new string[] { AssetDatabase.GetAssetPath( packedAsset ) } ); + if( texturesInFolder != null ) + { + for( int i = 0; i < texturesInFolder.Length; i++ ) + { + string texturePath = AssetDatabase.GUIDToAssetPath( texturesInFolder[i] ); + TextureImporter textureImporter = AssetImporter.GetAtPath( texturePath ) as TextureImporter; + if( textureImporter != null && textureImporter.textureType == TextureImporterType.Sprite ) + { + // Search the Texture and its sprites + SearchSpriteAtlas( referenceNode, AssetDatabase.LoadMainAssetAtPath( texturePath ) ); + } + } + } + } + } +#endif + +#if UNITY_2017_3_OR_NEWER + // Find references from an Assembly Definition File to its Assembly Definition References + private ReferenceNode SearchAssemblyDefinitionFile( object obj ) + { + if( assemblyDefinitionFilesToSearch.Count == 0 ) + return null; + + AssemblyDefinitionReferences assemblyDefinitionFile = JsonUtility.FromJson( ( (TextAsset) obj ).text ); + ReferenceNode referenceNode = PopReferenceNode( obj ); + + if( !string.IsNullOrEmpty( assemblyDefinitionFile.reference ) ) + { + if( assemblyDefinitionFile.references == null ) + assemblyDefinitionFile.references = new List( 1 ) { assemblyDefinitionFile.reference }; + else + assemblyDefinitionFile.references.Add( assemblyDefinitionFile.reference ); + } + + if( assemblyDefinitionFile.references != null ) + { + for( int i = 0; i < assemblyDefinitionFile.references.Count; i++ ) + { +#if UNITY_2019_1_OR_NEWER + string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyReference( assemblyDefinitionFile.references[i] ); +#else + string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName( assemblyDefinitionFile.references[i] ); +#endif + if( !string.IsNullOrEmpty( assemblyPath ) ) + { + Object searchedAssemblyDefinitionFile; + if( assemblyDefinitionFilesToSearch.TryGetValue( assemblyPath, out searchedAssemblyDefinitionFile ) ) + referenceNode.AddLinkTo( GetReferenceNode( searchedAssemblyDefinitionFile ), "Referenced Assembly" ); + } + } + } + + return referenceNode; + } +#endif + +#if UNITY_2018_1_OR_NEWER + // Searches Shader Graph assets for references + private ReferenceNode SearchShaderGraph( object obj ) + { + if( !searchTextureReferences && !searchShaderGraphsForSubGraphs && shaderIncludesToSearchSet.Count == 0 ) + return null; + + ReferenceNode referenceNode = PopReferenceNode( obj ); + + // Shader Graph assets are JSON files, they must be crawled manually to find references + string graphJson = File.ReadAllText( AssetDatabase.GetAssetPath( (Object) obj ) ); + if( graphJson.IndexOf( "\"m_ObjectId\"", 0, Mathf.Min( 200, graphJson.Length ) ) >= 0 ) + { + // New Shader Graph serialization format is used: https://github.com/Unity-Technologies/Graphics/pull/222 + // Iterate over all these occurrences: "guid\": \"GUID_VALUE\" (\" is used instead of " because it is a nested JSON) + IterateOverValuesInString( graphJson, new string[] { "\"guid\\\"" }, '"', ( guid ) => + { + if( guid.Length > 1 ) + { + if( guid[guid.Length - 1] == '\\' ) + guid = guid.Substring( 0, guid.Length - 1 ); + + string referencePath = AssetDatabase.GUIDToAssetPath( guid ); + if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) ) + { + Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath ); + if( objectsToSearchSet.Contains( reference ) ) + referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in graph" ); + } + } + } ); + + if( shaderIncludesToSearchSet.Count > 0 ) + { + // Iterate over all these occurrences: "m_FunctionSource": "GUID_VALUE" (this one is not nested JSON) + IterateOverValuesInString( graphJson, new string[] { "\"m_FunctionSource\"" }, '"', ( guid ) => + { + string referencePath = AssetDatabase.GUIDToAssetPath( guid ); + if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) ) + { + Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath ); + if( objectsToSearchSet.Contains( reference ) ) + referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in node: Custom Function" ); + } + } ); + } + } + else + { + // Old Shader Graph serialization format is used. Although we could use the same search method as the new serialization format (which + // is potentially faster), this alternative search method yields more information about references + ShaderGraphReferences shaderGraph = JsonUtility.FromJson( graphJson ); + + if( shaderGraph.m_SerializedProperties != null ) + { + for( int i = shaderGraph.m_SerializedProperties.Count - 1; i >= 0; i-- ) + { + string propertyJSON = shaderGraph.m_SerializedProperties[i].JSONnodeData; + if( string.IsNullOrEmpty( propertyJSON ) ) + continue; + + ShaderGraphReferences.PropertyData propertyData = JsonUtility.FromJson( propertyJSON ); + if( propertyData.m_Value == null ) + continue; + + string texturePath = propertyData.m_Value.GetTexturePath(); + if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) ) + continue; + + Texture texture = AssetDatabase.LoadAssetAtPath( texturePath ); + if( objectsToSearchSet.Contains( texture ) ) + referenceNode.AddLinkTo( GetReferenceNode( texture ), "Default Texture: " + propertyData.GetName() ); + } + } + + if( shaderGraph.m_SerializableNodes != null ) + { + for( int i = shaderGraph.m_SerializableNodes.Count - 1; i >= 0; i-- ) + { + string nodeJSON = shaderGraph.m_SerializableNodes[i].JSONnodeData; + if( string.IsNullOrEmpty( nodeJSON ) ) + continue; + + ShaderGraphReferences.NodeData nodeData = JsonUtility.FromJson( nodeJSON ); + if( !string.IsNullOrEmpty( nodeData.m_FunctionSource ) ) + { + string customFunctionPath = AssetDatabase.GUIDToAssetPath( nodeData.m_FunctionSource ); + if( !string.IsNullOrEmpty( customFunctionPath ) && assetsToSearchPathsSet.Contains( customFunctionPath ) ) + { + Object customFunction = AssetDatabase.LoadMainAssetAtPath( customFunctionPath ); + if( objectsToSearchSet.Contains( customFunction ) ) + referenceNode.AddLinkTo( GetReferenceNode( customFunction ), "Used in node: " + nodeData.m_Name ); + } + } + + if( searchShaderGraphsForSubGraphs ) + { + string subGraphPath = nodeData.GetSubGraphPath(); + if( !string.IsNullOrEmpty( subGraphPath ) && assetsToSearchPathsSet.Contains( subGraphPath ) ) + { + Object subGraph = AssetDatabase.LoadMainAssetAtPath( subGraphPath ); + if( objectsToSearchSet.Contains( subGraph ) ) + referenceNode.AddLinkTo( GetReferenceNode( subGraph ), "Used as Sub-graph" ); + } + } + + if( nodeData.m_SerializableSlots == null ) + continue; + + for( int j = nodeData.m_SerializableSlots.Count - 1; j >= 0; j-- ) + { + string nodeSlotJSON = nodeData.m_SerializableSlots[j].JSONnodeData; + if( string.IsNullOrEmpty( nodeSlotJSON ) ) + continue; + + string texturePath = JsonUtility.FromJson( nodeSlotJSON ).GetTexturePath(); + if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) ) + continue; + + Texture texture = AssetDatabase.LoadAssetAtPath( texturePath ); + if( objectsToSearchSet.Contains( texture ) ) + referenceNode.AddLinkTo( GetReferenceNode( texture ), "Used in node: " + nodeData.m_Name ); + } + } + } + } + + return referenceNode; + } +#endif + +#if ASSET_USAGE_VFX_GRAPH + private ReferenceNode SearchVFXGraphAsset( object obj ) + { + ReferenceNode referenceNode = PopReferenceNode( obj ); + + object vfxResource = vfxResourceGetter( AssetDatabase.GetAssetPath( (Object) obj ) ); + foreach( Object vfxResourceContent in (Object[]) vfxResourceContentsGetter.Invoke( vfxResource, null ) ) + referenceNode.AddLinkTo( SearchObject( vfxResourceContent ) ); + + return referenceNode; + } +#endif + + // Find references from an Animation/Animator component to the objects that it animates + private void SearchAnimatedObjects( ReferenceNode referenceNode ) + { + GameObject root = ( (Component) referenceNode.nodeObject ).gameObject; + AnimationClip[] clips = AnimationUtility.GetAnimationClips( root ); + for( int i = 0; i < clips.Length; i++ ) + { + AnimationClip clip = clips[i]; + if( !clip ) + continue; + + bool isClipUnique = true; + for( int j = i - 1; j >= 0; j-- ) + { + if( clips[j] == clip ) + { + isClipUnique = false; + break; + } + } + + if( !isClipUnique ) + continue; + + EditorCurveBinding[] uniqueBindings; + if( !animationClipUniqueBindings.TryGetValue( clip, out uniqueBindings ) ) + { + // Calculate all the "unique" paths that the animation clip's curves have + // Both float curves (GetCurveBindings) and object reference curves (GetObjectReferenceCurveBindings) are checked + List _uniqueBindings = new List( 2 ); + EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings( clip ); + for( int j = 0; j < bindings.Length; j++ ) + { + string bindingPath = bindings[j].path; + if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object + continue; + + bool isBindingUnique = true; + for( int k = _uniqueBindings.Count - 1; k >= 0; k-- ) + { + if( bindingPath == _uniqueBindings[k].path ) + { + isBindingUnique = false; + break; + } + } + + if( isBindingUnique ) + _uniqueBindings.Add( bindings[j] ); + } + + bindings = AnimationUtility.GetObjectReferenceCurveBindings( clip ); + for( int j = 0; j < bindings.Length; j++ ) + { + string bindingPath = bindings[j].path; + if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object + continue; + + bool isBindingUnique = true; + for( int k = _uniqueBindings.Count - 1; k >= 0; k-- ) + { + if( bindingPath == _uniqueBindings[k].path ) + { + isBindingUnique = false; + break; + } + } + + if( isBindingUnique ) + _uniqueBindings.Add( bindings[j] ); + } + + uniqueBindings = _uniqueBindings.ToArray(); + animationClipUniqueBindings[clip] = uniqueBindings; + } + + string clipName = clip.name; + for( int j = 0; j < uniqueBindings.Length; j++ ) + referenceNode.AddLinkTo( SearchObject( AnimationUtility.GetAnimatedObject( root, uniqueBindings[j] ) ), "Animated via clip: " + clipName ); + } + } + + // Search #include references in shader source code + private void SearchShaderSourceCodeForCGIncludes( ReferenceNode referenceNode ) + { + string shaderPath = AssetDatabase.GetAssetPath( (Object) referenceNode.nodeObject ); + + // Iterate over all these occurrences: #include "INCLUDE_REFERENCE" or #include_with_pragmas "INCLUDE_REFERENCE" + IterateOverValuesInString( File.ReadAllText( shaderPath ), new string[] { "#include ", "#include_with_pragmas " }, '"', ( include ) => + { + bool isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include ); + if( !isIncludePotentialReference ) + { + // Get absolute path of the #include + include = Path.GetFullPath( Path.Combine( Path.GetDirectoryName( shaderPath ), include ) ); + + int trimStartLength = Directory.GetCurrentDirectory().Length + 1; // Convert absolute path to a Project-relative path + if( include.Length > trimStartLength ) + { + include = include.Substring( trimStartLength ).Replace( '\\', '/' ); + isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include ); + } + } + + if( isIncludePotentialReference ) + { + Object cgShader = AssetDatabase.LoadMainAssetAtPath( include ); + if( objectsToSearchSet.Contains( cgShader ) ) + referenceNode.AddLinkTo( GetReferenceNode( cgShader ), "Used with #include" ); + } + } ); + } + + // Search through variables of an object with SerializedObject + private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode, bool forceUseSerializedObject = false ) + { + Object unityObject = (Object) referenceNode.nodeObject; + if( !isInPlayMode || unityObject.IsAsset() || forceUseSerializedObject ) + { +#if ASSET_USAGE_ADDRESSABLES + // See: https://github.com/yasirkula/UnityAssetUsageDetector/issues/29 + if( searchParameters.addressablesSupport && unityObject.name == "Deprecated EditorExtensionImpl" ) + return; +#endif + + SerializedObject so = new SerializedObject( unityObject ); + SerializedProperty iterator = so.GetIterator(); + SerializedProperty iteratorVisible = so.GetIterator(); + if( iterator.Next( true ) ) + { + bool iteratingVisible = iteratorVisible.NextVisible( true ); +#if UNITY_2018_3_OR_NEWER + bool searchPrefabOverridesOnly = searchParameters.hideReduntantPrefabVariantLinks && unityObject.IsAsset() && PrefabUtility.GetCorrespondingObjectFromSource( unityObject ) != null; +#endif + bool enterChildren; + do + { + // Iterate over NextVisible properties AND the properties that have corresponding FieldInfos (internal Unity + // properties don't have FieldInfos so we are skipping them, which is good because search results found in + // those properties aren't interesting and mostly confusing) + bool shouldMoveVisibleIterator = iteratingVisible && SerializedProperty.EqualContents( iterator, iteratorVisible ); + bool isVisible = shouldMoveVisibleIterator || iterator.type == "Array"; + if( !isVisible ) + { + Type propFieldType; + isVisible = fieldInfoGetter( iterator, out propFieldType ) != null; + } + + if( !isVisible ) + enterChildren = false; +#if UNITY_2018_3_OR_NEWER + else if( searchPrefabOverridesOnly && !iterator.prefabOverride ) + enterChildren = false; +#endif + else + { + Object propertyValue; + ReferenceNode searchResult; + switch( iterator.propertyType ) + { + case SerializedPropertyType.ObjectReference: + propertyValue = iterator.objectReferenceValue; + searchResult = SearchObject( PreferablyGameObject( propertyValue ) ); + enterChildren = false; + break; + case SerializedPropertyType.ExposedReference: + propertyValue = iterator.exposedReferenceValue; + searchResult = SearchObject( PreferablyGameObject( propertyValue ) ); + enterChildren = false; + break; +#if UNITY_2019_3_OR_NEWER + case SerializedPropertyType.ManagedReference: + object managedReferenceValue = GetRawSerializedPropertyValue( iterator ); + propertyValue = managedReferenceValue as Object; + searchResult = SearchObject( PreferablyGameObject( managedReferenceValue ) ); + enterChildren = false; + break; +#endif + case SerializedPropertyType.Generic: +#if ASSET_USAGE_ADDRESSABLES + if( searchParameters.addressablesSupport && iterator.type.StartsWithFast( "AssetReference" ) && GetRawSerializedPropertyValue( iterator ) is AssetReference assetReference ) + { + propertyValue = GetAddressablesAssetReferenceValue( assetReference ); + searchResult = SearchObject( PreferablyGameObject( propertyValue ) ); + enterChildren = false; + } + else +#endif +#if ASSET_USAGE_VFX_GRAPH + if( vfxSerializableObjectValueGetter != null && iterator.type == "VFXSerializableObject" && GetRawSerializedPropertyValue( iterator ) is object vfxSerializableObject ) + { + object vfxSerializableObjectValue = vfxSerializableObjectValueGetter.Invoke( vfxSerializableObject, null ); + propertyValue = vfxSerializableObjectValue as Object; + searchResult = SearchObject( PreferablyGameObject( vfxSerializableObjectValue ) ); + enterChildren = false; + } + else +#endif + { + propertyValue = null; + searchResult = null; + enterChildren = true; + } + + break; + default: + propertyValue = null; + searchResult = null; + enterChildren = false; + break; + } + + if( searchResult != null && searchResult != referenceNode ) + { + string propertyPath = iterator.propertyPath; + + // m_RD.texture is a redundant reference that shows up when searching sprites + if( !propertyPath.EndsWithFast( "m_RD.texture" ) ) + { + referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath.Replace( ".Array.data[", "[" ) ); // "arrayVariable.Array.data[0]" becomes "arrayVariable[0]" + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( propertyValue ) ) + searchParameters.searchRefactoring( new SerializedPropertyMatch( unityObject, propertyValue, iterator ) ); + } + } + } + + if( shouldMoveVisibleIterator ) + iteratingVisible = iteratorVisible.NextVisible( enterChildren ); + } while( iterator.Next( enterChildren ) ); + + return; + } + } + + // Use reflection algorithm as fallback + SearchVariablesWithReflection( referenceNode ); + } + + // Search through variables of an object with reflection + private void SearchVariablesWithReflection( ReferenceNode referenceNode ) + { + // Get filtered variables for this object + VariableGetterHolder[] variables = GetFilteredVariablesForType( referenceNode.nodeObject.GetType() ); + for( int i = 0; i < variables.Length; i++ ) + { + // When possible, don't search non-serializable variables + if( searchSerializableVariablesOnly && !variables[i].isSerializable ) + continue; + + try + { + object variableValue = variables[i].Get( referenceNode.nodeObject ); + if( variableValue == null || variableValue.Equals( null ) ) + continue; + + // Values stored inside ICollection objects are searched using IEnumerable, + // no need to have duplicate search entries + if( !( variableValue is ICollection ) ) + { +#if ASSET_USAGE_ADDRESSABLES + if( searchParameters.addressablesSupport && variableValue is AssetReference ) + { + variableValue = GetAddressablesAssetReferenceValue( (AssetReference) variableValue ); + if( variableValue == null || variableValue.Equals( null ) ) + continue; + } +#endif + + ReferenceNode searchResult = SearchObject( PreferablyGameObject( variableValue ) ); + if( searchResult != null && searchResult != referenceNode ) + { + referenceNode.AddLinkTo( searchResult, ( variables[i].IsProperty ? "Property: " : "Variable: " ) + variables[i].Name ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( variableValue as Object ) ) + searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, (Object) variableValue, variables[i].variable ) ); + } + } + + if( variableValue is IEnumerable && !( variableValue is Transform ) ) + { + // If the field is IEnumerable (possibly an array or collection), search through members of it + // Note that Transform IEnumerable (children of the transform) is not iterated + int index = 0; + List foundReferences = null; + foreach( object element in (IEnumerable) variableValue ) + { + ReferenceNode searchResult = SearchObject( PreferablyGameObject( element ) ); + if( searchResult != null && searchResult != referenceNode ) + { + referenceNode.AddLinkTo( searchResult, string.Concat( variables[i].IsProperty ? "Property: " : "Variable: ", variables[i].Name, "[", index + "]" ) ); + + if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( element as Object ) ) + { + if( foundReferences == null ) + foundReferences = new List( 2 ) { (Object) element }; + else if( !foundReferences.Contains( (Object) element ) ) + foundReferences.Add( (Object) element ); + } + } + + index++; + } + + if( foundReferences != null ) + { + for( int j = foundReferences.Count - 1; j >= 0; j-- ) + searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, foundReferences[j], variableValue ) ); + } + } + } + catch( UnassignedReferenceException ) { } + catch( MissingReferenceException ) { } + catch( MissingComponentException ) { } + catch( NotImplementedException ) { } + catch( Exception e ) + { + // Unknown exceptions usually occur when variableValue is an IEnumerable and its enumerator throws an unhandled exception in MoveNext or Current + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + sb.EnsureCapacity( callStack.Count * 50 + 1000 ); + + sb.Append( "Skipped searching " ).Append( referenceNode.nodeObject.GetType().FullName ).Append( "." ).Append( variables[i].Name ).AppendLine( " because it threw exception:" ).Append( e ).AppendLine(); + + Object latestUnityObjectInCallStack = AppendCallStackToStringBuilder( sb ); + Debug.LogWarning( sb.ToString(), latestUnityObjectInCallStack ); + } + } + } + + // Get filtered variables for a type + private VariableGetterHolder[] GetFilteredVariablesForType( Type type ) + { + VariableGetterHolder[] result; + if( typeToVariables.TryGetValue( type, out result ) ) + return result; + + // This is the first time this type of object is seen, filter and cache its variables + // Variable filtering process: + // 1- skip Obsolete variables + // 2- skip primitive types, enums and strings + // 3- skip common Unity types that can't hold any references (e.g. Vector3, Rect, Color, Quaternion) + // + // P.S. IsIgnoredUnityType() extension function handles steps 2) and 3) + + validVariables.Clear(); + + // Filter the fields + if( fieldModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) ) + { + Type currType = type; + while( currType != typeof( object ) ) + { + FieldInfo[] fields = currType.GetFields( fieldModifiers ); + for( int i = 0; i < fields.Length; i++ ) + { + FieldInfo field = fields[i]; + + // Skip obsolete fields + if( Attribute.IsDefined( field, typeof( ObsoleteAttribute ) ) ) + continue; + + // Skip primitive types + if( field.FieldType.IsIgnoredUnityType() ) + continue; + +#if UNITY_2021_2_OR_NEWER + // "ref struct"s can't be accessed via reflection + if( field.FieldType.IsByRefLike ) + continue; +#endif + + // Additional filtering for fields: + // 1- Ignore "m_RectTransform", "m_CanvasRenderer" and "m_Canvas" fields of Graphic components + string fieldName = field.Name; + if( typeof( Graphic ).IsAssignableFrom( currType ) && + ( fieldName == "m_RectTransform" || fieldName == "m_CanvasRenderer" || fieldName == "m_Canvas" ) ) + continue; + + VariableGetVal getter = field.CreateGetter( type ); + if( getter != null ) + validVariables.Add( new VariableGetterHolder( field, getter, searchSerializableVariablesOnly ? field.IsSerializable() : true ) ); + } + + currType = currType.BaseType; + } + } + + if( propertyModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) ) + { + Type currType = type; + while( currType != typeof( object ) ) + { + PropertyInfo[] properties = currType.GetProperties( propertyModifiers ); + for( int i = 0; i < properties.Length; i++ ) + { + PropertyInfo property = properties[i]; + + // Skip obsolete properties + if( Attribute.IsDefined( property, typeof( ObsoleteAttribute ) ) ) + continue; + + // Skip primitive types + if( property.PropertyType.IsIgnoredUnityType() ) + continue; + +#if UNITY_2021_2_OR_NEWER + // "ref struct"s can't be accessed via reflection + if( property.PropertyType.IsByRefLike ) + continue; +#endif + + // Skip properties without a getter function + MethodInfo propertyGetter = property.GetGetMethod( true ); + if( propertyGetter == null ) + continue; + + // Skip indexer properties + if( property.GetIndexParameters().Length > 0 ) + continue; + + // No need to check properties with 'override' keyword + if( propertyGetter.GetBaseDefinition().DeclaringType != propertyGetter.DeclaringType ) + continue; + + string propertyName = property.Name; + + // Ignore "gameObject", "transform", "rectTransform" and "attachedRigidbody" properties of components to get more useful results + if( typeof( Component ).IsAssignableFrom( currType ) && ( propertyName == "gameObject" || + propertyName == "transform" || propertyName == "attachedRigidbody" || propertyName == "rectTransform" ) ) + continue; + // Ignore "canvasRenderer" and "canvas" properties of Graphic components to get more useful results + else if( typeof( Graphic ).IsAssignableFrom( currType ) && + ( propertyName == "canvasRenderer" || propertyName == "canvas" ) ) + continue; + // Prevent accessing properties of Unity that instantiate an existing resource (causing memory leak) + else if( typeof( MeshFilter ).IsAssignableFrom( currType ) && propertyName == "mesh" ) + continue; + // Same as above + else if( ( propertyName == "material" || propertyName == "materials" ) && + ( typeof( Renderer ).IsAssignableFrom( currType ) || typeof( Collider ).IsAssignableFrom( currType ) || +#if !UNITY_2019_3_OR_NEWER +#pragma warning disable 0618 + typeof( GUIText ).IsAssignableFrom( currType ) || +#pragma warning restore 0618 +#endif + typeof( Collider2D ).IsAssignableFrom( currType ) ) ) + continue; + // Ignore certain Material properties that are already searched via SearchMaterial function (also, if a material doesn't have a _Color or _BaseColor + // property and its "color" property is called, it logs an error to the console, so this rule helps avoid that scenario, as well) + else if( ( propertyName == "color" || propertyName == "mainTexture" ) && typeof( Material ).IsAssignableFrom( currType ) ) + continue; + // Ignore "parameters" property of Animator since it doesn't contain any useful data and logs a warning to the console when Animator is inactive + else if( typeof( Animator ).IsAssignableFrom( currType ) && propertyName == "parameters" ) + continue; + // Ignore "spriteAnimator" property of TMP_Text component because this property adds a TMP_SpriteAnimator component to the object if it doesn't exist + else if( propertyName == "spriteAnimator" && currType.Name == "TMP_Text" ) + continue; + // Ignore "meshFilter" property of TextMeshPro and TMP_SubMesh components because this property adds a MeshFilter component to the object if it doesn't exist + else if( propertyName == "meshFilter" && ( currType.Name == "TextMeshPro" || currType.Name == "TMP_SubMesh" ) ) + continue; + // Ignore "users" property of TerrainData because it returns the Terrains in the scene that use that TerrainData. This causes issues with callStack because TerrainData + // is already in callStack when Terrains are searched via "users" property of it and hence, Terrain->TerrainData references for that TerrainData can't be found in scenes + // (this is how callStack works, it prevents searching an object if it's already in callStack to avoid infinite recursion) + else if( propertyName == "users" && typeof( TerrainData ).IsAssignableFrom( currType ) ) + continue; + else + { + VariableGetVal getter = property.CreateGetter(); + if( getter != null ) + validVariables.Add( new VariableGetterHolder( property, getter, searchSerializableVariablesOnly ? property.IsSerializable() : true ) ); + } + } + + currType = currType.BaseType; + } + } + + result = validVariables.ToArray(); + + // Cache the filtered fields + typeToVariables.Add( type, result ); + + return result; + } + + // Credit: http://answers.unity.com/answers/425602/view.html + // Returns the raw System.Object value of a SerializedProperty + private object GetRawSerializedPropertyValue( SerializedProperty property ) + { + object result = property.serializedObject.targetObject; + string[] path = property.propertyPath.Replace( ".Array.data[", "[" ).Split( '.' ); + for( int i = 0; i < path.Length; i++ ) + { + string pathElement = path[i]; + + int arrayStartIndex = pathElement.IndexOf( '[' ); + if( arrayStartIndex < 0 ) + result = GetFieldValue( result, pathElement ); + else + { + string variableName = pathElement.Substring( 0, arrayStartIndex ); + + int arrayEndIndex = pathElement.IndexOf( ']', arrayStartIndex + 1 ); + int arrayElementIndex = int.Parse( pathElement.Substring( arrayStartIndex + 1, arrayEndIndex - arrayStartIndex - 1 ) ); + result = GetFieldValue( result, variableName, arrayElementIndex ); + } + } + + return result; + } + + // Credit: http://answers.unity.com/answers/425602/view.html + private object GetFieldValue( object source, string fieldName ) + { + if( source == null ) + return null; + + FieldInfo fieldInfo = null; + Type type = source.GetType(); + while( fieldInfo == null && type != typeof( object ) ) + { + fieldInfo = type.GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly ); + type = type.BaseType; + } + + if( fieldInfo != null ) + return fieldInfo.GetValue( source ); + + PropertyInfo propertyInfo = null; + type = source.GetType(); + while( propertyInfo == null && type != typeof( object ) ) + { + propertyInfo = type.GetProperty( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase ); + type = type.BaseType; + } + + if( propertyInfo != null ) + return propertyInfo.GetValue( source, null ); + + if( fieldName.Length > 2 && fieldName.StartsWith( "m_", StringComparison.OrdinalIgnoreCase ) ) + return GetFieldValue( source, fieldName.Substring( 2 ) ); + + return null; + } + + // Credit: http://answers.unity.com/answers/425602/view.html + private object GetFieldValue( object source, string fieldName, int arrayIndex ) + { + IEnumerable enumerable = GetFieldValue( source, fieldName ) as IEnumerable; + if( enumerable == null ) + return null; + + if( enumerable is IList ) + return ( (IList) enumerable )[arrayIndex]; + + IEnumerator enumerator = enumerable.GetEnumerator(); + for( int i = 0; i <= arrayIndex; i++ ) + enumerator.MoveNext(); + + return enumerator.Current; + } + +#if ASSET_USAGE_ADDRESSABLES + private Object GetAddressablesAssetReferenceValue( AssetReference assetReference ) + { + Object result = assetReference.editorAsset; + if( !result ) + return null; + + string subObjectName = assetReference.SubObjectName; + if( !string.IsNullOrEmpty( subObjectName ) ) + { + if( result is SpriteAtlas ) + { + Sprite[] packedSprites = spriteAtlasPackedSpritesGetter( (SpriteAtlas) result ); + if( packedSprites != null ) + { + for( int i = 0; i < packedSprites.Length; i++ ) + { + if( packedSprites[i] && packedSprites[i].name == subObjectName ) + return packedSprites[i]; + } + } + } + else + { + Type subObjectType = (Type) assetReferenceSubObjectTypeGetter.GetValue( assetReference, null ) ?? typeof( Object ); + Object[] subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( result ) ); + for( int k = 0; k < subAssets.Length; k++ ) + { + if( subAssets[k] && subAssets[k].name == subObjectName && subObjectType.IsAssignableFrom( subAssets[k].GetType() ) ) + return subAssets[k]; + } + } + } + + return result; + } +#endif + + // Iterates over all occurrences of specific key-value pairs in string + // Example1: #include "VALUE" valuePrefix=#include, valueWrapperChar=" + // Example2: "guid": "VALUE" valuePrefix="guid", valueWrapperChar=" + private void IterateOverValuesInString( string str, string[] valuePrefixes, char valueWrapperChar, Action valueAction ) + { + for( int i = 0; i < valuePrefixes.Length; i++ ) + { + string valuePrefix = valuePrefixes[i]; + int valueStartIndex, valueEndIndex = 0; + while( true ) + { + valueStartIndex = str.IndexOf( valuePrefix, valueEndIndex ); + if( valueStartIndex < 0 ) + break; + + valueStartIndex = str.IndexOf( valueWrapperChar, valueStartIndex + valuePrefix.Length ); + if( valueStartIndex < 0 ) + break; + + valueStartIndex++; + valueEndIndex = str.IndexOf( valueWrapperChar, valueStartIndex ); + if( valueEndIndex < 0 ) + break; + + if( valueEndIndex > valueStartIndex ) + valueAction( str.Substring( valueStartIndex, valueEndIndex - valueStartIndex ) ); + } + } + } + + // If obj is Component, switches to its GameObject + private object PreferablyGameObject( object obj ) + { + Component component = obj as Component; + return ( component != null && !component.Equals( null ) ) ? component.gameObject : obj; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs.meta new file mode 100644 index 00000000..fc1cd02d --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93aaae685d4c3db44baeb91a0296855e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs new file mode 100644 index 00000000..6dc943af --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs @@ -0,0 +1,298 @@ +using UnityEditor; +using UnityEngine; + +namespace AssetUsageDetectorNamespace +{ + public static class AssetUsageDetectorSettings + { + private static readonly GUILayoutOption GL_WIDTH_60 = GUILayout.Width( 60f ); + + #region Colors + private static Color? m_settingsHeaderColor = null; + public static Color SettingsHeaderColor + { + get { if( m_settingsHeaderColor == null ) m_settingsHeaderColor = GetColor( "AUD_SettingsHeaderTint", Color.cyan ); return m_settingsHeaderColor.Value; } + set { if( m_settingsHeaderColor == value ) return; m_settingsHeaderColor = value; SetColor( "AUD_SettingsHeaderTint", value ); } + } + + private static Color? m_searchResultGroupHeaderColor = null; + public static Color SearchResultGroupHeaderColor + { + get { if( m_searchResultGroupHeaderColor == null ) m_searchResultGroupHeaderColor = GetColor( "AUD_ResultGroupHeaderTint", Color.cyan ); return m_searchResultGroupHeaderColor.Value; } + set { if( m_searchResultGroupHeaderColor == value ) return; m_searchResultGroupHeaderColor = value; SetColor( "AUD_ResultGroupHeaderTint", value ); } + } + + private static Color? m_rootRowsBackgroundColor = null; + public static Color RootRowsBackgroundColor + { + get { if( m_rootRowsBackgroundColor == null ) m_rootRowsBackgroundColor = GetColor( "AUD_RootRowsTint", EditorGUIUtility.isProSkin ? new Color( 0f, 1f, 1f, 0.15f ) : new Color( 0f, 1f, 1f, 0.25f ) ); return m_rootRowsBackgroundColor.Value; } + set { if( m_rootRowsBackgroundColor == value ) return; m_rootRowsBackgroundColor = value; SetColor( "AUD_RootRowsTint", value ); } + } + + private static Color? m_rootRowsBorderColor = null; + public static Color RootRowsBorderColor + { + get { if( m_rootRowsBorderColor == null ) m_rootRowsBorderColor = GetColor( "AUD_RootRowsBorderColor", EditorGUIUtility.isProSkin ? new Color( 0.15f, 0.15f, 0.15f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); return m_rootRowsBorderColor.Value; } + set { if( m_rootRowsBorderColor == value ) return; m_rootRowsBorderColor = value; SetColor( "AUD_RootRowsBorderColor", value ); } + } + + private static Color? m_mainReferencesBackgroundColor = null; + public static Color MainReferencesBackgroundColor + { + get { if( m_mainReferencesBackgroundColor == null ) m_mainReferencesBackgroundColor = GetColor( "AUD_MainRefRowsTint", EditorGUIUtility.isProSkin ? new Color( 0f, 0.35f, 0f, 1f ) : new Color( 0.25f, 0.75f, 0.25f, 1f ) ); return m_mainReferencesBackgroundColor.Value; } + set { if( m_mainReferencesBackgroundColor == value ) return; m_mainReferencesBackgroundColor = value; SetColor( "AUD_MainRefRowsTint", value ); } + } + + private static Color? m_selectedRowsParentTint = null; + public static Color SelectedRowParentsTint + { + get { if( m_selectedRowsParentTint == null ) m_selectedRowsParentTint = GetColor( "AUD_SelectedRowParentsTint", EditorGUIUtility.isProSkin ? new Color( 0.36f, 0.36f, 0.18f, 1f ) : new Color( 0.825f, 0.825f, 0.55f, 1f ) ); return m_selectedRowsParentTint.Value; } + set { if( m_selectedRowsParentTint == value ) return; m_selectedRowsParentTint = value; SetColor( "AUD_SelectedRowParentsTint", value ); } + } + + private static Color? m_selectedRowOccurrencesColor = null; + public static Color SelectedRowOccurrencesColor + { + get { if( m_selectedRowOccurrencesColor == null ) m_selectedRowOccurrencesColor = GetColor( "AUD_SelectedRowOccurrencesTint", EditorGUIUtility.isProSkin ? new Color( 0f, 0.3f, 0.75f, 1f ) : new Color( 0.25f, 0.75f, 1f, 1f ) ); return m_selectedRowOccurrencesColor.Value; } + set { if( m_selectedRowOccurrencesColor == value ) return; m_selectedRowOccurrencesColor = value; SetColor( "AUD_SelectedRowOccurrencesTint", value ); } + } + + private static Color? m_treeLinesColor = null; + public static Color TreeLinesColor + { + get { if( m_treeLinesColor == null ) m_treeLinesColor = GetColor( "AUD_TreeLinesColor", EditorGUIUtility.isProSkin ? new Color( 0.65f, 0.65f, 0.65f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); return m_treeLinesColor.Value; } + set { if( m_treeLinesColor == value ) return; m_treeLinesColor = value; SetColor( "AUD_TreeLinesColor", value ); } + } + + private static Color? m_highlightedTreeLinesColor = null; + public static Color HighlightedTreeLinesColor + { + get { if( m_highlightedTreeLinesColor == null ) m_highlightedTreeLinesColor = GetColor( "AUD_HighlightTreeLinesColor", Color.cyan ); return m_highlightedTreeLinesColor.Value; } + set { if( m_highlightedTreeLinesColor == value ) return; m_highlightedTreeLinesColor = value; SetColor( "AUD_HighlightTreeLinesColor", value ); } + } + + private static Color? m_searchMatchingTextColor = null; + public static Color SearchMatchingTextColor + { + get { if( m_searchMatchingTextColor == null ) m_searchMatchingTextColor = GetColor( "AUD_SearchTextColor", Color.red ); return m_searchMatchingTextColor.Value; } + set { if( m_searchMatchingTextColor == value ) return; m_searchMatchingTextColor = value; SetColor( "AUD_SearchTextColor", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged( highlightedSearchTextColorChanged: true ) ); } + } + + private static Color? m_tooltipDescriptionTextColor = null; + public static Color TooltipDescriptionTextColor + { + get { if( m_tooltipDescriptionTextColor == null ) m_tooltipDescriptionTextColor = GetColor( "AUD_TooltipUsageTextColor", EditorGUIUtility.isProSkin ? new Color( 0f, 0.9f, 0.9f, 1f ) : new Color( 0.9f, 0f, 0f, 1f ) ); return m_tooltipDescriptionTextColor.Value; } + set { if( m_tooltipDescriptionTextColor == value ) return; m_tooltipDescriptionTextColor = value; SetColor( "AUD_TooltipUsageTextColor", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged( tooltipDescriptionsColorChanged: true ) ); } + } + #endregion + + #region Size Adjustments + private static float? m_extraRowHeight = null; + public static float ExtraRowHeight + { + get { if( m_extraRowHeight == null ) m_extraRowHeight = EditorPrefs.GetFloat( "AUD_ExtraRowHeight", 0f ); return m_extraRowHeight.Value; } + set { if( m_extraRowHeight == value ) return; m_extraRowHeight = value; EditorPrefs.SetFloat( "AUD_ExtraRowHeight", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged() ); } + } + #endregion + + #region Other Settings + private static bool? m_showRootAssetName = null; + public static bool ShowRootAssetName + { + get { if( m_showRootAssetName == null ) m_showRootAssetName = EditorPrefs.GetBool( "AUD_ShowRootAssetName", true ); return m_showRootAssetName.Value; } + set { if( m_showRootAssetName == value ) return; m_showRootAssetName = value; EditorPrefs.SetBool( "AUD_ShowRootAssetName", value ); } + } + + private static bool? m_pingClickedObjects = null; + public static bool PingClickedObjects + { + get { if( m_pingClickedObjects == null ) m_pingClickedObjects = EditorPrefs.GetBool( "AUD_PingClickedObj", true ); return m_pingClickedObjects.Value; } + set { if( m_pingClickedObjects == value ) return; m_pingClickedObjects = value; EditorPrefs.SetBool( "AUD_PingClickedObj", value ); } + } + + private static bool? m_selectClickedObjects = null; + public static bool SelectClickedObjects + { + get { if( m_selectClickedObjects == null ) m_selectClickedObjects = EditorPrefs.GetBool( "AUD_SelectClickedObj", false ); return m_selectClickedObjects.Value; } + set { if( m_selectClickedObjects == value ) return; m_selectClickedObjects = value; EditorPrefs.SetBool( "AUD_SelectClickedObj", value ); } + } + + private static bool? m_selectDoubleClickedObjects = null; + public static bool SelectDoubleClickedObjects + { + get { if( m_selectDoubleClickedObjects == null ) m_selectDoubleClickedObjects = EditorPrefs.GetBool( "AUD_SelectDoubleClickedObj", true ); return m_selectDoubleClickedObjects.Value; } + set { if( m_selectDoubleClickedObjects == value ) return; m_selectDoubleClickedObjects = value; EditorPrefs.SetBool( "AUD_SelectDoubleClickedObj", value ); } + } + + private static bool? m_markUsedAssetsSubAssetsAsUsed = null; + public static bool MarkUsedAssetsSubAssetsAsUsed + { + get { if( m_markUsedAssetsSubAssetsAsUsed == null ) m_markUsedAssetsSubAssetsAsUsed = EditorPrefs.GetBool( "AUD_MarkUsedAssetsSubAssetsAsUsed", true ); return m_markUsedAssetsSubAssetsAsUsed.Value; } + set { if( m_markUsedAssetsSubAssetsAsUsed == value ) return; m_markUsedAssetsSubAssetsAsUsed = value; EditorPrefs.SetBool( "AUD_MarkUsedAssetsSubAssetsAsUsed", value ); } + } + + private static bool? m_showUnityTooltip = null; + public static bool ShowUnityTooltip + { + get { if( m_showUnityTooltip == null ) m_showUnityTooltip = EditorPrefs.GetBool( "AUD_ShowUnityTooltip", false ); return m_showUnityTooltip.Value; } + set { if( m_showUnityTooltip == value ) return; m_showUnityTooltip = value; EditorPrefs.SetBool( "AUD_ShowUnityTooltip", value ); } + } + + private static bool? m_showCustomTooltip = null; + public static bool ShowCustomTooltip + { + get { if( m_showCustomTooltip == null ) m_showCustomTooltip = EditorPrefs.GetBool( "AUD_ShowCustomTooltip", true ); return m_showCustomTooltip.Value; } + set { if( m_showCustomTooltip == value ) return; m_showCustomTooltip = value; EditorPrefs.SetBool( "AUD_ShowCustomTooltip", value ); ForEachAssetUsageDetectorWindow( ( window ) => window.OnSettingsChanged() ); } + } + + private static float? m_customTooltipDelay = null; + public static float CustomTooltipDelay + { + get { if( m_customTooltipDelay == null ) m_customTooltipDelay = EditorPrefs.GetFloat( "AUD_CustomTooltipDelay", 0.7f ); return m_customTooltipDelay.Value; } + set { if( m_customTooltipDelay == value ) return; m_customTooltipDelay = value; EditorPrefs.SetFloat( "AUD_CustomTooltipDelay", value ); } + } + + private static bool? m_showTreeLines = null; + public static bool ShowTreeLines + { + get { if( m_showTreeLines == null ) m_showTreeLines = EditorPrefs.GetBool( "AUD_ShowTreeLines", true ); return m_showTreeLines.Value; } + set { if( m_showTreeLines == value ) return; m_showTreeLines = value; EditorPrefs.SetBool( "AUD_ShowTreeLines", value ); } + } + + private static bool? m_applySelectedRowParentsTintToRootRows = null; + public static bool ApplySelectedRowParentsTintToRootRows + { + get { if( m_applySelectedRowParentsTintToRootRows == null ) m_applySelectedRowParentsTintToRootRows = EditorPrefs.GetBool( "AUD_SelectedRowParentsTintAtRoot", true ); return m_applySelectedRowParentsTintToRootRows.Value; } + set { if( m_applySelectedRowParentsTintToRootRows == value ) return; m_applySelectedRowParentsTintToRootRows = value; EditorPrefs.SetBool( "AUD_SelectedRowParentsTintAtRoot", value ); } + } + #endregion + +#if UNITY_2018_3_OR_NEWER + [SettingsProvider] + public static SettingsProvider CreatePreferencesGUI() + { + return new SettingsProvider( "Project/yasirkula/Asset Usage Detector", SettingsScope.Project ) + { + guiHandler = ( searchContext ) => PreferencesGUI(), + keywords = new System.Collections.Generic.HashSet() { "Asset", "Usage", "Detector" } + }; + } +#endif + +#if !UNITY_2018_3_OR_NEWER + [PreferenceItem( "Asset Usage Detector" )] +#endif + public static void PreferencesGUI() + { + float labelWidth = EditorGUIUtility.labelWidth; +#if UNITY_2018_3_OR_NEWER + EditorGUIUtility.labelWidth += 60f; +#else + EditorGUIUtility.labelWidth += 20f; +#endif + + EditorGUI.BeginChangeCheck(); + + ShowRootAssetName = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Root Asset's Name For Sub-Assets (Requires Refresh)", ShowRootAssetName ); + + EditorGUILayout.Space(); + + PingClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Ping Clicked Objects", PingClickedObjects ); + SelectClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Select Clicked Objects", SelectClickedObjects ); + SelectDoubleClickedObjects = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Select Double Clicked Objects", SelectDoubleClickedObjects ); + + EditorGUILayout.Space(); + + MarkUsedAssetsSubAssetsAsUsed = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Hide unused sub-assets in \"Unused Objects\" list if their parent assets are used (Requires Refresh)", MarkUsedAssetsSubAssetsAsUsed ); + + EditorGUILayout.Space(); + + ShowUnityTooltip = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Unity Tooltip", ShowUnityTooltip ); + ShowCustomTooltip = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Custom Tooltip", ShowCustomTooltip ); + EditorGUI.indentLevel++; + CustomTooltipDelay = FloatField( "Delay", CustomTooltipDelay, 0.7f ); + EditorGUI.indentLevel--; + TooltipDescriptionTextColor = ColorField( "Tooltip Descriptions Text Color", TooltipDescriptionTextColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.9f, 0.9f, 1f ) : new Color( 0.9f, 0f, 0f, 1f ) ); + + EditorGUILayout.Space(); + + ExtraRowHeight = Mathf.Max( 0f, FloatField( "Extra Row Height", ExtraRowHeight, 0f ) ); + + EditorGUILayout.Space(); + + SettingsHeaderColor = ColorField( "Settings Header Color", SettingsHeaderColor, Color.cyan ); + SearchResultGroupHeaderColor = ColorField( "Group Header Color", SearchResultGroupHeaderColor, Color.cyan ); + RootRowsBackgroundColor = ColorField( "Root Rows Background Color", RootRowsBackgroundColor, EditorGUIUtility.isProSkin ? new Color( 0f, 1f, 1f, 0.15f ) : new Color( 0f, 1f, 1f, 0.25f ) ); + RootRowsBorderColor = ColorField( "Root Rows Border Color", RootRowsBorderColor, EditorGUIUtility.isProSkin ? new Color( 0.15f, 0.15f, 0.15f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); + MainReferencesBackgroundColor = ColorField( "Main References Background Color", MainReferencesBackgroundColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.35f, 0f, 1f ) : new Color( 0.25f, 0.75f, 0.25f, 1f ) ); + SelectedRowParentsTint = ColorField( "Selected Row Parents Tint", SelectedRowParentsTint, EditorGUIUtility.isProSkin ? new Color( 0.36f, 0.36f, 0.18f, 1f ) : new Color( 0.825f, 0.825f, 0.55f, 1f ) ); + EditorGUI.indentLevel++; + ApplySelectedRowParentsTintToRootRows = !EditorGUILayout.Toggle( "Ignore Root Rows", !ApplySelectedRowParentsTintToRootRows ); + EditorGUI.indentLevel--; + SelectedRowOccurrencesColor = ColorField( "Selected Row All Occurrences Tint", SelectedRowOccurrencesColor, EditorGUIUtility.isProSkin ? new Color( 0f, 0.3f, 0.75f, 1f ) : new Color( 0.25f, 0.75f, 1f, 1f ) ); + SearchMatchingTextColor = ColorField( "Matching Search Text Color", SearchMatchingTextColor, Color.red ); + + ShowTreeLines = AssetUsageDetectorWindow.WordWrappingToggleLeft( "Show Tree Lines", ShowTreeLines ); + EditorGUI.indentLevel++; + TreeLinesColor = ColorField( "Normal Color", TreeLinesColor, EditorGUIUtility.isProSkin ? new Color( 0.65f, 0.65f, 0.65f, 1f ) : new Color( 0.375f, 0.375f, 0.375f, 1f ) ); + HighlightedTreeLinesColor = ColorField( "Highlighted Color", HighlightedTreeLinesColor, Color.cyan ); + EditorGUI.indentLevel--; + + EditorGUIUtility.labelWidth = labelWidth; + + if( EditorGUI.EndChangeCheck() ) + ForEachAssetUsageDetectorWindow( ( window ) => window.Repaint() ); + } + + private static Color ColorField( string label, Color value, Color defaultValue ) + { + GUILayout.BeginHorizontal(); + Color result = EditorGUILayout.ColorField( label, value ); + if( GUILayout.Button( "Reset", GL_WIDTH_60 ) ) + result = defaultValue; + GUILayout.EndHorizontal(); + + return result; + } + + private static float FloatField( string label, float value, float defaultValue ) + { + GUILayout.BeginHorizontal(); + float result = EditorGUILayout.FloatField( label, value ); + if( GUILayout.Button( "Reset", GL_WIDTH_60 ) ) + result = defaultValue; + GUILayout.EndHorizontal(); + + return result; + } + + private static Color GetColor( string pref, Color defaultColor ) + { + if( EditorGUIUtility.isProSkin ) + pref += "_Pro"; + + if( !EditorPrefs.HasKey( pref ) ) + return defaultColor; + + string[] parts = EditorPrefs.GetString( pref ).Split( ';' ); + return new Color32( byte.Parse( parts[0] ), byte.Parse( parts[1] ), byte.Parse( parts[2] ), byte.Parse( parts[3] ) ); + } + + private static void SetColor( string pref, Color32 value ) + { + if( EditorGUIUtility.isProSkin ) + pref += "_Pro"; + + EditorPrefs.SetString( pref, string.Concat( value.r.ToString(), ";", value.g.ToString(), ";", value.b.ToString(), ";", value.a.ToString() ) ); + } + + private static void ForEachAssetUsageDetectorWindow( System.Action action ) + { + foreach( AssetUsageDetectorWindow window in Resources.FindObjectsOfTypeAll() ) + { + if( window ) + action( window ); + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs.meta new file mode 100644 index 00000000..7b45f1fc --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 13295073724765e45aa3b77486e515f4 +timeCreated: 1639982865 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs new file mode 100644 index 00000000..eaf276ce --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs @@ -0,0 +1,812 @@ +// Asset Usage Detector - by Suleyman Yasir KULA (yasirkula@gmail.com) + +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Reflection; +using Object = UnityEngine.Object; +#if UNITY_2021_2_OR_NEWER +using PrefabStage = UnityEditor.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility; +#elif UNITY_2018_3_OR_NEWER +using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; +#endif + +namespace AssetUsageDetectorNamespace +{ + public enum Phase { Setup, Processing, Complete }; + + public class AssetUsageDetectorWindow : EditorWindow, IHasCustomMenu + { + private enum WindowFilter { AlwaysReturnActive, ReturnActiveIfNotLocked, AlwaysReturnNew }; + + private const string PREFS_SEARCH_SCENES = "AUD_SceneSearch"; + private const string PREFS_SEARCH_SCENE_LIGHTING_SETTINGS = "AUD_LightingSettingsSearch"; + private const string PREFS_SEARCH_ASSETS = "AUD_AssetsSearch"; + private const string PREFS_SEARCH_PROJECT_SETTINGS = "AUD_ProjectSettingsSearch"; + private const string PREFS_DONT_SEARCH_SOURCE_ASSETS = "AUD_AssetsExcludeSrc"; + private const string PREFS_SEARCH_DEPTH_LIMIT = "AUD_Depth"; + private const string PREFS_SEARCH_FIELDS = "AUD_Fields"; + private const string PREFS_SEARCH_PROPERTIES = "AUD_Properties"; + private const string PREFS_SEARCH_NON_SERIALIZABLES = "AUD_NonSerializables"; + private const string PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES = "AUD_SearchUnusedMaterialProps"; + private const string PREFS_LAZY_SCENE_SEARCH = "AUD_LazySceneSearch"; + private const string PREFS_ADDRESSABLES_SUPPORT = "AUD_AddressablesSupport"; + private const string PREFS_CALCULATE_UNUSED_OBJECTS = "AUD_FindUnusedObjs"; + private const string PREFS_HIDE_DUPLICATE_ROWS = "AUD_HideDuplicates"; + private const string PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS = "AUD_HideRedundantPVariantLinks"; + private const string PREFS_SHOW_PROGRESS = "AUD_Progress"; + + private static readonly GUIContent windowTitle = new GUIContent( "Asset Usage Detector" ); + private static readonly Vector2 windowMinSize = new Vector2( 325f, 220f ); + + private static readonly GUILayoutOption GL_WIDTH_12 = GUILayout.Width( 12f ); + + private GUIStyle lockButtonStyle; + + private readonly AssetUsageDetector core = new AssetUsageDetector(); + private SearchResult searchResult; // Overall search results + + // This isn't readonly so that it can be serialized + private List objectsToSearch = new List() { new ObjectToSearch( null ) }; + +#pragma warning disable 0649 + [SerializeField] // Since titleContent persists between Editor sessions, so should the IsLocked property because otherwise, "[L]" in title becomes confusing when the EditorWindow isn't actually locked + private bool m_isLocked; + private bool IsLocked + { + get { return m_isLocked; } + set + { + if( m_isLocked != value ) + { + m_isLocked = value; + titleContent = value ? new GUIContent( "[L] " + windowTitle.text, EditorGUIUtility.IconContent( "InspectorLock" ).image ) : windowTitle; + } + } + } +#pragma warning restore 0649 + + private Phase currentPhase = Phase.Setup; + + private bool searchInOpenScenes = true; // Scenes currently open in Hierarchy view + private bool searchInScenesInBuild = true; // Scenes in build + private bool searchInScenesInBuildTickedOnly = true; // Scenes in build (ticked only or not) + private bool searchInAllScenes = true; // All scenes (including scenes that are not in build) + private bool searchInSceneLightingSettings = true; // Window-Rendering-Lighting settings + private bool searchInAssetsFolder = true; // Assets in Project window + private bool dontSearchInSourceAssets = true; // objectsToSearch won't be searched for internal references + private bool searchInProjectSettings = true; // Player Settings, Graphics Settings etc. + + private List searchInAssetsSubset = new List() { null }; // If not empty, only these assets are searched for references + private List excludedAssets = new List() { null }; // These assets won't be searched for references + private List excludedScenes = new List() { null }; // These scenes won't be searched for references + + private int searchDepthLimit = 4; // Depth limit for recursively searching variables of objects + + private bool lazySceneSearch = true; +#if ASSET_USAGE_ADDRESSABLES + private bool addressablesSupport = false; +#endif + private bool searchNonSerializableVariables = true; + private bool searchUnusedMaterialProperties = true; + private bool calculateUnusedObjects = false; + private bool hideDuplicateRows = true; + private bool hideReduntantPrefabVariantLinks = true; + private bool noAssetDatabaseChanges = false; + private bool showDetailedProgressBar = true; + + private BindingFlags fieldModifiers, propertyModifiers; + + private SearchRefactoring searchRefactoring = null; // Its value can be assigned via ShowAndSearch + + private readonly ObjectToSearchListDrawer objectsToSearchDrawer = new ObjectToSearchListDrawer(); + private readonly ObjectListDrawer searchInAssetsSubsetDrawer = new ObjectListDrawer( "Search following asset(s) only:", false ); + private readonly ObjectListDrawer excludedAssetsDrawer = new ObjectListDrawer( "Don't search following asset(s):", false ); + private readonly ObjectListDrawer excludedScenesDrawer = new ObjectListDrawer( "Don't search in following scene(s):", false ); + + private bool drawObjectsToSearchSection = true; + + private Vector2 scrollPosition = Vector2.zero; + + private bool shouldRepositionSelf; + private Rect windowTargetPosition; + + void IHasCustomMenu.AddItemsToMenu( GenericMenu contextMenu ) + { + contextMenu.AddItem( new GUIContent( "Lock" ), IsLocked, () => IsLocked = !IsLocked ); + contextMenu.AddSeparator( "" ); + +#if UNITY_2018_3_OR_NEWER + contextMenu.AddItem( new GUIContent( "Settings" ), false, () => SettingsService.OpenProjectSettings( "Project/yasirkula/Asset Usage Detector" ) ); +#else + contextMenu.AddItem( new GUIContent( "Settings" ), false, () => + { + System.Type preferencesWindowType = typeof( EditorWindow ).Assembly.GetType( "UnityEditor.PreferencesWindow" ); + preferencesWindowType.GetMethod( "ShowPreferencesWindow", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).Invoke( null, null ); + + EditorWindow preferencesWindow = GetWindow( preferencesWindowType ); + if( (bool) preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow ) ) + { + preferencesWindowType.GetMethod( "AddCustomSections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( preferencesWindow, null ); + preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, false ); + } + + int targetSectionIndex = -1; + System.Collections.IList sections = (System.Collections.IList) preferencesWindowType.GetField( "m_Sections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow ); + for( int i = 0; i < sections.Count; i++ ) + { + if( ( (GUIContent) sections[i].GetType().GetField( "content", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( sections[i] ) ).text == "Asset Usage Detector" ) + { + targetSectionIndex = i; + break; + } + } + + if( targetSectionIndex >= 0 ) + preferencesWindowType.GetProperty( "selectedSectionIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, targetSectionIndex, null ); + } ); +#endif + + if( currentPhase == Phase.Setup ) + { + contextMenu.AddSeparator( "" ); + contextMenu.AddItem( new GUIContent( "Refresh Sub-Assets of Searched Objects" ), false, () => + { + for( int i = objectsToSearch.Count - 1; i >= 0; i-- ) + objectsToSearch[i].RefreshSubAssets(); + } ); + } + else if( currentPhase == Phase.Complete ) + { + if( searchResult != null && searchResult.NumberOfGroups > 0 ) + { + contextMenu.AddSeparator( "" ); + contextMenu.AddItem( new GUIContent( "Collapse All" ), false, searchResult.CollapseAllSearchResultGroups ); + } + } + } + + // Shows lock button at the top-right corner + // Credit: http://leahayes.co.uk/2013/04/30/adding-the-little-padlock-button-to-your-editorwindow.html + private void ShowButton( Rect position ) + { + if( lockButtonStyle == null ) + lockButtonStyle = "IN LockButton"; + + IsLocked = GUI.Toggle( position, IsLocked, GUIContent.none, lockButtonStyle ); + } + + private static AssetUsageDetectorWindow GetWindow( WindowFilter filter ) + { + AssetUsageDetectorWindow[] windows = Resources.FindObjectsOfTypeAll(); + AssetUsageDetectorWindow window = System.Array.Find( windows, ( w ) => w && !w.IsLocked ); + if( !window ) + window = System.Array.Find( windows, ( w ) => w ); + + if( window && ( filter == WindowFilter.AlwaysReturnActive || ( !window.IsLocked && filter == WindowFilter.ReturnActiveIfNotLocked ) ) ) + { + window.Show(); + window.Focus(); + + return window; + } + + Rect? windowTargetPosition = null; + if( window ) + { + Rect position = window.position; + position.position += new Vector2( 50f, 50f ); + windowTargetPosition = position; + } + + window = CreateInstance(); + window.titleContent = windowTitle; + window.minSize = windowMinSize; + + if( windowTargetPosition.HasValue ) + { + window.shouldRepositionSelf = true; + window.windowTargetPosition = windowTargetPosition.Value; + } + + window.Show( true ); + window.Focus(); + + return window; + } + + [MenuItem( "Window/Asset Usage Detector/Active Window" )] + private static void OpenActiveWindow() + { + GetWindow( WindowFilter.AlwaysReturnActive ); + } + + [MenuItem( "Window/Asset Usage Detector/New Window" )] + private static void OpenNewWindow() + { + GetWindow( WindowFilter.AlwaysReturnNew ); + } + + // Quickly initiate search for the selected assets + [MenuItem( "GameObject/Search for References/This Object Only", priority = 49 )] + [MenuItem( "Assets/Search for References", priority = 1000 )] + private static void SearchSelectedAssetReferences( MenuCommand command ) + { + // This happens when this button is clicked via hierarchy's right click context menu + // and is called once for each object in the selection. We don't want that, we want + // the function to be called only once + if( command.context ) + { + EditorApplication.update -= CallSearchSelectedAssetReferencesOnce; + EditorApplication.update += CallSearchSelectedAssetReferencesOnce; + } + else + ShowAndSearch( Selection.objects ); + } + + [MenuItem( "GameObject/Search for References/Include Children", priority = 49 )] + private static void SearchSelectedAssetReferencesWithChildren( MenuCommand command ) + { + if( command.context ) + { + EditorApplication.update -= CallSearchSelectedAssetReferencesWithChildrenOnce; + EditorApplication.update += CallSearchSelectedAssetReferencesWithChildrenOnce; + } + else + ShowAndSearch( Selection.objects, true ); + } + + // Show the menu item only if there is a selection in the Editor + [MenuItem( "GameObject/Search for References/This Object Only", validate = true )] + [MenuItem( "GameObject/Search for References/Include Children", validate = true )] + [MenuItem( "Assets/Search for References", validate = true )] + private static bool SearchSelectedAssetReferencesValidate( MenuCommand command ) + { + return Selection.objects.Length > 0; + } + + // Quickly show the AssetUsageDetector window and initiate a search + public static void ShowAndSearch( IEnumerable searchObjects, bool? shouldSearchChildren = null ) + { + GetWindow( WindowFilter.ReturnActiveIfNotLocked ).ShowAndSearchInternal( searchObjects, null, shouldSearchChildren ); + } + + // Quickly show the AssetUsageDetector window and initiate a search + public static void ShowAndSearch( AssetUsageDetector.Parameters searchParameters, bool? shouldSearchChildren = null ) + { + if( searchParameters == null ) + { + Debug.LogError( "searchParameters can't be null!" ); + return; + } + + GetWindow( WindowFilter.ReturnActiveIfNotLocked ).ShowAndSearchInternal( searchParameters.objectsToSearch, searchParameters, shouldSearchChildren ); + } + + private static void CallSearchSelectedAssetReferencesOnce() + { + EditorApplication.update -= CallSearchSelectedAssetReferencesOnce; + SearchSelectedAssetReferences( new MenuCommand( null ) ); + } + + private static void CallSearchSelectedAssetReferencesWithChildrenOnce() + { + EditorApplication.update -= CallSearchSelectedAssetReferencesWithChildrenOnce; + SearchSelectedAssetReferencesWithChildren( new MenuCommand( null ) ); + } + + private void ShowAndSearchInternal( IEnumerable searchObjects, AssetUsageDetector.Parameters searchParameters, bool? shouldSearchChildren ) + { + if( !ReturnToSetupPhase() ) + { + Debug.LogError( "Need to reset the previous search first!" ); + return; + } + + objectsToSearch.Clear(); + if( searchObjects != null ) + { + foreach( Object obj in searchObjects ) + objectsToSearch.Add( new ObjectToSearch( obj, shouldSearchChildren ) ); + } + + if( searchParameters != null ) + { + ParseSceneSearchMode( searchParameters.searchInScenes ); + searchInSceneLightingSettings = searchParameters.searchInSceneLightingSettings; + searchInAssetsFolder = searchParameters.searchInAssetsFolder; + dontSearchInSourceAssets = searchParameters.dontSearchInSourceAssets; + searchInProjectSettings = searchParameters.searchInProjectSettings; + searchDepthLimit = searchParameters.searchDepthLimit; + fieldModifiers = searchParameters.fieldModifiers; + propertyModifiers = searchParameters.propertyModifiers; + searchNonSerializableVariables = searchParameters.searchNonSerializableVariables; + searchUnusedMaterialProperties = searchParameters.searchUnusedMaterialProperties; + searchRefactoring = searchParameters.searchRefactoring; + lazySceneSearch = searchParameters.lazySceneSearch; +#if ASSET_USAGE_ADDRESSABLES + addressablesSupport = searchParameters.addressablesSupport; +#endif + calculateUnusedObjects = searchParameters.calculateUnusedObjects; + hideDuplicateRows = searchParameters.hideDuplicateRows; + hideReduntantPrefabVariantLinks = searchParameters.hideReduntantPrefabVariantLinks; + noAssetDatabaseChanges = searchParameters.noAssetDatabaseChanges; + showDetailedProgressBar = searchParameters.showDetailedProgressBar; + + searchInAssetsSubset.Clear(); + if( searchParameters.searchInAssetsSubset != null ) + { + foreach( Object obj in searchParameters.searchInAssetsSubset ) + searchInAssetsSubset.Add( obj ); + } + + excludedAssets.Clear(); + if( searchParameters.excludedAssetsFromSearch != null ) + { + foreach( Object obj in searchParameters.excludedAssetsFromSearch ) + excludedAssets.Add( obj ); + } + + excludedScenes.Clear(); + if( searchParameters.excludedScenesFromSearch != null ) + { + foreach( Object obj in searchParameters.excludedScenesFromSearch ) + excludedScenes.Add( obj ); + } + } + + InitiateSearch(); + Repaint(); + } + + private void Awake() + { + LoadPrefs(); + } + + private void OnEnable() + { + if( currentPhase == Phase.Complete && AssetUsageDetectorSettings.ShowCustomTooltip ) + wantsMouseMove = wantsMouseEnterLeaveWindow = true; // These values aren't preserved during domain reload on Unity 2020.3.0f1 + +#if UNITY_2018_3_OR_NEWER + PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets; + PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets; +#endif + } + + private void OnDisable() + { +#if UNITY_2018_3_OR_NEWER + PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets; +#endif + SearchResultTooltip.Hide(); + } + + private void OnDestroy() + { + if( core != null ) + core.SaveCache(); + + SavePrefs(); + + if( searchResult != null && currentPhase == Phase.Complete ) + searchResult.RestoreInitialSceneSetup(); + } + + private void SavePrefs() + { + EditorPrefs.SetInt( PREFS_SEARCH_SCENES, (int) GetSceneSearchMode( false ) ); + EditorPrefs.SetBool( PREFS_SEARCH_SCENE_LIGHTING_SETTINGS, searchInSceneLightingSettings ); + EditorPrefs.SetBool( PREFS_SEARCH_ASSETS, searchInAssetsFolder ); + EditorPrefs.SetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, dontSearchInSourceAssets ); + EditorPrefs.SetBool( PREFS_SEARCH_PROJECT_SETTINGS, searchInProjectSettings ); + EditorPrefs.SetInt( PREFS_SEARCH_DEPTH_LIMIT, searchDepthLimit ); + EditorPrefs.SetInt( PREFS_SEARCH_FIELDS, (int) fieldModifiers ); + EditorPrefs.SetInt( PREFS_SEARCH_PROPERTIES, (int) propertyModifiers ); + EditorPrefs.SetBool( PREFS_SEARCH_NON_SERIALIZABLES, searchNonSerializableVariables ); + EditorPrefs.SetBool( PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES, searchUnusedMaterialProperties ); + EditorPrefs.SetBool( PREFS_LAZY_SCENE_SEARCH, lazySceneSearch ); +#if ASSET_USAGE_ADDRESSABLES + EditorPrefs.SetBool( PREFS_ADDRESSABLES_SUPPORT, addressablesSupport ); +#endif + EditorPrefs.SetBool( PREFS_CALCULATE_UNUSED_OBJECTS, calculateUnusedObjects ); + EditorPrefs.SetBool( PREFS_HIDE_DUPLICATE_ROWS, hideDuplicateRows ); + EditorPrefs.SetBool( PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS, hideReduntantPrefabVariantLinks ); + EditorPrefs.SetBool( PREFS_SHOW_PROGRESS, showDetailedProgressBar ); + } + + private void LoadPrefs() + { + ParseSceneSearchMode( (SceneSearchMode) EditorPrefs.GetInt( PREFS_SEARCH_SCENES, (int) ( SceneSearchMode.OpenScenes | SceneSearchMode.ScenesInBuildSettingsTickedOnly | SceneSearchMode.AllScenes ) ) ); + searchInSceneLightingSettings = EditorPrefs.GetBool( PREFS_SEARCH_SCENE_LIGHTING_SETTINGS, true ); + searchInAssetsFolder = EditorPrefs.GetBool( PREFS_SEARCH_ASSETS, true ); + dontSearchInSourceAssets = EditorPrefs.GetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, true ); + searchInProjectSettings = EditorPrefs.GetBool( PREFS_SEARCH_PROJECT_SETTINGS, true ); + searchDepthLimit = EditorPrefs.GetInt( PREFS_SEARCH_DEPTH_LIMIT, 4 ); + fieldModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_FIELDS, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) ); + propertyModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_PROPERTIES, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) ); + searchNonSerializableVariables = EditorPrefs.GetBool( PREFS_SEARCH_NON_SERIALIZABLES, true ); + searchUnusedMaterialProperties = EditorPrefs.GetBool( PREFS_SEARCH_UNUSED_MATERIAL_PROPERTIES, true ); + lazySceneSearch = EditorPrefs.GetBool( PREFS_LAZY_SCENE_SEARCH, true ); +#if ASSET_USAGE_ADDRESSABLES + addressablesSupport = EditorPrefs.GetBool( PREFS_ADDRESSABLES_SUPPORT, false ); +#endif + calculateUnusedObjects = EditorPrefs.GetBool( PREFS_CALCULATE_UNUSED_OBJECTS, false ); + hideDuplicateRows = EditorPrefs.GetBool( PREFS_HIDE_DUPLICATE_ROWS, true ); + hideReduntantPrefabVariantLinks = EditorPrefs.GetBool( PREFS_HIDE_REDUNDANT_PREFAB_VARIANT_LINKS, true ); + showDetailedProgressBar = EditorPrefs.GetBool( PREFS_SHOW_PROGRESS, true ); + } + + private SceneSearchMode GetSceneSearchMode( bool hideOptionsInPlayMode ) + { + SceneSearchMode sceneSearchMode = SceneSearchMode.None; + if( searchInOpenScenes ) + sceneSearchMode |= SceneSearchMode.OpenScenes; + if( !hideOptionsInPlayMode || !EditorApplication.isPlaying ) + { + if( searchInScenesInBuild ) + sceneSearchMode |= searchInScenesInBuildTickedOnly ? SceneSearchMode.ScenesInBuildSettingsTickedOnly : SceneSearchMode.ScenesInBuildSettingsAll; + if( searchInAllScenes ) + sceneSearchMode |= SceneSearchMode.AllScenes; + } + + return sceneSearchMode; + } + + private void ParseSceneSearchMode( SceneSearchMode sceneSearchMode ) + { + searchInOpenScenes = ( sceneSearchMode & SceneSearchMode.OpenScenes ) == SceneSearchMode.OpenScenes; + searchInScenesInBuild = ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsAll ) == SceneSearchMode.ScenesInBuildSettingsAll || ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsTickedOnly ) == SceneSearchMode.ScenesInBuildSettingsTickedOnly; + searchInScenesInBuildTickedOnly = ( sceneSearchMode & SceneSearchMode.ScenesInBuildSettingsAll ) != SceneSearchMode.ScenesInBuildSettingsAll; + searchInAllScenes = ( sceneSearchMode & SceneSearchMode.AllScenes ) == SceneSearchMode.AllScenes; + } + + private void Update() + { + if( shouldRepositionSelf ) + { + shouldRepositionSelf = false; + position = windowTargetPosition; + } + } + + private void OnGUI() + { + // Make the window scrollable + scrollPosition = EditorGUILayout.BeginScrollView( scrollPosition, Utilities.GL_EXPAND_WIDTH, Utilities.GL_EXPAND_HEIGHT ); + + GUILayout.BeginVertical(); + + if( currentPhase == Phase.Processing ) + { + // If we are stuck at this phase, then we have encountered an exception + GUILayout.Label( ". . . Search in progress or something went wrong (check console) . . ." ); + + if( GUILayout.Button( "RETURN", Utilities.GL_HEIGHT_30 ) ) + { + ReturnToSetupPhase(); + GUIUtility.ExitGUI(); + } + } + else if( currentPhase == Phase.Setup ) + { + DrawObjectsToSearchSection(); + + GUILayout.Space( 10f ); + + Color c = GUI.backgroundColor; + GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor; + GUILayout.Box( "SEARCH IN", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH ); + GUI.backgroundColor = c; + + searchInAssetsFolder = WordWrappingToggleLeft( "Project window (Assets folder)", searchInAssetsFolder ); + + if( searchInAssetsFolder ) + { + GUILayout.BeginHorizontal(); + GUILayout.Space( 35f ); + GUILayout.BeginVertical(); + + searchInAssetsSubsetDrawer.Draw( searchInAssetsSubset ); + excludedAssetsDrawer.Draw( excludedAssets ); + + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + + GUILayout.Space( 5f ); + + dontSearchInSourceAssets = WordWrappingToggleLeft( "Don't search \"SEARCHED OBJECTS\" themselves for references", dontSearchInSourceAssets ); + searchUnusedMaterialProperties = WordWrappingToggleLeft( "Search unused material properties (e.g. normal map of a material that no longer uses normal mapping)", searchUnusedMaterialProperties ); + + Utilities.DrawSeparatorLine(); + + if( searchInAllScenes && !EditorApplication.isPlaying ) + GUI.enabled = false; + + searchInOpenScenes = WordWrappingToggleLeft( "Currently open (loaded) scene(s)", searchInOpenScenes ); + + if( !EditorApplication.isPlaying ) + { + searchInScenesInBuild = WordWrappingToggleLeft( "Scenes in Build Settings", searchInScenesInBuild ); + + if( searchInScenesInBuild ) + { + GUILayout.BeginHorizontal(); + GUILayout.Space( 35f ); + + searchInScenesInBuildTickedOnly = EditorGUILayout.ToggleLeft( "Ticked only", searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 ); + searchInScenesInBuildTickedOnly = !EditorGUILayout.ToggleLeft( "All", !searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 ); + + GUILayout.EndHorizontal(); + } + + GUI.enabled = true; + + searchInAllScenes = WordWrappingToggleLeft( "All scenes in the project", searchInAllScenes ); + } + + GUILayout.BeginHorizontal(); + GUILayout.Space( 35f ); + GUILayout.BeginVertical(); + + excludedScenesDrawer.Draw( excludedScenes ); + + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + + EditorGUI.BeginDisabledGroup( !searchInOpenScenes && !searchInScenesInBuild && !searchInAllScenes ); + searchInSceneLightingSettings = WordWrappingToggleLeft( "Scene Lighting Settings (WARNING: This may change the active scene during search)", searchInSceneLightingSettings ); + EditorGUI.EndDisabledGroup(); + + Utilities.DrawSeparatorLine(); + + searchInProjectSettings = WordWrappingToggleLeft( "Project Settings (Player Settings, Graphics Settings etc.)", searchInProjectSettings ); + + GUILayout.Space( 10f ); + + GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor; + GUILayout.Box( "SETTINGS", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH ); + GUI.backgroundColor = c; + +#if ASSET_USAGE_ADDRESSABLES + EditorGUI.BeginDisabledGroup( addressablesSupport ); +#endif + lazySceneSearch = WordWrappingToggleLeft( "Lazy scene search: scenes are searched in detail only when they are manually refreshed (faster search)", lazySceneSearch ); +#if ASSET_USAGE_ADDRESSABLES + EditorGUI.EndDisabledGroup(); + addressablesSupport = WordWrappingToggleLeft( "Addressables support (Experimental) (WARNING: 'Lazy scene search' will be disabled) (slower search)", addressablesSupport ); +#endif + calculateUnusedObjects = WordWrappingToggleLeft( "Calculate unused objects", calculateUnusedObjects ); + hideDuplicateRows = WordWrappingToggleLeft( "Hide duplicate rows in search results", hideDuplicateRows ); +#if UNITY_2018_3_OR_NEWER + hideReduntantPrefabVariantLinks = WordWrappingToggleLeft( "Hide redundant prefab variant links (when the same value is assigned to the same Component of a prefab and its variant(s))", hideReduntantPrefabVariantLinks ); +#endif + noAssetDatabaseChanges = WordWrappingToggleLeft( "I haven't modified any assets/scenes since the last search (faster search)", noAssetDatabaseChanges ); + showDetailedProgressBar = WordWrappingToggleLeft( "Update search progress bar more often (cancelable search) (slower search)", showDetailedProgressBar ); + + GUILayout.Space( 10f ); + + // Don't let the user press the GO button without any valid search location + if( !searchInAllScenes && !searchInOpenScenes && !searchInScenesInBuild && !searchInAssetsFolder && !searchInProjectSettings ) + GUI.enabled = false; + + if( GUILayout.Button( "GO!", Utilities.GL_HEIGHT_30 ) ) + { + InitiateSearch(); + GUIUtility.ExitGUI(); + } + + GUILayout.Space( 5f ); + } + else if( currentPhase == Phase.Complete ) + { + // Draw the results of the search + GUI.enabled = false; + + DrawObjectsToSearchSection(); + + if( drawObjectsToSearchSection ) + GUILayout.Space( 10f ); + + GUI.enabled = true; + + if( GUILayout.Button( "Reset Search", Utilities.GL_HEIGHT_30 ) ) + { + ReturnToSetupPhase(); + GUIUtility.ExitGUI(); + } + + if( searchResult == null ) + { + EditorGUILayout.HelpBox( "ERROR: searchResult is null", MessageType.Error ); + return; + } + else if( !searchResult.SearchCompletedSuccessfully ) + EditorGUILayout.HelpBox( "ERROR: search was interrupted, check the logs for more info", MessageType.Error ); + + if( searchResult.NumberOfGroups == 0 ) + { + GUILayout.Space( 10f ); + GUILayout.Box( "No references found...", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH ); + } + else + { + noAssetDatabaseChanges = WordWrappingToggleLeft( "I haven't modified any assets/scenes since the last search (faster Refresh)", noAssetDatabaseChanges ); + + EditorGUILayout.Space(); + + scrollPosition.y = searchResult.DrawOnGUI( this, scrollPosition.y, noAssetDatabaseChanges ); + } + } + + if( Event.current.type == EventType.MouseLeaveWindow ) + { + SearchResultTooltip.Hide(); + + if( searchResult != null ) + searchResult.CancelDelayedTreeViewTooltip(); + } + + GUILayout.EndVertical(); + + EditorGUILayout.EndScrollView(); + } + + private void DrawObjectsToSearchSection() + { + Color c = GUI.backgroundColor; + GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor; + GUILayout.Box( "SEARCHED OBJECTS", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH ); + GUI.backgroundColor = c; + + Rect searchedObjectsHeaderRect = GUILayoutUtility.GetLastRect(); + searchedObjectsHeaderRect.x += 5f; + searchedObjectsHeaderRect.yMin += ( searchedObjectsHeaderRect.height - EditorGUIUtility.singleLineHeight ) * 0.5f; + searchedObjectsHeaderRect.height = EditorGUIUtility.singleLineHeight; + + drawObjectsToSearchSection = EditorGUI.Foldout( searchedObjectsHeaderRect, drawObjectsToSearchSection, GUIContent.none, true ); + + if( drawObjectsToSearchSection ) + objectsToSearchDrawer.Draw( objectsToSearch ); + } + + public static bool WordWrappingToggleLeft( string label, bool value ) + { + GUILayout.BeginHorizontal(); + bool result = EditorGUILayout.ToggleLeft( GUIContent.none, value, GL_WIDTH_12 ); + if( GUILayout.Button( label, EditorStyles.wordWrappedLabel ) ) + { + GUI.FocusControl( null ); + result = !value; + } + GUILayout.EndHorizontal(); + + return result; + } + + private void InitiateSearch() + { + currentPhase = Phase.Processing; + + SavePrefs(); + +#if UNITY_2018_3_OR_NEWER + ReplacePrefabStageObjectsWithAssets( PrefabStageUtility.GetCurrentPrefabStage() ); +#endif + + // Start searching + searchResult = core.Run( new AssetUsageDetector.Parameters() + { + objectsToSearch = !objectsToSearch.IsEmpty() ? new ObjectToSearchEnumerator( objectsToSearch ).ToArray() : null, + searchInScenes = GetSceneSearchMode( true ), + searchInSceneLightingSettings = searchInSceneLightingSettings, + searchInAssetsFolder = searchInAssetsFolder, + searchInAssetsSubset = !searchInAssetsSubset.IsEmpty() ? searchInAssetsSubset.ToArray() : null, + excludedAssetsFromSearch = !excludedAssets.IsEmpty() ? excludedAssets.ToArray() : null, + dontSearchInSourceAssets = dontSearchInSourceAssets, + excludedScenesFromSearch = !excludedScenes.IsEmpty() ? excludedScenes.ToArray() : null, + searchInProjectSettings = searchInProjectSettings, + //fieldModifiers = fieldModifiers, + //propertyModifiers = propertyModifiers, + //searchDepthLimit = searchDepthLimit, + //searchNonSerializableVariables = searchNonSerializableVariables, + searchUnusedMaterialProperties = searchUnusedMaterialProperties, + searchRefactoring = searchRefactoring, +#if ASSET_USAGE_ADDRESSABLES + lazySceneSearch = lazySceneSearch && !addressablesSupport, + addressablesSupport = addressablesSupport, +#else + lazySceneSearch = lazySceneSearch, +#endif + calculateUnusedObjects = calculateUnusedObjects, + hideDuplicateRows = hideDuplicateRows, + hideReduntantPrefabVariantLinks = hideReduntantPrefabVariantLinks, + noAssetDatabaseChanges = noAssetDatabaseChanges, + showDetailedProgressBar = showDetailedProgressBar + } ); + + currentPhase = Phase.Complete; + + // We really don't want SearchRefactoring to affect next searches unless the search is initiated via ShowAndSearch again + searchRefactoring = null; + + if( AssetUsageDetectorSettings.ShowCustomTooltip ) + wantsMouseMove = wantsMouseEnterLeaveWindow = true; + } + +#if UNITY_2018_3_OR_NEWER + // Try replacing searched objects who are part of currently open prefab stage with their corresponding prefab assets + public void ReplacePrefabStageObjectsWithAssets( PrefabStage prefabStage ) + { + if( prefabStage == null || !prefabStage.stageHandle.IsValid() ) + return; + +#if UNITY_2020_1_OR_NEWER + GameObject prefabAsset = AssetDatabase.LoadAssetAtPath( prefabStage.assetPath ); +#else + GameObject prefabAsset = AssetDatabase.LoadAssetAtPath( prefabStage.prefabAssetPath ); +#endif + if( prefabAsset == null || prefabAsset.Equals( null ) ) + return; + + for( int i = 0; i < objectsToSearch.Count; i++ ) + { + Object obj = objectsToSearch[i].obj; + if( obj != null && !obj.Equals( null ) && obj is GameObject && prefabStage.IsPartOfPrefabContents( (GameObject) obj ) ) + { + GameObject prefabStageObjectSource = ( (GameObject) obj ).FollowSymmetricHierarchy( prefabStage.prefabContentsRoot, prefabAsset ); + if( prefabStageObjectSource != null ) + objectsToSearch[i].obj = prefabStageObjectSource; + + List subAssets = objectsToSearch[i].subAssets; + for( int j = 0; j < subAssets.Count; j++ ) + { + obj = subAssets[j].subAsset; + if( obj != null && !obj.Equals( null ) && obj is GameObject && prefabStage.IsPartOfPrefabContents( (GameObject) obj ) ) + { + prefabStageObjectSource = ( (GameObject) obj ).FollowSymmetricHierarchy( prefabStage.prefabContentsRoot, prefabAsset ); + if( prefabStageObjectSource != null ) + subAssets[j].subAsset = prefabStageObjectSource; + } + } + } + } + } +#endif + + private bool ReturnToSetupPhase() + { + if( searchResult != null && !EditorApplication.isPlaying && !searchResult.RestoreInitialSceneSetup() ) + return false; + + searchResult = null; + currentPhase = Phase.Setup; + wantsMouseMove = wantsMouseEnterLeaveWindow = false; + + SearchResultTooltip.Hide(); + + return true; + } + + internal void OnSettingsChanged( bool highlightedSearchTextColorChanged = false, bool tooltipDescriptionsColorChanged = false ) + { + if( searchResult == null ) + return; + + wantsMouseMove = wantsMouseEnterLeaveWindow = AssetUsageDetectorSettings.ShowCustomTooltip; + + for( int i = searchResult.NumberOfGroups - 1; i >= 0; i-- ) + { + if( searchResult[i].treeView != null ) + { + searchResult[i].treeView.rowHeight = EditorGUIUtility.singleLineHeight + AssetUsageDetectorSettings.ExtraRowHeight; + searchResult[i].treeView.OnSettingsChanged( highlightedSearchTextColorChanged, tooltipDescriptionsColorChanged ); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs.meta new file mode 100644 index 00000000..95c1c9fd --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 271a22c69c3d96c4dbdd04cca415a840 +timeCreated: 1520032279 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs b/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs new file mode 100644 index 00000000..2280128c --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs @@ -0,0 +1,130 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace AssetUsageDetectorNamespace +{ + public class EmptyEnumerator : IEnumerable, IEnumerator + { + public T Current { get { return default( T ); } } + object IEnumerator.Current { get { return Current; } } + + public void Dispose() { } + public void Reset() { } + + public bool MoveNext() + { + return false; + } + + public IEnumerator GetEnumerator() + { + return this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + } + + public class ObjectToSearchEnumerator : IEnumerable + { + public class Enumerator : IEnumerator + { + public Object Current + { + get + { + if( subAssetIndex < 0 ) + return source[index].obj; + + return source[index].subAssets[subAssetIndex].subAsset; + } + } + + object IEnumerator.Current { get { return Current; } } + + private List source; + private int index; + private int subAssetIndex; + + public Enumerator( List source ) + { + this.source = source; + Reset(); + } + + public void Dispose() + { + source = null; + } + + public bool MoveNext() + { + if( subAssetIndex < -1 ) + { + subAssetIndex = -1; + + if( ++index >= source.Count ) + return false; + + // Skip folder assets in the enumeration, AssetUsageDetector expands encountered folders automatically + // and we don't want that to happen as source[index].subAssets already contains the folder's contents + if( !source[index].obj.IsFolder() ) + return true; + } + + List subAssets = source[index].subAssets; + if( subAssets != null ) + { + while( ++subAssetIndex < subAssets.Count && !subAssets[subAssetIndex].shouldSearch ) + continue; + + if( subAssetIndex < subAssets.Count ) + return true; + } + + subAssetIndex = -2; + return MoveNext(); + } + + public void Reset() + { + index = -1; + subAssetIndex = -2; + } + } + + private readonly List source; + + public ObjectToSearchEnumerator( List source ) + { + this.source = source; + } + + public IEnumerator GetEnumerator() + { + return new Enumerator( source ); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Object[] ToArray() + { + int count = 0; + foreach( Object obj in this ) + count++; + + Object[] result = new Object[count]; + int index = 0; + foreach( Object obj in this ) + result[index++] = obj; + + return result; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs.meta new file mode 100644 index 00000000..498a808a --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 894047c47ce45cf40939dae24afcc72b +timeCreated: 1562079461 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs b/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs new file mode 100644 index 00000000..d7d50dd2 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs @@ -0,0 +1,252 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AssetUsageDetectorNamespace +{ + public abstract class ListDrawer + { + private readonly string label; + private readonly bool acceptSceneObjects; + + protected ListDrawer( string label, bool acceptSceneObjects ) + { + this.label = label; + this.acceptSceneObjects = acceptSceneObjects; + } + + // Exposes a list on GUI + public bool Draw( List list ) + { + bool hasChanged = false; + bool guiEnabled = GUI.enabled; + + Event ev = Event.current; + + GUILayout.BeginHorizontal(); + + GUILayout.Label( label ); + + if( guiEnabled ) + { + // Handle drag & drop references to array + // Credit: https://answers.unity.com/answers/657877/view.html + if( ( ev.type == EventType.DragPerform || ev.type == EventType.DragUpdated ) && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) ) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if( ev.type == EventType.DragPerform ) + { + DragAndDrop.AcceptDrag(); + + Object[] draggedObjects = DragAndDrop.objectReferences; + if( draggedObjects.Length > 0 ) + { + for( int i = 0; i < draggedObjects.Length; i++ ) + { + if( draggedObjects[i] != null && !draggedObjects[i].Equals( null ) ) + { + bool replacedNullElement = false; + for( int j = 0; j < list.Count; j++ ) + { + if( IsElementNull( list[j] ) ) + { + list[j] = CreateElement( draggedObjects[i] ); + + replacedNullElement = true; + break; + } + } + + if( !replacedNullElement ) + list.Add( CreateElement( draggedObjects[i] ) ); + + hasChanged = true; + } + } + } + } + + ev.Use(); + } + else if( ev.type == EventType.ContextClick && GUILayoutUtility.GetLastRect().Contains( ev.mousePosition ) ) + { + GenericMenu contextMenu = new GenericMenu(); + contextMenu.AddItem( new GUIContent( "Clear" ), false, () => + { + list.Clear(); + list.Add( CreateElement( null ) ); + } ); + contextMenu.ShowAsContext(); + + ev.Use(); + } + + if( GUILayout.Button( "+", Utilities.GL_WIDTH_25 ) ) + list.Insert( 0, CreateElement( null ) ); + } + + GUILayout.EndHorizontal(); + + for( int i = 0; i < list.Count; i++ ) + { + T element = list[i]; + + GUI.changed = false; + GUILayout.BeginHorizontal(); + + Object prevObject = GetObjectFromElement( element ); + Object newObject = EditorGUILayout.ObjectField( "", prevObject, typeof( Object ), acceptSceneObjects ); + + if( GUI.changed ) + { + hasChanged = true; + SetObjectOfElement( list, i, newObject ); + } + + if( guiEnabled ) + { + if( GUILayout.Button( "+", Utilities.GL_WIDTH_25 ) ) + list.Insert( i + 1, CreateElement( null ) ); + + if( GUILayout.Button( "-", Utilities.GL_WIDTH_25 ) ) + { + if( element != null && !element.Equals( null ) ) + hasChanged = true; + + // Lists with no elements look ugly, always keep a dummy null variable + if( list.Count > 1 ) + list.RemoveAt( i-- ); + else + list[0] = CreateElement( null ); + } + } + + GUILayout.EndHorizontal(); + + PostElementDrawer( element ); + } + + return hasChanged; + } + + protected abstract T CreateElement( Object source ); + protected abstract Object GetObjectFromElement( T element ); + protected abstract void SetObjectOfElement( List list, int index, Object value ); + protected abstract bool IsElementNull( T element ); + protected abstract void PostElementDrawer( T element ); + } + + public class ObjectListDrawer : ListDrawer + { + public ObjectListDrawer( string label, bool acceptSceneObjects ) : base( label, acceptSceneObjects ) + { + } + + protected override Object CreateElement( Object source ) + { + return source; + } + + protected override Object GetObjectFromElement( Object element ) + { + return element; + } + + protected override void SetObjectOfElement( List list, int index, Object value ) + { + list[index] = value; + } + + protected override bool IsElementNull( Object element ) + { + return element == null || element.Equals( null ); + } + + protected override void PostElementDrawer( Object element ) + { + } + } + + public class ObjectToSearchListDrawer : ListDrawer + { + public ObjectToSearchListDrawer() : base( "Find references of:", true ) + { + } + + protected override ObjectToSearch CreateElement( Object source ) + { + return new ObjectToSearch( source ); + } + + protected override Object GetObjectFromElement( ObjectToSearch element ) + { + return element.obj; + } + + protected override void SetObjectOfElement( List list, int index, Object value ) + { + list[index].obj = value; + list[index].RefreshSubAssets(); + } + + protected override bool IsElementNull( ObjectToSearch element ) + { + return element == null || element.obj == null || element.obj.Equals( null ); + } + + protected override void PostElementDrawer( ObjectToSearch element ) + { + List subAssetsToSearch = element.subAssets; + if( subAssetsToSearch.Count > 0 ) + { + GUILayout.BeginHorizontal(); + + // 0-> all toggles off, 1-> mixed, 2-> all toggles on + bool toggleAllSubAssets = subAssetsToSearch[0].shouldSearch; + bool mixedToggle = false; + for( int j = 1; j < subAssetsToSearch.Count; j++ ) + { + if( subAssetsToSearch[j].shouldSearch != toggleAllSubAssets ) + { + mixedToggle = true; + break; + } + } + + if( mixedToggle ) + EditorGUI.showMixedValue = true; + + GUI.changed = false; + toggleAllSubAssets = EditorGUILayout.Toggle( toggleAllSubAssets, Utilities.GL_WIDTH_25 ); + if( GUI.changed ) + { + for( int j = 0; j < subAssetsToSearch.Count; j++ ) + subAssetsToSearch[j].shouldSearch = toggleAllSubAssets; + } + + EditorGUI.showMixedValue = false; + + element.showSubAssetsFoldout = EditorGUILayout.Foldout( element.showSubAssetsFoldout, "Include sub-assets in search:", true ); + + GUILayout.EndHorizontal(); + + if( element.showSubAssetsFoldout ) + { + for( int j = 0; j < subAssetsToSearch.Count; j++ ) + { + GUILayout.BeginHorizontal(); + + subAssetsToSearch[j].shouldSearch = EditorGUILayout.Toggle( subAssetsToSearch[j].shouldSearch, Utilities.GL_WIDTH_25 ); + + bool guiEnabled = GUI.enabled; + GUI.enabled = false; + EditorGUILayout.ObjectField( string.Empty, subAssetsToSearch[j].subAsset, typeof( Object ), true ); + GUI.enabled = guiEnabled; + + GUILayout.EndHorizontal(); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs.meta new file mode 100644 index 00000000..555a3698 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 88a4a4e861026b2498a437ce1e12b054 +timeCreated: 1568758673 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs b/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs new file mode 100644 index 00000000..24ad8ac3 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace AssetUsageDetectorNamespace +{ + [Serializable] + public class ObjectToSearch + { + [Serializable] + public class SubAsset + { + public Object subAsset; + public bool shouldSearch; + + public SubAsset( Object subAsset, bool shouldSearch ) + { + this.subAsset = subAsset; + this.shouldSearch = shouldSearch; + } + } + + public Object obj; + public List subAssets; + public bool showSubAssetsFoldout; + + private static HashSet currentSubAssets; + + public ObjectToSearch( Object obj, bool? shouldSearchChildren = null ) + { + this.obj = obj; + RefreshSubAssets( shouldSearchChildren ); + } + + public void RefreshSubAssets( bool? shouldSearchChildren = null ) + { + if( subAssets == null ) + subAssets = new List(); + else + subAssets.Clear(); + + if( currentSubAssets == null ) + currentSubAssets = new HashSet(); + else + currentSubAssets.Clear(); + + AddSubAssets( obj, false, shouldSearchChildren ); + currentSubAssets.Clear(); + } + + private void AddSubAssets( Object target, bool includeTarget, bool? shouldSearchChildren ) + { + if( target == null || target.Equals( null ) ) + return; + + if( !target.IsAsset() ) + { + GameObject go = target as GameObject; + if( !go || !go.scene.IsValid() ) + return; + + // If this is a scene object, add its child objects to the sub-assets list + // but don't include them in the search by default + Transform goTransform = go.transform; + Transform[] children = go.GetComponentsInChildren( true ); + for( int i = 0; i < children.Length; i++ ) + { + if( ReferenceEquals( children[i], goTransform ) ) + continue; + + subAssets.Add( new SubAsset( children[i].gameObject, shouldSearchChildren ?? false ) ); + } + } + else + { + if( !AssetDatabase.IsMainAsset( target ) || target is SceneAsset ) + return; + + if( includeTarget ) + { + if( currentSubAssets.Add( target ) ) + subAssets.Add( new SubAsset( target, shouldSearchChildren ?? true ) ); + } + else + { + // If asset is a directory, add all of its contents as sub-assets recursively + if( target.IsFolder() ) + { + foreach( string filePath in Utilities.EnumerateFolderContents( target ) ) + AddSubAssets( AssetDatabase.LoadAssetAtPath( filePath ), true, shouldSearchChildren ); + + return; + } + } + + // Find sub-asset(s) of the asset (if any) + Object[] assets = AssetDatabase.LoadAllAssetsAtPath( AssetDatabase.GetAssetPath( target ) ); + for( int i = 0; i < assets.Length; i++ ) + { + Object asset = assets[i]; + if( asset == null || asset.Equals( null ) || asset is Component || asset == target ) + continue; + +#if UNITY_2018_3_OR_NEWER + // Nested prefabs in prefab assets add an additional native object of type 'UnityEngine.PrefabInstance' to the prefab. Managed type of that native type + // is UnityEngine.Object (i.e. GetType() returns UnityEngine.Object, not UnityEngine.PrefabInstance). There are no possible references to these native + // objects so skip them (we're checking for UnityEngine.Prefab because it includes other native types like UnityEngine.PrefabCreation, as well) + if( target is GameObject && asset.GetType() == typeof( Object ) && asset.ToString().Contains( "(UnityEngine.Prefab" ) ) + continue; +#endif + + if( currentSubAssets.Add( asset ) ) + subAssets.Add( new SubAsset( asset, shouldSearchChildren ?? true ) ); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs.meta new file mode 100644 index 00000000..787b3365 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66d5a144a723fea40945afc069d4231d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs b/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs new file mode 100644 index 00000000..ea2bf999 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace AssetUsageDetectorNamespace +{ + public delegate void SearchRefactoring( SearchMatch match ); + + public abstract class SearchMatch + { + public readonly object Source; + public readonly Object Context; // Almost always equal to Source. This is the Object that needs to be dirtied (if not null) to notify Unity of changes to Value + public Object Value { get; private set; } + + protected SearchMatch( object source, Object value ) + { + Source = source; + Context = source as Object; + Value = value; + } + + protected SearchMatch( object source, Object value, Object context ) : this( source, value ) + { + Context = context; + } + + public void ChangeValue( Object newValue ) + { + if( newValue == Value ) + return; + + if( Context && ( Context.hideFlags & HideFlags.NotEditable ) == HideFlags.NotEditable ) + { + Debug.LogWarning( "Can't change value of read-only Object: " + Context, Context ); + return; + } + + try + { + bool setContextDirty; + if( ChangeValue( newValue, out setContextDirty ) ) + OnValueChanged( newValue, setContextDirty ); + } + catch( Exception e ) + { + Debug.LogException( e ); + } + } + + protected abstract bool ChangeValue( Object newValue, out bool setContextDirty ); + + public void OnValueChanged( Object newValue, bool setContextDirty = true ) + { + Value = newValue; + + if( setContextDirty ) + { + if( Context ) + { + if( AssetDatabase.Contains( Context ) ) + EditorUtility.SetDirty( Context ); + else if( !EditorApplication.isPlaying ) + { + EditorUtility.SetDirty( Context ); + + if( Context is Component ) + EditorSceneManager.MarkSceneDirty( ( (Component) Context ).gameObject.scene ); + else if( Context is GameObject ) + EditorSceneManager.MarkSceneDirty( ( (GameObject) Context ).scene ); + else + EditorSceneManager.MarkAllScenesDirty(); + } + } + else if( !EditorApplication.isPlaying ) + EditorSceneManager.MarkAllScenesDirty(); + } + } + } + + public abstract class GenericSearchMatch : SearchMatch + { + public delegate void SetterFunction( Object newValue ); + + public readonly SetterFunction Setter; + + internal GenericSearchMatch( object source, Object value, SetterFunction setter ) : base( source, value ) { Setter = setter; } + internal GenericSearchMatch( object source, Object value, Object context, SetterFunction setter ) : base( source, value, context ) { Setter = setter; } + + protected override bool ChangeValue( Object newValue, out bool setContextDirty ) + { + Setter( newValue ); + + setContextDirty = true; + return true; + } + } + + public abstract class ReadOnlySearchMatch : SearchMatch + { + internal ReadOnlySearchMatch( object source, Object value ) : base( source, value ) { } + + protected override bool ChangeValue( Object newValue, out bool setContextDirty ) + { + Debug.LogWarning( "Can't change value of " + GetType().Name ); + + setContextDirty = false; + return false; + } + } + + /// + /// - Source: Object whose SerializedProperty points to Value + /// - Value: Referenced object + /// - SerializedProperty: The SerializedProperty that points to Value + /// + public class SerializedPropertyMatch : SearchMatch + { + public readonly SerializedProperty SerializedProperty; // Next or NextVisible mustn't be called with this SerializedProperty + + internal SerializedPropertyMatch( Object source, Object value, SerializedProperty property ) : base( source, value ) { SerializedProperty = property; } + + protected override bool ChangeValue( Object newValue, out bool setContextDirty ) + { + setContextDirty = true; + + switch( SerializedProperty.propertyType ) + { + case SerializedPropertyType.ObjectReference: + SerializedProperty.objectReferenceValue = newValue; + if( SerializedProperty.objectReferenceValue != newValue ) + { + Debug.LogWarning( "Couldn't cast " + newValue.GetType() + " to " + SerializedProperty.type ); + SerializedProperty.objectReferenceValue = Value; + + return false; + } + + break; + case SerializedPropertyType.ExposedReference: + SerializedProperty.exposedReferenceValue = newValue; + if( SerializedProperty.exposedReferenceValue != newValue ) + { + Debug.LogWarning( "Couldn't cast " + newValue.GetType() + " to " + SerializedProperty.type ); + SerializedProperty.exposedReferenceValue = Value; + + return false; + } + + break; +#if UNITY_2019_3_OR_NEWER + case SerializedPropertyType.ManagedReference: SerializedProperty.managedReferenceValue = newValue; break; +#endif + } + + SerializedProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); + return true; + } + } + + /// + /// - Source: Object whose variable points to Value + /// - Value: Referenced object + /// - Variable: FieldInfo, PropertyInfo or IEnumerable (ChangeValue may not work for all IEnumerables) + /// + public class ReflectionMatch : SearchMatch + { + public readonly object Variable; + + internal ReflectionMatch( object source, Object value, object variable ) : base( source, value ) { Variable = variable; } + + protected override bool ChangeValue( Object newValue, out bool setContextDirty ) + { + setContextDirty = true; + + if( Variable is FieldInfo ) + ( (FieldInfo) Variable ).SetValue( Source, newValue ); + else if( Variable is PropertyInfo ) + { + PropertyInfo property = (PropertyInfo) Variable; + if( !property.CanWrite ) + { + Debug.LogWarning( "Property is read-only: " + property.DeclaringType.FullName + "." + property.Name ); + return false; + } + + property.SetValue( Source, newValue, null ); + } + else if( Variable is IList ) + { + IList list = (IList) Variable; + for( int i = list.Count - 1; i >= 0; i-- ) + { + if( ReferenceEquals( list[i], Value ) ) + list[i] = newValue; + } + } + else if( Variable is IDictionary ) + { + IDictionary dictionary = (IDictionary) Variable; + bool dictionaryModified; + do + { + dictionaryModified = false; + foreach( object dictKey in dictionary.Keys ) + { + object dictValue = dictionary[dictKey]; + if( ReferenceEquals( dictKey, Value ) ) + { + dictionary.Remove( dictKey ); + if( newValue ) + dictionary[newValue] = dictValue; + + dictionaryModified = true; + break; + } + else if( ReferenceEquals( dictValue, Value ) ) + { + dictionary[dictKey] = newValue; + dictionaryModified = true; + break; + } + } + } while( dictionaryModified ); + } + else + { + Debug.LogWarning( "Can't change value of " + Variable.GetType().Name ); + return false; + } + + return true; + } + } + + /// + /// - Source: MonoImporter (for scripts) or ShaderImporter + /// - Value: Default value assigned to Source's specified variable in the Inspector + /// - Variable: The variable of Source that Value is assigned to as default value + /// - MonoScriptAllVariables: All variables of Source script if it's MonoImporter + /// + public class AssetImporterDefaultValueMatch : SearchMatch + { + public readonly string Variable; + public readonly VariableGetterHolder[] MonoScriptAllVariables; + + internal AssetImporterDefaultValueMatch( Object source, Object value, string variable, VariableGetterHolder[] monoScriptAllVariables ) : base( source, value ) + { + Variable = variable; + MonoScriptAllVariables = monoScriptAllVariables; + } + + protected override bool ChangeValue( Object newValue, out bool setContextDirty ) + { + setContextDirty = false; + + if( Source is MonoImporter ) + { + MonoImporter monoImporter = (MonoImporter) Source; + + List variableNames = new List( 8 ); + List variableValues = new List( 8 ); + + for( int i = 0; i < MonoScriptAllVariables.Length; i++ ) + { + if( MonoScriptAllVariables[i].isSerializable && !MonoScriptAllVariables[i].IsProperty ) + { + Object variableDefaultValue = monoImporter.GetDefaultReference( MonoScriptAllVariables[i].Name ); + if( variableDefaultValue == Value && MonoScriptAllVariables[i].Name == Variable ) + variableDefaultValue = newValue; + + variableNames.Add( MonoScriptAllVariables[i].Name ); + variableValues.Add( variableDefaultValue ); + } + } + + monoImporter.SetDefaultReferences( variableNames.ToArray(), variableValues.ToArray() ); + EditorApplication.delayCall += () => AssetDatabase.ImportAsset( monoImporter.assetPath ); // If code recompiles during search, it will break the search. Give it a 1 frame delay + } + else if( Source is ShaderImporter ) + { + ShaderImporter shaderImporter = (ShaderImporter) Source; + Shader shader = shaderImporter.GetShader(); + + List textureNames = new List( 16 ); + List textureValues = new List( 16 ); +#if UNITY_2018_1_OR_NEWER + List nonModifiableTextureNames = new List( 16 ); + List nonModifiableTextureValues = new List( 16 ); +#endif + + int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader ); + for( int i = 0; i < shaderPropertyCount; i++ ) + { + if( ShaderUtil.GetPropertyType( shader, i ) != ShaderUtil.ShaderPropertyType.TexEnv ) + continue; + + string propertyName = ShaderUtil.GetPropertyName( shader, i ); +#if UNITY_2018_1_OR_NEWER + if( ShaderUtil.IsShaderPropertyNonModifiableTexureProperty( shader, i ) ) + { + Texture propertyDefaultValue = shaderImporter.GetNonModifiableTexture( propertyName ); + if( propertyDefaultValue == Value && propertyName == Variable ) + propertyDefaultValue = (Texture) newValue; + + nonModifiableTextureNames.Add( propertyName ); + nonModifiableTextureValues.Add( propertyDefaultValue ); + } + else +#endif + { + Texture propertyDefaultValue = shaderImporter.GetDefaultTexture( propertyName ); + if( propertyDefaultValue == Value && propertyName == Variable ) + propertyDefaultValue = (Texture) newValue; + + textureNames.Add( propertyName ); + textureValues.Add( propertyDefaultValue ); + } + } + + shaderImporter.SetDefaultTextures( textureNames.ToArray(), textureValues.ToArray() ); +#if UNITY_2018_1_OR_NEWER + shaderImporter.SetNonModifiableTextures( nonModifiableTextureNames.ToArray(), nonModifiableTextureValues.ToArray() ); +#endif + AssetDatabase.ImportAsset( shaderImporter.assetPath ); + } + else + { + Debug.LogWarning( "Can't change default value of: " + Source.GetType() ); + return false; + } + + return true; + } + } + + /// + /// - Source: Animation, Animator, AnimatorStateMachine, AnimatorState, AnimatorControllerLayer, BlendTree, PlayableDirector* or AnimationClip* + /// - Context: If Source is AnimatorControllerLayer, then its RuntimeAnimatorController. Otherwise, equal to Source + /// - Value: AnimationClip, AnimatorController or AvatarMask used in Source (*for PlayableDirector and AnimationClip, it can be any Object value) + /// + public class AnimationSystemMatch : GenericSearchMatch + { + internal AnimationSystemMatch( object source, Object value, SetterFunction setter ) : base( source, value, setter ) { } + internal AnimationSystemMatch( object source, Object value, Object context, SetterFunction setter ) : base( source, value, context, setter ) { } + } + + /// + /// - Source: GameObject, AnimatorStateMachine or AnimatorState + /// - Value: The attached behaviour's source script (C# script or DLL, i.e. MonoScript) + /// - Behaviour: The attached behaviour (MonoBehaviour or StateMachineBehaviour) + /// + public class BehaviourUsageMatch : ReadOnlySearchMatch + { + public readonly Object Behaviour; + + internal BehaviourUsageMatch( Object source, MonoScript value, Object behaviour ) : base( source, value ) { Behaviour = behaviour; } + } + + /// + /// - Source: GameObject Instance + /// - Value: Prefab of that GameObject + /// + public class PrefabMatch : ReadOnlySearchMatch + { + internal PrefabMatch( Object source, Object value ) : base( source, value ) { } + } + + /// + /// - Source: Object that references Value + /// - Value: Matched object + /// + public class OtherSearchMatch : GenericSearchMatch + { + internal OtherSearchMatch( object source, Object value, SetterFunction setter ) : base( source, value, setter ) { } + internal OtherSearchMatch( object source, Object value, Object context, SetterFunction setter ) : base( source, value, context, setter ) { } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs.meta new file mode 100644 index 00000000..c586f0cc --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3d15ef8bd8f7c7c4e8d228c99713b7eb +timeCreated: 1641132238 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs b/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs new file mode 100644 index 00000000..e9b66413 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs @@ -0,0 +1,1395 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace AssetUsageDetectorNamespace +{ + // Custom class to hold search results + [Serializable] + public class SearchResult : IEnumerable, ISerializationCallbackReceiver + { + [Serializable] + internal class SerializableResultGroup + { + public string title; + public SearchResultGroup.GroupType type; + public bool isExpanded; + public bool pendingSearch; + public SearchResultTreeViewState treeViewState; + + public List initialSerializedNodes; + } + + [Serializable] + internal class SerializableNode + { + [Serializable] + public class SerializableLinkDescriptions + { + public List value; + } + + public string label; + public int instanceId; + public bool isUnityObject, isMainReference; + public ReferenceNode.UsedState usedState; + + public List links; + public List linkDescriptions; + public List linkWeakStates; + } + + internal class SortedEntry : IComparable + { + public readonly string assetPath, subAssetName; + public readonly bool isMainAsset; + public readonly Transform transform; + public readonly object entry; + + public SortedEntry( ReferenceNode node ) : this( node.nodeObject as Object ) + { + entry = node; + } + + public SortedEntry( ReferenceNode.Link link ) : this( link.targetNode.nodeObject as Object ) + { + entry = link; + } + + private SortedEntry( Object obj ) + { + if( obj ) + { + assetPath = AssetDatabase.GetAssetPath( obj ); + if( string.IsNullOrEmpty( assetPath ) ) + { + assetPath = null; + + if( obj is Component ) + transform = ( (Component) obj ).transform; + else if( obj is GameObject ) + transform = ( (GameObject) obj ).transform; + } + else + { + isMainAsset = AssetDatabase.IsMainAsset( ( obj is Component ) ? ( (Component) obj ).gameObject : obj ); + if( !isMainAsset ) + subAssetName = obj.name; + } + } + } + + // Sorting order: + // 1) Scene objects come first and are sorted by their absolute sibling indices in Hierarchy + // 2) Assets come later and are sorted by their asset paths (for assets sharing the same path, main assets come first and sub-assets are then sorted by their names) + // 3) Regular C# objects come last + int IComparable.CompareTo( SortedEntry other ) + { + if( this == other ) + return 0; + else if( assetPath == null ) + { + if( !transform ) + return 1; + else if( other.assetPath == null ) + return other.transform ? Utilities.CompareHierarchySiblingIndices( transform, other.transform ) : -1; + else + return -1; + } + else if( other.assetPath == null ) + return other.transform ? 1 : -1; + else + { + int assetPathComparison = EditorUtility.NaturalCompare( assetPath, other.assetPath ); + if( assetPathComparison != 0 ) + return assetPathComparison; + else if( isMainAsset ) + return -1; + else if( other.isMainAsset ) + return 1; + else + return subAssetName.CompareTo( other.subAssetName ); + } + } + } + + private bool success; // This isn't readonly so that it can be serialized + + private List result; + private SceneSetup[] initialSceneSetup; + + private AssetUsageDetector searchHandler; + private AssetUsageDetector.Parameters m_searchParameters; + + // Each TreeView in the drawn search results must use unique ids for their TreeViewItems. Otherwise, strange things happen: https://forum.unity.com/threads/multiple-editor-treeviews-selection-issue.601471/ + internal int nextTreeViewId = 1; + + private List serializedNodes; + private List serializedGroups; + private Object[] serializedUsedObjects; + + public int NumberOfGroups { get { return result.Count; } } + public SearchResultGroup this[int index] { get { return result[index]; } } + + public HashSet UsedObjects { get; private set; } + + public bool SearchCompletedSuccessfully { get { return success; } } + public bool InitialSceneSetupConfigured { get { return initialSceneSetup != null && initialSceneSetup.Length > 0; } } + public bool HasPendingLazySceneSearchResults { get { return result.Find( ( group ) => group.PendingSearch ) != null; } } + public AssetUsageDetector.Parameters SearchParameters { get { return m_searchParameters; } } + + public SearchResult( bool success, List result, HashSet usedObjects, SceneSetup[] initialSceneSetup, AssetUsageDetector searchHandler, AssetUsageDetector.Parameters searchParameters ) + { + if( result == null ) + result = new List( 0 ); + + this.success = success; + this.result = result; + this.UsedObjects = usedObjects ?? new HashSet(); + this.initialSceneSetup = initialSceneSetup; + this.searchHandler = searchHandler; + this.m_searchParameters = searchParameters; + } + + public void RemoveSearchResultGroup( SearchResultGroup searchResultGroup ) + { + result.Remove( searchResultGroup ); + } + + public void RefreshSearchResultGroup( SearchResultGroup searchResultGroup, bool noAssetDatabaseChanges ) + { + if( searchResultGroup == null ) + { + Debug.LogError( "SearchResultGroup is null!" ); + return; + } + + int searchResultGroupIndex = result.IndexOf( searchResultGroup ); + if( searchResultGroupIndex < 0 ) + { + Debug.LogError( "SearchResultGroup is not a part of SearchResult!" ); + return; + } + + if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene && EditorApplication.isPlaying && !EditorSceneManager.GetSceneByPath( searchResultGroup.ScenePath ).isLoaded ) + { + Debug.LogError( "Can't search unloaded scene while in Play Mode!" ); + return; + } + + if( searchHandler == null ) + searchHandler = new AssetUsageDetector(); + + SceneSearchMode searchInScenes = m_searchParameters.searchInScenes; + Object[] searchInScenesSubset = m_searchParameters.searchInScenesSubset; + bool searchInAssetsFolder = m_searchParameters.searchInAssetsFolder; + Object[] searchInAssetsSubset = m_searchParameters.searchInAssetsSubset; + bool searchInProjectSettings = m_searchParameters.searchInProjectSettings; + bool lazySceneSearch = m_searchParameters.lazySceneSearch; + bool calculateUnusedObjects = m_searchParameters.calculateUnusedObjects; + bool _noAssetDatabaseChanges = m_searchParameters.noAssetDatabaseChanges; + + try + { + if( searchResultGroup.Type == SearchResultGroup.GroupType.Assets ) + { + m_searchParameters.searchInScenes = SceneSearchMode.None; + m_searchParameters.searchInScenesSubset = null; + m_searchParameters.searchInProjectSettings = false; + } + else if( searchResultGroup.Type == SearchResultGroup.GroupType.ProjectSettings ) + { + m_searchParameters.searchInScenes = SceneSearchMode.None; + m_searchParameters.searchInScenesSubset = null; + m_searchParameters.searchInAssetsFolder = false; + m_searchParameters.searchInAssetsSubset = null; + m_searchParameters.searchInProjectSettings = true; + } + else if( searchResultGroup.Type == SearchResultGroup.GroupType.Scene ) + { + m_searchParameters.searchInScenes = SceneSearchMode.None; + m_searchParameters.searchInScenesSubset = new Object[1] { AssetDatabase.LoadAssetAtPath( searchResultGroup.ScenePath ) }; + m_searchParameters.searchInAssetsFolder = false; + m_searchParameters.searchInAssetsSubset = null; + m_searchParameters.searchInProjectSettings = false; + } + else if( searchResultGroup.Type == SearchResultGroup.GroupType.DontDestroyOnLoad ) + { + m_searchParameters.searchInScenes = (SceneSearchMode) 1024; // A unique value to search only the DontDestroyOnLoad scene + m_searchParameters.searchInScenesSubset = null; + m_searchParameters.searchInAssetsFolder = false; + m_searchParameters.searchInAssetsSubset = null; + m_searchParameters.searchInProjectSettings = false; + } + else + { + Debug.LogError( "Can't refresh group: " + searchResultGroup.Type ); + return; + } + + m_searchParameters.lazySceneSearch = false; + m_searchParameters.calculateUnusedObjects = result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects ) != null; + m_searchParameters.noAssetDatabaseChanges = noAssetDatabaseChanges; + + // Make sure the AssetDatabase is up-to-date + AssetDatabase.SaveAssets(); + + SearchResult searchResult = searchHandler.Run( m_searchParameters ); + if( !searchResult.success ) + { + EditorUtility.DisplayDialog( "Error", "Couldn't refresh, check console for more info.", "OK" ); + return; + } + + if( searchResult.result != null ) + { + SearchResultGroup newSearchResultGroup = searchResult.result.Find( ( group ) => group.Title == searchResultGroup.Title ); + if( newSearchResultGroup != null ) + result[searchResultGroupIndex] = newSearchResultGroup; + else + searchResultGroup.Clear(); + + UsedObjects.UnionWith( searchResult.UsedObjects ); + + SearchResultGroup unusedObjectsSearchResultGroup = result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects ); + if( unusedObjectsSearchResultGroup != null ) + { + SearchResultGroup newUnusedObjectsSearchResultGroup = searchResult.result.Find( ( group ) => group.Type == SearchResultGroup.GroupType.UnusedObjects ); + if( newUnusedObjectsSearchResultGroup == null ) + { + // UnusedObjects search result group doesn't exist in 2 cases: + // - When there are no search results found (NumberOfGroups == 0) + // - When all searched objects are referenced (NumberOfGroups > 0) + if( searchResult.result.Count > 0 ) + unusedObjectsSearchResultGroup.Clear(); + } + else + { + // NOTE: We can process UnusedObjects graphs iteratively (instead of recursively) because for the time being, these graphs have a maximum depth of 1 + bool unusedObjectsGraphChanged = false; + HashSet newUnusedObjectsSet = new HashSet(); + for( int i = newUnusedObjectsSearchResultGroup.NumberOfReferences - 1; i >= 0; i-- ) + { + ReferenceNode node = newUnusedObjectsSearchResultGroup[i]; + newUnusedObjectsSet.Add( node.UnityObject ); + + for( int j = node.NumberOfOutgoingLinks - 1; j >= 0; j-- ) + newUnusedObjectsSet.Add( node[j].targetNode.UnityObject ); + } + + for( int i = unusedObjectsSearchResultGroup.NumberOfReferences - 1; i >= 0; i-- ) + { + ReferenceNode node = unusedObjectsSearchResultGroup[i]; + bool parentNodeRemoved = false; + Object obj = node.UnityObject; + if( !obj || !newUnusedObjectsSet.Contains( obj ) ) + { + unusedObjectsSearchResultGroup.RemoveReference( i ); + unusedObjectsGraphChanged = parentNodeRemoved = true; + } + + bool hasUnusedSubObjects = false, hasUsedSubObjects = false; + bool hadSubObjects = node.NumberOfOutgoingLinks > 0; + for( int j = 0; j < node.NumberOfOutgoingLinks; j++ ) + { + if( node[j].targetNode.usedState == ReferenceNode.UsedState.Used ) // User has explicitly displayed this used child object/sub-asset in the TreeView + continue; + + Object _obj = node[j].targetNode.UnityObject; + if( newUnusedObjectsSet.Contains( _obj ) ) + { + hasUnusedSubObjects = true; + + if( parentNodeRemoved ) + unusedObjectsSearchResultGroup.AddReference( node[j].targetNode ); + } + else if( !parentNodeRemoved ) + { + node.RemoveLink( j-- ); + unusedObjectsGraphChanged = hasUsedSubObjects = true; + } + } + + if( !parentNodeRemoved ) + { + // When all sub-assets of a main asset are used, consider the main asset as used, as well + if( !hasUnusedSubObjects && hadSubObjects && AssetDatabase.IsMainAsset( obj ) ) + { + unusedObjectsSearchResultGroup.RemoveReference( i ); + unusedObjectsGraphChanged = true; + } + + if( hasUsedSubObjects && node.usedState == ReferenceNode.UsedState.Unused ) + node.usedState = ReferenceNode.UsedState.MixedCollapsed; + } + } + + if( unusedObjectsGraphChanged && unusedObjectsSearchResultGroup.treeView != null ) + { + unusedObjectsSearchResultGroup.treeView.SetSelection( new int[0], TreeViewSelectionOptions.FireSelectionChanged ); + unusedObjectsSearchResultGroup.treeViewState.preSearchExpandedIds = null; + unusedObjectsSearchResultGroup.treeView.Reload(); + unusedObjectsSearchResultGroup.treeView.ExpandAll(); + } + } + + if( unusedObjectsSearchResultGroup.NumberOfReferences == 0 ) + result.Remove( unusedObjectsSearchResultGroup ); + } + } + } + finally + { + m_searchParameters.searchInScenes = searchInScenes; + m_searchParameters.searchInScenesSubset = searchInScenesSubset; + m_searchParameters.searchInAssetsFolder = searchInAssetsFolder; + m_searchParameters.searchInAssetsSubset = searchInAssetsSubset; + m_searchParameters.searchInProjectSettings = searchInProjectSettings; + m_searchParameters.lazySceneSearch = lazySceneSearch; + m_searchParameters.calculateUnusedObjects = calculateUnusedObjects; + m_searchParameters.noAssetDatabaseChanges = _noAssetDatabaseChanges; + } + } + + public float DrawOnGUI( EditorWindow window, float scrollPosition, bool noAssetDatabaseChanges ) + { + for( int i = 0; i < result.Count; i++ ) + { + scrollPosition = result[i].DrawOnGUI( this, window, scrollPosition, noAssetDatabaseChanges ); + + if( i < result.Count - 1 ) + GUILayout.Space( 10f ); + } + + return scrollPosition; + } + + public int IndexOf( SearchResultGroup searchResultGroup ) + { + return result.IndexOf( searchResultGroup ); + } + + public void CollapseAllSearchResultGroups() + { + for( int i = 0; i < result.Count; i++ ) + result[i].Collapse(); + } + + public void CancelDelayedTreeViewTooltip() + { + for( int i = 0; i < result.Count; i++ ) + { + if( result[i].treeView != null ) + result[i].treeView.CancelDelayedTooltip(); + } + } + + // Returns if RestoreInitialSceneSetup will have any effect on the current scene setup + public bool IsSceneSetupDifferentThanCurrentSetup() + { + if( initialSceneSetup == null ) + return false; + + SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup(); + if( initialSceneSetup.Length != sceneFinalSetup.Length ) + return true; + + for( int i = 0; i < sceneFinalSetup.Length; i++ ) + { + bool sceneIsOneOfInitials = false; + for( int j = 0; j < initialSceneSetup.Length; j++ ) + { + if( sceneFinalSetup[i].path == initialSceneSetup[j].path ) + { + if( sceneFinalSetup[i].isLoaded != initialSceneSetup[j].isLoaded ) + return true; + + sceneIsOneOfInitials = true; + break; + } + } + + if( !sceneIsOneOfInitials ) + return true; + } + + return false; + } + + // Close the scenes that were not part of the initial scene setup + // Returns true if initial scene setup is restored successfully + public bool RestoreInitialSceneSetup() + { + if( initialSceneSetup == null || initialSceneSetup.Length == 0 ) + return true; + + if( EditorApplication.isPlaying ) + return false; + + if( !IsSceneSetupDifferentThanCurrentSetup() ) + return true; + + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + + sb.AppendLine( "Restore initial scene setup?" ); + for( int i = 0; i < initialSceneSetup.Length; i++ ) + sb.AppendLine().Append( "- " ).Append( initialSceneSetup[i].path ); + + switch( EditorUtility.DisplayDialogComplex( "Asset Usage Detector", sb.ToString(), "Yes", "Cancel", "Leave it as is" ) ) + { + case 1: return false; + case 2: return true; + } + + if( !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo() ) + return false; + + for( int i = 0; i < initialSceneSetup.Length; i++ ) + { + Scene scene = EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path ); + if( !scene.isLoaded ) + scene = EditorSceneManager.OpenScene( initialSceneSetup[i].path, initialSceneSetup[i].isLoaded ? OpenSceneMode.Additive : OpenSceneMode.AdditiveWithoutLoading ); + + if( initialSceneSetup[i].isActive ) + EditorSceneManager.SetActiveScene( scene ); + } + + SceneSetup[] sceneFinalSetup = EditorSceneManager.GetSceneManagerSetup(); + for( int i = 0; i < sceneFinalSetup.Length; i++ ) + { + bool sceneIsOneOfInitials = false; + for( int j = 0; j < initialSceneSetup.Length; j++ ) + { + if( sceneFinalSetup[i].path == initialSceneSetup[j].path ) + { + sceneIsOneOfInitials = true; + break; + } + } + + if( !sceneIsOneOfInitials ) + EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( sceneFinalSetup[i].path ), true ); + } + + for( int i = 0; i < initialSceneSetup.Length; i++ ) + { + if( !initialSceneSetup[i].isLoaded ) + EditorSceneManager.CloseScene( EditorSceneManager.GetSceneByPath( initialSceneSetup[i].path ), false ); + } + + initialSceneSetup = null; + return true; + } + + // Assembly reloading; serialize nodes in a way that Unity can serialize + // Credit: https://docs.unity3d.com/Manual/script-Serialization-Custom.html + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + if( result == null ) + return; + + if( serializedGroups == null ) + serializedGroups = new List( result.Count ); + else + serializedGroups.Clear(); + + if( serializedNodes == null ) + serializedNodes = new List( result.Count * 16 ); + else + serializedNodes.Clear(); + + Dictionary nodeToIndex = new Dictionary( result.Count * 16 ); + for( int i = 0; i < result.Count; i++ ) + serializedGroups.Add( result[i].Serialize( nodeToIndex, serializedNodes ) ); + + if( serializedUsedObjects == null || serializedUsedObjects.Length != UsedObjects.Count ) + serializedUsedObjects = new Object[UsedObjects.Count]; + + UsedObjects.CopyTo( serializedUsedObjects ); + } + + // Assembly reloaded; deserialize nodes to construct the original graph + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + if( serializedGroups == null || serializedNodes == null || serializedUsedObjects == null ) + return; + + if( result == null ) + result = new List( serializedGroups.Count ); + else + result.Clear(); + + List allNodes = new List( serializedNodes.Count ); + for( int i = 0; i < serializedNodes.Count; i++ ) + allNodes.Add( new ReferenceNode() ); + + for( int i = 0; i < serializedNodes.Count; i++ ) + allNodes[i].Deserialize( serializedNodes[i], allNodes ); + + for( int i = 0; i < serializedGroups.Count; i++ ) + { + result.Add( new SearchResultGroup( serializedGroups[i].title, serializedGroups[i].type, serializedGroups[i].isExpanded, serializedGroups[i].pendingSearch ) ); + result[i].Deserialize( serializedGroups[i], allNodes ); + } + + if( UsedObjects == null ) + UsedObjects = new HashSet( serializedUsedObjects ); + else + UsedObjects.UnionWith( serializedUsedObjects ); + + serializedNodes.Clear(); + serializedGroups.Clear(); + serializedUsedObjects = null; + } + + IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) result ).GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) result ).GetEnumerator(); } + } + + // Custom class to hold the results for a single scene or Assets folder + public class SearchResultGroup : IEnumerable + { + public enum GroupType { Assets = 0, Scene = 1, DontDestroyOnLoad = 2, ProjectSettings = 3, UnusedObjects = 4 }; + + private readonly List references = new List(); + + internal SearchResultTreeView treeView; + internal SearchResultTreeViewState treeViewState; + private Rect lastTreeViewRect; + private SearchField treeViewSearchField; + + public string Title { get; private set; } + public GroupType Type { get; private set; } + public string ScenePath { get; private set; } + public bool IsExpanded { get; private set; } + public bool PendingSearch { get; private set; } + + public int NumberOfReferences { get { return references.Count; } } + public ReferenceNode this[int index] { get { return references[index]; } } + + public SearchResultGroup( string title, GroupType type, bool isExpanded = true, bool pendingSearch = false ) + { + Title = title.StartsWith( "" ) ? title : string.Concat( "", title, "" ); + ScenePath = type != GroupType.Scene ? null : ( title.StartsWith( "" ) ? title.Substring( 3, title.Length - 7 ) : title ); + Type = type; + IsExpanded = isExpanded; + PendingSearch = pendingSearch; + } + + public void AddReference( ReferenceNode node ) + { + references.Add( node ); + } + + public void RemoveReference( int index ) + { + references.RemoveAt( index ); + } + + // Removes all nodes + public void Clear() + { + PendingSearch = false; + + references.Clear(); + + treeView = null; + treeViewState = null; + treeViewSearchField = null; + } + + public void Collapse() + { + IsExpanded = false; + } + + // Initializes commonly used variables of the nodes + public void InitializeNodes( HashSet objectsToSearchSet ) + { + List _references = new List( references ); + references.Clear(); + + // Reverse the links of the search results graph so that the root ReferenceNodes are the searched objects + Dictionary reverseGraphNodes = new Dictionary( references.Count * 16 ); + for( int i = 0; i < _references.Count; i++ ) + _references[i].CreateReverseGraphRecursively( this, references, reverseGraphNodes, objectsToSearchSet ); + + // Remove weak links if they aren't ultimately connected to a non-weak link + HashSet visitedNodes = new HashSet(); + for( int i = references.Count - 1; i >= 0; i-- ) + references[i].RemoveRedundantLinksRecursively( visitedNodes ); + + // When a GameObject is a root node, then any components of that GameObject that are also root nodes should omit their links to the + // GameObject's node because otherwise, search results are filled with redundant 'GameObject->Its Component' references + HashSet rootGameObjectNodes = new HashSet(); + for( int i = references.Count - 1; i >= 0; i-- ) + { + if( references[i].nodeObject as GameObject ) + rootGameObjectNodes.Add( references[i] ); + } + + for( int i = references.Count - 1; i >= 0; i-- ) + { + ReferenceNode node = references[i]; + Component component = node.nodeObject as Component; + if( component ) + { + for( int j = node.NumberOfOutgoingLinks - 1; j >= 0; j-- ) + { + if( ReferenceEquals( node[j].targetNode.nodeObject, component.gameObject ) && rootGameObjectNodes.Contains( node[j].targetNode ) ) + { + node.RemoveLink( j ); + break; + } + } + } + } + + // Remove root nodes that don't have any outgoing links + for( int i = references.Count - 1; i >= 0; i-- ) + { + if( references[i].NumberOfOutgoingLinks == 0 ) + references.RemoveAt( i ); + } + + // Sort root nodes + if( references.Count > 1 ) + { + SearchResult.SortedEntry[] sortedEntries = new SearchResult.SortedEntry[references.Count]; + for( int i = references.Count - 1; i >= 0; i-- ) + sortedEntries[i] = new SearchResult.SortedEntry( references[i] ); + + Array.Sort( sortedEntries ); + + for( int i = 0; i < sortedEntries.Length; i++ ) + references[i] = (ReferenceNode) sortedEntries[i].entry; + } + + for( int i = references.Count - 1; i >= 0; i-- ) + { + references[i].SortLinks(); // Sort immediate links of the root nodes + references[i].InitializeRecursively(); + } + } + + // Draw the results found for this container + public float DrawOnGUI( SearchResult searchResult, EditorWindow window, float scrollPosition, bool noAssetDatabaseChanges ) + { + Event ev = Event.current; + Color c = GUI.backgroundColor; + + float headerHeight = EditorGUIUtility.singleLineHeight * 2f; + float refreshButtonWidth = 100f; + + GUI.backgroundColor = AssetUsageDetectorSettings.SearchResultGroupHeaderColor; + + Rect headerRect = EditorGUILayout.GetControlRect( false, headerHeight ); + float width = headerRect.width; + headerRect.width = headerHeight; + if( GUI.Button( headerRect, IsExpanded ? "v" : ">" ) ) + { + IsExpanded = !IsExpanded; + if( ev.alt && treeView != null ) + { + if( !IsExpanded ) + treeView.CollapseAll(); + else + treeView.ExpandAll(); + } + + window.Repaint(); + GUIUtility.ExitGUI(); + } + + headerRect.x += headerHeight; + headerRect.width = width - ( searchResult != null ? ( refreshButtonWidth + headerHeight ) : headerHeight ); + + if( GUI.Button( headerRect, Title, Utilities.BoxGUIStyle ) ) + { + if( ev.button != 1 ) + { + if( Type == GroupType.Scene ) + { + // If the container (scene, usually) is left clicked, highlight it on Project view + SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath( ScenePath ); + if( sceneAsset ) + { + if( AssetUsageDetectorSettings.PingClickedObjects ) + EditorGUIUtility.PingObject( sceneAsset ); + if( AssetUsageDetectorSettings.SelectClickedObjects ) + Selection.activeObject = sceneAsset; + } + } + } + else + { + GenericMenu contextMenu = new GenericMenu(); + + if( searchResult != null ) + contextMenu.AddItem( new GUIContent( "Hide" ), false, () => searchResult.RemoveSearchResultGroup( this ) ); + + if( references.Count > 0 && treeView != null ) + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + if( Type != GroupType.UnusedObjects ) + { + contextMenu.AddItem( new GUIContent( "Expand Direct References Only" ), false, () => + { + treeView.ExpandDirectReferences(); + IsExpanded = true; + } ); + + contextMenu.AddItem( new GUIContent( "Expand Main References Only" ), false, () => + { + treeView.ExpandMainReferences(); + IsExpanded = true; + } ); + } + + if( !string.IsNullOrEmpty( treeViewState.searchTerm ) && treeViewState.searchMode == SearchResultTreeView.SearchMode.ReferencesOnly ) + { + contextMenu.AddItem( new GUIContent( "Expand Matching Search Results Only" ), false, () => + { + treeView.ExpandMatchingSearchResults(); + IsExpanded = true; + } ); + } + + contextMenu.AddItem( new GUIContent( "Expand All" ), false, () => + { + treeView.ExpandAll(); + IsExpanded = true; + } ); + + contextMenu.AddItem( new GUIContent( "Collapse All" ), false, () => + { + treeView.CollapseAll(); + IsExpanded = true; + } ); + + if( searchResult != null && searchResult.NumberOfGroups > 1 && !string.IsNullOrEmpty( treeViewState.searchTerm ) ) + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + contextMenu.AddItem( new GUIContent( "Apply Search to All Results" ), false, () => + { + for( int i = 0; i < searchResult.NumberOfGroups; i++ ) + { + if( searchResult[i].treeView == null ) + continue; + + string previousSearchTerm = searchResult[i].treeViewState.searchTerm ?? ""; + SearchResultTreeView.SearchMode previousSearchMode = searchResult[i].treeViewState.searchMode; + + searchResult[i].treeViewState.searchTerm = treeViewState.searchTerm ?? ""; + searchResult[i].treeViewState.searchMode = treeViewState.searchMode; + + if( treeViewState.searchTerm != previousSearchTerm || treeViewState.searchMode != previousSearchMode ) + searchResult[i].treeView.RefreshSearch( previousSearchTerm ); + } + } ); + } + } + +#if UNITY_2022_2_OR_NEWER + int loadedSceneCount = SceneManager.loadedSceneCount; +#else + int loadedSceneCount = EditorSceneManager.loadedSceneCount; +#endif + if( Type == GroupType.Scene && !EditorApplication.isPlaying && loadedSceneCount > 1 ) + { + // Show context menu when SearchResultGroup's header is right clicked + Scene scene = EditorSceneManager.GetSceneByPath( ScenePath ); + if( scene.isLoaded ) + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + contextMenu.AddItem( new GUIContent( "Close Scene" ), false, () => + { + if( !scene.isDirty || EditorSceneManager.SaveModifiedScenesIfUserWantsTo( new Scene[1] { scene } ) ) + EditorSceneManager.CloseScene( scene, true ); + } ); + } + } + + contextMenu.ShowAsContext(); + } + } + + if( searchResult != null ) + { + bool guiEnabled = GUI.enabled; + GUI.enabled = Type != GroupType.UnusedObjects; + + headerRect.x += width - ( refreshButtonWidth + headerHeight ); + headerRect.width = refreshButtonWidth; + if( GUI.Button( headerRect, "Refresh" ) ) + { + searchResult.RefreshSearchResultGroup( this, noAssetDatabaseChanges ); + GUIUtility.ExitGUI(); + } + + GUI.enabled = guiEnabled; + } + + GUI.backgroundColor = c; + + if( IsExpanded ) + { + if( PendingSearch ) + GUILayout.Box( "Lazy Search: this scene potentially has some references, hit Refresh to find them", Utilities.BoxGUIStyle ); + else if( references.Count == 0 ) + GUILayout.Box( ( Type == GroupType.UnusedObjects ) ? "No unused objects left..." : "No references found...", Utilities.BoxGUIStyle ); + else + { + if( Type == GroupType.UnusedObjects ) + { + if( searchResult != null && searchResult.HasPendingLazySceneSearchResults ) + EditorGUILayout.HelpBox( "Some scene(s) aren't searched yet (lazy scene search). Refreshing those scene(s) will automatically update this list.", MessageType.Warning ); + + if( searchResult != null && searchResult.SearchParameters.dontSearchInSourceAssets && searchResult.SearchParameters.objectsToSearch.Length > 1 ) + EditorGUILayout.HelpBox( "'Don't search \"SEARCHED OBJECTS\" themselves for references' is enabled, some of these objects might be used by \"SEARCHED OBJECTS\".", MessageType.Warning ); + + if( !AssetUsageDetectorSettings.MarkUsedAssetsSubAssetsAsUsed ) + EditorGUILayout.HelpBox( "'Hide unused sub-assets in \"Unused Objects\" list if their parent assets are used' is disabled, unused sub-assets' parent assets might be used.", MessageType.Warning ); + + EditorGUILayout.HelpBox( "Although no references to these objects are found, they might still be used somewhere (e.g. via Resources.Load). If you intend to delete these objects, consider creating a backup of your project first.", MessageType.Info ); + } + + if( treeView == null ) + { + bool isFirstInitialization = ( treeViewState == null ); + if( isFirstInitialization ) + treeViewState = new SearchResultTreeViewState(); + + // This isn't inside isFirstInitialization because SearchResultTreeViewState might have been initialized by + // Unity's serialization system after a domain reload + bool shouldUpdateInitialTreeViewNodeId = ( treeViewState.initialNodeId == 0 && searchResult != null ); + if( shouldUpdateInitialTreeViewNodeId ) + treeViewState.initialNodeId = searchResult.nextTreeViewId; + + treeView = new SearchResultTreeView( treeViewState, references, ( Type == GroupType.UnusedObjects ) ? SearchResultTreeView.TreeType.UnusedObjects : SearchResultTreeView.TreeType.Normal, searchResult != null ? searchResult.UsedObjects : null, searchResult != null && searchResult.SearchParameters.hideDuplicateRows, searchResult != null && searchResult.SearchParameters.hideReduntantPrefabVariantLinks, true ); + + if( isFirstInitialization ) + { + if( Type != GroupType.UnusedObjects ) + treeView.ExpandMainReferences(); + else + treeView.ExpandAll(); + } + + if( shouldUpdateInitialTreeViewNodeId ) + searchResult.nextTreeViewId = treeViewState.finalNodeId; + } + + if( treeViewSearchField == null ) + { + treeViewSearchField = new SearchField() { autoSetFocusOnFindCommand = false }; + treeViewSearchField.downOrUpArrowKeyPressed += () => treeView.SetFocusAndEnsureSelectedItem(); // Not assigning SetFocusAndEnsureSelectedItem directly in case treeView's value changes + } + + Rect searchFieldRect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight ); + string previousSearchTerm = treeViewState.searchTerm ?? ""; + SearchResultTreeView.SearchMode previousSearchMode = treeViewState.searchMode; + treeViewState.searchTerm = treeViewSearchField.OnToolbarGUI( new Rect( searchFieldRect.x, searchFieldRect.y, searchFieldRect.width - 100f, searchFieldRect.height ), treeViewState.searchTerm ) ?? ""; + treeViewState.searchMode = (SearchResultTreeView.SearchMode) EditorGUI.EnumPopup( new Rect( searchFieldRect.xMax - 100f, searchFieldRect.y, 100f, searchFieldRect.height ), treeViewState.searchMode ); + if( treeViewState.searchTerm != previousSearchTerm || treeViewState.searchMode != previousSearchMode ) + treeView.RefreshSearch( previousSearchTerm ); + + KeyCode pressedKeyboardNavigationKey = KeyCode.None; + bool treeViewKeyboardNavigation = false; + if( ev.type == EventType.KeyDown ) + { + pressedKeyboardNavigationKey = ev.keyCode; + switch( pressedKeyboardNavigationKey ) + { + case KeyCode.UpArrow: + case KeyCode.DownArrow: + case KeyCode.LeftArrow: + case KeyCode.RightArrow: + case KeyCode.PageUp: + case KeyCode.PageDown: + case KeyCode.Home: + case KeyCode.End: + case KeyCode.F: treeViewKeyboardNavigation = true; break; + } + + SearchResultTooltip.Hide(); + } + else if( ( ev.type == EventType.ValidateCommand || ev.type == EventType.ExecuteCommand ) && ev.commandName == "Find" && treeView.HasFocus() ) + { + if( ev.type == EventType.ExecuteCommand ) + { + treeViewSearchField.SetFocus(); + + // Framed rect padding: Top = 2, Bottom = 2 + the first element in the TreeView + scrollPosition = FrameRectInScrollView( scrollPosition, new Vector2( searchFieldRect.y - 2f, searchFieldRect.yMax + EditorGUIUtility.singleLineHeight + 2f ), window.position.height ); + window.Repaint(); + + SearchResultTooltip.Hide(); + } + + ev.Use(); + } + else if( ev.type == EventType.ScrollWheel ) + SearchResultTooltip.Hide(); + + bool isFirstRowSelected = false, isLastRowSelected = false, isSelectedRowExpanded = false, canExpandSelectedRow = false; + if( treeViewKeyboardNavigation && treeView.HasFocus() && treeView.HasSelection() ) + treeView.GetRowStateWithId( treeViewState.lastClickedID, out isFirstRowSelected, out isLastRowSelected, out isSelectedRowExpanded, out canExpandSelectedRow ); + + Rect treeViewRect = EditorGUILayout.GetControlRect( false, treeView.totalHeight ); + if( ev.type == EventType.Repaint ) + { + lastTreeViewRect = treeViewRect; + +#if !UNITY_2018_2_OR_NEWER + // TreeView calls RowGUI for all rows instead of only the visible rows on early Unity versions which leads to performance issues. Do manual row culling on those versions + // Credit: https://github.com/Unity-Technologies/UnityCsReference/blob/a048de916b23331bf6dfe92c4a6c205989b83b4f/Editor/Mono/GUI/TreeView/TreeViewGUI.cs#L273-L276 + float topPixel = scrollPosition - treeViewRect.y; + float heightInPixels = window.position.height; + treeView.visibleRowTop = (int) Mathf.Floor( topPixel / treeView.rowHeight ); + treeView.visibleRowBottom = treeView.visibleRowTop + (int) Mathf.Ceil( heightInPixels / treeView.rowHeight ); +#endif + } + + treeView.OnGUI( treeViewRect ); + + if( treeViewKeyboardNavigation && treeView.HasFocus() && treeView.HasSelection() ) + { + Rect targetTreeViewRowRect; + if( treeView.GetRowRectWithId( treeViewState.lastClickedID, out targetTreeViewRowRect ) ) + { + // Allow keyboard navigation between different SearchResultGroups' TreeViews + Rect targetTreeViewRect = lastTreeViewRect; + if( !ev.control && !ev.command && !ev.shift ) + { + if( isFirstRowSelected && ( pressedKeyboardNavigationKey == KeyCode.UpArrow || pressedKeyboardNavigationKey == KeyCode.PageUp || pressedKeyboardNavigationKey == KeyCode.Home || ( pressedKeyboardNavigationKey == KeyCode.LeftArrow && !isSelectedRowExpanded ) ) ) + { + int searchResultGroupIndex = searchResult.IndexOf( this ); + for( int i = searchResultGroupIndex - 1; i >= 0; i-- ) + { + if( !searchResult[i].PendingSearch && searchResult[i].IsExpanded && searchResult[i].references.Count > 0 ) + { + searchResult[i].treeView.SetFocus(); + + targetTreeViewRect = searchResult[i].lastTreeViewRect; + targetTreeViewRowRect = searchResult[i].treeView.SelectLastRowAndReturnRect(); + + break; + } + } + } + else if( isLastRowSelected && ( pressedKeyboardNavigationKey == KeyCode.DownArrow || pressedKeyboardNavigationKey == KeyCode.PageDown || pressedKeyboardNavigationKey == KeyCode.End || ( pressedKeyboardNavigationKey == KeyCode.RightArrow && !canExpandSelectedRow ) ) ) + { + int searchResultGroupIndex = searchResult.IndexOf( this ); + for( int i = searchResultGroupIndex + 1; i < searchResult.NumberOfGroups; i++ ) + { + if( !searchResult[i].PendingSearch && searchResult[i].IsExpanded && searchResult[i].references.Count > 0 ) + { + searchResult[i].treeView.SetFocus(); + + targetTreeViewRect = searchResult[i].lastTreeViewRect; + targetTreeViewRowRect = searchResult[i].treeView.SelectFirstRowAndReturnRect(); + + break; + } + } + } + } + + // When key event isn't automatically used by the focused TreeView (happens when its search results are empty), if we navigate to + // a new TreeView, key event will be consumed by that TreeView and hence, keyboard navigation will occur twice + if( ev.type != EventType.Used ) + ev.Use(); + + float scrollTop = targetTreeViewRect.y + targetTreeViewRowRect.y; + float scrollBottom = targetTreeViewRect.y + targetTreeViewRowRect.yMax; + + scrollPosition = FrameRectInScrollView( scrollPosition, new Vector2( scrollTop, scrollBottom ), window.position.height ); + window.Repaint(); + } + } + } + } + + return scrollPosition; + } + + // Frame selection (it isn't handled automatically when using an external scroll view) + // Credit: https://github.com/Unity-Technologies/UnityCsReference/blob/d0fe81a19ce788fd1d94f826cf797aafc37db8ea/Editor/Mono/GUI/TreeView/TreeViewController.cs#L1329-L1351 + private float FrameRectInScrollView( float scrollPosition, Vector2 rectBounds, float windowHeight ) + { + return Mathf.Clamp( scrollPosition, rectBounds.y - windowHeight, rectBounds.x ); + } + + // Serialize this result group + internal SearchResult.SerializableResultGroup Serialize( Dictionary nodeToIndex, List serializedNodes ) + { + SearchResult.SerializableResultGroup serializedResultGroup = new SearchResult.SerializableResultGroup() + { + title = Title, + type = Type, + isExpanded = IsExpanded, + pendingSearch = PendingSearch, + treeViewState = treeViewState + }; + + if( references != null ) + { + serializedResultGroup.initialSerializedNodes = new List( references.Count ); + + for( int i = 0; i < references.Count; i++ ) + serializedResultGroup.initialSerializedNodes.Add( references[i].SerializeRecursively( nodeToIndex, serializedNodes ) ); + } + + return serializedResultGroup; + } + + // Deserialize this result group from the serialized data + internal void Deserialize( SearchResult.SerializableResultGroup serializedResultGroup, List allNodes ) + { + treeViewState = serializedResultGroup.treeViewState; + + if( serializedResultGroup.initialSerializedNodes != null ) + { + for( int i = 0; i < serializedResultGroup.initialSerializedNodes.Count; i++ ) + references.Add( allNodes[serializedResultGroup.initialSerializedNodes[i]] ); + } + } + + IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) references ).GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() { return ( (IEnumerable) references ).GetEnumerator(); } + } + + // Custom class to hold an object in the path to a reference as a node + public class ReferenceNode + { + internal enum UsedState { Unused, MixedCollapsed, MixedExpanded, Used }; + + public class Link + { + public readonly ReferenceNode targetNode; + public readonly List descriptions; + public bool isWeakLink; // Weak links can be omitted from search results if this ReferenceNode isn't referenced by any other node + + public Link( ReferenceNode targetNode, string description, bool isWeakLink ) + { + this.targetNode = targetNode; + this.descriptions = string.IsNullOrEmpty( description ) ? new List() : new List( 1 ) { description }; + this.isWeakLink = isWeakLink; + } + + public Link( ReferenceNode targetNode, List descriptions, bool isWeakLink ) + { + this.targetNode = targetNode; + this.descriptions = descriptions; + this.isWeakLink = isWeakLink; + } + } + + // Unique identifier is used while serializing the node + private static int uid_last = 0; + private readonly int uid; + + public string Label { get; private set; } + public bool IsMainReference { get; private set; } // True: if belongs to a scene search result group, then it's an object in that scene. If belongs to the assets search result group, then it's an asset + + internal object nodeObject; + private int? instanceId; // instanceId of the nodeObject if it is a Unity object, null otherwise + public Object UnityObject { get { return instanceId.HasValue ? EditorUtility.InstanceIDToObject( instanceId.Value ) : null; } } + + private readonly List links = new List( 2 ); + public int NumberOfOutgoingLinks { get { return links.Count; } } + public Link this[int index] { get { return links[index]; } } + + internal UsedState usedState; + + public ReferenceNode() + { + uid = uid_last++; + usedState = UsedState.Used; + } + + // Add a one-way connection to another node + public void AddLinkTo( ReferenceNode nextNode, string description = null, bool isWeakLink = false ) + { + if( nextNode != null && nextNode != this ) + { + if( !string.IsNullOrEmpty( description ) ) + description = "[" + description + "]"; + + // Avoid duplicate links + for( int i = 0; i < links.Count; i++ ) + { + if( links[i].targetNode == nextNode ) + { + if( !string.IsNullOrEmpty( description ) && !links[i].descriptions.Contains( description ) ) + links[i].descriptions.Add( description ); + + links[i].isWeakLink &= isWeakLink; + return; + } + } + + links.Add( new Link( nextNode, description, isWeakLink ) ); + } + } + + public void RemoveLink( int index ) + { + links.RemoveAt( index ); + } + + public bool RemoveLink( ReferenceNode nextNode ) + { + for( int i = links.Count - 1; i >= 0; i-- ) + { + if( links[i].targetNode == nextNode ) + { + links.RemoveAt( i ); + return true; + } + } + + return false; + } + + public void SortLinks() + { + if( links.Count > 1 ) + { + SearchResult.SortedEntry[] sortedEntries = new SearchResult.SortedEntry[links.Count]; + for( int i = links.Count - 1; i >= 0; i-- ) + sortedEntries[i] = new SearchResult.SortedEntry( links[i] ); + + Array.Sort( sortedEntries ); + + for( int i = 0; i < sortedEntries.Length; i++ ) + links[i] = (Link) sortedEntries[i].entry; + } + } + + public void CopyReferencesTo( ReferenceNode other ) + { + other.links.Clear(); + other.links.AddRange( links ); + } + + // Clear this node so that it can be reused later + public void Clear() + { + nodeObject = null; + links.Clear(); + } + + public void InitializeRecursively() + { + if( Label != null ) // Already initialized + return; + + Object unityObject = nodeObject as Object; + if( unityObject != null ) + { + instanceId = unityObject.GetInstanceID(); + Label = unityObject.name + " (" + unityObject.GetType().Name + ")"; + + if( AssetUsageDetectorSettings.ShowRootAssetName && unityObject.IsAsset() && !AssetDatabase.IsMainAsset( unityObject ) ) + { + string mainAssetName = Path.GetFileNameWithoutExtension( AssetDatabase.GetAssetPath( unityObject ) ); + if( unityObject.name != mainAssetName ) + Label += " "; + } + } + else if( nodeObject != null ) + { + instanceId = null; + Label = nodeObject.GetType() + " object"; + } + else + { + instanceId = null; + Label = "<>"; + } + + nodeObject = null; // Don't hold Object reference, allow Unity to GC used memory + + for( int i = 0; i < links.Count; i++ ) + links[i].targetNode.InitializeRecursively(); + } + + public ReferenceNode CreateReverseGraphRecursively( SearchResultGroup searchResultGroup, List reverseGraphRoots, Dictionary reverseGraphNodes, HashSet objectsToSearchSet ) + { + ReferenceNode result; + if( !reverseGraphNodes.TryGetValue( this, out result ) ) + { + reverseGraphNodes[this] = result = new ReferenceNode() { nodeObject = nodeObject }; + + Object obj = nodeObject as Object; + if( obj && objectsToSearchSet.Contains( obj ) ) + reverseGraphRoots.Add( result ); + //else // When 'else' is uncommented, 'Don't search "Find referenced of" themselves for references" option simply does nothing. I am not entirely sure if commenting it out will have any side effects, so fingers crossed? + { + for( int i = 0; i < links.Count; i++ ) + { + ReferenceNode linkedNode = links[i].targetNode.CreateReverseGraphRecursively( searchResultGroup, reverseGraphRoots, reverseGraphNodes, objectsToSearchSet ); + linkedNode.links.Add( new Link( result, links[i].descriptions, links[i].isWeakLink ) ); + } + } + + if( obj ) + { + if( obj is Component ) + obj = ( (Component) obj ).gameObject; + + switch( searchResultGroup.Type ) + { + case SearchResultGroup.GroupType.Assets: result.IsMainReference = obj.IsAsset() && ( obj is GameObject || ( obj.hideFlags & ( HideFlags.HideInInspector | HideFlags.HideInHierarchy ) ) == HideFlags.None ); break; + case SearchResultGroup.GroupType.ProjectSettings: result.IsMainReference = obj.IsAsset() && AssetDatabase.GetAssetPath( obj ).StartsWith( "ProjectSettings/" ); break; + case SearchResultGroup.GroupType.Scene: + case SearchResultGroup.GroupType.DontDestroyOnLoad: + { + if( obj is GameObject ) + { + Scene scene = ( (GameObject) obj ).scene; + if( scene.IsValid() ) + result.IsMainReference = ( searchResultGroup.Type == SearchResultGroup.GroupType.Scene ) ? scene.path == searchResultGroup.ScenePath : scene.name == "DontDestroyOnLoad"; + } + + break; + } + } + } + } + + return result; + } + + public void RemoveRedundantLinksRecursively( HashSet visitedNodes ) + { + if( !visitedNodes.Add( this ) ) + return; + + List stack = null; + for( int i = links.Count - 1; i >= 0; i-- ) + { + if( !links[i].isWeakLink ) + continue; + + if( links[i].targetNode.links.Count == 0 ) + links.RemoveAt( i ); + else + { + if( stack == null ) + stack = new List( 2 ); + else + stack.Clear(); + + if( !links[i].targetNode.CheckForNonWeakLinksRecursively( stack ) ) + links.RemoveAt( i ); + } + } + + for( int i = links.Count - 1; i >= 0; i-- ) + links[i].targetNode.RemoveRedundantLinksRecursively( visitedNodes ); + } + + private bool CheckForNonWeakLinksRecursively( List stack ) + { + if( stack.Contains( this ) || links.Count == 0 ) + return false; + + for( int i = links.Count - 1; i >= 0; i-- ) + { + if( !links[i].isWeakLink ) + return true; + } + + stack.Add( this ); + + for( int i = links.Count - 1; i >= 0; i-- ) + { + if( links[i].targetNode.CheckForNonWeakLinksRecursively( stack ) ) + return true; + } + + stack.RemoveAt( stack.Count - 1 ); + + return false; + } + + // Serialize this node and its connected nodes recursively + internal int SerializeRecursively( Dictionary nodeToIndex, List serializedNodes ) + { + int index; + if( nodeToIndex.TryGetValue( this, out index ) ) + return index; + + SearchResult.SerializableNode serializedNode = new SearchResult.SerializableNode() + { + label = Label, + isMainReference = IsMainReference, + instanceId = instanceId ?? 0, + isUnityObject = instanceId.HasValue, + usedState = usedState + }; + + index = serializedNodes.Count; + nodeToIndex[this] = index; + serializedNodes.Add( serializedNode ); + + if( links.Count > 0 ) + { + serializedNode.links = new List( links.Count ); + serializedNode.linkDescriptions = new List( links.Count ); + serializedNode.linkWeakStates = new List( links.Count ); + + for( int i = 0; i < links.Count; i++ ) + { + serializedNode.links.Add( links[i].targetNode.SerializeRecursively( nodeToIndex, serializedNodes ) ); + serializedNode.linkDescriptions.Add( new SearchResult.SerializableNode.SerializableLinkDescriptions() { value = links[i].descriptions } ); + serializedNode.linkWeakStates.Add( links[i].isWeakLink ); + } + } + + return index; + } + + // Deserialize this node and its links from the serialized data + internal void Deserialize( SearchResult.SerializableNode serializedNode, List allNodes ) + { + if( serializedNode.isUnityObject ) + instanceId = serializedNode.instanceId; + else + instanceId = null; + + Label = serializedNode.label; + IsMainReference = serializedNode.isMainReference; + usedState = serializedNode.usedState; + + if( serializedNode.links != null ) + { + for( int i = 0; i < serializedNode.links.Count; i++ ) + links.Add( new Link( allNodes[serializedNode.links[i]], serializedNode.linkDescriptions[i].value, serializedNode.linkWeakStates[i] ) ); + } + } + + public override int GetHashCode() + { + return uid; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs.meta new file mode 100644 index 00000000..7764d6bb --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca236e4f3c5a9f447be89f0e61e485fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs new file mode 100644 index 00000000..97857174 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs @@ -0,0 +1,91 @@ +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace AssetUsageDetectorNamespace +{ + public class SearchResultTooltip : EditorWindow + { + private static SearchResultTooltip mainWindow; + private static string tooltip; + + private static GUIStyle m_style; + internal static GUIStyle Style + { + get + { + if( m_style == null ) + { + m_style = (GUIStyle) typeof( EditorStyles ).GetProperty( "tooltip", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).GetValue( null, null ); + m_style.richText = true; + } + + return m_style; + } + } + + public static void Show( Rect sourcePosition, string tooltip ) + { + Vector2 preferredSize = Style.CalcSize( new GUIContent( tooltip ) ) + Style.contentOffset + new Vector2( Style.padding.horizontal + Style.margin.horizontal, Style.padding.vertical + Style.margin.vertical ); + Rect preferredPosition; + + Rect positionLeft = new Rect( sourcePosition.position - new Vector2( preferredSize.x, 0f ), preferredSize ); + Rect screenFittedPositionLeft = Utilities.GetScreenFittedRect( positionLeft ); + + Vector2 positionOffset = positionLeft.position - screenFittedPositionLeft.position; + Vector2 sizeOffset = positionLeft.size - screenFittedPositionLeft.size; + if( positionOffset.sqrMagnitude <= 400f && sizeOffset.sqrMagnitude <= 400f ) + preferredPosition = screenFittedPositionLeft; + else + { + Rect positionRight = new Rect( sourcePosition.position + new Vector2( sourcePosition.width, 0f ), preferredSize ); + Rect screenFittedPositionRight = Utilities.GetScreenFittedRect( positionRight ); + + Vector2 positionOffset2 = positionRight.position - screenFittedPositionRight.position; + Vector2 sizeOffset2 = positionRight.size - screenFittedPositionRight.size; + if( positionOffset2.magnitude + sizeOffset2.magnitude < positionOffset.magnitude + sizeOffset.magnitude ) + preferredPosition = screenFittedPositionRight; + else + preferredPosition = screenFittedPositionLeft; + } + + // Don't lose focus to the previous window + EditorWindow prevFocusedWindow = focusedWindow; + + if( !mainWindow ) + { + mainWindow = CreateInstance(); + mainWindow.ShowPopup(); + } + + SearchResultTooltip.tooltip = tooltip; + mainWindow.minSize = preferredPosition.size; + mainWindow.position = preferredPosition; + mainWindow.Repaint(); + + if( prevFocusedWindow ) + prevFocusedWindow.Focus(); + } + + public static void Hide() + { + if( mainWindow ) + { + mainWindow.Close(); + mainWindow = null; + } + } + + private void OnGUI() + { + // If somehow the tooltip isn't automatically closed, allow closing it by clicking on it + if( Event.current.type == EventType.MouseDown ) + { + Hide(); + GUIUtility.ExitGUI(); + } + + GUI.Label( new Rect( Vector2.zero, position.size ), tooltip, Style ); + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs.meta new file mode 100644 index 00000000..06a26622 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c8bd4351b5024324ca5974ebcad1dde3 +timeCreated: 1639247551 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs new file mode 100644 index 00000000..cbb68fa8 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs @@ -0,0 +1,1353 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; +#if UNITY_2021_2_OR_NEWER +using PrefabStage = UnityEditor.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility; +#elif UNITY_2018_3_OR_NEWER +using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; +#endif + +namespace AssetUsageDetectorNamespace +{ + [System.Serializable] + public class SearchResultTreeViewState : TreeViewState + { + // - initialNodeId is serialized because we want to preserve the expanded states of the TreeViewItems after domain reload and + // it's only possible if TreeView is reconstructed with the same ids + // - finalNodeId is serialized because if the same id used for multiple TreeViewItems across multiple TreeViews, strange issues occur. + // Thus, each new TreeView will set its initialNodeId to the previous TreeView's finalNodeId + // - Each TreeViewItem's id is different even if two TreeViewItems point to the exact same ReferenceNode. That's because TreeView + // doesn't work well when some TreeViewItems share the same id (e.g. while navigating the tree with arrow keys) + public int initialNodeId, finalNodeId; + + // Not using the built-in searchString and hasSearch properties of TreeView because: + // - This search algorithm is a bit more complicated than usual, we don't flatten the tree during the search + // - If code is recompiled while searchString wasn't empty, the tree isn't rebuilt and remains empty (at least on Unity 5.6) + public string searchTerm; + public SearchResultTreeView.SearchMode searchMode = SearchResultTreeView.SearchMode.All; + public bool selectionChangedDuringSearch; + + public List preSearchExpandedIds; + } + + public class SearchResultTreeView : TreeView + { + public enum TreeType { Normal, UnusedObjects, IsolatedView }; + public enum SearchMode { SearchedObjectsOnly, ReferencesOnly, All }; + + private class ReferenceNodeData + { + public readonly TreeViewItem item; + public readonly ReferenceNode node; + public readonly ReferenceNodeData parent; + public readonly int linkIndex; + public bool isLastLink; + public bool isDuplicate; + public bool shouldExpandAfterSearch; + + private string m_tooltipText; + public string tooltipText + { + get + { + if( m_tooltipText != null ) + return m_tooltipText; + + return GetTooltipText( Utilities.stringBuilder ); + } + } + + public ReferenceNodeData( TreeViewItem item, ReferenceNode node, ReferenceNodeData parent, int linkIndex ) + { + this.item = item; + this.node = node; + this.parent = parent; + this.linkIndex = linkIndex; + } + + private string GetTooltipText( StringBuilder sb ) + { + sb.Length = 0; + sb.Append( "- " ).Append( node.Label ); + + if( parent != null ) + { + sb.Append( "\n" ); + + if( parent.node[linkIndex].descriptions.Count > 0 ) + { + List linkDescriptions = parent.node[linkIndex].descriptions; + for( int i = 0; i < linkDescriptions.Count; i++ ) + sb.Append( " " ).Append( linkDescriptions[i] ).Append( "\n" ); + } + + if( parent.m_tooltipText != null ) + sb.Append( parent.m_tooltipText ); + else // Cache parents' tooltips along the way because they'll likely be reused frequently. We need to use new StringBuilder instances for them + sb.Append( parent.GetTooltipText( new StringBuilder( 256 ) ) ); + } + + m_tooltipText = sb.ToString(); + return m_tooltipText; + } + + public void ResetTooltip() + { + m_tooltipText = null; + } + } + + private const float SEARCHED_OBJECTS_BORDER_THICKNESS = 1f; +#if UNITY_2019_3_OR_NEWER + private const float TREE_VIEW_LINES_THICKNESS = 1.5f; // There are inexplicable spaces between the vertical and horizontal lines if we don't change thickness by 0.5f on 2019.3+ +#else + private const float TREE_VIEW_LINES_THICKNESS = 2f; +#endif + private const float HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS = TREE_VIEW_LINES_THICKNESS * 2f; + + private readonly new SearchResultTreeViewState state; + + private readonly List references; + private readonly List idToNodeDataLookup = new List( 128 ); + + private readonly HashSet selectedReferenceNodes = new HashSet(); + private readonly HashSet selectedReferenceNodesHierarchyIds = new HashSet(); + private readonly HashSet selectedReferenceNodesHierarchyIndirectIds = new HashSet(); + + private readonly HashSet usedObjectsSet; + + private readonly TreeType treeType; + private readonly bool hideDuplicateRows; + private readonly bool hideReduntantPrefabVariantLinks; + + private bool isSearching; + +#if !UNITY_2018_2_OR_NEWER + public int visibleRowTop = 0, visibleRowBottom = int.MaxValue; +#endif + + private readonly CompareInfo textComparer = new CultureInfo( "en-US" ).CompareInfo; + private readonly CompareOptions textCompareOptions = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace; + + private readonly GUIContent sharedGUIContent = new GUIContent(); + private GUIStyle foldoutLabelStyle; + private Texture2D whiteGradientTexture; + private string highlightedSearchTextColor; + + private ReferenceNodeData prevHoveredData, hoveredData; + private Rect hoveredDataRect; + + private bool isTreeViewEmpty; + private bool isLMBDown; + + private double customTooltipShowTime; + + public new float rowHeight + { + get { return base.rowHeight; } + set + { + base.rowHeight = value; +#if !UNITY_2019_3_OR_NEWER + customFoldoutYOffset = ( value - EditorGUIUtility.singleLineHeight ) * 0.5f; +#endif + } + } + + // Avoid using these properties of TreeView by mistake + [System.Obsolete] private new string searchString { get; } + [System.Obsolete] private new bool hasSearch { get; } + + public SearchResultTreeView( SearchResultTreeViewState state, List references, TreeType treeType, HashSet usedObjectsSet, bool hideDuplicateRows, bool hideReduntantPrefabVariantLinks, bool usesExternalScrollView ) : base( state ) + { + this.state = state; + this.references = references; + this.treeType = treeType; + this.hideDuplicateRows = hideDuplicateRows; + this.hideReduntantPrefabVariantLinks = hideReduntantPrefabVariantLinks; + + highlightedSearchTextColor = ""; + + rowHeight = EditorGUIUtility.singleLineHeight + AssetUsageDetectorSettings.ExtraRowHeight; + + if( treeType == TreeType.UnusedObjects ) + { + showBorder = true; + this.usedObjectsSet = usedObjectsSet; + } + + if( treeType != TreeType.IsolatedView ) + { + // Draw only the visible rows. This requires setting useScrollView to false because we are using an external scroll view: https://docs.unity3d.com/ScriptReference/IMGUI.Controls.TreeView-useScrollView.html +#if UNITY_2018_2_OR_NEWER + useScrollView = false; +#else + // In my tests, SetUseScrollView seems to have no effect unfortunately but let's keep this line in case it fixes some other issues with the external scroll view + object treeViewController = typeof( TreeView ).GetField( "m_TreeView", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( this ); + treeViewController.GetType().GetMethod( "SetUseScrollView", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( treeViewController, new object[1] { false } ); +#endif + } + + isSearching = !string.IsNullOrEmpty( state.searchTerm ); + + Reload(); + + if( HasSelection() ) + RefreshSelectedNodes( GetSelection() ); + } + + public void RefreshSearch( string prevSearchTerm ) + { + bool wasSearchTermEmpty = string.IsNullOrEmpty( prevSearchTerm ); + bool isSearchTermEmpty = string.IsNullOrEmpty( state.searchTerm ); + + isSearching = !isSearchTermEmpty; + + if( !wasSearchTermEmpty || !isSearchTermEmpty ) + { + Reload(); + + if( !isSearchTermEmpty ) + { + if( wasSearchTermEmpty ) + { + state.preSearchExpandedIds = new List( GetExpanded() ?? new int[0] ); + state.selectionChangedDuringSearch = false; + } + + ExpandMatchingSearchResults(); + } + else if( !wasSearchTermEmpty && state.preSearchExpandedIds != null && state.preSearchExpandedIds.Count > 0 ) + { + List expandedIds = state.preSearchExpandedIds; + HashSet expandedIdsSet = new HashSet( expandedIds ); + if( state.selectionChangedDuringSearch ) + { + IList selection = GetSelection(); + for( int i = 0; i < selection.Count; i++ ) + { + for( TreeViewItem item = GetDataFromId( selection[i] ).item; item != null; item = item.parent ) + { + if( expandedIdsSet.Add( item.id ) ) + expandedIds.Add( item.id ); + else + break; + } + } + } + + SetExpanded( state.preSearchExpandedIds ); + expandedIds.Clear(); + } + + if( HasSelection() ) + RefreshSelectedNodes( GetSelection() ); + } + } + + protected override TreeViewItem BuildRoot() + { + TreeViewItem root = new TreeViewItem { id = state.initialNodeId, depth = -1, displayName = "Root" }; + int id = state.initialNodeId + 1; + + idToNodeDataLookup.Clear(); + + List stack = new List( 8 ); + HashSet processedNodes = null; + if( hideDuplicateRows ) + { + processedNodes = new HashSet(); + for( int i = references.Count - 1; i >= 0; i-- ) + { + // Don't mark root nodes as duplicates unless we're in ReferencesOnly search mode (in which case, it's just technically unfeasible to know which root nodes will be displayed in advance) + if( !isSearching || ( state.searchMode != SearchMode.ReferencesOnly && textComparer.IndexOf( references[i].Label, state.searchTerm, textCompareOptions ) >= 0 ) ) + processedNodes.Add( references[i] ); + } + } + + for( int i = 0; i < references.Count; i++ ) + GenerateRowsRecursive( root, references[i], null, i, 0, null, stack, processedNodes, ref id ); + + isTreeViewEmpty = !root.hasChildren; + if( isTreeViewEmpty ) // May happen if all items are hidden inside HideItems function or there are no matching search results. If we don't create a dummy child, Unity throws an exception + root.AddChild( new TreeViewItem( state.initialNodeId + 1 ) ); // If we don't give it a valid id, some functions throw exceptions when there are no matching search results + else + GetDataFromId( root.children[root.children.Count - 1].id ).isLastLink = true; + + state.finalNodeId = id + 1; + + return root; + } + + private bool GenerateRowsRecursive( TreeViewItem parent, ReferenceNode referenceNode, ReferenceNodeData parentData, int siblingIndex, int depth, bool? itemForcedVisibility, List stack, HashSet processedNodes, ref int id ) + { + TreeViewItem item = new TreeViewItem( id++, depth, "" ); + ReferenceNodeData data = new ReferenceNodeData( item, referenceNode, parentData, siblingIndex ); + + bool shouldShowItem; + if( itemForcedVisibility.HasValue ) + shouldShowItem = itemForcedVisibility.Value; + else + { + if( !isSearching ) + shouldShowItem = true; + else if( state.searchMode == SearchMode.All || ( ( depth == 0 ) == ( state.searchMode == SearchMode.SearchedObjectsOnly ) ) ) + { + shouldShowItem = textComparer.IndexOf( referenceNode.Label, state.searchTerm, textCompareOptions ) >= 0; + if( !shouldShowItem && depth > 0 ) + { + List descriptions = parentData.node[siblingIndex].descriptions; + for( int i = descriptions.Count - 1; i >= 0; i-- ) + { + if( textComparer.IndexOf( descriptions[i], state.searchTerm, textCompareOptions ) >= 0 ) + { + shouldShowItem = true; + break; + } + } + } + + data.shouldExpandAfterSearch = shouldShowItem; + + if( state.searchMode == SearchMode.SearchedObjectsOnly || ( state.searchMode == SearchMode.All && shouldShowItem ) ) + itemForcedVisibility = shouldShowItem; + } + else + shouldShowItem = false; + } + + idToNodeDataLookup.Add( data ); + + // Disallow recursion (stack) because it would crash Unity + if( referenceNode.NumberOfOutgoingLinks > 0 && !stack.ContainsFast( referenceNode ) ) + { + // Add children only if hideDuplicateRows is false (processedNodes == null) or this node hasn't been seen before + if( processedNodes != null && !processedNodes.Add( referenceNode ) && depth > 0 ) // "depth > 0": Root nodes are either added to processedNodes prior to generating rows (so that they're never marked as duplicate), or they just shouldn't be trimmed + data.isDuplicate = true; + else + { + stack.Add( referenceNode ); + + // Generate child items even if they will be forced invisible so that each visible row's id is deterministic and doesn't change when some rows become invisible + for( int i = 0; i < referenceNode.NumberOfOutgoingLinks; i++ ) + shouldShowItem |= GenerateRowsRecursive( item, referenceNode[i].targetNode, data, i, depth + 1, itemForcedVisibility, stack, processedNodes, ref id ); + + stack.RemoveAt( stack.Count - 1 ); + } + } + + if( shouldShowItem ) + { + if( item.hasChildren ) + GetDataFromId( item.children[item.children.Count - 1].id ).isLastLink = true; + + parent.AddChild( item ); + return true; + } + + return false; + } + + private ReferenceNodeData GetDataFromId( int id ) + { + return idToNodeDataLookup[id - state.initialNodeId - 1]; + } + + public override void OnGUI( Rect rect ) + { + // Disallow clicking on "No matching results" text when in search mode + bool guiEnabled = GUI.enabled; + if( isTreeViewEmpty ) + GUI.enabled = false; + + // Mouse and special keyboard events are already in Used state in CommandEventHandling, so we need to process them here + Event ev = Event.current; + if( ev.type == EventType.MouseDown ) + { + if( ev.button == 0 ) + isLMBDown = true; + } + else if( ev.type == EventType.MouseUp ) + { + if( ev.button == 0 ) + isLMBDown = false; + } + else if( ev.type == EventType.MouseMove ) + hoveredData = null; + else if( ev.type == EventType.KeyDown ) + { + if( ( ev.keyCode == KeyCode.Return || ev.keyCode == KeyCode.KeypadEnter ) && HasSelection() && HasFocus() ) + { + DoubleClickedItem( state.lastClickedID ); + ev.Use(); + } + } + + base.OnGUI( rect ); + + if( prevHoveredData != hoveredData ) + { + if( AssetUsageDetectorSettings.CustomTooltipDelay > 0f ) + EditorApplication.update -= ShowTooltipDelayed; + + prevHoveredData = hoveredData; + if( hoveredData != null ) + { + if( AssetUsageDetectorSettings.CustomTooltipDelay <= 0f ) + SearchResultTooltip.Show( hoveredDataRect, hoveredData.tooltipText ); + else + { + customTooltipShowTime = EditorApplication.timeSinceStartup + AssetUsageDetectorSettings.CustomTooltipDelay; + EditorApplication.update += ShowTooltipDelayed; + } + } + else + SearchResultTooltip.Hide(); + + Repaint(); + } + + GUI.enabled = guiEnabled; + } + + protected override void RowGUI( RowGUIArgs args ) + { +#if !UNITY_2018_2_OR_NEWER + // Do manual row culling on early Unity versions + if( args.row < visibleRowTop || args.row > visibleRowBottom ) + return; +#endif + + if( isTreeViewEmpty ) + { + EditorGUI.LabelField( args.rowRect, "No matching results..." ); + return; + } + + Event ev = Event.current; + ReferenceNodeData data = GetDataFromId( args.item.id ); + Rect rect = args.rowRect; + + if( string.IsNullOrEmpty( args.item.displayName ) ) + { + Object unityObject = data.node.UnityObject; + if( unityObject ) + args.item.icon = AssetPreview.GetMiniThumbnail( unityObject ); + + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + + if( data.isDuplicate ) + sb.Append( "[D] " ); + + if( data.parent == null ) + { + if( treeType != TreeType.UnusedObjects ) + sb.Append( "" ); + else if( data.node.usedState == ReferenceNode.UsedState.MixedCollapsed ) + sb.Append( "[!] " ); + else if( data.node.usedState == ReferenceNode.UsedState.MixedExpanded ) + sb.Append( "[!] " ); + + if( !isSearching || state.searchMode == SearchMode.ReferencesOnly ) + sb.Append( data.node.Label ); + else + HighlightSearchTermInString( sb, data.node.Label ); + + if( treeType != TreeType.UnusedObjects ) + sb.Append( "" ); + } + else + { + List linkDescriptions = data.parent.node[data.linkIndex].descriptions; + if( linkDescriptions.Count > 0 ) + { + if( !isSearching || state.searchMode == SearchMode.SearchedObjectsOnly ) + sb.Append( data.node.Label ).Append( " " ).Append( linkDescriptions[0] ); + else + { + HighlightSearchTermInString( sb, data.node.Label ); + sb.Append( " " ); + HighlightSearchTermInString( sb, linkDescriptions[0] ); + } + + if( linkDescriptions.Count > 1 ) + { + bool shouldHighlightRemainingLinkDescriptions = false; + if( isSearching && state.searchMode != SearchMode.SearchedObjectsOnly ) + { + for( int i = linkDescriptions.Count - 1; i > 0; i-- ) + { + if( textComparer.IndexOf( linkDescriptions[i], state.searchTerm, textCompareOptions ) >= 0 ) + { + shouldHighlightRemainingLinkDescriptions = true; + sb.Append( highlightedSearchTextColor ); + + break; + } + } + } + + sb.Append( " and " ).Append( linkDescriptions.Count - 1 ).Append( " more" ); + + if( shouldHighlightRemainingLinkDescriptions ) + sb.Append( "" ); + } + + sb.Append( "" ); + } + else if( isSearching && state.searchMode != SearchMode.SearchedObjectsOnly ) + HighlightSearchTermInString( sb, data.node.Label ); + else + sb.Append( data.node.Label ); + } + + args.item.displayName = sb.ToString(); + } + + sharedGUIContent.text = args.item.displayName; + sharedGUIContent.tooltip = AssetUsageDetectorSettings.ShowUnityTooltip ? data.tooltipText : null; + sharedGUIContent.image = args.item.icon; + + if( ev.type == EventType.Repaint ) + { + if( treeType != TreeType.UnusedObjects ) + { + if( args.item.depth == 0 ) + { + Color guiColor = GUI.color; + + // Draw background + if( !args.selected ) + { + GUI.color = guiColor * ( ( AssetUsageDetectorSettings.ApplySelectedRowParentsTintToRootRows && selectedReferenceNodesHierarchyIds.Contains( args.item.id ) ) ? AssetUsageDetectorSettings.SelectedRowParentsTint : AssetUsageDetectorSettings.RootRowsBackgroundColor ); + GUI.DrawTexture( rect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + } + + // Draw border: https://github.com/Unity-Technologies/UnityCsReference/blob/33cbfe062d795667c39e16777230e790fcd4b28b/Editor/Mono/GUI/InternalEditorGUI.cs#L262-L275 + if( AssetUsageDetectorSettings.RootRowsBorderColor.a > 0f ) + { + GUI.color = guiColor * AssetUsageDetectorSettings.RootRowsBorderColor; + GUI.DrawTexture( new Rect( rect.x, rect.y, rect.width, SEARCHED_OBJECTS_BORDER_THICKNESS ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + + // Draw bottom border only if there isn't another searched object immediately below this one (otherwise, this bottom border and the following top border are drawn at the same space, resulting in darker shade for that edge) + if( data.isLastLink || ( args.item.hasChildren && IsExpanded( args.item.id ) ) ) + { + GUI.DrawTexture( new Rect( rect.x, rect.yMax - SEARCHED_OBJECTS_BORDER_THICKNESS, rect.width, SEARCHED_OBJECTS_BORDER_THICKNESS ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + GUI.DrawTexture( new Rect( rect.x, rect.y + 1, SEARCHED_OBJECTS_BORDER_THICKNESS, rect.height - 2f * SEARCHED_OBJECTS_BORDER_THICKNESS ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + GUI.DrawTexture( new Rect( rect.xMax - SEARCHED_OBJECTS_BORDER_THICKNESS, rect.y + 1, SEARCHED_OBJECTS_BORDER_THICKNESS, rect.height - 2f * SEARCHED_OBJECTS_BORDER_THICKNESS ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + } + else + { + GUI.DrawTexture( new Rect( rect.x, rect.y + 1, SEARCHED_OBJECTS_BORDER_THICKNESS, rect.height ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + GUI.DrawTexture( new Rect( rect.xMax - SEARCHED_OBJECTS_BORDER_THICKNESS, rect.y + 1, SEARCHED_OBJECTS_BORDER_THICKNESS, rect.height ), EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + } + } + + GUI.color = guiColor; + } + else + { + if( !args.selected ) + { + if( selectedReferenceNodesHierarchyIds.Contains( args.item.id ) ) + EditorGUI.DrawRect( rect, AssetUsageDetectorSettings.SelectedRowParentsTint ); + + if( data.node.IsMainReference ) + EditorGUI.DrawRect( new Rect( rect.x, rect.y, GetContentIndent( args.item ) - 1f, rect.height ), AssetUsageDetectorSettings.MainReferencesBackgroundColor ); + } + } + } + else + { + if( !args.selected && data.node.usedState == ReferenceNode.UsedState.Used ) + EditorGUI.DrawRect( new Rect( rect.x, rect.y, GetContentIndent( args.item ) - 1f, rect.height ), AssetUsageDetectorSettings.MainReferencesBackgroundColor ); + } + + if( !isLMBDown && treeType != TreeType.UnusedObjects && !args.selected && selectedReferenceNodes.Contains( data.node ) ) + { + if( !whiteGradientTexture ) + { + whiteGradientTexture = new Texture2D( 2, 1, TextureFormat.RGBA32, false ) + { + hideFlags = HideFlags.HideAndDontSave, + alphaIsTransparency = true, + filterMode = FilterMode.Bilinear, + wrapMode = TextureWrapMode.Clamp + }; + + whiteGradientTexture.SetPixels32( new Color32[2] { Color.white, new Color32( 255, 255, 255, 0 ) } ); + whiteGradientTexture.Apply( false, true ); + } + + Color guiColor = GUI.color; + GUI.color = guiColor * AssetUsageDetectorSettings.SelectedRowOccurrencesColor; + GUI.DrawTexture( new Rect( GetContentIndent( args.item ), rect.y, 125f, rect.height ), whiteGradientTexture, ScaleMode.StretchToFill, true, 0f ); + GUI.color = guiColor; + } + + if( hoveredData == data ) + EditorGUI.DrawRect( rect, new Color( 0.5f, 0.5f, 0.5f, 0.25f ) ); + + if( AssetUsageDetectorSettings.ShowTreeLines && args.item.depth > 0 ) + { + // I was using EditorGUI.DrawRect here but looking at its source code, it's more performant to call GUI.DrawTexture directly: https://github.com/Unity-Technologies/UnityCsReference/blob/e740821767d2290238ea7954457333f06e952bad/Editor/Mono/GUI/InternalEditorGUI.cs#L246-L255 + Color guiColor = GUI.color; + bool shouldHighlightTreeLine; + + Rect verticalLineRect = new Rect( rect.x + GetContentIndent( args.item.parent ) - ( foldoutWidth + TREE_VIEW_LINES_THICKNESS ) * 0.5f - 2f, rect.y, TREE_VIEW_LINES_THICKNESS, rect.height ); + Rect horizontalLineRect = new Rect( verticalLineRect.x, verticalLineRect.y + ( verticalLineRect.height - TREE_VIEW_LINES_THICKNESS ) * 0.5f, foldoutWidth + TREE_VIEW_LINES_THICKNESS - 4f, TREE_VIEW_LINES_THICKNESS ); + + for( ReferenceNodeData parentData = data.parent; parentData.parent != null; parentData = parentData.parent ) + { + if( !parentData.isLastLink ) + { + shouldHighlightTreeLine = selectedReferenceNodesHierarchyIndirectIds.Contains( parentData.item.id ); + Rect _verticalLineRect = new Rect( verticalLineRect.x - depthIndentWidth * ( args.item.depth - parentData.item.depth ), verticalLineRect.y, verticalLineRect.width, verticalLineRect.height ); + if( shouldHighlightTreeLine ) + { + _verticalLineRect.x -= ( HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS - TREE_VIEW_LINES_THICKNESS ) * 0.5f; + _verticalLineRect.width = HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS; + } + + GUI.color = guiColor * ( shouldHighlightTreeLine ? AssetUsageDetectorSettings.HighlightedTreeLinesColor : AssetUsageDetectorSettings.TreeLinesColor ); + GUI.DrawTexture( _verticalLineRect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + } + } + + bool isInSelectedReferenceNodesHierarchy = selectedReferenceNodesHierarchyIds.Contains( args.item.id ); + if( isInSelectedReferenceNodesHierarchy ) + { + horizontalLineRect.y -= ( HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS - TREE_VIEW_LINES_THICKNESS ) * 0.5f; + horizontalLineRect.height = HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS; + } + + GUI.color = guiColor * ( isInSelectedReferenceNodesHierarchy ? AssetUsageDetectorSettings.HighlightedTreeLinesColor : AssetUsageDetectorSettings.TreeLinesColor ); + GUI.DrawTexture( horizontalLineRect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + + if( data.isLastLink ) + verticalLineRect.height = ( verticalLineRect.height + TREE_VIEW_LINES_THICKNESS ) * 0.5f; + + GUI.color = guiColor * AssetUsageDetectorSettings.TreeLinesColor; + GUI.DrawTexture( verticalLineRect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + + bool isInSelectedReferenceNodesIndirectHierarchy = selectedReferenceNodesHierarchyIndirectIds.Contains( args.item.id ); + if( isInSelectedReferenceNodesHierarchy || isInSelectedReferenceNodesIndirectHierarchy ) + { + GUI.color = guiColor * AssetUsageDetectorSettings.HighlightedTreeLinesColor; + + if( isInSelectedReferenceNodesHierarchy && !isInSelectedReferenceNodesIndirectHierarchy ) + { + if( !data.isLastLink ) + verticalLineRect.height = ( verticalLineRect.height + HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS ) * 0.5f; + else + verticalLineRect.height += ( HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS - TREE_VIEW_LINES_THICKNESS ) * 0.5f; + } + + verticalLineRect.x -= ( HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS - TREE_VIEW_LINES_THICKNESS ) * 0.5f; + verticalLineRect.width = HIGHLIGHTED_TREE_VIEW_LINES_THICKNESS; + + GUI.DrawTexture( verticalLineRect, EditorGUIUtility.whiteTexture, ScaleMode.StretchToFill, true, 0f ); + } + + GUI.color = guiColor; + } + + rect.xMin += GetContentIndent( args.item ); + rect.y += AssetUsageDetectorSettings.ExtraRowHeight * 0.5f; +#if !UNITY_2019_3_OR_NEWER + rect.y -= 2f; +#endif + rect.height += 4f; // Incrementing height fixes cropped icon issue on Unity 2019.2 or earlier + + if( foldoutLabelStyle == null ) + foldoutLabelStyle = new GUIStyle( DefaultStyles.foldoutLabel ) { richText = true }; + + foldoutLabelStyle.Draw( rect, sharedGUIContent, false, false, args.selected && args.focused, args.selected ); + + // The only way to support Unity's tooltips seems to be by drawing an invisible GUI.Label over our own label + if( sharedGUIContent.tooltip != null ) + { + sharedGUIContent.text = ""; + sharedGUIContent.image = null; + GUI.Label( rect, sharedGUIContent, foldoutLabelStyle ); + } + } + else if( ev.type == EventType.MouseDown ) + { + if( ev.button == 2 && rect.Contains( ev.mousePosition ) ) + { + HideItems( new int[1] { args.item.id } ); + GUIUtility.ExitGUI(); + } + } + else if( ev.type == EventType.MouseMove ) + { + if( hoveredData != data && AssetUsageDetectorSettings.ShowCustomTooltip && rect.Contains( ev.mousePosition ) ) + { + hoveredData = data; + hoveredDataRect = new Rect( GUIUtility.GUIToScreenPoint( rect.position ), new Vector2( EditorGUIUtility.currentViewWidth, 0f ) ); + } + } + } + + protected override void SelectionChanged( IList selectedIds ) + { + if( isTreeViewEmpty ) + return; + + RefreshSelectedNodes( selectedIds ); + + if( selectedIds.Count == 0 ) + return; + + if( isSearching ) + state.selectionChangedDuringSearch = true; + + Object selection, pingTarget = null; + List selectedUnityObjects = new List( selectedIds.Count ); + for( int i = 0; i < selectedIds.Count; i++ ) + { + Object obj = GetDataFromId( selectedIds[i] ).node.UnityObject; + if( obj ) + { + obj.GetObjectsToSelectAndPing( out selection, out pingTarget ); + if( selection && !selectedUnityObjects.Contains( selection ) ) + selectedUnityObjects.Add( selection ); + } + } + + if( selectedUnityObjects.Count > 0 ) + { + if( AssetUsageDetectorSettings.PingClickedObjects && pingTarget ) + EditorGUIUtility.PingObject( pingTarget ); + if( AssetUsageDetectorSettings.SelectClickedObjects || ( AssetUsageDetectorSettings.SelectDoubleClickedObjects && selectedUnityObjects.Count > 1 ) ) + Selection.objects = selectedUnityObjects.ToArray(); + } + } + + protected override void DoubleClickedItem( int id ) + { + if( isTreeViewEmpty ) + return; + + isLMBDown = false; + + Object clickedObject = GetDataFromId( id ).node.UnityObject; +#if UNITY_2018_3_OR_NEWER + if( clickedObject && clickedObject.IsAsset() ) + { + GameObject clickedPrefabRoot = null; + if( clickedObject is Component ) + clickedPrefabRoot = ( (Component) clickedObject ).transform.root.gameObject; + else if( clickedObject is GameObject ) + clickedPrefabRoot = ( (GameObject) clickedObject ).transform.root.gameObject; + + if( clickedPrefabRoot ) + { + PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType( clickedPrefabRoot ); + if( prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant ) + { + // Try to open the prefab stage of this prefab + string assetPath = AssetDatabase.GetAssetPath( clickedPrefabRoot ); + PrefabStage openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage(); +#if UNITY_2020_1_OR_NEWER + if( openPrefabStage == null || !openPrefabStage.stageHandle.IsValid() || assetPath != openPrefabStage.assetPath ) +#else + if( openPrefabStage == null || !openPrefabStage.stageHandle.IsValid() || assetPath != openPrefabStage.prefabAssetPath ) +#endif + AssetDatabase.OpenAsset( clickedPrefabRoot ); + } + } + } +#endif + + // Ping the clicked GameObject in the open prefab stage + Object selection, pingTarget; + clickedObject.GetObjectsToSelectAndPing( out selection, out pingTarget ); + + if( AssetUsageDetectorSettings.PingClickedObjects && pingTarget ) + EditorGUIUtility.PingObject( pingTarget ); + if( AssetUsageDetectorSettings.SelectDoubleClickedObjects ) + Selection.activeObject = selection; + } + + protected override void ContextClickedItem( int id ) + { + ContextClicked(); + } + + protected override void ContextClicked() + { + if( !isTreeViewEmpty && HasSelection() && HasFocus() ) + { + IList selection = SortItemIDsInRowOrder( GetSelection() ); + + bool hasAnyDuplicateRows = false, hasAnyRowWithOutgoingLinks = false, hasAnyUnusedMixedCollapsedNode = false; + for( int i = 0; i < selection.Count; i++ ) + { + ReferenceNodeData data = GetDataFromId( selection[i] ); + if( !hasAnyDuplicateRows && data.isDuplicate ) + hasAnyDuplicateRows = true; + if( !hasAnyRowWithOutgoingLinks && data.node.NumberOfOutgoingLinks > 0 ) + hasAnyRowWithOutgoingLinks = true; + if( !hasAnyUnusedMixedCollapsedNode && data.node.usedState == ReferenceNode.UsedState.MixedCollapsed ) + hasAnyUnusedMixedCollapsedNode = true; + } + + GenericMenu contextMenu = new GenericMenu(); + + if( treeType != TreeType.IsolatedView ) + contextMenu.AddItem( new GUIContent( "Hide" ), false, () => HideItems( selection ) ); + + if( treeType == TreeType.UnusedObjects ) + { + if( hasAnyUnusedMixedCollapsedNode ) + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + contextMenu.AddItem( new GUIContent( "Show Used Children" ), false, ShowChildrenOfSelectedUnusedObjects ); + } + } + else + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + if( hasAnyDuplicateRows ) + contextMenu.AddItem( new GUIContent( "Select First Occurrence" ), false, SelectFirstOccurrencesOfDuplicateSelection ); + + contextMenu.AddItem( new GUIContent( "Expand All Occurrences" ), false, ExpandAllSelectionOccurrences ); + } + + if( hasAnyRowWithOutgoingLinks ) + { + if( contextMenu.GetItemCount() > 0 ) + contextMenu.AddSeparator( "" ); + + contextMenu.AddItem( new GUIContent( "Show Children In New Window" ), false, ShowChildrenOfSelectionInNewWindow ); + } + + contextMenu.ShowAsContext(); + + if( Event.current != null && Event.current.type == EventType.ContextClick ) + Event.current.Use(); // It's safer to eat the event and if we don't, the context menu is sometimes displayed with a delay + } + } + + protected override void CommandEventHandling() + { + if( !isTreeViewEmpty && HasFocus() ) // There may be multiple SearchResultTreeViews. Execute the event only for the currently focused one + { + Event ev = Event.current; + if( ev.type == EventType.ValidateCommand || ev.type == EventType.ExecuteCommand ) + { + if( ev.commandName == "Delete" || ev.commandName == "SoftDelete" ) + { + if( ev.type == EventType.ExecuteCommand ) + HideItems( GetSelection() ); + + ev.Use(); + return; + } + } + } + + base.CommandEventHandling(); + } + + protected override bool CanStartDrag( CanStartDragArgs args ) + { + return true; + } + + protected override void SetupDragAndDrop( SetupDragAndDropArgs args ) + { + IList draggedItemIds = args.draggedItemIDs; + if( draggedItemIds.Count == 0 ) + return; + + List draggedUnityObjects = new List( draggedItemIds.Count ); + for( int i = 0; i < draggedItemIds.Count; i++ ) + { + Object obj = GetDataFromId( draggedItemIds[i] ).node.UnityObject; + if( obj ) + draggedUnityObjects.Add( obj ); + } + + if( draggedUnityObjects.Count > 0 ) + { + DragAndDrop.objectReferences = draggedUnityObjects.ToArray(); + DragAndDrop.StartDrag( draggedUnityObjects.Count > 1 ? "" : draggedUnityObjects[0].name ); + } + } + + public void ExpandDirectReferences() + { + List expandedIds = new List( rootItem.children.Count ); + for( int i = 0; i < rootItem.children.Count; i++ ) + expandedIds.Add( rootItem.children[i].id ); + + SetExpanded( expandedIds ); + } + + public void ExpandMainReferences() + { + List expandedIds = new List( references.Count * 12 ); + for( int i = 0; i < rootItem.children.Count; i++ ) + GetMainReferenceIdsRecursive( rootItem.children[i], expandedIds ); + + SetExpanded( expandedIds ); + } + + public void ExpandMatchingSearchResults() + { + if( state.searchMode != SearchMode.ReferencesOnly ) + return; + + List expandedIds = new List( references.Count * 12 ); + for( int i = 0; i < rootItem.children.Count; i++ ) + GetMatchingSearchResultIdsRecursive( rootItem.children[i], expandedIds ); + + SetExpanded( expandedIds ); + } + + private void ExpandAllSelectionOccurrences() + { + IList selection = GetSelection(); + if( selection.Count == 0 ) + return; + + HashSet selectedNodes = new HashSet(); + for( int i = selection.Count - 1; i >= 0; i-- ) + selectedNodes.Add( GetDataFromId( selection[i] ).node ); + + List expandedIds = new List( GetExpanded() ); + for( int i = 0; i < rootItem.children.Count; i++ ) + GetReferenceNodeOccurrenceIdsRecursive( rootItem.children[i], selectedNodes, expandedIds ); + + SetExpanded( expandedIds ); + } + + private bool GetMainReferenceIdsRecursive( TreeViewItem item, List ids ) + { + if( item.depth > 0 && GetDataFromId( item.id ).node.IsMainReference ) + return true; + + bool shouldExpand = false; + if( item.hasChildren ) + { + for( int i = 0; i < item.children.Count; i++ ) + shouldExpand |= GetMainReferenceIdsRecursive( item.children[i], ids ); + } + else + shouldExpand = true; // No main reference is encountered in this branch; expand the whole branch + + if( shouldExpand ) + ids.Add( item.id ); + + return shouldExpand; + } + + private bool GetMatchingSearchResultIdsRecursive( TreeViewItem item, List ids ) + { + bool shouldExpand = false; + if( item.hasChildren ) + { + for( int i = 0; i < item.children.Count; i++ ) + shouldExpand |= GetMatchingSearchResultIdsRecursive( item.children[i], ids ); + } + + if( shouldExpand ) + ids.Add( item.id ); + else + shouldExpand = GetDataFromId( item.id ).shouldExpandAfterSearch; + + return shouldExpand; + } + + private bool GetReferenceNodeOccurrenceIdsRecursive( TreeViewItem item, HashSet referenceNodes, List ids ) + { + bool shouldExpand = false; + if( item.hasChildren ) + { + for( int i = 0; i < item.children.Count; i++ ) + shouldExpand |= GetReferenceNodeOccurrenceIdsRecursive( item.children[i], referenceNodes, ids ); + } + + if( shouldExpand ) + { + if( !ids.Contains( item.id ) ) + ids.Add( item.id ); + + return true; + } + else + return referenceNodes.Contains( GetDataFromId( item.id ).node ); + } + + private void HideItems( IList ids ) + { + if( ids.Count > 0 ) + { + List hiddenNodes = new List( ids.Count ); + List hiddenLinks = new List( ids.Count ); + List newExpandedItemIDs = new List( 32 ); + List newSelectedItemIDs = new List( 16 ); + + for( int i = 0; i < ids.Count; i++ ) + { + ReferenceNodeData data = GetDataFromId( ids[i] ); + if( data.item.depth > 0 ) + hiddenLinks.Add( data.parent.node[data.linkIndex] ); + else + hiddenNodes.Add( data.node ); + } + + int id = state.initialNodeId + 1; + for( int i = 0; i < rootItem.children.Count; i++ ) + CalculateNewItemIdsAfterHideRecursive( rootItem.children[i], hiddenNodes, hiddenLinks, newExpandedItemIDs, newSelectedItemIDs, ref id ); + + for( int i = 0; i < ids.Count; i++ ) + { + ReferenceNodeData data = GetDataFromId( ids[i] ); + if( data.item.depth > 0 ) + { + // Can't remove by index here because if multiple sibling nodes are removed at once, the latter sibling nodes' linkIndex + // will be different than their actual sibling indices until this TreeView is refreshed + data.parent.node.RemoveLink( data.node ); + } + else + references.Remove( data.node ); + } + + SetSelection( newSelectedItemIDs ); + SetExpanded( newExpandedItemIDs ); + Reload(); + } + } + + private void CalculateNewItemIdsAfterHideRecursive( TreeViewItem item, List hiddenNodes, List hiddenLinks, List newExpandedItemIDs, List newSelectedItemIDs, ref int id ) + { + ReferenceNodeData data = GetDataFromId( item.id ); + if( hiddenNodes.Contains( data.node ) || ( data.parent != null && hiddenLinks.Contains( data.parent.node[data.linkIndex] ) ) ) + return; + + if( IsExpanded( item.id ) ) + newExpandedItemIDs.Add( id ); + if( IsSelected( item.id ) ) + newSelectedItemIDs.Add( id ); + + id++; + + if( item.hasChildren ) + { + for( int i = 0; i < item.children.Count; i++ ) + CalculateNewItemIdsAfterHideRecursive( item.children[i], hiddenNodes, hiddenLinks, newExpandedItemIDs, newSelectedItemIDs, ref id ); + } + } + + private void SelectFirstOccurrencesOfDuplicateSelection() + { + IList selection = GetSelection(); + if( selection.Count == 0 ) + return; + + HashSet selectedNodes = new HashSet(); + for( int i = selection.Count - 1; i >= 0; i-- ) + { + ReferenceNodeData data = GetDataFromId( selection[i] ); + if( data.isDuplicate ) + selectedNodes.Add( data.node ); + } + + List newSelection = new List( selection.Count ); + for( int i = 0; i < rootItem.children.Count; i++ ) + FindFirstOccurrencesOfSelectionRecursive( rootItem.children[i], selectedNodes, newSelection ); + + if( newSelection.Count > 0 ) + { + SetSelection( newSelection, TreeViewSelectionOptions.FireSelectionChanged | TreeViewSelectionOptions.RevealAndFrame ); + + if( treeType != TreeType.IsolatedView ) + EditorWindow.focusedWindow.SendEvent( new Event() { type = EventType.KeyDown, keyCode = KeyCode.F } ); // To actually frame the row when external scroll view is used + } + } + + private void FindFirstOccurrencesOfSelectionRecursive( TreeViewItem item, HashSet selectedNodes, List result ) + { + ReferenceNodeData data = GetDataFromId( item.id ); + if( !data.isDuplicate && selectedNodes.Remove( data.node ) ) + result.Add( item.id ); + + if( item.hasChildren ) + { + for( int i = 0; i < item.children.Count; i++ ) + FindFirstOccurrencesOfSelectionRecursive( item.children[i], selectedNodes, result ); + } + } + + private void ShowChildrenOfSelectedUnusedObjects() + { + IList selection = GetSelection(); + if( selection.Count == 0 ) + return; + + for( int i = selection.Count - 1; i >= 0; i-- ) + { + ReferenceNodeData data = GetDataFromId( selection[i] ); + if( data.node.usedState != ReferenceNode.UsedState.MixedCollapsed ) + continue; + + data.node.usedState = ReferenceNode.UsedState.MixedExpanded; + + Object unityObject = data.node.UnityObject; + if( !unityObject ) + continue; + + string assetPath = AssetDatabase.GetAssetPath( unityObject ); + if( string.IsNullOrEmpty( assetPath ) ) + { + foreach( Object obj in usedObjectsSet ) + { + if( obj && obj is GameObject && obj != unityObject && ( (GameObject) obj ).transform.IsChildOf( ( (GameObject) unityObject ).transform ) ) + { + ReferenceNode childNode = new ReferenceNode() { nodeObject = obj }; + childNode.InitializeRecursively(); + data.node.AddLinkTo( childNode, "USED" ); + } + } + } + else + { + foreach( Object obj in usedObjectsSet ) + { + if( obj && AssetDatabase.GetAssetPath( obj ) == assetPath ) + { + ReferenceNode childNode = new ReferenceNode() { nodeObject = obj }; + childNode.InitializeRecursively(); + data.node.AddLinkTo( childNode, "USED" ); + } + } + } + } + + Reload(); + } + + private void ShowChildrenOfSelectionInNewWindow() + { + IList selection = SortItemIDsInRowOrder( GetSelection() ); + if( selection.Count == 0 ) + return; + + List selectedNodes = new List( selection.Count ); + for( int i = 0; i < selection.Count; i++ ) + { + ReferenceNodeData data = GetDataFromId( selection[i] ); + if( data.node.NumberOfOutgoingLinks > 0 && !selectedNodes.Contains( data.node ) ) + selectedNodes.Add( data.node ); + } + + if( selectedNodes.Count > 0 ) + { + SearchResultTreeView isolatedTreeView = new SearchResultTreeView( new SearchResultTreeViewState(), selectedNodes, TreeType.IsolatedView, null, hideDuplicateRows, hideReduntantPrefabVariantLinks, false ); + isolatedTreeView.ExpandMainReferences(); + + SearchResultTreeViewIsolatedView.Show( new Vector2( EditorWindow.focusedWindow.position.width, Mathf.Max( isolatedTreeView.totalHeight, EditorGUIUtility.singleLineHeight * 5f ) + 1f ), isolatedTreeView, new GUIContent( selectedNodes[0].Label + ( selectedNodes.Count <= 1 ? "" : ( " (and " + ( selectedNodes.Count - 1 ) + " more)" ) ) ) ); + } + } + + private void RefreshSelectedNodes( IList selectedIds ) + { + selectedReferenceNodes.Clear(); + selectedReferenceNodesHierarchyIds.Clear(); + selectedReferenceNodesHierarchyIndirectIds.Clear(); + + for( int i = 0; i < selectedIds.Count; i++ ) + { + ReferenceNodeData data = GetDataFromId( selectedIds[i] ); + + selectedReferenceNodes.Add( data.node ); + selectedReferenceNodesHierarchyIds.Add( selectedIds[i] ); + + if( data.item.parent == null ) + continue; + + TreeViewItem linkItem = data.item; + for( TreeViewItem parentItem = linkItem.parent; parentItem.depth >= 0; parentItem = parentItem.parent ) + { + selectedReferenceNodesHierarchyIds.Add( parentItem.id ); + + List parentItemChildren = parentItem.children; + for( int j = 0; parentItemChildren[j] != linkItem; j++ ) + selectedReferenceNodesHierarchyIndirectIds.Add( parentItemChildren[j].id ); + + linkItem = parentItem; + } + } + } + + private void ShowTooltipDelayed() + { + if( EditorApplication.timeSinceStartup >= customTooltipShowTime ) + { + EditorApplication.update -= ShowTooltipDelayed; + + if( GetRows().Contains( hoveredData.item ) ) // Make sure that the hovered item is still a part of the tree (e.g. it might have been removed with middle mouse button) + SearchResultTooltip.Show( hoveredDataRect, hoveredData.tooltipText ); + } + } + + public void CancelDelayedTooltip() + { + EditorApplication.update -= ShowTooltipDelayed; + } + + public void GetRowStateWithId( int id, out bool isFirstRow, out bool isLastRow, out bool isExpanded, out bool canExpand ) + { + if( isTreeViewEmpty ) + { + isFirstRow = isLastRow = true; + isExpanded = canExpand = false; + + return; + } + + IList rows = GetRows(); + for( int i = 0; i < rows.Count; i++ ) + { + if( rows[i].id == id ) + { + isFirstRow = ( i <= 0 ); + isLastRow = ( i >= rows.Count - 1 ); + isExpanded = rows[i].hasChildren && IsExpanded( id ); + canExpand = rows[i].hasChildren && !IsExpanded( id ); + + return; + } + } + + isFirstRow = isLastRow = isExpanded = canExpand = false; + } + + public bool GetRowRectWithId( int id, out Rect rect ) + { + IList rows = GetRows(); + for( int i = 0; i < rows.Count; i++ ) + { + if( rows[i].id == id ) + { + rect = GetRowRect( i ); + return true; + } + } + + rect = new Rect(); + return false; + } + + public Rect SelectFirstRowAndReturnRect() + { + SetSelection( new int[1] { GetRows()[0].id }, TreeViewSelectionOptions.FireSelectionChanged ); + return GetRowRect( 0 ); + } + + public Rect SelectLastRowAndReturnRect() + { + IList rows = GetRows(); + SetSelection( new int[1] { rows[rows.Count - 1].id }, TreeViewSelectionOptions.FireSelectionChanged ); + return GetRowRect( rows.Count - 1 ); + } + + private void HighlightSearchTermInString( StringBuilder sb, string str ) + { + int prevSearchOccurrenceIndex = 0, searchOccurrenceIndex = 0; + while( ( searchOccurrenceIndex = textComparer.IndexOf( str, state.searchTerm, searchOccurrenceIndex, textCompareOptions ) ) >= 0 ) + { + sb.Append( str, prevSearchOccurrenceIndex, searchOccurrenceIndex - prevSearchOccurrenceIndex ); + sb.Append( highlightedSearchTextColor ).Append( "" ); + sb.Append( str, searchOccurrenceIndex, state.searchTerm.Length ); + sb.Append( "" ).Append( "" ); + + searchOccurrenceIndex += state.searchTerm.Length; + prevSearchOccurrenceIndex = searchOccurrenceIndex; + } + + if( prevSearchOccurrenceIndex < str.Length ) + sb.Append( str, prevSearchOccurrenceIndex, str.Length - prevSearchOccurrenceIndex ); + } + + public void OnSettingsChanged( bool resetHighlightedSearchTextColor, bool resetTooltipDescriptionsTextColor ) + { + hoveredData = null; + + if( !resetHighlightedSearchTextColor && !resetTooltipDescriptionsTextColor ) + return; + + if( resetHighlightedSearchTextColor ) + highlightedSearchTextColor = ""; + + for( int i = idToNodeDataLookup.Count - 1; i >= 0; i-- ) + { + if( isSearching && resetHighlightedSearchTextColor ) + idToNodeDataLookup[i].item.displayName = ""; + if( resetTooltipDescriptionsTextColor ) + idToNodeDataLookup[i].ResetTooltip(); + } + } + } + + public class SearchResultTreeViewIsolatedView : EditorWindow + { + private SearchResultTreeView treeView; + private bool shouldRepositionSelf = true; + + public static void Show( Vector2 preferredSize, SearchResultTreeView treeView, GUIContent title ) + { + SearchResultTreeViewIsolatedView window = CreateInstance(); + window.treeView = treeView; + window.titleContent = title; + window.Show(); + + window.minSize = new Vector2( 150f, Mathf.Min( preferredSize.y, EditorGUIUtility.singleLineHeight * 2f ) ); + window.position = new Rect( new Vector2( -9999f, -9999f ), preferredSize ); + window.Repaint(); + } + + private void OnEnable() + { + wantsMouseMove = wantsMouseEnterLeaveWindow = true; + } + + private void OnGUI() + { + if( treeView == null ) // After domain reload + Close(); + else + { + treeView.OnGUI( GUILayoutUtility.GetRect( 0f, 100000f, 0f, 100000f ) ); + + if( shouldRepositionSelf ) + { + float preferredHeight = GUILayoutUtility.GetLastRect().height; + if( preferredHeight > 10f ) + { + Vector2 size = position.size; + position = Utilities.GetScreenFittedRect( new Rect( GUIUtility.GUIToScreenPoint( Event.current.mousePosition ) + new Vector2( size.x * -0.5f, 10f ), size ) ); + + shouldRepositionSelf = false; + GUIUtility.ExitGUI(); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs.meta new file mode 100644 index 00000000..94363a2b --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b3903f1d3149e9b4990e49206f8255c8 +timeCreated: 1638690728 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs b/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs new file mode 100644 index 00000000..f1c6661b --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs @@ -0,0 +1,565 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +#if UNITY_2018_1_OR_NEWER +using Unity.Collections; +#endif +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; +#if UNITY_2018_3_OR_NEWER && !UNITY_2021_2_OR_NEWER +using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage; +using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; +#endif + +namespace AssetUsageDetectorNamespace +{ + public static class Utilities + { + // A set of commonly used Unity types + private static readonly HashSet primitiveUnityTypes = new HashSet() + { + typeof( string ), typeof( Vector4 ), typeof( Vector3 ), typeof( Vector2 ), typeof( Rect ), + typeof( Quaternion ), typeof( Color ), typeof( Color32 ), typeof( LayerMask ), typeof( Bounds ), + typeof( Matrix4x4 ), typeof( AnimationCurve ), typeof( Gradient ), typeof( RectOffset ), + typeof( bool[] ), typeof( byte[] ), typeof( sbyte[] ), typeof( char[] ), typeof( decimal[] ), + typeof( double[] ), typeof( float[] ), typeof( int[] ), typeof( uint[] ), typeof( long[] ), + typeof( ulong[] ), typeof( short[] ), typeof( ushort[] ), typeof( string[] ), + typeof( Vector4[] ), typeof( Vector3[] ), typeof( Vector2[] ), typeof( Rect[] ), + typeof( Quaternion[] ), typeof( Color[] ), typeof( Color32[] ), typeof( LayerMask[] ), typeof( Bounds[] ), + typeof( Matrix4x4[] ), typeof( AnimationCurve[] ), typeof( Gradient[] ), typeof( RectOffset[] ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), typeof( List ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), typeof( List ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), typeof( List ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ), +#if UNITY_2017_2_OR_NEWER + typeof( Vector3Int ), typeof( Vector2Int ), typeof( RectInt ), typeof( BoundsInt ), + typeof( Vector3Int[] ), typeof( Vector2Int[] ), typeof( RectInt[] ), typeof( BoundsInt[] ), + typeof( List ), typeof( List ), typeof( List ), typeof( List ) +#endif + }; + + private static readonly string reflectionNamespace = typeof( Assembly ).Namespace; +#if UNITY_2018_1_OR_NEWER + private static readonly string nativeCollectionsNamespace = typeof( NativeArray ).Namespace; +#endif + + private static MethodInfo screenFittedRectGetter; + + private static readonly HashSet folderContentsSet = new HashSet(); + + internal static readonly StringBuilder stringBuilder = new StringBuilder( 400 ); + + public static readonly GUILayoutOption GL_EXPAND_WIDTH = GUILayout.ExpandWidth( true ); + public static readonly GUILayoutOption GL_EXPAND_HEIGHT = GUILayout.ExpandHeight( true ); + public static readonly GUILayoutOption GL_WIDTH_25 = GUILayout.Width( 25 ); + public static readonly GUILayoutOption GL_WIDTH_100 = GUILayout.Width( 100 ); + public static readonly GUILayoutOption GL_WIDTH_250 = GUILayout.Width( 250 ); + public static readonly GUILayoutOption GL_HEIGHT_0 = GUILayout.Height( 0 ); + public static readonly GUILayoutOption GL_HEIGHT_2 = GUILayout.Height( 2 ); + public static readonly GUILayoutOption GL_HEIGHT_30 = GUILayout.Height( 30 ); + public static readonly GUILayoutOption GL_HEIGHT_35 = GUILayout.Height( 35 ); + public static readonly GUILayoutOption GL_HEIGHT_40 = GUILayout.Height( 40 ); + + private static GUIStyle m_boxGUIStyle; // GUIStyle used to draw the results of the search + public static GUIStyle BoxGUIStyle + { + get + { + if( m_boxGUIStyle == null ) + { + m_boxGUIStyle = new GUIStyle( EditorStyles.helpBox ) + { + alignment = TextAnchor.MiddleCenter, + font = EditorStyles.label.font, + richText = true + }; + + Color textColor = GUI.skin.button.normal.textColor; + m_boxGUIStyle.normal.textColor = textColor; + m_boxGUIStyle.hover.textColor = textColor; + m_boxGUIStyle.focused.textColor = textColor; + m_boxGUIStyle.active.textColor = textColor; + +#if !UNITY_2019_1_OR_NEWER || UNITY_2019_3_OR_NEWER + // On 2019.1 and 2019.2 versions, GUI.skin.button.fontSize returns 0 on some devices + // https://forum.unity.com/threads/asset-usage-detector-find-references-to-an-asset-object-open-source.408134/page-3#post-7285954 + m_boxGUIStyle.fontSize = ( m_boxGUIStyle.fontSize + GUI.skin.button.fontSize ) / 2; +#endif + } + + return m_boxGUIStyle; + } + } + + // Check if object is an asset or a Scene object + public static bool IsAsset( this object obj ) + { + return obj is Object && AssetDatabase.Contains( (Object) obj ); + } + + public static bool IsAsset( this Object obj ) + { + return AssetDatabase.Contains( obj ); + } + + // Check if object is a folder asset + public static bool IsFolder( this Object obj ) + { + return obj is DefaultAsset && AssetDatabase.IsValidFolder( AssetDatabase.GetAssetPath( obj ) ); + } + + // Returns an enumerator to iterate through all asset paths in the folder + public static IEnumerable EnumerateFolderContents( Object folderAsset ) + { + string[] folderContents = AssetDatabase.FindAssets( "", new string[] { AssetDatabase.GetAssetPath( folderAsset ) } ); + if( folderContents == null ) + return new EmptyEnumerator(); + + folderContentsSet.Clear(); + for( int i = 0; i < folderContents.Length; i++ ) + { + string filePath = AssetDatabase.GUIDToAssetPath( folderContents[i] ); + if( !string.IsNullOrEmpty( filePath ) && !AssetDatabase.IsValidFolder( filePath ) ) + folderContentsSet.Add( filePath ); + } + + return folderContentsSet; + } + + public static void GetObjectsToSelectAndPing( this Object obj, out Object selection, out Object pingTarget ) + { + if( obj == null || obj.Equals( null ) ) + { + selection = pingTarget = null; + return; + } + + if( obj is Component ) + obj = ( (Component) obj ).gameObject; + + selection = pingTarget = obj; + + if( obj.IsAsset() ) + { + if( obj is GameObject ) + { + // Pinging a prefab only works if the pinged object is the root of the prefab or a direct child of it. Pinging any grandchildren + // of the prefab doesn't work; in which case, traverse the parent hierarchy until a pingable parent is reached +#if UNITY_2018_3_OR_NEWER + Transform objTR = ( (GameObject) obj ).transform.root; + + PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType( objTR.gameObject ); + if( prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant ) + { + string assetPath = AssetDatabase.GetAssetPath( objTR.gameObject ); + PrefabStage openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage(); +#if UNITY_2020_1_OR_NEWER + if( openPrefabStage != null && openPrefabStage.stageHandle.IsValid() && assetPath == openPrefabStage.assetPath ) +#else + if( openPrefabStage != null && openPrefabStage.stageHandle.IsValid() && assetPath == openPrefabStage.prefabAssetPath ) +#endif + { + GameObject prefabStageGO = FollowSymmetricHierarchy( (GameObject) obj, ( (GameObject) obj ).transform.root.gameObject, openPrefabStage.prefabContentsRoot ); + if( prefabStageGO != null ) + { + objTR = prefabStageGO.transform; + selection = objTR.gameObject; + } + } +#if UNITY_2019_1_OR_NEWER + else if( obj != objTR.gameObject ) + selection = objTR.gameObject; +#endif + } + else if( prefabAssetType == PrefabAssetType.Model ) + { + objTR = ( (GameObject) obj ).transform; + while( objTR.parent != null && objTR.parent.parent != null ) + objTR = objTR.parent; + } +#else + Transform objTR = ( (GameObject) obj ).transform; + while( objTR.parent != null && objTR.parent.parent != null ) + objTR = objTR.parent; +#endif + + pingTarget = objTR.gameObject; + } + else if( ( obj.hideFlags & ( HideFlags.HideInInspector | HideFlags.HideInHierarchy ) ) != HideFlags.None ) + { + // Can't ping assets that are hidden from Project window (e.g. animator states of AnimatorController), ping the main asset at that path instead + pingTarget = AssetDatabase.LoadMainAssetAtPath( AssetDatabase.GetAssetPath( obj ) ); + } + else if( !AssetDatabase.IsMainAsset( obj ) && Array.IndexOf( AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( obj ) ), obj ) < 0 ) + { + // VFX Graph assets' nodes are serialized as part of the graph but they are invisible in the Project window even though their hideFlags is None (I don't know how) + pingTarget = AssetDatabase.LoadMainAssetAtPath( AssetDatabase.GetAssetPath( obj ) ); + } + } + } + + // We are passing "go"s root Transform to thisRoot parameter. If we use go.transform.root instead, when we are in prefab mode on + // newer Unity versions, it points to the preview scene at the root of the prefab stage instead of pointing to the actual root of "go" + public static GameObject FollowSymmetricHierarchy( this GameObject go, GameObject thisRoot, GameObject symmetricRoot ) + { + Transform target = go.transform; + Transform root1 = thisRoot.transform; + Transform root2 = symmetricRoot.transform; + while( root1 != target ) + { + Transform temp = target; + while( temp.parent != root1 ) + temp = temp.parent; + + Transform newRoot2; + int siblingIndex = temp.GetSiblingIndex(); + if( siblingIndex < root2.childCount ) + { + newRoot2 = root2.GetChild( siblingIndex ); + if( newRoot2.name != temp.name ) + newRoot2 = root2.Find( temp.name ); + } + else + newRoot2 = root2.Find( temp.name ); + + if( newRoot2 == null ) + return null; + + root2 = newRoot2; + root1 = temp; + } + + return root2.gameObject; + } + + // Returns -1 if t1 is above t2 in Hierarchy, 1 if t1 is below t2 in Hierarchy and 0 if they are the same object + public static int CompareHierarchySiblingIndices( Transform t1, Transform t2 ) + { + Transform parent1 = t1.parent; + Transform parent2 = t2.parent; + + if( parent1 == parent2 ) + return t1.GetSiblingIndex() - t2.GetSiblingIndex(); + + int deltaHierarchyDepth = 0; + for( ; parent1; parent1 = parent1.parent ) + deltaHierarchyDepth++; + for( ; parent2; parent2 = parent2.parent ) + deltaHierarchyDepth--; + + for( ; deltaHierarchyDepth > 0; deltaHierarchyDepth-- ) + { + t1 = t1.parent; + if( t1 == t2 ) + return 1; + } + for( ; deltaHierarchyDepth < 0; deltaHierarchyDepth++ ) + { + t2 = t2.parent; + if( t1 == t2 ) + return -1; + } + + while( t1.parent != t2.parent ) + { + t1 = t1.parent; + t2 = t2.parent; + } + + return t1.GetSiblingIndex() - t2.GetSiblingIndex(); + } + + // Check if the field is serializable + public static bool IsSerializable( this FieldInfo fieldInfo ) + { + // See Serialization Rules: https://docs.unity3d.com/Manual/script-Serialization.html + if( fieldInfo.IsInitOnly ) + return false; + +#if UNITY_2019_3_OR_NEWER + // SerializeReference makes even System.Object fields serializable + if( Attribute.IsDefined( fieldInfo, typeof( SerializeReference ) ) ) + return true; +#endif + + if( ( !fieldInfo.IsPublic || fieldInfo.IsNotSerialized ) && !Attribute.IsDefined( fieldInfo, typeof( SerializeField ) ) ) + return false; + + return IsTypeSerializable( fieldInfo.FieldType ); + } + + // Check if the property is serializable + public static bool IsSerializable( this PropertyInfo propertyInfo ) + { + return IsTypeSerializable( propertyInfo.PropertyType ); + } + + // Check if type is serializable + private static bool IsTypeSerializable( Type type ) + { + // see Serialization Rules: https://docs.unity3d.com/Manual/script-Serialization.html + if( typeof( Object ).IsAssignableFrom( type ) ) + return true; + + if( type.IsArray ) + { + if( type.GetArrayRank() != 1 ) + return false; + + type = type.GetElementType(); + + if( typeof( Object ).IsAssignableFrom( type ) ) + return true; + } + else if( type.IsGenericType ) + { + // Generic types are allowed on 2020.1 and later +#if UNITY_2020_1_OR_NEWER + if( type.GetGenericTypeDefinition() == typeof( List<> ) ) + { + type = type.GetGenericArguments()[0]; + + if( typeof( Object ).IsAssignableFrom( type ) ) + return true; + } +#else + if( type.GetGenericTypeDefinition() != typeof( List<> ) ) + return false; + + type = type.GetGenericArguments()[0]; + + if( typeof( Object ).IsAssignableFrom( type ) ) + return true; +#endif + } + +#if !UNITY_2020_1_OR_NEWER + if( type.IsGenericType ) + return false; +#endif + + return Attribute.IsDefined( type, typeof( SerializableAttribute ), false ); + } + + // Check if instances of this type should be searched for references + public static bool IsIgnoredUnityType( this Type type ) + { + if( type.IsPrimitive || primitiveUnityTypes.Contains( type ) || type.IsEnum ) + return true; + +#if UNITY_2018_1_OR_NEWER + // Searching NativeArrays for reference can throw InvalidOperationException if the collection is disposed + if( type.Namespace == nativeCollectionsNamespace ) + return true; +#endif + + // Searching assembly variables for reference throws InvalidCastException on .NET 4.0 runtime + if( typeof( Type ).IsAssignableFrom( type ) || type.Namespace == reflectionNamespace ) + return true; + + // Searching pointers or ref variables for reference throws ArgumentException + if( type.IsPointer || type.IsByRef ) + return true; + + return false; + } + + // Get function for a field + public static VariableGetVal CreateGetter( this FieldInfo fieldInfo, Type type ) + { + // Commented the IL generator code below because it might actually be slower than simply using reflection + // Credit: https://www.codeproject.com/Articles/14560/Fast-Dynamic-Property-Field-Accessors + //DynamicMethod dm = new DynamicMethod( "Get" + fieldInfo.Name, fieldInfo.FieldType, new Type[] { typeof( object ) }, type ); + //ILGenerator il = dm.GetILGenerator(); + //// Load the instance of the object (argument 0) onto the stack + //il.Emit( OpCodes.Ldarg_0 ); + //// Load the value of the object's field (fi) onto the stack + //il.Emit( OpCodes.Ldfld, fieldInfo ); + //// return the value on the top of the stack + //il.Emit( OpCodes.Ret ); + + //return (VariableGetVal) dm.CreateDelegate( typeof( VariableGetVal ) ); + + return fieldInfo.GetValue; + } + + // Get function for a property + public static VariableGetVal CreateGetter( this PropertyInfo propertyInfo ) + { + // Can't use PropertyWrapper (which uses CreateDelegate) for property getters of structs + if( propertyInfo.DeclaringType.IsValueType ) + { + return !propertyInfo.CanRead ? (VariableGetVal) null : ( obj ) => + { + try + { + return propertyInfo.GetValue( obj, null ); + } + catch + { + // Property getters may return various kinds of exceptions if their backing fields are not initialized (yet) + return null; + } + }; + } + + Type GenType = typeof( PropertyWrapper<,> ).MakeGenericType( propertyInfo.DeclaringType, propertyInfo.PropertyType ); + return ( (IPropertyAccessor) Activator.CreateInstance( GenType, propertyInfo.GetGetMethod( true ) ) ).GetValue; + } + + // Check if all open scenes are saved (not dirty) + public static bool AreScenesSaved() + { + for( int i = 0; i < SceneManager.sceneCount; i++ ) + { + Scene scene = SceneManager.GetSceneAt( i ); + if( scene.isDirty || string.IsNullOrEmpty( scene.path ) ) + return false; + } + + return true; + } + + // Returns file extension in lowercase (period not included) + public static string GetFileExtension( string path ) + { + int extensionIndex = path.LastIndexOf( '.' ); + if( extensionIndex < 0 || extensionIndex >= path.Length - 1 ) + return ""; + + stringBuilder.Length = 0; + for( extensionIndex++; extensionIndex < path.Length; extensionIndex++ ) + { + char ch = path[extensionIndex]; + if( ch >= 65 && ch <= 90 ) // A-Z + ch += (char) 32; // Converted to a-z + + stringBuilder.Append( ch ); + } + + return stringBuilder.ToString(); + } + + // Draw horizontal line inside OnGUI + public static void DrawSeparatorLine() + { + GUILayout.Space( 4f ); + GUILayout.Box( "", GL_HEIGHT_2, GL_EXPAND_WIDTH ); + GUILayout.Space( 4f ); + } + + // Restricts the given Rect within the screen's bounds + public static Rect GetScreenFittedRect( Rect originalRect ) + { + if( screenFittedRectGetter == null ) + screenFittedRectGetter = typeof( EditorWindow ).Assembly.GetType( "UnityEditor.ContainerWindow" ).GetMethod( "FitRectToScreen", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ); + + return (Rect) screenFittedRectGetter.Invoke( null, new object[3] { originalRect, true, true } ); + } + + // Check if all the objects inside the list are null + public static bool IsEmpty( this List objectsToSearch ) + { + if( objectsToSearch == null ) + return true; + + for( int i = 0; i < objectsToSearch.Count; i++ ) + { + if( objectsToSearch[i].obj != null && !objectsToSearch[i].obj.Equals( null ) ) + return false; + } + + return true; + } + + // Check if all the objects inside the list are null + public static bool IsEmpty( this List objects ) + { + if( objects == null ) + return true; + + for( int i = 0; i < objects.Count; i++ ) + { + if( objects[i] != null && !objects[i].Equals( null ) ) + return false; + } + + return true; + } + + // Check if all the objects that are enumerated are null + public static bool IsEmpty( this IEnumerable objects ) + { + if( objects == null ) + return true; + + using( IEnumerator enumerator = objects.GetEnumerator() ) + { + while( enumerator.MoveNext() ) + { + if( enumerator.Current != null && !enumerator.Current.Equals( null ) ) + return false; + } + } + + return true; + } + + // Returns true is str starts with prefix + public static bool StartsWithFast( this string str, string prefix ) + { + int aLen = str.Length; + int bLen = prefix.Length; + int ap = 0; int bp = 0; + while( ap < aLen && bp < bLen && str[ap] == prefix[bp] ) + { + ap++; + bp++; + } + + return bp == bLen; + } + + // Returns true is str ends with postfix + public static bool EndsWithFast( this string str, string postfix ) + { + int ap = str.Length - 1; + int bp = postfix.Length - 1; + while( ap >= 0 && bp >= 0 && str[ap] == postfix[bp] ) + { + ap--; + bp--; + } + + return bp < 0; + } + + public static bool ContainsFast( this List list, T element ) + { + if( !( element is ValueType ) ) + { + for( int i = list.Count - 1; i >= 0; i-- ) + { + if( ReferenceEquals( list[i], element ) ) + return true; + } + } + else + { + for( int i = list.Count - 1; i >= 0; i-- ) + { + if( element.Equals( list[i] ) ) + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs.meta new file mode 100644 index 00000000..2daf8acf --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52b272b7591fb90499916205261524e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs b/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs new file mode 100644 index 00000000..87c8fa9c --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs @@ -0,0 +1,83 @@ +using System; +using System.Reflection; +using System.Text; +using UnityEngine; + +namespace AssetUsageDetectorNamespace +{ + // Delegate to get the value of a variable (either field or property) + public delegate object VariableGetVal( object obj ); + + // Custom struct to hold a variable, its important properties and its getter function + public struct VariableGetterHolder + { + public readonly MemberInfo variable; + public readonly bool isSerializable; + private readonly VariableGetVal getter; + + public string Name { get { return variable.Name; } } + public bool IsProperty { get { return variable is PropertyInfo; } } + + public VariableGetterHolder( FieldInfo fieldInfo, VariableGetVal getter, bool isSerializable ) + { + this.variable = fieldInfo; + this.isSerializable = isSerializable; + this.getter = getter; + } + + public VariableGetterHolder( PropertyInfo propertyInfo, VariableGetVal getter, bool isSerializable ) + { + this.variable = propertyInfo; + this.isSerializable = isSerializable; + this.getter = getter; + } + + public object Get( object obj ) + { + try + { + return getter( obj ); + } + catch( Exception e ) + { + StringBuilder sb = Utilities.stringBuilder; + sb.Length = 0; + sb.Append( "Error while getting the value of (" ).Append( IsProperty ? ( (PropertyInfo) variable ).PropertyType : ( (FieldInfo) variable ).FieldType ).Append( ") " ) + .Append( variable.DeclaringType ).Append( "." ).Append( Name ).Append( ": " ).Append( e ); + + Debug.LogError( sb.ToString() ); + return null; + } + } + } + + // Credit: http://stackoverflow.com/questions/724143/how-do-i-create-a-delegate-for-a-net-property + public interface IPropertyAccessor + { + object GetValue( object source ); + } + + // A wrapper class for properties to get their values more efficiently + public class PropertyWrapper : IPropertyAccessor where TObject : class + { + private readonly Func getter; + + public PropertyWrapper( MethodInfo getterMethod ) + { + getter = (Func) Delegate.CreateDelegate( typeof( Func ), getterMethod ); + } + + public object GetValue( object obj ) + { + try + { + return getter( (TObject) obj ); + } + catch + { + // Property getters may return various kinds of exceptions if their backing fields are not initialized (yet) + return null; + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs.meta b/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs.meta new file mode 100644 index 00000000..b78cc0e8 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e94c83e8b850514ca0217aeff1491a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/AssetUsageDetector/README.txt b/Assets/Plugins/AssetUsageDetector/README.txt new file mode 100644 index 00000000..f962275f --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/README.txt @@ -0,0 +1,4 @@ += Asset Usage Detector (v2.5.3) = + +Documentation: https://github.com/yasirkula/UnityAssetUsageDetector +E-mail: yasirkula@gmail.com \ No newline at end of file diff --git a/Assets/Plugins/AssetUsageDetector/README.txt.meta b/Assets/Plugins/AssetUsageDetector/README.txt.meta new file mode 100644 index 00000000..66705238 --- /dev/null +++ b/Assets/Plugins/AssetUsageDetector/README.txt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac528f1751f33a647a45caeff6a9344b +timeCreated: 1520032521 +licenseType: Store +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/MainMenu.unity b/Assets/Scenes/MainMenu.unity index f9b890c2..7aa55966 100644 --- a/Assets/Scenes/MainMenu.unity +++ b/Assets/Scenes/MainMenu.unity @@ -134,14 +134,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 9 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -154,6 +170,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 9 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_10 @@ -248,7 +268,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -274,14 +294,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 5 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -294,6 +330,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 5 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_6 @@ -388,7 +428,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -2783,14 +2823,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 4 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -2803,6 +2859,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 4 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_5 @@ -2897,7 +2957,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -3198,14 +3258,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 10 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -3218,6 +3294,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 10 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_11 @@ -3312,7 +3392,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -3343,14 +3423,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 8 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -3363,6 +3459,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 8 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_9 @@ -3457,7 +3557,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -3483,14 +3583,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 1 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -3503,6 +3619,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 1 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_2 @@ -3597,7 +3717,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -3623,14 +3743,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 19 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -3643,6 +3779,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 19 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_20 @@ -3737,7 +3877,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -3935,14 +4075,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 16 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -3955,6 +4111,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 16 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_17 @@ -4049,7 +4209,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -4568,14 +4728,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 18 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -4588,6 +4764,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 18 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_19 @@ -4682,7 +4862,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -4842,14 +5022,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 15 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -4862,6 +5058,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 15 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_16 @@ -4956,7 +5156,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -5132,14 +5332,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 7 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -5152,6 +5368,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 7 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_8 @@ -5246,7 +5466,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -5922,7 +6142,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.5} m_AnchorMax: {x: 0, y: 0.5} - m_AnchoredPosition: {x: 119.45999, y: -185} + m_AnchoredPosition: {x: 119.45996, y: -185} m_SizeDelta: {x: 101.493, y: 188.487} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1335917395 @@ -6099,14 +6319,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 17 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -6119,6 +6355,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 17 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_18 @@ -6213,7 +6453,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -6406,7 +6646,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 9084287967982315181, guid: 51c371247ba47482c9e9475f94f0d3f4, type: 3} propertyPath: m_AnchoredPosition.x - value: 72.0051 + value: 72.00513 objectReference: {fileID: 0} - target: {fileID: 9084287967982315181, guid: 51c371247ba47482c9e9475f94f0d3f4, type: 3} propertyPath: m_AnchoredPosition.y @@ -6540,14 +6780,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 3 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -6560,6 +6816,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 3 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_4 @@ -6654,7 +6914,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -26518,14 +26778,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 11 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -26538,6 +26814,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 11 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_12 @@ -26632,7 +26912,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -26792,14 +27072,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 13 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -26812,6 +27108,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 13 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_14 @@ -26906,7 +27206,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -27881,14 +28181,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 14 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -27901,6 +28217,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 14 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_15 @@ -27995,7 +28315,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -28663,14 +28983,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 2 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -28683,6 +29019,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 2 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_3 @@ -28777,7 +29117,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -28895,14 +29235,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 12 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -28915,6 +29271,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 12 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_13 @@ -29009,7 +29369,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -29040,14 +29400,30 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_IntArgument value: 6 objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -29060,6 +29436,10 @@ PrefabInstance: propertyPath: m_IsActive value: 0 objectReference: {fileID: 0} + - target: {fileID: 3857689146710161209, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: id + value: 6 + objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name value: LevelButton_7 @@ -29154,7 +29534,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7335990495736439642, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8346622786181217203, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive @@ -29180,10 +29560,26 @@ PrefabInstance: propertyPath: m_TargetGraphic value: objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 3 + objectReference: {fileID: 0} - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 2102374907} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: OnButtonClicked + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: LevelSelectionUI, Assembly-CSharp + objectReference: {fileID: 0} + - target: {fileID: 1039565001715846327, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} + propertyPath: m_OnClick.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 1312287172325328597, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_AnchoredPosition.x value: -3.0719986 @@ -29194,7 +29590,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 1424223140922706931, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 5612874387815100987, guid: 3b13e9fdb991912489e63e95b7e37c8a, type: 3} propertyPath: m_Name diff --git a/Assets/Scripts/DataManager.cs b/Assets/Scripts/DataManager.cs new file mode 100644 index 00000000..18ef754f --- /dev/null +++ b/Assets/Scripts/DataManager.cs @@ -0,0 +1,370 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; +using System.IO; + +public class DataManager +{ + //public static DataManager Instance { get; set; } + int val = 0; + private readonly string _pref_VAL = "VAL"; + public int VAL + { + get => val; + set + { + val = value; + PlayerPrefs.SetInt(_pref_VAL, val); + } + } + int currentlevelindex = 0; + private readonly string _pref_CURRENTLEVELINDEX= "CURRENTLEVELINDEX"; + public int CURRENTLEVELINDEX + { + get => currentlevelindex; + set + { + currentlevelindex = value; + PlayerPrefs.SetInt(_pref_CURRENTLEVELINDEX, currentlevelindex); + } + } + int maxlevelindex = 0; + private readonly string _pref_MAXLEVELINDEX = "MAXLEVELINDEX"; + public int MAXLEVELINDEX + { + get => maxlevelindex; + set + { + maxlevelindex = value; + PlayerPrefs.SetInt(_pref_MAXLEVELINDEX, maxlevelindex); + } + } + + + //int currentdogindex = 0; + //private readonly string _pref_CURRENTDOGINDEX = "CURRENTDOGINDEX"; + //public int CURRENTDOGINDEX + //{ + // get => currentdogindex; + // set + // { + // currentdogindex = value; + // PlayerPrefs.SetInt(_pref_CURRENTDOGINDEX, currentdogindex); + // } + //} + //int currentactivepropindex = 0; + //private readonly string _pref_CURRENTACTIVEPROPINDEX = "CURRENTACTIVEPROPINDEX"; + //public int CURRENTACTIVEPROPINDEX + //{ + // get => currentactivepropindex; + // set + // { + // currentactivepropindex = value; + // PlayerPrefs.SetInt(_pref_CURRENTACTIVEPROPINDEX, currentactivepropindex); + // } + //} + + //int currentzone = 0; + //private readonly string _pref_currentzone = "CurrentZone"; + //public int CurrentZone + //{ + // get => currentzone; + // set + // { + // currentzone = Mathf.Clamp(value, 0, 2); + // PlayerPrefs.SetInt(_pref_currentzone, currentzone); + // } + //} + + //int interact_p_counter = 0; + //private readonly string _pref_INTERACT_P_COUNTER = "INTERACT_P_COUNTER"; + //public int INTERACT_P_COUNTER + //{ + // get => interact_p_counter; + // set + // { + // interact_p_counter = value; + // PlayerPrefs.SetInt(_pref_INTERACT_P_COUNTER, interact_p_counter); + // } + //} + //int interact_c_counter = 0; + //private readonly string _pref_INTERACT_C_COUNTER = "INTERACT_C_COUNTER"; + //public int INTERACT_C_COUNTER + //{ + // get => interact_c_counter; + // set + // { + // interact_c_counter = value; + // PlayerPrefs.SetInt(_pref_INTERACT_C_COUNTER, interact_c_counter); + // } + //} + //public bool IsTutorialCompleted + //{ + // get => istutorialcompleted; + // set + // { + // istutorialcompleted = value; + // PlayerPrefs.SetInt(_pref_istutorialcompleted, istutorialcompleted ? 1 : 0); + // } + //} + //bool istutorialcompleted = false; + //private readonly string _pref_istutorialcompleted = "IsTutorialCompleted"; + + //// public bool IsTutorialCompleted + //// { + //// get => istutorialcompleted; + //// set + //// { + //// istutorialcompleted = value; + //// PlayerPrefs.SetInt(_pref_istutorialcompleted, (istutorialcompleted ? 0 : 1)); + //// } + //// } + //int correctdecisioncounter = 0; + //private readonly string _pref_correctdecisioncounter = "CorrectDecisionCounter"; + //public int CorrectDecisionCounter + //{ + // get => correctdecisioncounter; + // set + // { + // correctdecisioncounter = value; + // PlayerPrefs.SetInt(_pref_correctdecisioncounter, correctdecisioncounter); + // } + //} + //int rp = 0; + //private readonly string _pref_rp = "RP"; + + //[SerializeField] + //public int RP + //{ + // get => rp; + // set + // { + // rp = value; + // PlayerPrefs.SetInt(_pref_rp, rp); + // } + //} + //bool runningstatus; + //private readonly string _pref_runningstatus = "RunnigStatus"; + //public bool RunnigStatus + //{ + // get => runningstatus; + // set + // { + // runningstatus = value; + // PlayerPrefs.SetInt(_pref_runningstatus, (runningstatus ? 1 : 0)); + // } + //} + //bool sfxstatus; + //private readonly string _pref_sfxstatus = "SFXStatus"; + //public bool SFXStatus + //{ + // get => sfxstatus; + // set + // { + // sfxstatus = value; + // PlayerPrefs.SetInt(_pref_sfxstatus, (sfxstatus ? 1 : 0)); + // } + //} + //bool musicstatus; + //private readonly string _pref_musicstatus = "MusicStatusStatus"; + //public bool MusicStatus + //{ + // get => musicstatus; + // set + // { + // musicstatus = value; + // PlayerPrefs.SetInt(_pref_musicstatus, (musicstatus ? 1 : 0)); + // } + //} + //int days = 0; + //private readonly string _pref_days = "Days"; + //public int Days + //{ + // get => days; + // set + // { + // days = value; + // PlayerPrefs.SetInt(_pref_days, days); + // } + //} + //bool ismaleselected; + //private readonly string _pref_ismaleselected = "IsMaleSelected"; + //public bool IsMaleSelected + //{ + // get => ismaleselected; + // set + // { + // ismaleselected = value; + // PlayerPrefs.SetInt(_pref_ismaleselected, (ismaleselected ? 1 : 0)); + // } + //} + //int taskscompleted = 0; + //private readonly string _pref_taskscompleted = "TasksCompleted"; + //public int TasksCompleted + //{ + // get => taskscompleted; + // set + // { + // taskscompleted = value; + // PlayerPrefs.SetInt(_pref_taskscompleted, taskscompleted); + // } + //} + //int gems = 0; + //private readonly string _pref_gems = "Gems"; + //public int Gems + //{ + // get => gems; + // set + // { + // gems = value; + // PlayerPrefs.SetInt(_pref_gems, gems); + // } + //} + + //int totalcurrentlevel = 0; + //private readonly string _pref_totalcurrentlevel = "TotalCurrentLevel"; + //public int TotalCurrentLevel + //{ + // get => totalcurrentlevel; + // set + // { + // totalcurrentlevel = value; + // PlayerPrefs.SetInt(_pref_totalcurrentlevel, totalcurrentlevel); + // } + //} + //int currentcounter = 0; + //private readonly string _pref_currentcounter = "CurrentCounter"; + //public int CurrentCounter + //{ + // get => currentcounter; + // set + // { + // currentcounter = value; + // PlayerPrefs.SetInt(_pref_currentcounter, currentcounter); + // } + //} + //int cash = 0; + //private readonly string _pref_cash = "Cash"; + //public int Cash + //{ + // get => cash; + // set + // { + // cash = value; + // PlayerPrefs.SetInt(_pref_cash, cash); + // } + //} + + void LoadData() + { + //istutorialcompleted = PlayerPrefs.GetInt(_pref_istutorialcompleted, 0) == 0; + currentlevelindex= PlayerPrefs.GetInt(_pref_CURRENTLEVELINDEX, currentlevelindex); + maxlevelindex= PlayerPrefs.GetInt(_pref_MAXLEVELINDEX, maxlevelindex); + //cash = PlayerPrefs.GetInt(_pref_cash, cash); + //totalcurrentlevel = PlayerPrefs.GetInt(_pref_totalcurrentlevel, totalcurrentlevel); + //currentcounter = PlayerPrefs.GetInt(_pref_currentcounter, currentcounter); + + //rp = PlayerPrefs.GetInt(_pref_rp, rp); + //gems = PlayerPrefs.GetInt(_pref_gems, gems); + //correctdecisioncounter = PlayerPrefs.GetInt( + // _pref_correctdecisioncounter, + // correctdecisioncounter + //); + + //interact_c_counter = PlayerPrefs.GetInt(_pref_INTERACT_C_COUNTER, interact_c_counter); + //interact_p_counter = PlayerPrefs.GetInt(_pref_INTERACT_P_COUNTER, interact_p_counter); + //currentdogindex = PlayerPrefs.GetInt(_pref_CURRENTDOGINDEX, currentdogindex); + //currentactivepropindex = PlayerPrefs.GetInt( + // _pref_CURRENTACTIVEPROPINDEX, + // currentactivepropindex + //); + + //currentzone = PlayerPrefs.GetInt(_pref_currentzone, currentzone); + //sfxstatus = PlayerPrefs.GetInt(_pref_sfxstatus, 1) == 1; + //musicstatus = PlayerPrefs.GetInt(_pref_musicstatus, 1) == 1; + //ismaleselected = PlayerPrefs.GetInt(_pref_ismaleselected, 1) == 1; + //runningstatus = PlayerPrefs.GetInt(_pref_runningstatus, 1) == 1; + //days = PlayerPrefs.GetInt(_pref_days, 1); + //taskscompleted = PlayerPrefs.GetInt(_pref_taskscompleted, taskscompleted); + //istutorialcompleted = + // PlayerPrefs.GetInt(_pref_istutorialcompleted, istutorialcompleted ? 1 : 0) == 1; + } + + public void Checker() + { + LoadData(); + //if (PlayerPrefs.GetInt(_pref_istutorialcompleted) == 1) + //{ + // LoadData(); + //} + //else + //{ + // ResetData(); + //} + } + + DataManager() + { + Checker(); + } + + void ResetData() + { + currentlevelindex = 0; + maxlevelindex = 0; + //cash = 0; + //totalcurrentlevel = 0; + //currentcounter = 0; + //rp = 0; + //gems = 0; + //correctdecisioncounter = 0; + //interact_c_counter = 0; + //interact_p_counter = 0; + //days = 1; + //taskscompleted = 0; + //currentzone = 0; + //sfxstatus = PlayerPrefs.GetInt(_pref_sfxstatus, 1) == 1; + //musicstatus = PlayerPrefs.GetInt(_pref_musicstatus, 1) == 1; + //runningstatus = PlayerPrefs.GetInt(_pref_runningstatus, 1) == 1; + //ismaleselected = PlayerPrefs.GetInt(_pref_ismaleselected, 1) == 1; + //currentdogindex = -1; + //currentactivepropindex = -1; + } + + private static DataManager instance; + public static DataManager Instance + { + get + { + if (instance == null) + instance = new DataManager(); + return instance; + } + } + // void Awake() + // { + // if (Instance == null) + // { + // //DontDestroyOnLoad(gameObject); + // Instance = this; + // } + // else if (Instance != this) + // { + // DestroySelf(); + // } + // LoadData(); + // } + + // private void DestroySelf() + // { + // if (Application.isPlaying) + // { + // Destroy(this); + // } + // else + // { + // DestroyImmediate(this); + // } + // } +} diff --git a/Assets/Scripts/DataManager.cs.meta b/Assets/Scripts/DataManager.cs.meta new file mode 100644 index 00000000..d9f69f06 --- /dev/null +++ b/Assets/Scripts/DataManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99fbb98431fbdb943a7ea40bc8eb4f7a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Dev/GameManager.cs b/Assets/Scripts/Dev/GameManager.cs index 56b836e7..04d63951 100644 --- a/Assets/Scripts/Dev/GameManager.cs +++ b/Assets/Scripts/Dev/GameManager.cs @@ -17,13 +17,14 @@ public class GameManager : MonoBehaviour } } - - + + public GameObject ReviveCanvas; public void OnRevival() { - ReviveCanvas.gameObject.SetActive(false); + if (ReviveCanvas != null) + ReviveCanvas.SetActive(false); } - + } diff --git a/Assets/Scripts/Utils/LevelsSwitcher.cs b/Assets/Scripts/Utils/LevelsSwitcher.cs index f0d3bd231b3098be072d12d78fd8f361f682ad11..9b4fffeb980b36514784e5fb334132085445bc9d 100644 GIT binary patch literal 3602 lcmeIufdBvi0K=g9Qy=7oP+`D;0RsjM7%*VKfB^%a0|O2c00961 literal 3493 zcmb7H-*4MC5PtVxaW&9FMjEy0Tbm}pkhnnxw;AeYdoTinmd*|_n$$?jNjl_z?;T~! zqNHRQ9~_C|efQ(LJ6=jB^$MmB&g1%cP&VGuGout{UK;Jv3)E<3wmWhD;ha8OY`&ih zFBZa~Yi?e9sic?t&1)_F!&tARu7#Z!US`0U_3?Cy-jikSi^xR&{LwSBUK>3TKObIv z_(H6)P@)zXXxeav$dJt#{5t?zvvXXEQu*;cW+jK=%I0X3!;c?FH{n20E)*G_Dd8MU zaD&QC@1)Oe(ZVNS9aC}*zl!XZ+`V>agII+gH2g3|d08aG!y{N43s&gdtcP3taAZnx z7fSxbFA`OL1BI0v0tKl(%yV3ntFzKt)IJyhCvbRl9MrIM{V4`A~p?X^q3yjIDewfuB{4n7DmPK$wYT{H=FpofSSRgSP6akH(C_QY1DiOSa z4=?w0E%kRSP}FXcmpxYeUOEp81Xo(pa4S3rrJx{!FyGqZwiG$I6&r+HE|~cnWjM9zQXZP| zDwmMVSL7x8kp^mAdg;EEN?qCUy6_LlXphViye#_Cl6BQv^&7ne*lsGHp1}JUEw+`B zE<>hyNfpttY}PH0!=sx~YzTw&8Th!*E20}O zD$E1f-5wzl~0#SEE|3@@Y1*dcNe6J{s)uUg3IpP@qEyy?%*?dMarFv9x3 z{Pd-M1>UCVG15bg7Nd=>;%)Ed!b)0UY*E|q`w1)sY1vcTklNK`D5*$MZB%R!XHTe> zz%OS}zDWNFO(_(wtDN2|t5c4f0iACMzlKf)o|#hnShzNXk>0(7%O%_)*iwU`25goU zqJxuD$PKK;itaIO?jW(D$!a=LRvXBV&(mQ)*^zlqd(ZsOLFN&U59a=uE#jLg30vLi2#3?Naw4oEwF)< zWTE%f`|Wz%q45BeZ~`cjpeP*E!)JOD_w;md&(c&5AA%W(G2)?w|42bgSbFnjZtHTYZ@{7JdZnD3-}pEC`Lg|tDSrM5V6vY&$Cx% zfNVEkc8_`pO|dGcNoiIcNS@8d~k6Yvef(w&}#I z#?~5}@U68A^6u|*e$f9b?a22aG<|x1^Fn_b3>EgNDT`-+n#i6Uv!1$h;wifd;^W}) E7w4VPJOBUy diff --git a/Assets/Source/UI/Prefabs/LevelButton.prefab b/Assets/Source/UI/Prefabs/LevelButton.prefab index d76066d5..581f5b60 100644 --- a/Assets/Source/UI/Prefabs/LevelButton.prefab +++ b/Assets/Source/UI/Prefabs/LevelButton.prefab @@ -17,7 +17,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!224 &4824462840222488472 RectTransform: m_ObjectHideFlags: 0 @@ -32,7 +32,6 @@ RectTransform: m_Children: - {fileID: 7221084408269435541} m_Father: {fileID: 8580338126328501593} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -109,7 +108,6 @@ RectTransform: - {fileID: 1812442370543413229} - {fileID: 4824462840222488472} m_Father: {fileID: 6135300618136579417} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -147,7 +145,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 4824462840222488472} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -202,6 +199,7 @@ GameObject: m_Component: - component: {fileID: 6135300618136579417} - component: {fileID: 1039565001715846327} + - component: {fileID: 3857689146710161209} m_Layer: 5 m_Name: LevelButton m_TagString: Untagged @@ -224,7 +222,6 @@ RectTransform: - {fileID: 8580338126328501593} - {fileID: 1312287172325328597} m_Father: {fileID: 0} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -276,9 +273,21 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 0} - m_TargetAssemblyTypeName: LevelSelectionUI, Assembly-CSharp - m_MethodName: OnButtonClicked - m_Mode: 3 + m_TargetAssemblyTypeName: LevelButton, Assembly-CSharp + m_MethodName: + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + - m_Target: {fileID: 3857689146710161209} + m_TargetAssemblyTypeName: LevelButton, Assembly-CSharp + m_MethodName: currentLevelIndexSet + m_Mode: 1 m_Arguments: m_ObjectArgument: {fileID: 0} m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine @@ -287,6 +296,25 @@ MonoBehaviour: m_StringArgument: m_BoolArgument: 0 m_CallState: 2 +--- !u!114 &3857689146710161209 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5612874387815100987} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8b00c509790002f4eb548c0faadfc2fb, type: 3} + m_Name: + m_EditorClassIdentifier: + levelState: 0 + levelStateImages: + - {fileID: 7335990495736439642} + - {fileID: 8346622786181217203} + - {fileID: 1424223140922706931} + button: {fileID: 1039565001715846327} + id: 0 --- !u!1 &5878284462595594540 GameObject: m_ObjectHideFlags: 0 @@ -318,7 +346,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 6135300618136579417} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -453,7 +480,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 8580338126328501593} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -529,7 +555,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 8580338126328501593} - m_RootOrder: -1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index 51adf7c9..491ee2e1 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -14,4 +14,7 @@ EditorBuildSettings: - enabled: 1 path: Assets/Scenes/Gameplay.unity guid: c5ddf6fc4e21e4ec18c592064edf342f + - enabled: 0 + path: Assets/Scenes/Level_1.unity + guid: 00000000000000000000000000000000 m_configObjects: {} diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset index 936dc0a3..6856ebbf 100644 --- a/ProjectSettings/EditorSettings.asset +++ b/ProjectSettings/EditorSettings.asset @@ -3,13 +3,14 @@ --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 12 m_SerializationMode: 2 m_LineEndingsForNewScripts: 0 m_DefaultBehaviorMode: 0 m_PrefabRegularEnvironment: {fileID: 0} m_PrefabUIEnvironment: {fileID: 0} m_SpritePackerMode: 4 + m_SpritePackerCacheSize: 10 m_SpritePackerPaddingPower: 1 m_Bc7TextureCompressor: 0 m_EtcTextureCompressorBehavior: 1 @@ -20,14 +21,15 @@ EditorSettings: m_ProjectGenerationRootNamespace: m_EnableTextureStreamingInEditMode: 1 m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 m_AsyncShaderCompilation: 1 - m_CachingShaderPreprocessor: 1 m_PrefabModeAllowAutoSave: 1 - m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptionsEnabled: 0 m_EnterPlayModeOptions: 3 m_GameObjectNamingDigits: 1 m_GameObjectNamingScheme: 2 m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 m_UseLegacyProbeSampleCount: 0 m_SerializeInlineMappingsOnOneLine: 1 m_DisableCookiesInLightmapper: 0 @@ -41,3 +43,5 @@ EditorSettings: m_CacheServerEnableAuth: 0 m_CacheServerEnableTls: 0 m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0