Restructured for new direction.

This commit is contained in:
2026-05-12 12:01:09 +01:00
parent 0439b6c1d2
commit c203f836b1
1134 changed files with 125569 additions and 213519 deletions

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 19f677a9eb83d3942af6d4c5fa8dbeee
folderAsset: yes
timeCreated: 1520032274
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
{
"name": "AssetUsageDetector.Editor",
"rootNamespace": "",
"references": [
"Unity.Addressables",
"Unity.Addressables.Editor"
],
"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
}

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8579ab42c9ab63d4bac5fb07bd390b46
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.Editor.asmdef
uploadId: 894428

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 2c0dea52dcdb16e4e9b13f8dacc1590f
timeCreated: 1520032279
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetector.cs
uploadId: 894428

View File

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

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 71ea9a3fd0b82594d8130d882dbfc844
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorCache.cs
uploadId: 894428

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 93aaae685d4c3db44baeb91a0296855e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSearchFunctions.cs
uploadId: 894428

View File

@@ -0,0 +1,289 @@
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
[SettingsProvider]
public static SettingsProvider CreatePreferencesGUI()
{
return new SettingsProvider( "Project/yasirkula/Asset Usage Detector", SettingsScope.Project )
{
guiHandler = ( searchContext ) => PreferencesGUI(),
keywords = new System.Collections.Generic.HashSet<string>() { "Asset", "Usage", "Detector" }
};
}
public static void PreferencesGUI()
{
float labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth += 60f;
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<AssetUsageDetectorWindow> action )
{
foreach( AssetUsageDetectorWindow window in Resources.FindObjectsOfTypeAll<AssetUsageDetectorWindow>() )
{
if( window )
action( window );
}
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 13295073724765e45aa3b77486e515f4
timeCreated: 1639982865
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorSettings.cs
uploadId: 894428

View File

@@ -0,0 +1,753 @@
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.Collections.Generic;
using System.Reflection;
using Object = UnityEngine.Object;
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_UNUSED_MATERIAL_PROPERTIES = "AUD_SearchUnusedMaterialProps";
private const string PREFS_LAZY_SCENE_SEARCH = "AUD_LazySceneSearch";
#if ASSET_USAGE_ADDRESSABLES
private const string PREFS_ADDRESSABLES_SUPPORT = "AUD_AddressablesSupport";
#endif
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_REFERENCES_IN_ASSETS = "AUD_HideRedundantPRefsInAssets";
private const string PREFS_HIDE_REDUNDANT_PREFAB_REFERENCES_IN_SCENES = "AUD_HideRedundantPRefsInScenes";
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 static readonly GUIContent sharedGUIContent = new GUIContent();
private readonly GUIContent hideRedundantPrefabReferencesInAssetsLabel = new GUIContent( "Hide redundant prefab references in Assets", "Hides redundant/non-overridden references in prefab variants and nested prefabs. " +
"This will help focus on only the references that actually matter. For example:\n\n" +
"- Material CloudMat is assigned to prefab Cloud and its variant CloudBig. Since changing Cloud's material will also affect CloudBig, search results won't show CloudBig's reference" );
private readonly GUIContent hideRedundantPrefabReferencesInScenesLabel = new GUIContent( "Hide redundant prefab references in Scenes", "Hides redundant/non-overridden references in prefab instances. " +
"This will help focus on only the references that actually matter. For example:\n\n" +
"- Prefab Healthbar is nested inside prefab Player. An instance of Player exists in the current scene and its Healthbar isn't modified. Since modifying Healthbar in Player prefab will also affect " +
"the instance in the scene, search results won't show the instance in the scene while searching for Healthbar's references" );
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<ObjectToSearch> objectsToSearch = new List<ObjectToSearch>() { new ObjectToSearch( null ) };
[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;
}
}
}
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<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 bool lazySceneSearch = true;
#if ASSET_USAGE_ADDRESSABLES
private bool addressablesSupport = false;
#endif
private bool searchUnusedMaterialProperties = true;
private bool calculateUnusedObjects = false;
private bool hideDuplicateRows = true;
private bool hideRedundantPrefabReferencesInAssets = false;
private bool hideRedundantPrefabReferencesInScenes = false;
private bool noAssetDatabaseChanges = false;
private bool showDetailedProgressBar = true;
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( "" );
contextMenu.AddItem( new GUIContent( "Settings" ), false, () => SettingsService.OpenProjectSettings( "Project/yasirkula/Asset Usage Detector" ) );
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>();
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<AssetUsageDetectorWindow>();
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<Object> 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<Object> 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;
searchUnusedMaterialProperties = searchParameters.searchUnusedMaterialProperties;
searchRefactoring = searchParameters.searchRefactoring;
lazySceneSearch = searchParameters.lazySceneSearch;
#if ASSET_USAGE_ADDRESSABLES
addressablesSupport = searchParameters.addressablesSupport;
#endif
calculateUnusedObjects = searchParameters.calculateUnusedObjects;
hideDuplicateRows = searchParameters.hideDuplicateRows;
hideRedundantPrefabReferencesInAssets = searchParameters.hideRedundantPrefabReferencesInAssets;
hideRedundantPrefabReferencesInScenes = searchParameters.hideRedundantPrefabReferencesInScenes;
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
PrefabStage.prefabStageClosing += ReplacePrefabStageObjectsWithAssets;
}
private void OnDisable()
{
PrefabStage.prefabStageClosing -= ReplacePrefabStageObjectsWithAssets;
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.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_REFERENCES_IN_ASSETS, hideRedundantPrefabReferencesInAssets );
EditorPrefs.SetBool( PREFS_HIDE_REDUNDANT_PREFAB_REFERENCES_IN_SCENES, hideRedundantPrefabReferencesInScenes );
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 );
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 );
hideRedundantPrefabReferencesInAssets = EditorPrefs.GetBool( PREFS_HIDE_REDUNDANT_PREFAB_REFERENCES_IN_ASSETS, hideRedundantPrefabReferencesInAssets );
hideRedundantPrefabReferencesInScenes = EditorPrefs.GetBool( PREFS_HIDE_REDUNDANT_PREFAB_REFERENCES_IN_SCENES, hideRedundantPrefabReferencesInScenes );
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 );
Utilities.DrawHeader( "<b>SEARCH IN</b>" );
searchInAssetsFolder = WordWrappingToggleLeft( "Project window (Assets folder)", searchInAssetsFolder );
if( searchInAssetsFolder )
{
BeginIndentedGUI();
searchInAssetsSubsetDrawer.Draw( searchInAssetsSubset );
excludedAssetsDrawer.Draw( excludedAssets );
EndIndentedGUI();
}
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 )
{
BeginIndentedGUI( false );
searchInScenesInBuildTickedOnly = EditorGUILayout.ToggleLeft( "Ticked only", searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 );
searchInScenesInBuildTickedOnly = !EditorGUILayout.ToggleLeft( "All", !searchInScenesInBuildTickedOnly, Utilities.GL_WIDTH_100 );
EndIndentedGUI( false );
}
GUI.enabled = true;
searchInAllScenes = WordWrappingToggleLeft( "All scenes in the project", searchInAllScenes );
}
BeginIndentedGUI();
excludedScenesDrawer.Draw( excludedScenes );
EndIndentedGUI();
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 );
Utilities.DrawHeader( "<b>SETTINGS</b>" );
#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 );
hideRedundantPrefabReferencesInAssets = WordWrappingToggleLeft( hideRedundantPrefabReferencesInAssetsLabel, hideRedundantPrefabReferencesInAssets );
hideRedundantPrefabReferencesInScenes = WordWrappingToggleLeft( hideRedundantPrefabReferencesInScenesLabel, hideRedundantPrefabReferencesInScenes );
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()
{
Utilities.DrawHeader( "<b>SEARCHED OBJECTS</b>" );
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 );
}
private void BeginIndentedGUI( bool isVertical = true )
{
GUILayout.BeginHorizontal();
GUILayout.Space( 35f );
if( isVertical )
GUILayout.BeginVertical();
}
private void EndIndentedGUI( bool isVertical = true )
{
if( isVertical )
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
public static bool WordWrappingToggleLeft( string label, bool value )
{
sharedGUIContent.text = label;
sharedGUIContent.tooltip = null;
return WordWrappingToggleLeft( sharedGUIContent, value );
}
public static bool WordWrappingToggleLeft( GUIContent 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();
ReplacePrefabStageObjectsWithAssets( PrefabStageUtility.GetCurrentPrefabStage() );
// 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,
searchUnusedMaterialProperties = searchUnusedMaterialProperties,
searchRefactoring = searchRefactoring,
#if ASSET_USAGE_ADDRESSABLES
lazySceneSearch = lazySceneSearch && !addressablesSupport,
addressablesSupport = addressablesSupport,
#else
lazySceneSearch = lazySceneSearch,
#endif
calculateUnusedObjects = calculateUnusedObjects,
hideDuplicateRows = hideDuplicateRows,
hideRedundantPrefabReferencesInAssets = hideRedundantPrefabReferencesInAssets,
hideRedundantPrefabReferencesInScenes = hideRedundantPrefabReferencesInScenes && searchInAssetsFolder,
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;
}
// 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;
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>( prefabStage.assetPath );
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<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( prefabStage.prefabContentsRoot, prefabAsset );
if( prefabStageObjectSource != null )
subAssets[j].subAsset = prefabStageObjectSource;
}
}
}
}
}
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 );
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 271a22c69c3d96c4dbdd04cca415a840
timeCreated: 1520032279
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/AssetUsageDetectorWindow.cs
uploadId: 894428

View File

@@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public class EmptyEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
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<T> GetEnumerator()
{
return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
}
public class ObjectToSearchEnumerator : IEnumerable<Object>
{
public class Enumerator : IEnumerator<Object>
{
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<ObjectToSearch> source;
private int index;
private int subAssetIndex;
public Enumerator( List<ObjectToSearch> 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<ObjectToSearch.SubAsset> 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<ObjectToSearch> source;
public ObjectToSearchEnumerator( List<ObjectToSearch> source )
{
this.source = source;
}
public IEnumerator<Object> 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;
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 894047c47ce45cf40939dae24afcc72b
timeCreated: 1562079461
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/Enumerators.cs
uploadId: 894428

View File

@@ -0,0 +1,252 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public abstract class ListDrawer<T>
{
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<T> 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<T> list, int index, Object value );
protected abstract bool IsElementNull( T element );
protected abstract void PostElementDrawer( T element );
}
public class ObjectListDrawer : ListDrawer<Object>
{
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<Object> 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<ObjectToSearch>
{
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<ObjectToSearch> 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<ObjectToSearch.SubAsset> 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();
}
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 88a4a4e861026b2498a437ce1e12b054
timeCreated: 1568758673
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/ListDrawer.cs
uploadId: 894428

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.U2D;
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<SubAsset> subAssets;
public bool showSubAssetsFoldout;
private static HashSet<Object> 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<SubAsset>();
else
subAssets.Clear();
if( currentSubAssets == null )
currentSubAssets = new HashSet<Object>();
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<Transform>( 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<Object>( filePath ), true, shouldSearchChildren );
return;
}
}
// Add Sprites of SpriteAtlases to the sub-assets list
if( target is SpriteAtlas spriteAtlas )
{
Sprite[] packedSprites = AssetUsageDetector.spriteAtlasPackedSpritesGetter( spriteAtlas );
if( packedSprites != null )
{
for( int i = 0; i < packedSprites.Length; i++ )
{
if( packedSprites[i] != null && currentSubAssets.Add( packedSprites[i] ) )
subAssets.Add( new SubAsset( packedSprites[i], shouldSearchChildren ?? true ) );
}
}
}
// 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;
// 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;
if( currentSubAssets.Add( asset ) )
subAssets.Add( new SubAsset( asset, shouldSearchChildren ?? true ) );
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 66d5a144a723fea40945afc069d4231d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/ObjectToSearch.cs
uploadId: 894428

View File

@@ -0,0 +1,375 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
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;
}
}
/// <summary>
/// - Source: Object whose SerializedProperty points to Value
/// - Value: Referenced object
/// - SerializedProperty: The SerializedProperty that points to Value
/// </summary>
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;
case SerializedPropertyType.ManagedReference: SerializedProperty.managedReferenceValue = newValue; break;
}
SerializedProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
return true;
}
}
/// <summary>
/// - Source: Object whose variable points to Value
/// - Value: Referenced object
/// - Variable: FieldInfo, PropertyInfo or IEnumerable (ChangeValue may not work for all IEnumerables)
/// </summary>
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;
}
}
/// <summary>
/// - 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
/// </summary>
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<string> variableNames = new List<string>( 8 );
List<Object> variableValues = new List<Object>( 8 );
for( int i = 0; i < MonoScriptAllVariables.Length; i++ )
{
if (!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<string> textureNames = new List<string>( 16 );
List<Texture> textureValues = new List<Texture>( 16 );
List<string> nonModifiableTextureNames = new List<string>( 16 );
List<Texture> nonModifiableTextureValues = new List<Texture>( 16 );
int shaderPropertyCount = shader.GetPropertyCount();
for( int i = 0; i < shaderPropertyCount; i++ )
{
if (shader.GetPropertyType(i) != ShaderPropertyType.Texture)
continue;
string propertyName = shader.GetPropertyName(i);
if ((shader.GetPropertyFlags(i) & ShaderPropertyFlags.NonModifiableTextureData) != 0)
{
Texture propertyDefaultValue = shaderImporter.GetNonModifiableTexture( propertyName );
if( propertyDefaultValue == Value && propertyName == Variable )
propertyDefaultValue = (Texture) newValue;
nonModifiableTextureNames.Add( propertyName );
nonModifiableTextureValues.Add( propertyDefaultValue );
}
else
{
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() );
shaderImporter.SetNonModifiableTextures( nonModifiableTextureNames.ToArray(), nonModifiableTextureValues.ToArray() );
AssetDatabase.ImportAsset( shaderImporter.assetPath );
}
else
{
Debug.LogWarning( "Can't change default value of: " + Source.GetType() );
return false;
}
return true;
}
}
/// <summary>
/// - 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)
/// </summary>
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 ) { }
}
/// <summary>
/// - 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)
/// </summary>
public class BehaviourUsageMatch : ReadOnlySearchMatch
{
public readonly Object Behaviour;
internal BehaviourUsageMatch( Object source, MonoScript value, Object behaviour ) : base( source, value ) { Behaviour = behaviour; }
}
/// <summary>
/// - Source: GameObject Instance
/// - Value: Prefab of that GameObject
/// </summary>
public class PrefabMatch : ReadOnlySearchMatch
{
internal PrefabMatch( Object source, Object value ) : base( source, value ) { }
}
/// <summary>
/// - Source: Object that references Value
/// - Value: Matched object
/// </summary>
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 ) { }
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 3d15ef8bd8f7c7c4e8d228c99713b7eb
timeCreated: 1641132238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/SearchRefactoring.cs
uploadId: 894428

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ca236e4f3c5a9f447be89f0e61e485fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/SearchResult.cs
uploadId: 894428

View File

@@ -0,0 +1,91 @@
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
public class SearchResultTooltip : EditorWindow
{
private static SearchResultTooltip mainWindow;
private static string tooltip;
private static GUIStyle m_style;
internal static GUIStyle Style
{
get
{
if( m_style == null )
{
m_style = (GUIStyle) typeof( EditorStyles ).GetProperty( "tooltip", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).GetValue( null, null );
m_style.richText = true;
}
return m_style;
}
}
public static void Show( Rect sourcePosition, string tooltip )
{
// Don't lose focus to the previous window
EditorWindow prevFocusedWindow = focusedWindow;
if (!mainWindow)
{
mainWindow = CreateInstance<SearchResultTooltip>();
mainWindow.ShowPopup();
}
Vector2 preferredSize = Style.CalcSize( new GUIContent( tooltip ) ) + Style.contentOffset + new Vector2( Style.padding.horizontal + Style.margin.horizontal, Style.padding.vertical + Style.margin.vertical );
Rect preferredPosition;
Rect positionLeft = new Rect( sourcePosition.position - new Vector2( preferredSize.x, 0f ), preferredSize );
Rect screenFittedPositionLeft = Utilities.GetScreenFittedRect(positionLeft, mainWindow);
Vector2 positionOffset = positionLeft.position - screenFittedPositionLeft.position;
Vector2 sizeOffset = positionLeft.size - screenFittedPositionLeft.size;
if( positionOffset.sqrMagnitude <= 400f && sizeOffset.sqrMagnitude <= 400f )
preferredPosition = screenFittedPositionLeft;
else
{
Rect positionRight = new Rect( sourcePosition.position + new Vector2( sourcePosition.width, 0f ), preferredSize );
Rect screenFittedPositionRight = Utilities.GetScreenFittedRect(positionRight, mainWindow);
Vector2 positionOffset2 = positionRight.position - screenFittedPositionRight.position;
Vector2 sizeOffset2 = positionRight.size - screenFittedPositionRight.size;
if( positionOffset2.magnitude + sizeOffset2.magnitude < positionOffset.magnitude + sizeOffset.magnitude )
preferredPosition = screenFittedPositionRight;
else
preferredPosition = screenFittedPositionLeft;
}
SearchResultTooltip.tooltip = tooltip;
mainWindow.minSize = preferredPosition.size;
mainWindow.position = preferredPosition;
mainWindow.Repaint();
if( prevFocusedWindow )
prevFocusedWindow.Focus();
}
public static void Hide()
{
if( mainWindow )
{
mainWindow.Close();
mainWindow = null;
}
}
private void OnGUI()
{
// If somehow the tooltip isn't automatically closed, allow closing it by clicking on it
if( Event.current.type == EventType.MouseDown )
{
Hide();
GUIUtility.ExitGUI();
}
GUI.Label( new Rect( Vector2.zero, position.size ), tooltip, Style );
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: c8bd4351b5024324ca5974ebcad1dde3
timeCreated: 1639247551
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/SearchResultTooltip.cs
uploadId: 894428

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: b3903f1d3149e9b4990e49206f8255c8
timeCreated: 1638690728
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/SearchResultTreeView.cs
uploadId: 894428

View File

@@ -0,0 +1,568 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Unity.Collections;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace AssetUsageDetectorNamespace
{
public static class Utilities
{
// A set of commonly used Unity types
private static readonly HashSet<Type> primitiveUnityTypes = new HashSet<Type>()
{
typeof( string ), typeof( Vector4 ), typeof( Vector3 ), typeof( Vector2 ), typeof( Rect ),
typeof( Quaternion ), typeof( Color ), typeof( Color32 ), typeof( LayerMask ), typeof( Bounds ),
typeof( Matrix4x4 ), typeof( AnimationCurve ), typeof( Gradient ), typeof( RectOffset ),
typeof( bool[] ), typeof( byte[] ), typeof( sbyte[] ), typeof( char[] ), typeof( decimal[] ),
typeof( double[] ), typeof( float[] ), typeof( int[] ), typeof( uint[] ), typeof( long[] ),
typeof( ulong[] ), typeof( short[] ), typeof( ushort[] ), typeof( string[] ),
typeof( Vector4[] ), typeof( Vector3[] ), typeof( Vector2[] ), typeof( Rect[] ),
typeof( Quaternion[] ), typeof( Color[] ), typeof( Color32[] ), typeof( LayerMask[] ), typeof( Bounds[] ),
typeof( Matrix4x4[] ), typeof( AnimationCurve[] ), typeof( Gradient[] ), typeof( RectOffset[] ),
typeof( List<bool> ), typeof( List<byte> ), typeof( List<sbyte> ), typeof( List<char> ), typeof( List<decimal> ),
typeof( List<double> ), typeof( List<float> ), typeof( List<int> ), typeof( List<uint> ), typeof( List<long> ),
typeof( List<ulong> ), typeof( List<short> ), typeof( List<ushort> ), typeof( List<string> ),
typeof( List<Vector4> ), typeof( List<Vector3> ), typeof( List<Vector2> ), typeof( List<Rect> ),
typeof( List<Quaternion> ), typeof( List<Color> ), typeof( List<Color32> ), typeof( List<LayerMask> ), typeof( List<Bounds> ),
typeof( List<Matrix4x4> ), typeof( List<AnimationCurve> ), typeof( List<Gradient> ), typeof( List<RectOffset> ),
typeof( Vector3Int ), typeof( Vector2Int ), typeof( RectInt ), typeof( BoundsInt ),
typeof( Vector3Int[] ), typeof( Vector2Int[] ), typeof( RectInt[] ), typeof( BoundsInt[] ),
typeof( List<Vector3Int> ), typeof( List<Vector2Int> ), typeof( List<RectInt> ), typeof( List<BoundsInt> )
};
private static readonly string reflectionNamespace = typeof( Assembly ).Namespace;
private static readonly string nativeCollectionsNamespace = typeof( NativeArray<int> ).Namespace;
private static MethodInfo screenFittedRectGetter;
private static FieldInfo editorWindowHostViewGetter;
private static PropertyInfo hostViewContainerWindowGetter;
private static readonly Func<Object, bool, bool> prefabHasAnyOverridesGetter = (Func<Object, bool, bool>) Delegate.CreateDelegate( typeof( Func<Object, bool, bool> ), typeof( PrefabUtility ).GetMethod( "HasObjectOverride", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
private static readonly HashSet<string> folderContentsSet = new HashSet<string>();
internal static readonly StringBuilder stringBuilder = new StringBuilder( 400 );
public static readonly GUILayoutOption GL_EXPAND_WIDTH = GUILayout.ExpandWidth( true );
public static readonly GUILayoutOption GL_EXPAND_HEIGHT = GUILayout.ExpandHeight( true );
public static readonly GUILayoutOption GL_WIDTH_25 = GUILayout.Width( 25 );
public static readonly GUILayoutOption GL_WIDTH_100 = GUILayout.Width( 100 );
public static readonly GUILayoutOption GL_WIDTH_250 = GUILayout.Width( 250 );
public static readonly GUILayoutOption GL_HEIGHT_0 = GUILayout.Height( 0 );
public static readonly GUILayoutOption GL_HEIGHT_2 = GUILayout.Height( 2 );
public static readonly GUILayoutOption GL_HEIGHT_30 = GUILayout.Height( 30 );
public static readonly GUILayoutOption GL_HEIGHT_35 = GUILayout.Height( 35 );
public static readonly GUILayoutOption GL_HEIGHT_40 = GUILayout.Height( 40 );
private static GUIStyle m_boxGUIStyle; // GUIStyle used to draw the results of the search
public static GUIStyle BoxGUIStyle
{
get
{
if( m_boxGUIStyle == null )
{
m_boxGUIStyle = new GUIStyle( EditorStyles.helpBox )
{
alignment = TextAnchor.MiddleCenter,
font = EditorStyles.label.font,
richText = true
};
Color textColor = GUI.skin.button.normal.textColor;
m_boxGUIStyle.normal.textColor = textColor;
m_boxGUIStyle.hover.textColor = textColor;
m_boxGUIStyle.focused.textColor = textColor;
m_boxGUIStyle.active.textColor = textColor;
m_boxGUIStyle.fontSize = ( m_boxGUIStyle.fontSize + GUI.skin.button.fontSize ) / 2;
}
return m_boxGUIStyle;
}
}
// Check if object is an asset or a Scene object
public static bool IsAsset( this object obj )
{
return obj is Object && AssetDatabase.Contains( (Object) obj );
}
public static bool IsAsset( this Object obj )
{
return AssetDatabase.Contains( obj );
}
// Check if object is a folder asset
public static bool IsFolder( this Object obj )
{
return obj is DefaultAsset && AssetDatabase.IsValidFolder( AssetDatabase.GetAssetPath( obj ) );
}
public static T GetPrefabParent<T>( this T obj ) where T : Object
{
return PrefabUtility.GetCorrespondingObjectFromSource( obj );
}
public static bool HasAnyPrefabOverrides( this Object obj )
{
if( obj.GetPrefabParent() == null )
return false;
return prefabHasAnyOverridesGetter( obj, false );
}
public static bool HasAnyPrefabOverrides( this GameObject gameObject )
{
if( gameObject.GetPrefabParent() == null )
return false;
if( !PrefabUtility.HasPrefabInstanceAnyOverrides( gameObject, false ) )
return false;
Transform rootTransform = gameObject.transform;
List<Component> components = new List<Component>( 8 );
Stack<Transform> stack = new Stack<Transform>( 8 );
stack.Push( rootTransform );
while( stack.Count > 0 )
{
Transform transform = stack.Pop();
Transform prefab = transform.GetPrefabParent();
if( prefab == null ) // GameObject is added as override
return true;
if( transform.childCount != prefab.childCount ) // Has some added/destroyed children as override
return true;
bool isRootTransform = ReferenceEquals( transform, rootTransform );
if( !isRootTransform && ( transform.gameObject as Object ).HasAnyPrefabOverrides() ) // GameObject's properties are modified (e.g. name or tag) (excluding root GameObject)
return true;
components.Clear();
transform.GetComponents( components );
foreach( Component component in components )
{
if( component == null )
continue;
if( component.GetPrefabParent() == null ) // Component is added as override
return true;
if( ( !isRootTransform || !( component is Transform ) ) && component.HasAnyPrefabOverrides() ) // Component is modified (excluding root Transform)
return true;
}
int componentCount = components.Count;
components.Clear();
prefab.GetComponents( components );
if( components.Count != componentCount ) // Has some destroyed components as override
return true;
for( int i = 0, childCount = transform.childCount; i < childCount; i++ )
{
Transform child = transform.GetChild( i );
if( child != null )
stack.Push( child );
}
}
return false;
}
#if UNITY_6000_3_OR_NEWER
public static EntityId GetEntityId(this Object obj)
{
return obj.GetEntityId();
}
public static Object EntityIdToObject(EntityId entityId)
{
return EditorUtility.EntityIdToObject(entityId);
}
#else
public static int GetEntityId(this Object obj)
{
return obj.GetInstanceID();
}
public static Object EntityIdToObject(int instanceID)
{
return EditorUtility.InstanceIDToObject(instanceID);
}
#endif
// Returns an enumerator to iterate through all asset paths in the folder
public static IEnumerable<string> EnumerateFolderContents( Object folderAsset )
{
string[] folderContents = AssetDatabase.FindAssets( "", new string[] { AssetDatabase.GetAssetPath( folderAsset ) } );
if( folderContents == null )
return new EmptyEnumerator<string>();
folderContentsSet.Clear();
for( int i = 0; i < folderContents.Length; i++ )
{
string filePath = AssetDatabase.GUIDToAssetPath( folderContents[i] );
if( !string.IsNullOrEmpty( filePath ) && !AssetDatabase.IsValidFolder( filePath ) )
folderContentsSet.Add( filePath );
}
return folderContentsSet;
}
public static void GetObjectsToSelectAndPing( this Object obj, out Object selection, out Object pingTarget )
{
if( obj == null || obj.Equals( null ) )
{
selection = pingTarget = null;
return;
}
if( obj is Component )
obj = ( (Component) obj ).gameObject;
selection = pingTarget = obj;
if( obj.IsAsset() )
{
if( obj is GameObject )
{
// Pinging a prefab only works if the pinged object is the root of the prefab or a direct child of it. Pinging any grandchildren
// of the prefab doesn't work; in which case, traverse the parent hierarchy until a pingable parent is reached
Transform objTR = ( (GameObject) obj ).transform.root;
PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType( objTR.gameObject );
if( prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant )
{
string assetPath = AssetDatabase.GetAssetPath( objTR.gameObject );
PrefabStage openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if( openPrefabStage != null && openPrefabStage.stageHandle.IsValid() && assetPath == openPrefabStage.assetPath )
{
GameObject prefabStageGO = FollowSymmetricHierarchy( (GameObject) obj, ( (GameObject) obj ).transform.root.gameObject, openPrefabStage.prefabContentsRoot );
if( prefabStageGO != null )
{
objTR = prefabStageGO.transform;
selection = objTR.gameObject;
}
}
else if( obj != objTR.gameObject )
selection = objTR.gameObject;
}
else if( prefabAssetType == PrefabAssetType.Model )
{
objTR = ( (GameObject) obj ).transform;
while( objTR.parent != null && objTR.parent.parent != null )
objTR = objTR.parent;
}
pingTarget = objTR.gameObject;
}
else if( ( obj.hideFlags & ( HideFlags.HideInInspector | HideFlags.HideInHierarchy ) ) != HideFlags.None )
{
// Can't ping assets that are hidden from Project window (e.g. animator states of AnimatorController), ping the main asset at that path instead
pingTarget = AssetDatabase.LoadMainAssetAtPath( AssetDatabase.GetAssetPath( obj ) );
}
else if( !AssetDatabase.IsMainAsset( obj ) && Array.IndexOf( AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( obj ) ), obj ) < 0 )
{
// VFX Graph assets' nodes are serialized as part of the graph but they are invisible in the Project window even though their hideFlags is None (I don't know how)
pingTarget = AssetDatabase.LoadMainAssetAtPath( AssetDatabase.GetAssetPath( obj ) );
}
}
}
// We are passing "go"s root Transform to thisRoot parameter. If we use go.transform.root instead, when we are in prefab mode on
// newer Unity versions, it points to the preview scene at the root of the prefab stage instead of pointing to the actual root of "go"
public static GameObject FollowSymmetricHierarchy( this GameObject go, GameObject thisRoot, GameObject symmetricRoot )
{
Transform target = go.transform;
Transform root1 = thisRoot.transform;
Transform root2 = symmetricRoot.transform;
while( root1 != target )
{
Transform temp = target;
while( temp.parent != root1 )
temp = temp.parent;
Transform newRoot2;
int siblingIndex = temp.GetSiblingIndex();
if( siblingIndex < root2.childCount )
{
newRoot2 = root2.GetChild( siblingIndex );
if( newRoot2.name != temp.name )
newRoot2 = root2.Find( temp.name );
}
else
newRoot2 = root2.Find( temp.name );
if( newRoot2 == null )
return null;
root2 = newRoot2;
root1 = temp;
}
return root2.gameObject;
}
// Returns -1 if t1 is above t2 in Hierarchy, 1 if t1 is below t2 in Hierarchy and 0 if they are the same object
public static int CompareHierarchySiblingIndices( Transform t1, Transform t2 )
{
Transform parent1 = t1.parent;
Transform parent2 = t2.parent;
if( parent1 == parent2 )
return t1.GetSiblingIndex() - t2.GetSiblingIndex();
int deltaHierarchyDepth = 0;
for( ; parent1; parent1 = parent1.parent )
deltaHierarchyDepth++;
for( ; parent2; parent2 = parent2.parent )
deltaHierarchyDepth--;
for( ; deltaHierarchyDepth > 0; deltaHierarchyDepth-- )
{
t1 = t1.parent;
if( t1 == t2 )
return 1;
}
for( ; deltaHierarchyDepth < 0; deltaHierarchyDepth++ )
{
t2 = t2.parent;
if( t1 == t2 )
return -1;
}
while( t1.parent != t2.parent )
{
t1 = t1.parent;
t2 = t2.parent;
}
return t1.GetSiblingIndex() - t2.GetSiblingIndex();
}
// Check if instances of this type should be searched for references
public static bool IsIgnoredUnityType( this Type type )
{
if( type.IsPrimitive || primitiveUnityTypes.Contains( type ) || type.IsEnum )
return true;
// Searching NativeArrays for reference can throw InvalidOperationException if the collection is disposed
if( type.Namespace == nativeCollectionsNamespace )
return true;
// Searching assembly variables for reference throws InvalidCastException on .NET 4.0 runtime
if( typeof( Type ).IsAssignableFrom( type ) || type.Namespace == reflectionNamespace )
return true;
// Searching Tasks for reference may freeze Unity since Task.Result freezes the active thread if Task hasn't completed yet
if (typeof(Task).IsAssignableFrom(type))
return true;
// Searching pointers, ref variables or ref structs for reference throws ArgumentException
if (type.IsPointer || type.IsByRef || type.IsByRefLike)
return true;
return false;
}
// Get <get> function for a field
public static VariableGetVal CreateGetter( this FieldInfo fieldInfo, Type type )
{
// Commented the IL generator code below because it might actually be slower than simply using reflection
// Credit: https://www.codeproject.com/Articles/14560/Fast-Dynamic-Property-Field-Accessors
//DynamicMethod dm = new DynamicMethod( "Get" + fieldInfo.Name, fieldInfo.FieldType, new Type[] { typeof( object ) }, type );
//ILGenerator il = dm.GetILGenerator();
//// Load the instance of the object (argument 0) onto the stack
//il.Emit( OpCodes.Ldarg_0 );
//// Load the value of the object's field (fi) onto the stack
//il.Emit( OpCodes.Ldfld, fieldInfo );
//// return the value on the top of the stack
//il.Emit( OpCodes.Ret );
//return (VariableGetVal) dm.CreateDelegate( typeof( VariableGetVal ) );
return fieldInfo.GetValue;
}
// Get <get> function for a property
public static VariableGetVal CreateGetter( this PropertyInfo propertyInfo )
{
// Can't use PropertyWrapper (which uses CreateDelegate) for property getters of structs
if( propertyInfo.DeclaringType.IsValueType )
{
return !propertyInfo.CanRead ? (VariableGetVal) null : ( obj ) =>
{
try
{
return propertyInfo.GetValue( obj, null );
}
catch
{
// Property getters may return various kinds of exceptions if their backing fields are not initialized (yet)
return null;
}
};
}
Type GenType = typeof( PropertyWrapper<,> ).MakeGenericType( propertyInfo.DeclaringType, propertyInfo.PropertyType );
return ( (IPropertyAccessor) Activator.CreateInstance( GenType, propertyInfo.GetGetMethod( true ) ) ).GetValue;
}
// Check if all open scenes are saved (not dirty)
public static bool AreScenesSaved()
{
for( int i = 0; i < SceneManager.sceneCount; i++ )
{
Scene scene = SceneManager.GetSceneAt( i );
if( scene.isDirty || string.IsNullOrEmpty( scene.path ) )
return false;
}
return true;
}
// Returns file extension in lowercase (period not included)
public static string GetFileExtension( string path )
{
int extensionIndex = path.LastIndexOf( '.' );
if( extensionIndex < 0 || extensionIndex >= path.Length - 1 )
return "";
stringBuilder.Length = 0;
for( extensionIndex++; extensionIndex < path.Length; extensionIndex++ )
{
char ch = path[extensionIndex];
if( ch >= 65 && ch <= 90 ) // A-Z
ch += (char) 32; // Converted to a-z
stringBuilder.Append( ch );
}
return stringBuilder.ToString();
}
// Draw header inside OnGUI
public static void DrawHeader( string label )
{
Color c = GUI.backgroundColor;
GUI.backgroundColor = AssetUsageDetectorSettings.SettingsHeaderColor;
GUILayout.Box( label, BoxGUIStyle, GL_EXPAND_WIDTH );
GUI.backgroundColor = c;
}
// Draw horizontal line inside OnGUI
public static void DrawSeparatorLine()
{
GUILayout.Space( 4f );
GUILayout.Box( "", GL_HEIGHT_2, GL_EXPAND_WIDTH );
GUILayout.Space( 4f );
}
/// <summary>
/// Restricts the given Rect within the screen's bounds.
/// </summary>
public static Rect GetScreenFittedRect(Rect originalRect, EditorWindow editorWindow)
{
screenFittedRectGetter ??= typeof(EditorWindow).Assembly.GetType("UnityEditor.ContainerWindow").GetMethod("FitRectToScreen", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (screenFittedRectGetter.GetParameters().Length == 3)
return (Rect)screenFittedRectGetter.Invoke(null, new object[] { originalRect, true, true });
else
{
// New version introduced in Unity 2022.3.62f1, Unity 6.0.49f1 and Unity 6.1.0f1.
// Usage example: https://github.com/Unity-Technologies/UnityCsReference/blob/10f8718268a7e34844ba7d59792117c28d75a99b/Editor/Mono/EditorWindow.cs#L1264
editorWindowHostViewGetter ??= typeof(EditorWindow).GetField("m_Parent", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
hostViewContainerWindowGetter ??= typeof(EditorWindow).Assembly.GetType("UnityEditor.HostView").GetProperty("window", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return (Rect)screenFittedRectGetter.Invoke(null, new object[] { originalRect, originalRect.center, true, hostViewContainerWindowGetter.GetValue(editorWindowHostViewGetter.GetValue(editorWindow), null) });
}
}
// Check if all the objects inside the list are null
public static bool IsEmpty( this List<ObjectToSearch> objectsToSearch )
{
if( objectsToSearch == null )
return true;
for( int i = 0; i < objectsToSearch.Count; i++ )
{
if( objectsToSearch[i].obj != null )
return false;
}
return true;
}
// Check if all the objects inside the list are null
public static bool IsEmpty<T>( this List<T> objects ) where T : Object
{
if( objects == null )
return true;
for( int i = 0; i < objects.Count; i++ )
{
if( objects[i] != null )
return false;
}
return true;
}
// Returns true is str starts with prefix
public static bool StartsWithFast( this string str, string prefix )
{
int aLen = str.Length;
int bLen = prefix.Length;
int ap = 0; int bp = 0;
while( ap < aLen && bp < bLen && str[ap] == prefix[bp] )
{
ap++;
bp++;
}
return bp == bLen;
}
// Returns true is str ends with postfix
public static bool EndsWithFast( this string str, string postfix )
{
int ap = str.Length - 1;
int bp = postfix.Length - 1;
while( ap >= 0 && bp >= 0 && str[ap] == postfix[bp] )
{
ap--;
bp--;
}
return bp < 0;
}
public static bool ContainsFast<T>( this List<T> list, T element )
{
if( !( element is ValueType ) )
{
for( int i = list.Count - 1; i >= 0; i-- )
{
if( ReferenceEquals( list[i], element ) )
return true;
}
}
else
{
for( int i = list.Count - 1; i >= 0; i-- )
{
if( element.Equals( list[i] ) )
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 52b272b7591fb90499916205261524e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/Utilities.cs
uploadId: 894428

View File

@@ -0,0 +1,80 @@
using System;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace AssetUsageDetectorNamespace
{
// Delegate to get the value of a variable (either field or property)
public delegate object VariableGetVal( object obj );
// Custom struct to hold a variable, its important properties and its getter function
public readonly struct VariableGetterHolder
{
public readonly MemberInfo variable;
private readonly VariableGetVal getter;
public readonly string Name { get { return variable.Name; } }
public readonly bool IsProperty { get { return variable is PropertyInfo; } }
public VariableGetterHolder(FieldInfo fieldInfo, VariableGetVal getter)
{
this.variable = fieldInfo;
this.getter = getter;
}
public VariableGetterHolder(PropertyInfo propertyInfo, VariableGetVal getter)
{
this.variable = propertyInfo;
this.getter = getter;
}
public readonly object Get( object obj )
{
try
{
return getter( obj );
}
catch( Exception e )
{
StringBuilder sb = Utilities.stringBuilder;
sb.Length = 0;
sb.Append( "Error while getting the value of (" ).Append( IsProperty ? ( (PropertyInfo) variable ).PropertyType : ( (FieldInfo) variable ).FieldType ).Append( ") " )
.Append( variable.DeclaringType ).Append( "." ).Append( Name ).Append( ": " ).Append( e );
Debug.LogError( sb.ToString() );
return null;
}
}
}
// Credit: http://stackoverflow.com/questions/724143/how-do-i-create-a-delegate-for-a-net-property
public interface IPropertyAccessor
{
object GetValue( object source );
}
// A wrapper class for properties to get their values more efficiently
public class PropertyWrapper<TObject, TValue> : IPropertyAccessor where TObject : class
{
private readonly Func<TObject, TValue> getter;
public PropertyWrapper( MethodInfo getterMethod )
{
getter = (Func<TObject, TValue>) Delegate.CreateDelegate( typeof( Func<TObject, TValue> ), getterMethod );
}
public object GetValue( object obj )
{
try
{
return getter( (TObject) obj );
}
catch
{
// Property getters may return various kinds of exceptions if their backing fields are not initialized (yet)
return null;
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9e94c83e8b850514ca0217aeff1491a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/Editor/VariableGetter.cs
uploadId: 894428

View File

@@ -0,0 +1,4 @@
= Asset Usage Detector (v2.6.2) =
Documentation: https://github.com/yasirkula/UnityAssetUsageDetector
E-mail: yasirkula@gmail.com

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: ac528f1751f33a647a45caeff6a9344b
timeCreated: 1520032521
licenseType: Store
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 112837
packageName: Asset Usage Detector
packageVersion: 2.6.2
assetPath: Assets/Plugins/AssetUsageDetector/README.txt
uploadId: 894428