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 00000000..15851212
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_filterModes.png differ
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 00000000..be2432c9
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png differ
diff --git a/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png.meta b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png.meta
new file mode 100644
index 00000000..fb2d2eef
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Documentation/Images/bgtools_ppe_manual_layout.png.meta
@@ -0,0 +1,92 @@
+fileFormatVersion: 2
+guid: 34408ed3bf23927459af6b3263c3b147
+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/MANUAL.html b/Assets/PlayerPrefsEditor/Documentation/MANUAL.html
new file mode 100644
index 00000000..e48936c0
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Documentation/MANUAL.html
@@ -0,0 +1,474 @@
+
+
+
+ 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.
+
+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).
+
+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 00000000..69f9ae38
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png differ
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png.meta
new file mode 100644
index 00000000..203157f4
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Editor Resources/exclamation.png.meta
@@ -0,0 +1,99 @@
+fileFormatVersion: 2
+guid: 058af12bb195cdc43a0f974953fc4afd
+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/info.png b/Assets/PlayerPrefsEditor/Editor Resources/info.png
new file mode 100644
index 00000000..87694f80
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/info.png differ
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 00000000..683501e9
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png differ
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png.meta
new file mode 100644
index 00000000..06813194
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Editor Resources/not_watching.png.meta
@@ -0,0 +1,86 @@
+fileFormatVersion: 2
+guid: 01600487ba432264983788be42b0b029
+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 Resources/os_linux_icon.png b/Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png
new file mode 100644
index 00000000..711900ee
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/os_linux_icon.png differ
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 00000000..3c4b9c29
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png differ
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png.meta
new file mode 100644
index 00000000..f0b3ae0a
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Editor Resources/os_mac_icon.png.meta
@@ -0,0 +1,100 @@
+fileFormatVersion: 2
+guid: c94af9de33f2f524ca4141c5ea383090
+timeCreated: 1500322442
+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_win_icon.png b/Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png
new file mode 100644
index 00000000..280b555a
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/os_win_icon.png differ
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 00000000..ef52cc74
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/refresh.png differ
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/refresh.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/refresh.png.meta
new file mode 100644
index 00000000..1e206529
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Editor Resources/refresh.png.meta
@@ -0,0 +1,100 @@
+fileFormatVersion: 2
+guid: 7d9ae81a8b3252449820b277748395bc
+timeCreated: 1500927179
+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/sort.png b/Assets/PlayerPrefsEditor/Editor Resources/sort.png
new file mode 100644
index 00000000..ad191356
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/sort.png differ
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 00000000..19357741
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/sort_asc.png differ
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 00000000..b146ae8b
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/sort_desc.png differ
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 00000000..098dad50
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/trash.png differ
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/trash.png.meta b/Assets/PlayerPrefsEditor/Editor Resources/trash.png.meta
new file mode 100644
index 00000000..2846b0b8
--- /dev/null
+++ b/Assets/PlayerPrefsEditor/Editor Resources/trash.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: cc312847c3bca82428ff672fea7385c7
+timeCreated: 1502315347
+licenseType: Store
+TextureImporter:
+ fileIDToRecycleName: {}
+ 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}
+ 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: []
+ physicsShape: []
+ spritePackingTag:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/PlayerPrefsEditor/Editor Resources/watching.png b/Assets/PlayerPrefsEditor/Editor Resources/watching.png
new file mode 100644
index 00000000..8c0aa507
Binary files /dev/null and b/Assets/PlayerPrefsEditor/Editor Resources/watching.png differ
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