U3D资源管理之查看资源引用(Search for References)

一共两个脚本:①AssetUsageDetectorWindow        ②AssetUsageDetector

AssetUsageDetectorWindow.cs

// 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;

namespace AssetUsageDetectorNamespace
{
	public enum Phase { Setup, Processing, Complete };

	public class AssetUsageDetectorWindow : EditorWindow
	{
		private const float PLAY_MODE_REFRESH_INTERVAL = 1f; // Interval to refresh the editor window in play mode

		private const string PREFS_SEARCH_SCENES = "AUD_SceneSearch";
		private const string PREFS_SEARCH_ASSETS = "AUD_AssetsSearch";
		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_LAZY_SCENE_SEARCH = "AUD_LazySceneSearch";
		private const string PREFS_PATH_DRAWING_MODE = "AUD_PathDrawing";
		private const string PREFS_SHOW_PROGRESS = "AUD_Progress";
		private const string PREFS_SHOW_TOOLTIPS = "AUD_Tooltips";

		private static readonly GUIContent windowTitle = new GUIContent( "Asset Usage Detector" );
		private static readonly Vector2 windowMinSize = new Vector2( 325f, 220f );

		private AssetUsageDetector core = new AssetUsageDetector();
		private SearchResult searchResult; // Overall search results

		private List<ObjectToSearch> objectsToSearch = new List<ObjectToSearch>() { new ObjectToSearch( null ) };

		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 searchInAssetsFolder = true; // Assets in Project window
		private bool dontSearchInSourceAssets = true; // objectsToSearch won't be searched for internal references

		private List<Object> searchInAssetsSubset = new List<Object>() { null }; // If not empty, only these assets are searched for references
		private List<Object> excludedAssets = new List<Object>() { null }; // These assets won't be searched for references
		private List<Object> excludedScenes = new List<Object>() { null }; // These scenes won't be searched for references

		private int searchDepthLimit = 4; // Depth limit for recursively searching variables of objects

		private bool restoreInitialSceneSetup = true; // Close the additively loaded scenes that were not part of the initial scene setup

		private string errorMessage = string.Empty;

		private bool lazySceneSearch = false;
		private bool searchNonSerializableVariables = true;
		private bool noAssetDatabaseChanges = false;
		private bool showDetailedProgressBar = false;

		private BindingFlags fieldModifiers, propertyModifiers;

		private SearchResultDrawParameters searchResultDrawParameters = new SearchResultDrawParameters( PathDrawingMode.ShortRelevantParts, false, false );

		private double nextPlayModeRefreshTime = 0f;

		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 Vector2 scrollPosition = Vector2.zero;

		private bool shouldRepositionSelf;
		private Rect windowTargetPosition;

		private static AssetUsageDetectorWindow mainWindow = null;

		// Add "Asset Usage Detector" menu item to the Window menu
		[MenuItem( "Window/Asset Usage Detector/Active Window" )]
		private static void OpenActiveWindow()
		{
			if( !mainWindow )
			{
				mainWindow = GetWindow<AssetUsageDetectorWindow>();
				mainWindow.titleContent = windowTitle;
				mainWindow.minSize = windowMinSize;
			}

			mainWindow.Show();
		}

		[MenuItem( "Window/Asset Usage Detector/New Window" )]
		private static void OpenNewWindow()
		{
			Rect? windowTargetPosition = null;
			if( mainWindow )
			{
				Rect position = mainWindow.position;
				position.position += new Vector2( 50f, 50f );
				windowTargetPosition = position;
			}

			mainWindow = CreateInstance<AssetUsageDetectorWindow>();
			mainWindow.titleContent = windowTitle;
			mainWindow.minSize = windowMinSize;

			if( windowTargetPosition.HasValue )
			{
				mainWindow.shouldRepositionSelf = true;
				mainWindow.windowTargetPosition = windowTargetPosition.Value;
			}

			mainWindow.Show( true );
		}

		// Quickly initiate search for the selected assets
		[MenuItem( "GameObject/Search for References", 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 so that there aren't multiple empty parents 
			// generated in one call
			if( command.context )
			{
				EditorApplication.update -= CallSearchSelectedAssetReferencesOnce;
				EditorApplication.update += CallSearchSelectedAssetReferencesOnce;
			}
			else
				ShowAndSearch( Selection.objects );
		}

		// Show the menu item only if there is a selection in the Editor
		[MenuItem( "GameObject/Search for References", 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<Object> searchObjects )
		{
			ShowAndSearchInternal( searchObjects, null );
		}

		// Quickly show the AssetUsageDetector window and initiate a search
		public static void ShowAndSearch( AssetUsageDetector.Parameters searchParameters )
		{
			if( searchParameters == null )
			{
				Debug.LogError( "searchParameters can't be null!" );
				return;
			}

			ShowAndSearchInternal( searchParameters.objectsToSearch, searchParameters );
		}

		private static void CallSearchSelectedAssetReferencesOnce()
		{
			EditorApplication.update -= CallSearchSelectedAssetReferencesOnce;
			SearchSelectedAssetReferences( new MenuCommand( null ) );
		}

		private static void ShowAndSearchInternal( IEnumerable<Object> searchObjects, AssetUsageDetector.Parameters searchParameters )
		{
			if( mainWindow != null && !mainWindow.ReturnToSetupPhase( true ) )
			{
				Debug.LogError( "Need to reset the previous search first!" );
				return;
			}

			OpenActiveWindow();

			mainWindow.objectsToSearch.Clear();
			if( searchObjects != null )
			{
				foreach( Object obj in searchObjects )
					mainWindow.objectsToSearch.Add( new ObjectToSearch( obj ) );
			}

			if( searchParameters != null )
			{
				mainWindow.ParseSceneSearchMode( searchParameters.searchInScenes );
				mainWindow.searchInAssetsFolder = searchParameters.searchInAssetsFolder;
				mainWindow.dontSearchInSourceAssets = searchParameters.dontSearchInSourceAssets;
				mainWindow.searchDepthLimit = searchParameters.searchDepthLimit;
				mainWindow.fieldModifiers = searchParameters.fieldModifiers;
				mainWindow.propertyModifiers = searchParameters.propertyModifiers;
				mainWindow.searchNonSerializableVariables = searchParameters.searchNonSerializableVariables;
				mainWindow.lazySceneSearch = searchParameters.lazySceneSearch;
				mainWindow.noAssetDatabaseChanges = searchParameters.noAssetDatabaseChanges;
				mainWindow.showDetailedProgressBar = searchParameters.showDetailedProgressBar;

				mainWindow.searchInAssetsSubset.Clear();
				if( searchParameters.searchInAssetsSubset != null )
				{
					foreach( Object obj in searchParameters.searchInAssetsSubset )
						mainWindow.searchInAssetsSubset.Add( obj );
				}

				mainWindow.excludedAssets.Clear();
				if( searchParameters.excludedAssetsFromSearch != null )
				{
					foreach( Object obj in searchParameters.excludedAssetsFromSearch )
						mainWindow.excludedAssets.Add( obj );
				}

				mainWindow.excludedScenes.Clear();
				if( searchParameters.excludedScenesFromSearch != null )
				{
					foreach( Object obj in searchParameters.excludedScenesFromSearch )
						mainWindow.excludedScenes.Add( obj );
				}
			}

			mainWindow.InitiateSearch();
			mainWindow.Repaint();
		}

		private void Awake()
		{
			LoadPrefs();
		}

		private void OnEnable()
		{
			mainWindow = this;

#if UNITY_2018_3_OR_NEWER
			UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
			UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets;
#endif
		}

		private void OnDisable()
		{
#if UNITY_2018_3_OR_NEWER
			UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
#endif

			if( mainWindow == this )
				mainWindow = null;
		}

		private void OnDestroy()
		{
			if( core != null )
				core.SaveCache();

			SavePrefs();

			if( searchResult != null && currentPhase == Phase.Complete && !EditorApplication.isPlaying && searchResult.IsSceneSetupDifferentThanCurrentSetup() )
			{
				if( EditorUtility.DisplayDialog( "Scenes", "Restore initial scene setup?", "Yes", "Leave it as is" ) )
					searchResult.RestoreInitialSceneSetup();
			}
		}

		private void OnFocus()
		{
			mainWindow = this;
		}

		private void SavePrefs()
		{
			EditorPrefs.SetInt( PREFS_SEARCH_SCENES, (int) GetSceneSearchMode( false ) );
			EditorPrefs.SetBool( PREFS_SEARCH_ASSETS, searchInAssetsFolder );
			EditorPrefs.SetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, dontSearchInSourceAssets );
			EditorPrefs.SetInt( PREFS_SEARCH_DEPTH_LIMIT, searchDepthLimit );
			EditorPrefs.SetInt( PREFS_SEARCH_FIELDS, (int) fieldModifiers );
			EditorPrefs.SetInt( PREFS_SEARCH_PROPERTIES, (int) propertyModifiers );
			EditorPrefs.SetInt( PREFS_PATH_DRAWING_MODE, (int) searchResultDrawParameters.pathDrawingMode );
			EditorPrefs.SetBool( PREFS_SEARCH_NON_SERIALIZABLES, searchNonSerializableVariables );
			EditorPrefs.SetBool( PREFS_LAZY_SCENE_SEARCH, lazySceneSearch );
			EditorPrefs.SetBool( PREFS_SHOW_TOOLTIPS, searchResultDrawParameters.showTooltips );
			EditorPrefs.SetBool( PREFS_SHOW_PROGRESS, showDetailedProgressBar );
		}

		private void LoadPrefs()
		{
			ParseSceneSearchMode( (SceneSearchMode) EditorPrefs.GetInt( PREFS_SEARCH_SCENES, (int) ( SceneSearchMode.OpenScenes | SceneSearchMode.ScenesInBuildSettingsTickedOnly | SceneSearchMode.AllScenes ) ) );

			searchInAssetsFolder = EditorPrefs.GetBool( PREFS_SEARCH_ASSETS, true );
			dontSearchInSourceAssets = EditorPrefs.GetBool( PREFS_DONT_SEARCH_SOURCE_ASSETS, true );
			searchDepthLimit = EditorPrefs.GetInt( PREFS_SEARCH_DEPTH_LIMIT, 4 );

			// Fetch public, protected and private non-static fields and properties from objects by default
			fieldModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_FIELDS, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) );
			propertyModifiers = (BindingFlags) EditorPrefs.GetInt( PREFS_SEARCH_PROPERTIES, (int) ( BindingFlags.Public | BindingFlags.NonPublic ) );

			try
			{
				searchResultDrawParameters.pathDrawingMode = (PathDrawingMode) EditorPrefs.GetInt( PREFS_PATH_DRAWING_MODE, (int) PathDrawingMode.ShortRelevantParts );
			}
			catch
			{
				searchResultDrawParameters.pathDrawingMode = PathDrawingMode.ShortRelevantParts;
			}

			searchNonSerializableVariables = EditorPrefs.GetBool( PREFS_SEARCH_NON_SERIALIZABLES, true );
			lazySceneSearch = EditorPrefs.GetBool( PREFS_LAZY_SCENE_SEARCH, false );
			searchResultDrawParameters.showTooltips = EditorPrefs.GetBool( PREFS_SHOW_TOOLTIPS, false );
			showDetailedProgressBar = EditorPrefs.GetBool( PREFS_SHOW_PROGRESS, false );
		}

		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()
		{
			// Refresh the window at a regular interval in play mode to update the tooltip
			if( EditorApplication.isPlaying && currentPhase == Phase.Complete && searchResultDrawParameters.showTooltips && EditorApplication.timeSinceStartup >= nextPlayModeRefreshTime )
			{
				nextPlayModeRefreshTime = EditorApplication.timeSinceStartup + PLAY_MODE_REFRESH_INTERVAL; ;
				Repaint();
			}

			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();

			GUILayout.Space( 10 );

			// Show the error message, if it is not empty
			if( errorMessage.Length > 0 )
				EditorGUILayout.HelpBox( errorMessage, MessageType.Error );

			GUILayout.Space( 10 );

			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) . . ." );

				restoreInitialSceneSetup = EditorGUILayout.ToggleLeft( "Restore initial scene setup (Recommended)", restoreInitialSceneSetup );

				if( GUILayout.Button( "RETURN", Utilities.GL_HEIGHT_30 ) )
					ReturnToSetupPhase( restoreInitialSceneSetup );
			}
			else if( currentPhase == Phase.Setup )
			{
				if( objectsToSearchDrawer.Draw( objectsToSearch ) )
					errorMessage = string.Empty;

				GUILayout.Space( 10 );

				GUILayout.Box( "SEARCH IN", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );

				searchInAssetsFolder = EditorGUILayout.ToggleLeft( "Project window (Assets folder)", searchInAssetsFolder );

				if( searchInAssetsFolder )
				{
					GUILayout.BeginHorizontal();
					GUILayout.Space( 35 );
					GUILayout.BeginVertical();

					searchInAssetsSubsetDrawer.Draw( searchInAssetsSubset );
					excludedAssetsDrawer.Draw( excludedAssets );

					dontSearchInSourceAssets = EditorGUILayout.ToggleLeft( "Don't search \"Find references of\" themselves for references", dontSearchInSourceAssets );

					GUILayout.EndVertical();
					GUILayout.EndHorizontal();
				}

				GUILayout.Space( 10 );

				if( searchInAllScenes && !EditorApplication.isPlaying )
					GUI.enabled = false;

				searchInOpenScenes = EditorGUILayout.ToggleLeft( "Currently open (loaded) scene(s)", searchInOpenScenes );

				if( !EditorApplication.isPlaying )
				{
					searchInScenesInBuild = EditorGUILayout.ToggleLeft( "Scenes in Build Settings", searchInScenesInBuild );

					if( searchInScenesInBuild )
					{
						GUILayout.BeginHorizontal();
						GUILayout.Space( 35 );

						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 = EditorGUILayout.ToggleLeft( "All scenes in the project", searchInAllScenes );
				}

				GUILayout.BeginHorizontal();
				GUILayout.Space( 35 );
				GUILayout.BeginVertical();

				excludedScenesDrawer.Draw( excludedScenes );

				GUILayout.EndVertical();
				GUILayout.EndHorizontal();

				GUILayout.Space( 10 );

				//GUILayout.Box( "SEARCH SETTINGS", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );

				//GUILayout.BeginHorizontal();

				//GUILayout.Label( new GUIContent( "> Search depth: " + searchDepthLimit, "Depth limit for recursively searching variables of objects" ), Utilities.GL_WIDTH_250 );

				//searchDepthLimit = (int) GUILayout.HorizontalSlider( searchDepthLimit, 0, 4 );

				//GUILayout.EndHorizontal();

				//GUILayout.Label( "> Search variables:" );

				//GUILayout.BeginHorizontal();

				//GUILayout.Space( 35 );

				//if( EditorGUILayout.ToggleLeft( "Public", ( fieldModifiers & BindingFlags.Public ) == BindingFlags.Public, Utilities.GL_WIDTH_100 ) )
				//	fieldModifiers |= BindingFlags.Public;
				//else
				//	fieldModifiers &= ~BindingFlags.Public;

				//if( EditorGUILayout.ToggleLeft( "Non-public", ( fieldModifiers & BindingFlags.NonPublic ) == BindingFlags.NonPublic, Utilities.GL_WIDTH_100 ) )
				//	fieldModifiers |= BindingFlags.NonPublic;
				//else
				//	fieldModifiers &= ~BindingFlags.NonPublic;

				//GUILayout.EndHorizontal();

				//GUILayout.Label( "> Search properties:" );

				//GUILayout.BeginHorizontal();

				//GUILayout.Space( 35 );

				//if( EditorGUILayout.ToggleLeft( "Public", ( propertyModifiers & BindingFlags.Public ) == BindingFlags.Public, Utilities.GL_WIDTH_100 ) )
				//	propertyModifiers |= BindingFlags.Public;
				//else
				//	propertyModifiers &= ~BindingFlags.Public;

				//if( EditorGUILayout.ToggleLeft( "Non-public", ( propertyModifiers & BindingFlags.NonPublic ) == BindingFlags.NonPublic, Utilities.GL_WIDTH_100 ) )
				//	propertyModifiers |= BindingFlags.NonPublic;
				//else
				//	propertyModifiers &= ~BindingFlags.NonPublic;

				//GUILayout.EndHorizontal();

				//GUILayout.Space( 10 );

				//searchNonSerializableVariables = EditorGUILayout.ToggleLeft( "Search non-serializable fields and properties", searchNonSerializableVariables );

				//GUILayout.Space( 10 );

				GUILayout.Box( "SETTINGS", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );

				lazySceneSearch = EditorGUILayout.ToggleLeft( "Lazy scene search: scenes are searched in detail only when they are manually refreshed (faster search)", lazySceneSearch );
				noAssetDatabaseChanges = EditorGUILayout.ToggleLeft( "I haven't modified any assets/scenes since the last search (faster search)", noAssetDatabaseChanges );
				showDetailedProgressBar = EditorGUILayout.ToggleLeft( "Show detailed progress bar (slower search)", showDetailedProgressBar );

				GUILayout.Space( 10 );

				// Don't let the user press the GO button without any valid search location
				if( !searchInAllScenes && !searchInOpenScenes && !searchInScenesInBuild && !searchInAssetsFolder )
					GUI.enabled = false;

				if( GUILayout.Button( "GO!", Utilities.GL_HEIGHT_30 ) )
					InitiateSearch();
			}
			else if( currentPhase == Phase.Complete )
			{
				// Draw the results of the search
				GUI.enabled = false;

				objectsToSearchDrawer.Draw( objectsToSearch );

				GUILayout.Space( 10 );
				GUI.enabled = true;

				restoreInitialSceneSetup = EditorGUILayout.ToggleLeft( "Restore initial scene setup after search is reset (Recommended)", restoreInitialSceneSetup );

				if( GUILayout.Button( "Reset Search", Utilities.GL_HEIGHT_30 ) )
					ReturnToSetupPhase( restoreInitialSceneSetup );

				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 );

				Color c = GUI.color;
				GUI.color = Color.green;
				GUILayout.Box( "Don't forget to save scene(s) if you made any changes!", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
				GUI.color = c;

				if( searchResult.NumberOfGroups == 0 )
				{
					GUILayout.Space( 10 );
					GUILayout.Box( "No references found...", Utilities.BoxGUIStyle, Utilities.GL_EXPAND_WIDTH );
				}
				else
				{
					//GUILayout.Space( 10 );
					//GUILayout.BeginHorizontal();

					 Select all the references after filtering them (select only the GameObject's)
					//if( GUILayout.Button( "Select All\n(GameObject-wise)", Utilities.GL_HEIGHT_35 ) )
					//{
					//	GameObject[] objects = searchResult.SelectAllAsGameObjects();
					//	if( objects != null && objects.Length > 0 )
					//		Selection.objects = objects;
					//}

					 Select all the references without filtering them
					//if( GUILayout.Button( "Select All\n(Object-wise)", Utilities.GL_HEIGHT_35 ) )
					//{
					//	Object[] objects = searchResult.SelectAllAsObjects();
					//	if( objects != null && objects.Length > 0 )
					//		Selection.objects = objects;
					//}

					//GUILayout.EndHorizontal();

					//GUILayout.Space( 10 );

					searchResultDrawParameters.showTooltips = EditorGUILayout.ToggleLeft( "Show tooltips", searchResultDrawParameters.showTooltips );
					noAssetDatabaseChanges = EditorGUILayout.ToggleLeft( "I haven't modified any assets/scenes since the last search (faster Refresh)", noAssetDatabaseChanges );
					searchResultDrawParameters.noAssetDatabaseChanges = noAssetDatabaseChanges;

					GUILayout.Space( 10 );

					GUILayout.Label( "Path drawing mode:" );

					GUILayout.BeginHorizontal();

					GUILayout.Space( 35 );

					if( EditorGUILayout.ToggleLeft( "Full: Draw the complete paths to the references", searchResultDrawParameters.pathDrawingMode == PathDrawingMode.Full ) )
						searchResultDrawParameters.pathDrawingMode = PathDrawingMode.Full;

					GUILayout.EndHorizontal();

					GUILayout.BeginHorizontal();

					GUILayout.Space( 35 );

					if( EditorGUILayout.ToggleLeft( "Shorter: Draw only the most relevant parts (that start with a UnityEngine.Object) of the complete paths", searchResultDrawParameters.pathDrawingMode == PathDrawingMode.ShortRelevantParts ) )
						searchResultDrawParameters.pathDrawingMode = PathDrawingMode.ShortRelevantParts;

					GUILayout.EndHorizontal();

					GUILayout.BeginHorizontal();

					GUILayout.Space( 35 );

					if( EditorGUILayout.ToggleLeft( "Shortest: Draw only the last two nodes of complete paths", searchResultDrawParameters.pathDrawingMode == PathDrawingMode.Shortest ) )
						searchResultDrawParameters.pathDrawingMode = PathDrawingMode.Shortest;

					GUILayout.EndHorizontal();

					searchResult.DrawOnGUI( searchResultDrawParameters );
				}
			}

			GUILayout.Space( 10 );

			GUILayout.EndVertical();

			EditorGUILayout.EndScrollView();
		}

		private void InitiateSearch()
		{
			if( objectsToSearch.IsEmpty() )
				errorMessage = "ADD AN ASSET TO THE LIST FIRST!";
			else if( !EditorApplication.isPlaying && !Utilities.AreScenesSaved() )
			{
				// Don't start the search if at least one scene is currently dirty (not saved)
				errorMessage = "SAVE OPEN SCENES FIRST!";
			}
			else
			{
				errorMessage = string.Empty;
				currentPhase = Phase.Processing;

				SavePrefs();

#if UNITY_2018_3_OR_NEWER
				ReplacePrefabStageObjectsWithAssets( UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() );
#endif

				// Start searching
				searchResult = core.Run( new AssetUsageDetector.Parameters()
				{
					objectsToSearch = new ObjectToSearchEnumerator( objectsToSearch ).ToArray(),
					searchInScenes = GetSceneSearchMode( true ),
					searchInAssetsFolder = searchInAssetsFolder,
					searchInAssetsSubset = !searchInAssetsSubset.IsEmpty() ? searchInAssetsSubset.ToArray() : null,
					excludedAssetsFromSearch = !excludedAssets.IsEmpty() ? excludedAssets.ToArray() : null,
					dontSearchInSourceAssets = dontSearchInSourceAssets,
					excludedScenesFromSearch = !excludedScenes.IsEmpty() ? excludedScenes.ToArray() : null,
					//fieldModifiers = fieldModifiers,
					//propertyModifiers = propertyModifiers,
					//searchDepthLimit = searchDepthLimit,
					//searchNonSerializableVariables = searchNonSerializableVariables,
					lazySceneSearch = lazySceneSearch,
					noAssetDatabaseChanges = noAssetDatabaseChanges,
					showDetailedProgressBar = showDetailedProgressBar
				} );

				currentPhase = Phase.Complete;
			}
		}

#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( UnityEditor.Experimental.SceneManagement.PrefabStage prefabStage )
		{
			if( prefabStage == null || !prefabStage.stageHandle.IsValid() )
				return;

			GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.prefabAssetPath );
			if( prefabAsset == 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( prefabAsset );
					if( prefabStageObjectSource != null )
						objectsToSearch[i].obj = prefabStageObjectSource;

					List<ObjectToSearch.SubAsset> 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( prefabAsset );
							if( prefabStageObjectSource != null )
								subAssets[j].subAsset = prefabStageObjectSource;
						}
					}
				}
			}
		}
#endif

		private bool ReturnToSetupPhase( bool restoreInitialSceneSetup )
		{
			if( searchResult != null && restoreInitialSceneSetup && !EditorApplication.isPlaying && !searchResult.RestoreInitialSceneSetup() )
				return false;

			searchResult = null;

			errorMessage = string.Empty;
			currentPhase = Phase.Setup;

			return true;
		}
	}
}

AssetUsageDetector

// Asset Usage Detector - by Suleyman Yasir KULA (yasirkula@gmail.com)

using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System;
using System.IO;
using UnityEngine.UI;
using System.Text;
#if UNITY_2017_1_OR_NEWER
using UnityEngine.U2D;
#if UNITY_2018_2_OR_NEWER
using UnityEditor.U2D;
#endif
using UnityEngine.Playables;
#endif
#if UNITY_2017_2_OR_NEWER
using UnityEngine.Tilemaps;
#endif
using Object = UnityEngine.Object;

namespace AssetUsageDetectorNamespace
{
	[Flags]
	public enum SceneSearchMode { None = 0, OpenScenes = 1, ScenesInBuildSettingsAll = 2, ScenesInBuildSettingsTickedOnly = 4, AllScenes = 8 };
	public enum PathDrawingMode { Full = 0, ShortRelevantParts = 1, Shortest = 2 };

	public class AssetUsageDetector
	{
		#region Helper Classes
		[Serializable]
		public class Parameters
		{
			public Object[] objectsToSearch = null;

			public SceneSearchMode searchInScenes = SceneSearchMode.AllScenes;
			public Object[] searchInScenesSubset = null;
			public bool searchInAssetsFolder = true;
			public Object[] searchInAssetsSubset = null;
			public Object[] excludedAssetsFromSearch = null;
			public bool dontSearchInSourceAssets = true;
			public Object[] excludedScenesFromSearch = null;

			public int searchDepthLimit = 4;
			public BindingFlags fieldModifiers = BindingFlags.Public | BindingFlags.NonPublic;
			public BindingFlags propertyModifiers = BindingFlags.Public | BindingFlags.NonPublic;
			public bool searchNonSerializableVariables = true;

			public bool lazySceneSearch = false;
			public bool noAssetDatabaseChanges = false;
			public bool showDetailedProgressBar = false;
		}

		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];

				for( int i = 0; i < dependencies.Length; i++ )
				{
					FileInfo assetFile = new FileInfo( dependencies[i] );
					fileSizes[i] = assetFile.Exists ? assetFile.Length : 0L;
				}
			}
		}
		#endregion

		// A set that contains the searched asset(s) and their sub-assets (if any)
		private readonly HashSet<Object> objectsToSearchSet = new HashSet<Object>();

		// Scene object(s) in objectsToSearchSet
		private readonly HashSet<Object> sceneObjectsToSearchSet = new HashSet<Object>();
		// sceneObjectsToSearchSet's scene(s)
		private readonly HashSet<string> sceneObjectsToSearchScenesSet = new HashSet<string>();

		// Project asset(s) in objectsToSearchSet
		private readonly HashSet<Object> assetsToSearchSet = new HashSet<Object>();
		// assetsToSearchSet's path(s)
		private readonly HashSet<string> assetsToSearchPathsSet = new HashSet<string>();
		// The root prefab objects in assetsToSearch that will be used to search for prefab references
		private readonly List<GameObject> assetsToSearchRootPrefabs = new List<GameObject>( 4 );
		// Path(s) of the assets that should be excluded from the search
		private readonly HashSet<string> excludedAssetsPathsSet = new HashSet<string>();

		// Results for the currently searched scene
		private SearchResultGroup currentSearchResultGroup;

		// An optimization to fetch & filter fields and properties of a class only once
		private readonly Dictionary<Type, VariableGetterHolder[]> typeToVariables = new Dictionary<Type, VariableGetterHolder[]>( 4096 );
		// An optimization to search an object only once (key is a hash of the searched object)
		private readonly Dictionary<string, ReferenceNode> searchedObjects = new Dictionary<string, ReferenceNode>( 32768 );
		// An optimization to fetch an animation clip's curve bindings only once
		private readonly Dictionary<AnimationClip, EditorCurveBinding[]> animationClipUniqueBindings = new Dictionary<AnimationClip, EditorCurveBinding[]>( 256 );
		// An optimization to fetch the dependencies of an asset only once (key is the path of the asset)
		private Dictionary<string, CacheEntry> assetDependencyCache;
		private CacheEntry lastRefreshedCacheEntry;

		// Dictionary to quickly find the function to search a specific type with
		private Dictionary<Type, Func<Object, ReferenceNode>> typeToSearchFunction;

		// Stack of SearchObject function parameters to avoid infinite loops (which happens when same object is passed as parameter to function)
		private readonly List<object> callStack = new List<object>( 64 );

		private bool searchPrefabConnections;
		private bool searchMonoBehavioursForScript;
		private bool searchRenderers;
		private bool searchMaterialsForShader;
		private bool searchMaterialsForTexture;

		private bool searchSerializableVariablesOnly;
		private bool prevSearchSerializableVariablesOnly;

		private int searchDepthLimit; // Depth limit for recursively searching variables of objects

		private Object currentSearchedObject;
		private int currentDepth;

		private bool dontSearchInSourceAssets;
		private bool searchingSourceAssets;
		private bool isInPlayMode;

#if UNITY_2018_3_OR_NEWER
		private UnityEditor.Experimental.SceneManagement.PrefabStage openPrefabStage;
		private GameObject openPrefabStagePrefabAsset;
#endif

		private BindingFlags fieldModifiers, propertyModifiers;
		private BindingFlags prevFieldModifiers, prevPropertyModifiers;

		private int searchedObjectsCount; // Number of searched objects
		private double searchStartTime;

		private readonly List<ReferenceNode> nodesPool = new List<ReferenceNode>( 32 );
		private readonly List<VariableGetterHolder> validVariables = new List<VariableGetterHolder>( 32 );

		private string CachePath { get { return Application.dataPath + "/../Library/AssetUsageDetector.cache"; } } // Path of the cache file

		// Search for references!
		public SearchResult Run( Parameters searchParameters )
		{
			if( searchParameters == null )
			{
				Debug.LogError( "searchParameters must not be null!" );
				return new SearchResult( false, null, null, this, searchParameters );
			}

			if( searchParameters.objectsToSearch == null )
			{
				Debug.LogError( "objectsToSearch list is empty!" );
				return new SearchResult( false, null, null, this, searchParameters );
			}

			if( !EditorApplication.isPlaying && !Utilities.AreScenesSaved() )
			{
				// Don't start the search if at least one scene is currently dirty (not saved)
				Debug.LogError( "Save open scenes first!" );
				return new SearchResult( false, null, null, this, searchParameters );
			}

			List<SearchResultGroup> searchResult = null;

			isInPlayMode = EditorApplication.isPlaying;
#if UNITY_2018_3_OR_NEWER
			openPrefabStagePrefabAsset = null;
			string openPrefabStageAssetPath = null;
			openPrefabStage = UnityEditor.Experimental.SceneManagement.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 prefabs first!" );
						return new SearchResult( false, null, null, this, searchParameters );
					}

					openPrefabStagePrefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( openPrefabStage.prefabAssetPath );
					openPrefabStageAssetPath = openPrefabStage.prefabAssetPath;
				}
			}
#endif

			// Get the scenes that are open right now
			SceneSetup[] initialSceneSetup = !isInPlayMode ? EditorSceneManager.GetSceneManagerSetup() : null;

			// Make sure the AssetDatabase is up-to-date
			AssetDatabase.SaveAssets();

			try
			{
				currentDepth = 0;
				searchedObjectsCount = 0;
				searchStartTime = EditorApplication.timeSinceStartup;

				this.fieldModifiers = searchParameters.fieldModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
				this.propertyModifiers = searchParameters.propertyModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
				this.searchDepthLimit = searchParameters.searchDepthLimit;
				this.searchSerializableVariablesOnly = !searchParameters.searchNonSerializableVariables;
				this.dontSearchInSourceAssets = searchParameters.dontSearchInSourceAssets;

				// Initialize commonly used variables
				searchResult = new List<SearchResultGroup>(); // Overall search results

				if( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly )
					typeToVariables.Clear();

				searchedObjects.Clear();
				animationClipUniqueBindings.Clear();
				callStack.Clear();
				objectsToSearchSet.Clear();
				sceneObjectsToSearchSet.Clear();
				sceneObjectsToSearchScenesSet.Clear();
				assetsToSearchSet.Clear();
				assetsToSearchPathsSet.Clear();
				assetsToSearchRootPrefabs.Clear();
				excludedAssetsPathsSet.Clear();

				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;

				if( typeToSearchFunction == null )
				{
					typeToSearchFunction = new Dictionary<Type, Func<Object, ReferenceNode>>()
					{
						{ typeof( GameObject ), SearchGameObject },
						{ typeof( Material ), SearchMaterial },
						{ typeof( RuntimeAnimatorController ), SearchAnimatorController },
						{ typeof( AnimatorOverrideController ), SearchAnimatorController },
						{ typeof( AnimatorController ), SearchAnimatorController },
						{ typeof( AnimatorStateMachine ), SearchAnimatorStateMachine },
						{ typeof( AnimatorState ), SearchAnimatorState },
						{ typeof( AnimatorStateTransition ), SearchAnimatorStateTransition },
						{ typeof( BlendTree ), SearchBlendTree },
						{ typeof( AnimationClip ), SearchAnimationClip },
#if UNITY_2017_1_OR_NEWER
						{ typeof( SpriteAtlas ), SearchSpriteAtlas },
#endif
					};
				}

				prevFieldModifiers = fieldModifiers;
				prevPropertyModifiers = propertyModifiers;
				prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly;

				searchPrefabConnections = false;
				searchMonoBehavioursForScript = false;
				searchRenderers = false;
				searchMaterialsForShader = false;
				searchMaterialsForTexture = false;

				// Store the searched objects(s) in HashSets
				HashSet<string> folderContentsSet = new HashSet<string>();
				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.EndsWith( ".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 );
				}

				foreach( Object obj in objectsToSearchSet )
				{
					if( obj is Texture )
					{
						searchRenderers = true;
						searchMaterialsForTexture = true;
					}
					else if( obj is Material )
					{
						searchRenderers = true;
					}
					else if( obj is MonoScript )
					{
						searchMonoBehavioursForScript = true;
					}
					else if( obj is Shader )
					{
						searchRenderers = true;
						searchMaterialsForShader = true;
					}
					else if( obj is GameObject )
					{
						searchPrefabConnections = true;
					}
				}

				// Find the scenes to search for references
				HashSet<string> scenesToSearch = new HashSet<string>();
				if( searchParameters.searchInScenesSubset != null )
				{
					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 < EditorSceneManager.loadedSceneCount; i++ )
						{
							Scene scene = EditorSceneManager.GetSceneAt( i );
							if( scene.IsValid() )
								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<string> openScenes = new HashSet<string>();
					for( int i = 0; i < EditorSceneManager.loadedSceneCount; i++ )
					{
						Scene scene = EditorSceneManager.GetSceneAt( i );
						if( scene.IsValid() )
							openScenes.Add( scene.path );
					}

					scenesToSearch.RemoveWhere( ( path ) =>
					{
						if( !openScenes.Contains( path ) )
						{
							Debug.Log( "Can't search unloaded scenes while in play mode, skipped " + path );
							return true;
						}

						return false;
					} );
				}

				// Initialize the nodes of searched asset(s)
				foreach( Object obj in objectsToSearchSet )
					searchedObjects.Add( obj.Hash(), PopReferenceNode( obj ) );

				// Progressbar values
				int searchProgress = 0;
				int searchTotalProgress = scenesToSearch.Count;
				if( isInPlayMode && searchParameters.searchInScenes != SceneSearchMode.None )
					searchTotalProgress++; // DontDestroyOnLoad scene

				// 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<string> assetPaths;
					if( searchParameters.searchInAssetsSubset == null )
					{
						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.StartsWith( "Assets/" ) && !path.EndsWith( ".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 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<string> excludedScenesPathsSet = new HashSet<string>();
					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 ) );
						}
					}

					foreach( string scenePath in scenesToSearch )
					{
						if( EditorUtility.DisplayCancelableProgressBar( "Please wait...", "Searching scene: " + scenePath, (float) ++searchProgress / searchTotalProgress ) )
							throw new Exception( "Search aborted" );

						// Search scene for references
						if( string.IsNullOrEmpty( scenePath ) )
							continue;

						if( excludedScenesPathsSet.Contains( scenePath ) )
							continue;

						SearchScene( scenePath, searchResult, searchParameters.lazySceneSearch, initialSceneSetup );
					}
				}

				// Search through all the GameObjects under the DontDestroyOnLoad scene (if exists)
				if( isInPlayMode && searchParameters.searchInScenes != SceneSearchMode.None )
				{
					if( searchParameters.showDetailedProgressBar && 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( !dontSearchInSourceAssets )
				{
					searchingSourceAssets = true;

					foreach( Object obj in objectsToSearchSet )
					{
						currentSearchedObject = obj;
						SearchObject( obj );
					}

					searchingSourceAssets = false;
				}

				InitializeSearchResultNodes( searchResult );

				// Log some c00l stuff to console
				Debug.Log( "Searched " + searchedObjectsCount + " objects in " + ( EditorApplication.timeSinceStartup - searchStartTime ).ToString( "F2" ) + " seconds" );

				return new SearchResult( true, searchResult, initialSceneSetup, this, searchParameters );
			}
			catch( Exception e )
			{
				StringBuilder sb = new StringBuilder( objectsToSearchSet.Count * 50 + callStack.Count * 50 + 500 );
				sb.AppendLine( "<b>AssetUsageDetector Error:</b>" ).AppendLine();
				if( callStack.Count > 0 )
				{
					sb.AppendLine( "Stack contents: " );
					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( "<<destroyed>>" );
					}

					sb.AppendLine();
				}

				sb.AppendLine( "Searching references of: " );
				foreach( Object obj in objectsToSearchSet )
				{
					if( obj )
						sb.Append( obj.name ).Append( " (" ).Append( obj.GetType() ).AppendLine( ")" );
				}

				sb.AppendLine();
				sb.Append( e ).AppendLine();

				Debug.LogError( sb.ToString() );

				try
				{
					InitializeSearchResultNodes( searchResult );
				}
				catch
				{ }

				return new SearchResult( false, searchResult, initialSceneSetup, this, searchParameters );
			}
			finally
			{
				currentSearchResultGroup = null;
				currentSearchedObject = null;

				EditorUtility.ClearProgressBar();

#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 ) )
					AssetDatabase.OpenAsset( AssetDatabase.LoadAssetAtPath<GameObject>( openPrefabStageAssetPath ) );
#endif
			}
		}

		private void InitializeSearchResultNodes( List<SearchResultGroup> searchResult )
		{
			for( int i = 0; i < searchResult.Count; i++ )
				searchResult[i].InitializeNodes( GetReferenceNode );

			// If there are any empty groups after node initialization, remove those groups
			for( int i = searchResult.Count - 1; i >= 0; i-- )
			{
				if( !searchResult[i].PendingSearch && searchResult[i].NumberOfReferences == 0 )
					searchResult.RemoveAtFast( i );
			}
		}

		// 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;

			if( obj is SceneAsset )
				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( 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( 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
			{
				sceneObjectsToSearchSet.Add( obj );

				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<Component>();
				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
						sceneObjectsToSearchSet.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<SearchResultGroup> searchResult, bool lazySearch, 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( lazySearch )
				{
					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] );

			// 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
			if( callStack.ContainsFast( 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
			string objHash = null;
			if( !( obj is ValueType ) )
			{
				objHash = obj.Hash();

				if( !searchingSourceAsset )
				{
					// If object was searched before, return the cached result
					ReferenceNode cachedResult;
					if( searchedObjects.TryGetValue( objHash, 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)
				if( unityObject.IsAsset() && ( assetsToSearchSet.Count == 0 || !AssetHasAnyReference( AssetDatabase.GetAssetPath( unityObject ) ) ) )
				{
					searchedObjects.Add( objHash, null );
					return null;
				}

				callStack.Add( unityObject );

				// Search the Object in detail
				Func<Object, ReferenceNode> func;
				if( typeToSearchFunction.TryGetValue( unityObject.GetType(), out func ) )
					result = func( unityObject );
				else if( unityObject is Component )
					result = SearchComponent( unityObject );
				else
				{
					result = PopReferenceNode( unityObject );
					SearchWithSerializedObject( result );
				}

				callStack.RemoveAt( callStack.Count - 1 );
			}
			else
			{
				// Comply with the recursive search limit
				if( currentDepth >= searchDepthLimit )
					return null;

				callStack.Add( obj );
				currentDepth++;

				result = PopReferenceNode( obj );
				SearchFieldsAndPropertiesOf( 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( objHash != null && ( result != null || unityObject != null || currentDepth == 0 ) )
			{
				if( !searchingSourceAsset )
					searchedObjects.Add( objHash, result );
				else if( result != null )
				{
					result.CopyReferencesTo( searchedObjects[objHash] );
					PoolReferenceNode( result );
				}
			}

			return result;
		}

		private ReferenceNode SearchGameObject( Object unityObject )
		{
			GameObject go = (GameObject) unityObject;
			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 = PrefabUtility.GetCorrespondingObjectFromSource( go );
#else
				Object prefab = PrefabUtility.GetPrefabParent( go );
#endif
				if( objectsToSearchSet.Contains( prefab ) && assetsToSearchRootPrefabs.ContainsFast( prefab as GameObject ) )
					referenceNode.AddLinkTo( GetReferenceNode( prefab ), "Prefab object" );
			}

			// Search through all the components of the object
			Component[] components = go.GetComponents<Component>();
			for( int i = 0; i < components.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( components[i] ) );

			return referenceNode;
		}

		private ReferenceNode SearchComponent( Object unityObject )
		{
			Component component = (Component) unityObject;

			// 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
				MonoScript script = MonoScript.FromMonoBehaviour( (MonoBehaviour) component );
				if( objectsToSearchSet.Contains( script ) )
					referenceNode.AddLinkTo( GetReferenceNode( script ) );
			}
			else if( searchRenderers && component is Renderer )
			{
				// If an asset is a shader, texture or material, and this component is a Renderer,
				// search it for references
				Material[] materials = ( (Renderer) component ).sharedMaterials;
				for( int i = 0; i < materials.Length; i++ )
					referenceNode.AddLinkTo( SearchObject( materials[i] ) );
			}
			else if( component is Animation )
			{
				// If this component is an Animation, search its animation clips for references
				foreach( AnimationState anim in (Animation) component )
					referenceNode.AddLinkTo( SearchObject( anim.clip ) );

				// Search the objects that are animated by this Animation component for references
				SearchAnimatedObjects( referenceNode );
			}
			else if( component is Animator )
			{
				// If this component is an Animator, search its animation clips for references (via AnimatorController)
				referenceNode.AddLinkTo( SearchObject( ( (Animator) component ).runtimeAnimatorController ) );

				// 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 )
			{
				// If this component is a Tilemap, search its 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" );
				}
			}
#endif
#if UNITY_2017_1_OR_NEWER
			else if( component is PlayableDirector )
			{
				// If this component is a PlayableDirectory, search its PlayableAsset's scene bindings for references
				PlayableAsset playableAsset = ( (PlayableDirector) component ).playableAsset;
				if( playableAsset != null && !playableAsset.Equals( null ) )
				{
					foreach( PlayableBinding binding in playableAsset.outputs )
						referenceNode.AddLinkTo( SearchObject( ( (PlayableDirector) component ).GetGenericBinding( binding.sourceObject ) ), "Binding: " + binding.streamName );
				}
			}
#endif

			SearchWithSerializedObject( referenceNode );
			return referenceNode;
		}

		private ReferenceNode SearchMaterial( Object unityObject )
		{
			Material material = (Material) unityObject;
			ReferenceNode referenceNode = PopReferenceNode( material );

			if( searchMaterialsForShader && objectsToSearchSet.Contains( material.shader ) )
				referenceNode.AddLinkTo( GetReferenceNode( material.shader ), "Shader" );

			if( searchMaterialsForTexture )
			{
				// Search through all the textures attached to this material
				// Credit: http://answers.unity3d.com/answers/1116025/view.html
				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 );
					}
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorController( Object unityObject )
		{
			RuntimeAnimatorController controller = (RuntimeAnimatorController) unityObject;
			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" );

					referenceNode.AddLinkTo( SearchObject( layers[i].stateMachine ) );
				}
			}
			else
			{
				AnimationClip[] animClips = controller.animationClips;
				for( int i = 0; i < animClips.Length; i++ )
					referenceNode.AddLinkTo( SearchObject( animClips[i] ) );
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorStateMachine( Object unityObject )
		{
			AnimatorStateMachine animatorStateMachine = (AnimatorStateMachine) unityObject;
			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 ) );
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorState( Object unityObject )
		{
			AnimatorState animatorState = (AnimatorState) unityObject;
			ReferenceNode referenceNode = PopReferenceNode( animatorState );

			referenceNode.AddLinkTo( SearchObject( animatorState.motion ), "Motion" );

			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 ) );
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorStateTransition( Object unityObject )
		{
			// Don't search AnimatorStateTransition objects, it will just return duplicate results of SearchAnimatorStateMachine
			return PopReferenceNode( unityObject );
		}

		private ReferenceNode SearchBlendTree( Object unityObject )
		{
			BlendTree blendTree = (BlendTree) unityObject;
			ReferenceNode referenceNode = PopReferenceNode( blendTree );

			ChildMotion[] children = blendTree.children;
			for( int i = 0; i < children.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( children[i].motion ), "Motion" );

			return referenceNode;
		}

		private ReferenceNode SearchAnimationClip( Object unityObject )
		{
			AnimationClip clip = (AnimationClip) unityObject;
			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] );
				for( int j = 0; j < keyframes.Length; j++ )
					referenceNode.AddLinkTo( SearchObject( keyframes[j].value ), "Keyframe: " + keyframes[j].time );
			}

			// Get all events from animation clip
			AnimationEvent[] events = AnimationUtility.GetAnimationEvents( clip );
			for( int i = 0; i < events.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( events[i].objectReferenceParameter ), "AnimationEvent: " + events[i].time );

			return referenceNode;
		}

#if UNITY_2017_1_OR_NEWER
		private ReferenceNode SearchSpriteAtlas( Object unityObject )
		{
			SpriteAtlas spriteAtlas = (SpriteAtlas) unityObject;
			ReferenceNode referenceNode = PopReferenceNode( spriteAtlas );

			SerializedObject spriteAtlasSO = new SerializedObject( spriteAtlas );
			if( spriteAtlas.isVariant )
			{
				Object masterAtlas = spriteAtlasSO.FindProperty( "m_MasterAtlas" ).objectReferenceValue;
				if( objectsToSearchSet.Contains( masterAtlas ) )
					referenceNode.AddLinkTo( SearchObject( masterAtlas ), "Master Atlas" );
			}

#if UNITY_2018_2_OR_NEWER
			Object[] packables = spriteAtlas.GetPackables();
			if( packables != null )
			{
				for( int i = 0; i < packables.Length; i++ )
					referenceNode.AddLinkTo( SearchObject( packables[i] ), "Packed Texture" );
			}
#else
			SerializedProperty packables = spriteAtlasSO.FindProperty( "m_EditorData.packables" );
			if( packables != null )
			{
				for( int i = 0, length = packables.arraySize; i < length; i++ )
					referenceNode.AddLinkTo( SearchObject( packables.GetArrayElementAtIndex( i ).objectReferenceValue ), "Packed Texture" );
			}
#endif

			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];
				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<EditorCurveBinding> _uniqueBindings = new List<EditorCurveBinding>( 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 through field and properties of an object for references with SerializedObject
		private void SearchWithSerializedObject( ReferenceNode referenceNode )
		{
			if( !isInPlayMode || referenceNode.nodeObject.IsAsset() )
			{
				SerializedObject so = new SerializedObject( (Object) referenceNode.nodeObject );
				SerializedProperty iterator = so.GetIterator();
				if( iterator.NextVisible( true ) )
				{
					bool enterChildren;
					do
					{
						ReferenceNode searchResult;
						switch( iterator.propertyType )
						{
							case SerializedPropertyType.ObjectReference:
								searchResult = SearchObject( iterator.objectReferenceValue );
								enterChildren = false;
								break;
							case SerializedPropertyType.ExposedReference:
								searchResult = SearchObject( iterator.exposedReferenceValue );
								enterChildren = false;
								break;
							case SerializedPropertyType.Generic:
								searchResult = null;
								enterChildren = true;
								break;
							default:
								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.EndsWith( "m_RD.texture", StringComparison.Ordinal ) )
								referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath );
						}
					} while( iterator.NextVisible( enterChildren ) );

					return;
				}
			}

			// Use reflection algorithm as fallback
			SearchFieldsAndPropertiesOf( referenceNode );
		}

		// Search through field and properties of an object for references
		private void SearchFieldsAndPropertiesOf( 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 )
						continue;

					// Values stored inside ICollection objects are searched using IEnumerable,
					// no need to have duplicate search entries
					if( !( variableValue is ICollection ) )
					{
						ReferenceNode searchResult = SearchObject( variableValue );
						if( searchResult != null && searchResult != referenceNode )
							referenceNode.AddLinkTo( searchResult, ( variables[i].isProperty ? "Property: " : "Variable: " ) + variables[i].name );
					}

					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;
						foreach( object element in (IEnumerable) variableValue )
						{
							ReferenceNode searchResult = SearchObject( element );
							if( searchResult != null && searchResult != referenceNode )
								referenceNode.AddLinkTo( searchResult, string.Concat( variables[i].isProperty ? "Property: " : "Variable: ", variables[i].name, "[", index + "]" ) );

							index++;
						}
					}
				}
				catch( UnassignedReferenceException ) { }
				catch( MissingReferenceException ) { }
				catch( MissingComponentException ) { }
				catch( NotImplementedException ) { }
			}
		}

		// 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;

						// 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;

						// 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;

						// Additional filtering for properties:
						// 1- Ignore "gameObject", "transform", "rectTransform" and "attachedRigidbody" properties of Component's to get more useful results
						// 2- Ignore "canvasRenderer" and "canvas" properties of Graphic components
						// 3 & 4- Prevent accessing properties of Unity that instantiate an existing resource (causing memory leak)
						string propertyName = property.Name;
						if( typeof( Component ).IsAssignableFrom( currType ) && ( propertyName == "gameObject" ||
							propertyName == "transform" || propertyName == "attachedRigidbody" || propertyName == "rectTransform" ) )
							continue;
						else if( typeof( Graphic ).IsAssignableFrom( currType ) &&
							( propertyName == "canvasRenderer" || propertyName == "canvas" ) )
							continue;
						else if( typeof( MeshFilter ).IsAssignableFrom( currType ) && propertyName == "mesh" )
							continue;
						else if( typeof( Renderer ).IsAssignableFrom( currType ) &&
							( propertyName == "sharedMaterial" || propertyName == "sharedMaterials" ) )
							continue;
						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;
						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;
		}

		// Check if the asset at specified path depends on any of the references
		private bool AssetHasAnyReference( string assetPath )
		{
			// "Find references of" assets can contain internal references
			if( assetsToSearchPathsSet.Contains( assetPath ) )
				return true;

			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 = new StringBuilder( 1000 );
							sb.AppendLine( "<b>Infinite loop while refreshing a cache entry, please report it to the author.</b>" ).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 AssetHasAnyReference( assetPath );
					}
				}

				if( assetsToSearchPathsSet.Contains( dependencies[i] ) )
				{
					cacheEntry.searchResult = CacheEntry.Result.Yes;
					return true;
				}
			}

			for( int i = 0; i < dependencies.Length; i++ )
			{
				if( AssetHasAnyReference( dependencies[i] ) )
				{
					cacheEntry.searchResult = CacheEntry.Result.Yes;
					return true;
				}
			}

			return false;
		}

		// Get reference node for object
		private ReferenceNode GetReferenceNode( object nodeObject )
		{
			ReferenceNode result;
			string hash = nodeObject.Hash();
			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 );
		}

		// 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 );
			}
		}

		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<string, CacheEntry>( 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<string, CacheEntry>( 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;
							}

							AssetHasAnyReference( 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 );
					}
				}
			}
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值