475 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			475 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | ||
| using System.Linq;
 | ||
| using System.Reflection;
 | ||
| using SceneProfiler.Editor.GUI;
 | ||
| using UnityEditor;
 | ||
| using UnityEditor.SceneManagement;
 | ||
| using UnityEngine;
 | ||
| using UnityEngine.SceneManagement;
 | ||
| using Application = UnityEngine.Application;
 | ||
| using Object = UnityEngine.Object;
 | ||
| 
 | ||
| 
 | ||
| namespace SceneProfiler.Editor
 | ||
| {
 | ||
|     public class SceneProfiler : EditorWindow
 | ||
|     {
 | ||
|         public enum InspectType
 | ||
|         {
 | ||
|             Textures, Materials, Meshes, AudioClips, Missing, Particles, Lights, Physics, Expensive
 | ||
|         };
 | ||
| 
 | ||
|         public bool includeDisabledObjects = true;
 | ||
|         public bool IncludeSpriteAnimations = true;
 | ||
|         public bool IncludeScriptReferences = true;
 | ||
|         public bool IncludeGuiElements = true;
 | ||
|         public bool IncludeLightmapTextures = true;
 | ||
|         public bool IncludeSelectedFolder = false;
 | ||
|         public bool thingsMissing = false;
 | ||
| 
 | ||
|         public InspectType ActiveInspectType = InspectType.Textures;
 | ||
| 
 | ||
|         public List<TextureDetails> ActiveTextures = new List<TextureDetails>();
 | ||
|         public List<MaterialDetails> ActiveMaterials = new List<MaterialDetails>();
 | ||
|         public List<MeshDetails> ActiveMeshDetails = new List<MeshDetails>();
 | ||
|         public List<AudioClipDetails> ActiveClipDetails = new List<AudioClipDetails>();
 | ||
|         public List<ParticleSystemDetails> ActiveParticleSystems = new List<ParticleSystemDetails>();
 | ||
|         public List<Missing> MissingObjects = new List<Missing>();
 | ||
|         public List<LightDetails> ActiveLights = new List<LightDetails>();
 | ||
|         public List<PhysicsObjectDetails> ActivePhysicsObjects = new List<PhysicsObjectDetails>();
 | ||
|         public List<SceneWarningDetails> Warnings = new List<SceneWarningDetails>();
 | ||
|         public List<ExpensiveObjectDetails> ActiveExpensiveObjects = new List<ExpensiveObjectDetails>();
 | ||
| 
 | ||
|         public float TotalTextureMemory = 0;
 | ||
|         public int TotalMeshVertices = 0;
 | ||
| 
 | ||
|         public bool ctrlPressed = false;
 | ||
| 
 | ||
|         private static int _minWidth = 800;
 | ||
|         public int currentObjectsInColumnCount = 100;
 | ||
| 
 | ||
|         private SceneProfilerGUI _sceneProfilerGUI;
 | ||
|         private CollectTextureData _collectTextureData;
 | ||
|         private CollectMaterialsData _collectMaterialsData;
 | ||
|         private CollectAudioClipData _collectAudioClipData;
 | ||
|         private CollectMeshData _collectMeshData;
 | ||
|         private CollectParticleSystemData _сollectParticleSystemData;
 | ||
|         private CollectLightData _collectLightData;
 | ||
|         private CollectPhysicsData _collectPhysicsData;
 | ||
|         private CollectWarningsData _collectWarningsData;
 | ||
|         private CollectExpensiveObject _collectExpensiveObject;
 | ||
| 
 | ||
|         [MenuItem("Window/Analysis/Scene Profiler")]
 | ||
|         public static void Init()
 | ||
|         {
 | ||
|             SceneProfiler window = (SceneProfiler)EditorWindow.GetWindow(typeof(SceneProfiler));
 | ||
|             GUIContent titleContent = EditorGUIUtility.IconContent("UnityEditor.SceneView");
 | ||
|             titleContent.text = "Scene Profiler";
 | ||
|             window.titleContent = titleContent; 
 | ||
|             window.minSize = new Vector2(_minWidth, 400);
 | ||
|         }
 | ||
| 
 | ||
|         private void OnEnable()
 | ||
|         {
 | ||
|             _sceneProfilerGUI = new SceneProfilerGUI(this);
 | ||
|             _collectTextureData = new CollectTextureData(this);
 | ||
|             _collectMaterialsData = new CollectMaterialsData(this);
 | ||
|             _collectAudioClipData = new CollectAudioClipData(this);
 | ||
|             _collectMeshData = new CollectMeshData(this);
 | ||
|             _сollectParticleSystemData = new CollectParticleSystemData(this);
 | ||
|             _collectLightData = new CollectLightData(this);
 | ||
|             _collectPhysicsData = new CollectPhysicsData(this);
 | ||
|             _collectWarningsData = new CollectWarningsData(this);
 | ||
|             _collectExpensiveObject = new CollectExpensiveObject(this);
 | ||
|             EditorSceneManager.sceneOpened += OnSceneOpened;
 | ||
|             EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
 | ||
|             SceneManager.sceneUnloaded += OnSceneUnloaded;
 | ||
|             SceneManager.sceneLoaded += OnSceneLoaded;
 | ||
|             ClearAndRepaint();
 | ||
|         }
 | ||
| 
 | ||
|         private void OnDisable()
 | ||
|         {
 | ||
|             EditorSceneManager.sceneOpened -= OnSceneOpened;
 | ||
|             EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
 | ||
|         }
 | ||
| 
 | ||
|         private void OnSceneOpened(Scene scene, OpenSceneMode mode)
 | ||
|         {
 | ||
|             ClearAndRepaint();
 | ||
|         }
 | ||
|         
 | ||
|         private void OnSceneUnloaded(Scene scene)
 | ||
|         {
 | ||
|             ClearAndRepaint();
 | ||
|         }
 | ||
| 
 | ||
|         private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
 | ||
|         {
 | ||
|             ClearAndRepaint();
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         private void OnPlayModeStateChanged(PlayModeStateChange state)
 | ||
|         {
 | ||
|             if (state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode)
 | ||
|             {
 | ||
|                 ClearAndRepaint();
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         public void ClearAndRepaint()
 | ||
|         {
 | ||
|             ActiveTextures.Clear();
 | ||
|             ActiveMaterials.Clear();
 | ||
|             ActiveMeshDetails.Clear();
 | ||
|             ActiveClipDetails.Clear();
 | ||
|             MissingObjects.Clear();
 | ||
|             ActiveParticleSystems.Clear();
 | ||
|             ActiveLights.Clear();
 | ||
|             ActivePhysicsObjects.Clear();
 | ||
|             ActiveExpensiveObjects.Clear();
 | ||
|             Warnings.Clear();
 | ||
| 
 | ||
|             TotalTextureMemory = 0;
 | ||
|             TotalMeshVertices = 0;
 | ||
| 
 | ||
|             thingsMissing = false;
 | ||
| 
 | ||
|             Repaint();
 | ||
|         }
 | ||
| 
 | ||
|         void OnGUI()
 | ||
|         {
 | ||
|             _sceneProfilerGUI.DrawGUI();
 | ||
|         }
 | ||
| 
 | ||
|         public int UniqueShadersCount
 | ||
|         {
 | ||
|             get
 | ||
|             {
 | ||
|                 return ActiveMaterials
 | ||
|                     .Where(mat => mat.material != null && mat.material.shader != null)
 | ||
|                     .Select(mat => mat.material.shader)
 | ||
|                     .Distinct()
 | ||
|                     .Count();
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         public void SelectObject(Object selectedObject, bool append)
 | ||
|         {
 | ||
|             if (append)
 | ||
|             {
 | ||
|                 List<Object> currentSelection = new List<Object>(Selection.objects);
 | ||
|                 if (currentSelection.Contains(selectedObject)) currentSelection.Remove(selectedObject);
 | ||
|                 else currentSelection.Add(selectedObject);
 | ||
| 
 | ||
|                 Selection.objects = currentSelection.ToArray();
 | ||
|             }
 | ||
|             else Selection.activeObject = selectedObject;
 | ||
|         }
 | ||
| 
 | ||
|         public void SelectObjects(List<Object> selectedObjects, bool append)
 | ||
|         {
 | ||
|             if (append)
 | ||
|             {
 | ||
|                 List<Object> currentSelection = new List<Object>(Selection.objects);
 | ||
|                 currentSelection.AddRange(selectedObjects);
 | ||
|                 Selection.objects = currentSelection.ToArray();
 | ||
|             }
 | ||
|             else Selection.objects = selectedObjects.ToArray();
 | ||
|         }
 | ||
| 
 | ||
|         private static int MaterialSorter(MaterialDetails first, MaterialDetails second)
 | ||
|         {
 | ||
|             var firstIsNull = first.material == null;
 | ||
|             var secondIsNull = second.material == null;
 | ||
| 
 | ||
|             if (firstIsNull && secondIsNull) return 0;
 | ||
|             if (firstIsNull) return int.MaxValue;
 | ||
|             if (secondIsNull) return int.MinValue;
 | ||
| 
 | ||
|             return first.material.renderQueue - second.material.renderQueue;
 | ||
|         }
 | ||
| 
 | ||
|         public string FormatSizeString(float memSizeKB)
 | ||
|         {
 | ||
|             if (memSizeKB < 1024) return "" + memSizeKB + "k";
 | ||
|             else
 | ||
|             {
 | ||
|                 float memSizeMB = ((float)memSizeKB) / 1024.0f;
 | ||
|                 return memSizeMB.ToString("0.00") + "Mb";
 | ||
|             }
 | ||
|         }
 | ||
|         // Main method
 | ||
|         public void CollectData()
 | ||
|         {
 | ||
|             ActiveTextures.Clear();
 | ||
|             ActiveMaterials.Clear();
 | ||
|             ActiveMeshDetails.Clear();
 | ||
|             MissingObjects.Clear();
 | ||
|             ActiveClipDetails.Clear();
 | ||
|             ActiveParticleSystems.Clear();
 | ||
|             ActiveLights.Clear();
 | ||
|             ActivePhysicsObjects.Clear();
 | ||
|             ActiveExpensiveObjects.Clear();
 | ||
|             thingsMissing = false;
 | ||
|             _collectExpensiveObject.CollectData();
 | ||
|             _сollectParticleSystemData.CheckParticleSystems();
 | ||
|             _collectLightData.CheckLights();
 | ||
|             _collectPhysicsData.CheckPhysicsObjects();
 | ||
|             _collectMaterialsData.CheckRenderers();
 | ||
|             _collectTextureData.CheckRenderers();
 | ||
|             _collectTextureData.CheckLightmaps();
 | ||
|             _collectMaterialsData.CheckGUIElements();
 | ||
|             _collectTextureData.CheckGUIElements();
 | ||
|             _collectMeshData.CheckMeshFilters();
 | ||
|             _collectMeshData.CheckSkinnedMeshRenderers();
 | ||
|             _collectMeshData.CheckLODGroups();
 | ||
|             _collectTextureData.CheckSelectedFolder();
 | ||
|             _collectAudioClipData.CheckSelectedFolder();
 | ||
|             _collectTextureData.CheckSpriteAnimations();
 | ||
|             CheckScriptReferences();
 | ||
|             _collectTextureData.CheckMaterials(); //Collect all materials before
 | ||
|             _collectAudioClipData.CheckAudioSources();
 | ||
|             CheckForMissingScriptsInAllGameObjects();
 | ||
| 
 | ||
|             CalculateTotals();
 | ||
| 
 | ||
|             ActiveMaterials.Sort(MaterialSorter);
 | ||
|             CheckWarnings();
 | ||
|         }
 | ||
| 
 | ||
|         public void AddMissingSprite(SpriteRenderer tSpriteRenderer)
 | ||
|         {
 | ||
|             Missing tMissing = new Missing
 | ||
|             {
 | ||
|                 Object = tSpriteRenderer.transform,
 | ||
|                 type = "sprite",
 | ||
|                 name = tSpriteRenderer.transform.name
 | ||
|             };
 | ||
|             MissingObjects.Add(tMissing);
 | ||
|             thingsMissing = true;
 | ||
|         }
 | ||
|     
 | ||
|         private void CheckScriptReferences()
 | ||
|         {
 | ||
|             if (!IncludeScriptReferences) return;
 | ||
| 
 | ||
|             MonoBehaviour[] scripts = FindObjects<MonoBehaviour>();
 | ||
|             foreach (MonoBehaviour script in scripts)
 | ||
|             {
 | ||
|                 BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
 | ||
|                 FieldInfo[] fields = script.GetType().GetFields(flags);
 | ||
| 
 | ||
|                 foreach (FieldInfo field in fields)
 | ||
|                 {
 | ||
|                     _collectTextureData.CheckSpriteReferences(field, script);
 | ||
|                     _collectMeshData.CheckMeshReferences(field, script);
 | ||
|                     CheckMaterialReferences(field, script);
 | ||
|                     _collectAudioClipData.CheckAudioClipReferences(field, script);
 | ||
|                     _collectLightData.CheckLightReferences(field, script);
 | ||
|                     _сollectParticleSystemData.CheckParticleSystemReferences(field, script);
 | ||
|                     _collectPhysicsData.CheckRigidbodyReferences(field, script);
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|     
 | ||
|         private void CheckMaterialReferences(FieldInfo field, MonoBehaviour script)
 | ||
|         {
 | ||
|             if (field.FieldType == typeof(Material))
 | ||
|             {
 | ||
|                 Material tMaterial = field.GetValue(script) as Material;
 | ||
|                 if (tMaterial != null)
 | ||
|                 {
 | ||
|                     _collectMaterialsData.AddMaterialDetails(tMaterial);
 | ||
|                     _collectTextureData.CheckMaterialTextures(tMaterial);
 | ||
|                     _collectTextureData.CheckMaterialDependencies(tMaterial);
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|     
 | ||
|         private void CheckForMissingScriptsInAllGameObjects()
 | ||
|         {
 | ||
|             GameObject[] allGameObjects = GetAllRootGameObjects();
 | ||
|             foreach (GameObject go in allGameObjects)
 | ||
|             {
 | ||
|                 CheckForMissingScripts(go);
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         void CheckForMissingScripts(GameObject go)
 | ||
|         {
 | ||
|             Component[] components = go.GetComponents<Component>();
 | ||
| 
 | ||
|             foreach (Component component in components)
 | ||
|             {
 | ||
|                 if (component == null)
 | ||
|                 {
 | ||
|                     Missing tMissing = new Missing();
 | ||
|                     tMissing.Object = go.transform;
 | ||
|                     tMissing.type = "missing script";
 | ||
|                     tMissing.name = go.name;
 | ||
|                     MissingObjects.Add(tMissing);
 | ||
|                     thingsMissing = true;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             foreach (Transform child in go.transform)
 | ||
|             {
 | ||
|                 CheckForMissingScripts(child.gameObject);
 | ||
|             }
 | ||
|         }
 | ||
|     
 | ||
|         private void CalculateTotals()
 | ||
|         {
 | ||
|             TotalMeshVertices = 0;
 | ||
|             foreach (MeshDetails tMeshDetails in ActiveMeshDetails) TotalMeshVertices += tMeshDetails.mesh.vertexCount;
 | ||
| 
 | ||
|             ActiveTextures.Sort(delegate (TextureDetails details1, TextureDetails details2) { return (int)(details2.memSizeKB - details1.memSizeKB); });
 | ||
|             ActiveTextures = ActiveTextures.Distinct().ToList();
 | ||
|             TotalTextureMemory = 0;
 | ||
|             foreach (TextureDetails tTextureDetails in ActiveTextures) TotalTextureMemory += tTextureDetails.memSizeKB;
 | ||
|             ActiveMeshDetails.Sort(delegate (MeshDetails details1, MeshDetails details2) { return details2.mesh.vertexCount - details1.mesh.vertexCount; });
 | ||
|         }
 | ||
| 
 | ||
|         private void CheckWarnings()
 | ||
|         {
 | ||
|             Warnings.Clear();
 | ||
| 
 | ||
|             if (thingsMissing)
 | ||
|             {
 | ||
|                 Warnings.Add(new SceneWarningDetails("Some GameObjects are missing elements.", MessageType.Error));
 | ||
|             }
 | ||
|         
 | ||
|             float sceneFileSizeMB = _collectWarningsData.GetSceneFileSize();
 | ||
|             Warnings.Add(new SceneWarningDetails($"Scene file size: {sceneFileSizeMB:F2} MB.", MessageType.Info));
 | ||
|         
 | ||
|             int objectCount = _collectWarningsData.CountObjectsInScene();
 | ||
|             Warnings.Add(new SceneWarningDetails($"There are {objectCount} objects in the scene.", MessageType.Info));
 | ||
|         
 | ||
|             int canvasCount = _collectWarningsData.CountCanvasComponentsInScene();
 | ||
|             if (canvasCount > 0)
 | ||
|             {
 | ||
|                 Warnings.Add(new SceneWarningDetails($"There are {canvasCount} Canvas components in the scene.", MessageType.Info));
 | ||
|             }
 | ||
| 
 | ||
|             var platformsWithoutStaticBatching = _collectWarningsData.GetPlatformsWithoutStaticBatching();
 | ||
|             if (platformsWithoutStaticBatching.Count > 0)
 | ||
|             {
 | ||
|                 string message = "Static batching is not enabled: " + string.Join(", ", platformsWithoutStaticBatching);
 | ||
|                 Warnings.Add(new SceneWarningDetails(message, MessageType.Warning));
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         public GameObject[] GetAllRootGameObjects()
 | ||
|         {
 | ||
|             List<GameObject> allGo = new List<GameObject>();
 | ||
|             for (int sceneIdx = 0; sceneIdx < UnityEngine.SceneManagement.SceneManager.sceneCount; ++sceneIdx)
 | ||
|             {
 | ||
|                 Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(sceneIdx);
 | ||
|                 if (scene.isLoaded)
 | ||
|                 {
 | ||
|                     allGo.AddRange(scene.GetRootGameObjects());
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             allGo.AddRange(GetDontDestroyOnLoadRoots());
 | ||
|             return allGo.ToArray();
 | ||
|         }
 | ||
| 
 | ||
|         private static List<GameObject> GetDontDestroyOnLoadRoots()
 | ||
|         {
 | ||
|             List<GameObject> objs = new List<GameObject>();
 | ||
|             if (Application.isPlaying)
 | ||
|             {
 | ||
|                 GameObject temp = null;
 | ||
|                 try
 | ||
|                 {
 | ||
|                     temp = new GameObject();
 | ||
|                     DontDestroyOnLoad(temp);
 | ||
|                     UnityEngine.SceneManagement.Scene dontDestryScene = temp.scene;
 | ||
|                     DestroyImmediate(temp);
 | ||
|                     temp = null;
 | ||
| 
 | ||
|                     if(dontDestryScene.IsValid())
 | ||
|                     {
 | ||
|                         objs =  dontDestryScene.GetRootGameObjects().ToList();
 | ||
|                     }
 | ||
|                 }
 | ||
|                 catch (System.Exception e)
 | ||
|                 {
 | ||
|                     Debug.LogException(e);
 | ||
|                     return null;
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     if(temp != null)
 | ||
|                         DestroyImmediate(temp);
 | ||
|                 }
 | ||
|             }
 | ||
|             return objs;
 | ||
|         }
 | ||
| 
 | ||
|         public T[] FindObjects<T>() where T : Object
 | ||
|         {
 | ||
|             if (includeDisabledObjects) {
 | ||
|                 List<T> meshfilters = new List<T> ();
 | ||
|                 GameObject[] allGo = GetAllRootGameObjects();
 | ||
|                 foreach (GameObject go in allGo) {
 | ||
|                     Transform[] tgo = go.GetComponentsInChildren<Transform> (true).ToArray ();
 | ||
|                     foreach (Transform tr in tgo) {
 | ||
|                         if (tr.GetComponent<T> ())
 | ||
|                             meshfilters.Add (tr.GetComponent<T> ());
 | ||
|                     }
 | ||
|                 }
 | ||
|                 return (T[])meshfilters.ToArray ();
 | ||
|             }
 | ||
|             else
 | ||
|                 return (T[])FindObjectsOfType(typeof(T));
 | ||
|         }
 | ||
|         
 | ||
|         public List<GameObject> FindAllGameObjects()
 | ||
|         {
 | ||
|             List<GameObject> allObjects = new List<GameObject>();
 | ||
|             for (int i = 0; i < SceneManager.sceneCount; i++)
 | ||
|             {
 | ||
|                 Scene scene = SceneManager.GetSceneAt(i);
 | ||
|                 if (scene.isLoaded)
 | ||
|                 {
 | ||
|                     allObjects.AddRange(scene.GetRootGameObjects());
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             List<GameObject> dontDestroyOnLoadObjects = GetDontDestroyOnLoadRoots();
 | ||
|             allObjects.AddRange(dontDestroyOnLoadObjects);
 | ||
| 
 | ||
|             if (includeDisabledObjects)
 | ||
|             {
 | ||
|                 List<GameObject> allObjectsWithChildren = new List<GameObject>();
 | ||
|                 foreach (GameObject go in allObjects)
 | ||
|                 {
 | ||
|                     allObjectsWithChildren.AddRange(go.GetComponentsInChildren<Transform>(true)
 | ||
|                         .Select(t => t.gameObject));
 | ||
|                 }
 | ||
|                 return allObjectsWithChildren;
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 List<GameObject> allActiveObjectsWithChildren = new List<GameObject>();
 | ||
|                 foreach (GameObject go in allObjects)
 | ||
|                 {
 | ||
|                     if (go.activeInHierarchy)
 | ||
|                     {
 | ||
|                         allActiveObjectsWithChildren.AddRange(go.GetComponentsInChildren<Transform>(false)
 | ||
|                             .Where(t => t.gameObject.activeInHierarchy)
 | ||
|                             .Select(t => t.gameObject));
 | ||
|                     }
 | ||
|                 }
 | ||
|                 return allActiveObjectsWithChildren;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|     }
 | ||
| } | 
