123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using UnityEditor;
- using UnityEngine;
- namespace Editor
- {
- /// ---------------------------------------------------------------------------
- /// <summary>
- /// Analyzes the currently selected gameobjects in scene recursively and lists all materials in an EditorWindow.
- /// The list allows to (mutli)select materials which automatically selects the scene game objects which use it.
- /// Additionally every list item provides a button to jump to the material asset in project window.
- /// </summary>
- /// ---------------------------------------------------------------------------
- public class MaterialAnalyzer : EditorWindow
- {
- /// PRIVATE ===============================================================
-
- private static GUIStyle mListStyle;
- private static GUIStyle mItemStyle;
- private static GUIStyle mItemSelectedStyle;
- private Texture2D mListBackgroundTex;
- private Texture2D mItemBackgroundTex;
- private Texture2D mItemSelectedBackgroundTex;
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Defines a single list item encapsulating a set of game objects and a selection state.
- /// </summary>
- /// -----------------------------------------------------------------------
- private class ListItem
- {
- public ListItem( bool selected = false )
- {
- this.Selected = selected;
- }
-
- public HashSet<GameObject> GameObjects = new HashSet<GameObject>();
- public bool Selected;
- };
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Material comparer calls the material name comparer.
- /// </summary>
- /// -----------------------------------------------------------------------
- private class MaterialComp : IComparer<Material>
- {
- public int Compare( Material x, Material y )
- {
- return x.name.CompareTo( y.name );
- }
- }
- /// <summary>
- /// Stores list items by material instance.
- /// </summary>
- private SortedDictionary<Material, ListItem> mSelectionMaterials = new SortedDictionary<Material, ListItem>( new MaterialComp() );
-
- /// <summary>
- /// The current scroll position.
- /// </summary>
- private Vector2 mScrollPosition;
-
- /// <summary>
- /// A text dump of the material hierarchy.
- /// </summary>
- private string mMaterialHierarchyDump = string.Empty;
-
- /// METHODS ===============================================================
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Adds menu named "Analyze Scene" to the "Debug" menu which creates and initializes a new instance of this class.
- /// </summary>
- /// -----------------------------------------------------------------------
- [MenuItem("Custom/Analyze Materials")]
- public static void Init()
- {
- MaterialAnalyzer win = EditorWindow.GetWindow( typeof(MaterialAnalyzer) ) as MaterialAnalyzer;
- win.init();
- win.Show();
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Draws the GUI window.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void OnGUI()
- {
- GUILayout.BeginVertical();
- if( GUILayout.Button( "Analyze Selection" ) )
- analyzeSelection();
- if( GUILayout.Button( "Dump Hierarchy" ) )
- dumpMaterialHierarchy();
- if( GUILayout.Button( "Dump List" ) )
- dumpMaterialList();
- GUILayout.EndVertical();
-
- GUILayout.Label( "Materials: " + mSelectionMaterials.Count.ToString(), EditorStyles.boldLabel );
-
- mScrollPosition = GUILayout.BeginScrollView( mScrollPosition, false, true, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, mListStyle, GUILayout.MinWidth( 400 ), GUILayout.MaxWidth( 1000 ), GUILayout.MaxHeight( 1000 ) );
- foreach( KeyValuePair<Material, ListItem> item in mSelectionMaterials )
- {
- GUILayout.BeginHorizontal();
-
- // select the material asset in project hierarchy
- if( GUILayout.Button( "<", EditorStyles.miniButton, GUILayout.Width( 20 ) ) )
- {
- // unselect all selected and select the material instance in project
- foreach( ListItem listItem in mSelectionMaterials.Values )
- listItem.Selected = false;
-
- Selection.activeObject = item.Key;
- }
- if( GUILayout.Button( item.Key.name, item.Value.Selected ? mItemSelectedStyle : mItemStyle, GUILayout.MinWidth( 200 ) ) )
- processListItemClick( item.Value );
-
- if( GUILayout.Button( item.Key.shader != null ? item.Key.shader.name : " <MISSING>", item.Value.Selected ? mItemSelectedStyle : mItemStyle, GUILayout.Width( 300 ) ) )
- processListItemClick( item.Value );
-
- GUILayout.EndHorizontal();
- }
-
- GUILayout.EndScrollView();
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Processes the list item click.
- /// </summary>
- /// <param name='itemClicked'>
- /// The item clicked.
- /// </param>
- /// -----------------------------------------------------------------------
- private void processListItemClick( ListItem itemClicked )
- {
- Event e = Event.current;
-
- // if shift/control is pressed just add this element
- if( e.control )
- {
- itemClicked.Selected = !itemClicked.Selected;
- updateSceneSelection();
- }
- else
- {
- // unselect all selected and select this
- foreach( ListItem listItem in mSelectionMaterials.Values )
- listItem.Selected = false;
-
- itemClicked.Selected = true;
- updateSceneSelection();
- }
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Starts recursive analyze process iterating through every selected GameObject.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void analyzeSelection()
- {
- mSelectionMaterials.Clear();
-
- if( Selection.transforms.Length == 0 )
- {
- Debug.LogError( "Please select the object(s) you wish to analyze." );
- return;
- }
-
- StringBuilder dump = new StringBuilder();
- foreach( Transform transform in Selection.transforms )
- analyzeGameObject( transform.gameObject, dump, "" );
-
- mMaterialHierarchyDump = dump.ToString();
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Analyzes the given game object.
- /// </summary>
- /// <param name='gameObject'>
- /// The game object to analyze.
- /// </param>
- /// -----------------------------------------------------------------------
- private void analyzeGameObject( GameObject gameObject, StringBuilder dump, string indent )
- {
- dump.Append( indent + gameObject.name + "\n" );
-
- foreach( Component component in gameObject.GetComponents<Component>() )
- analyzeComponent( component, dump, indent + " " );
-
- foreach( Transform child in gameObject.transform )
- analyzeGameObject( child.gameObject, dump, indent + " " );
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Analyzes the given component.
- /// </summary>
- /// <param name='component'>
- /// The component to analyze.
- /// </param>
- /// -----------------------------------------------------------------------
- private void analyzeComponent( Component component, StringBuilder dump, string indent )
- {
- // early out if component is missing
- if( component == null )
- return;
-
- List<Material> materials = new List<Material>();
- switch( component.GetType().ToString() )
- {
- case "UnityEngine.MeshRenderer":
- {
- MeshRenderer mr = component as MeshRenderer;
- foreach( Material mat in mr.sharedMaterials )
- materials.Add( mat );
- }
- break;
- default:
- break;
- }
-
- bool materialMissing = false;
- foreach( Material mat in materials )
- {
- if( mat == null )
- {
- materialMissing = true;
- dump.Append( indent + "> MISSING\n" );
- }
- else
- {
- ListItem item;
- mSelectionMaterials.TryGetValue( mat, out item );
- if( item == null )
- {
- item = new ListItem();
- mSelectionMaterials.Add( mat, item );
- }
- item.GameObjects.Add( component.gameObject );
-
- string matName = mat.shader != null ?
- mat.name + " <" + mat.shader.name + ">" :
- mat.name + " <MISSING>";
- dump.Append( indent + "> " + matName + "\n" );
- }
- }
-
- if( materialMissing )
- Debug.LogWarning( "Material(s) missing in game object '" + component.gameObject + "'!" );
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Dumps the current selection hierarchy to a file.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void dumpMaterialHierarchy()
- {
- if( mMaterialHierarchyDump == string.Empty )
- {
- Debug.LogError( "There is nothing to dump yet." );
- return;
- }
-
- string path = EditorUtility.SaveFilePanel( "Hierarchy Dump File", "", "material_hierarchy.txt", "txt" );
- File.WriteAllText( path, mMaterialHierarchyDump );
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Dumps the current list of materials.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void dumpMaterialList()
- {
- if( mSelectionMaterials.Count == 0 )
- {
- Debug.LogError( "The material list is empty. Dump aborted." );
- return;
- }
-
- // create string from current list
- StringBuilder materialItems = new StringBuilder();
- materialItems.Append( String.Format( "{0,-40}", "Material" ) ).Append( " | " );
- materialItems.Append( String.Format( "{0,-60}", "Shader" ) ).Append( " | " );
- materialItems.Append( String.Format( "{0,-9}", "Occurence" ) + " | " );
- materialItems.Append( "GameObject List\n" );
- materialItems.Append( "----------------------------------------------------------------------------" +
- "----------------------------------------------------------------------------\n" );
-
- foreach( KeyValuePair<Material, ListItem> item in mSelectionMaterials )
- {
- materialItems.Append( String.Format( "{0,-40}", item.Key.name ) ).Append( " | " );
- materialItems.Append( String.Format( "{0,-60}", item.Key.shader != null ? item.Key.shader.name : " MISSING" ) ).Append( " | " );
- materialItems.Append( String.Format( "{0,-9}", " " + item.Value.GameObjects.Count.ToString() ) + " | " );
-
- foreach( GameObject go in item.Value.GameObjects )
- materialItems.Append( " " + go.name + " |" );
-
- materialItems.Append( "\n" );
- }
-
- string path = EditorUtility.SaveFilePanel( "Material Dump File", "", "material_list.txt", "txt" );
- File.WriteAllText( path, materialItems.ToString() );
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Selects the game objects in scene stored in all selected list items.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void updateSceneSelection()
- {
- HashSet<UnityEngine.Object> sceneObjectsToSelect = new HashSet<UnityEngine.Object>();
- foreach( ListItem item in mSelectionMaterials.Values )
- if( item.Selected )
- foreach( GameObject go in item.GameObjects )
- sceneObjectsToSelect.Add( go );
-
- UnityEngine.Object[] array = new UnityEngine.Object[sceneObjectsToSelect.Count];
- sceneObjectsToSelect.CopyTo( array );
- Selection.objects = array;
- }
-
- /// -----------------------------------------------------------------------
- /// <summary>
- /// Initializes GUI styles and textures used.
- /// </summary>
- /// -----------------------------------------------------------------------
- private void init()
- {
- // list background
- mListBackgroundTex = new Texture2D( 1, 1 );
- mListBackgroundTex.SetPixel( 0, 0, new Color( 0.6f, 0.6f, 0.6f ) );
- mListBackgroundTex.Apply();
- mListBackgroundTex.wrapMode = TextureWrapMode.Repeat;
-
- // list style
- mListStyle = new GUIStyle();
- mListStyle.normal.background = mListBackgroundTex;
- mListStyle.margin = new RectOffset( 4, 4, 4, 4 );
- mListStyle.border = new RectOffset( 1, 1, 1, 1 );
-
- // item background
- mItemBackgroundTex = new Texture2D( 1, 1 );
- Color wBGColor = new Color( 0.0f, 0.0f, 0.0f );
- string wBGColorData = EditorPrefs.GetString( "Windows/Background" );
- string[] wBGColorArray = wBGColorData.Split( ';' );
- if( wBGColorArray.Length == 5 )
- wBGColor = new Color( float.Parse( wBGColorArray[1] ), float.Parse( wBGColorArray[2] ), float.Parse( wBGColorArray[3] ) );
- else
- Debug.LogError( "Invalid window color in EditorPref found." + wBGColorArray.Length );
- mItemBackgroundTex.SetPixel( 0, 0, wBGColor );
- mItemBackgroundTex.Apply();
- mItemBackgroundTex.wrapMode = TextureWrapMode.Repeat;
-
- // item selected background
- mItemSelectedBackgroundTex = new Texture2D( 1, 1 );
- mItemSelectedBackgroundTex.SetPixel( 0, 0, new Color( 0.239f, 0.376f, 0.568f ) );
- mItemSelectedBackgroundTex.Apply();
- mItemSelectedBackgroundTex.wrapMode = TextureWrapMode.Repeat;
-
- // item style
- mItemStyle = new GUIStyle( EditorStyles.textField );
- mItemStyle.normal.background = mItemBackgroundTex;
- mItemStyle.hover.textColor = Color.cyan;
- mItemStyle.padding = new RectOffset( 2, 2, 2, 2 );
- mItemStyle.margin = new RectOffset( 1, 2, 1, 1 );
- mItemSelectedStyle = new GUIStyle( mItemStyle );
- mItemSelectedStyle.normal.background = mItemSelectedBackgroundTex;
- mItemSelectedStyle.normal.textColor = Color.white;
- }
- }
- }
|