using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; /// /// Utility script for finding missing references to objects. /// public class MissingRefFinder { private const string RootMenu = "Tools/Utility/Missing References Finder/"; /// /// Finds all missing references to objects in the currently loaded scene. /// [MenuItem(RootMenu + "Search in scene", false, 50)] public static void FindMissingReferencesInCurrentScene() { var sceneObjects = GetSceneObjects(); FindMissingReferences(EditorSceneManager.GetActiveScene().path, sceneObjects); } /// /// Finds all missing references to objects in all enabled scenes in the project. /// This works by loading the scenes one by one and checking for missing object references. /// [MenuItem(RootMenu + "Search in all scenes", false, 51)] public static void FindMissingReferencesInAllScenes() { foreach (var scene in EditorBuildSettings.scenes.Where(s => s.enabled)) { EditorSceneManager.OpenScene(scene.path); FindMissingReferencesInCurrentScene(); } } /// /// Finds all missing references to objects in assets (objects from the project window). /// [MenuItem(RootMenu + "Search in assets", false, 52)] public static void FindMissingReferencesInAssets() { var allAssets = AssetDatabase.GetAllAssetPaths().Where(path => path.StartsWith("Assets/")).ToArray(); var objs = allAssets.Select(a => AssetDatabase.LoadAssetAtPath(a, typeof(GameObject)) as GameObject) .Where(a => a != null).ToArray(); FindMissingReferences("Project", objs); } private static void FindMissingReferences(string context, IEnumerable gameObjects) { if (gameObjects == null) return; var missingObjs = new List(); var missingScriptLog = new StringBuilder(); var missingPropertyLog = new StringBuilder(); foreach (var go in gameObjects) { var comps = go.GetComponents(); foreach (var comp in comps) { // Missing components will be null, we can't find their type, etc. if (comp == null) { missingObjs.Add(go); missingScriptLog.AppendLine(GetMissingScriptLog(go, context)); continue; } SerializedObject so = new SerializedObject(comp); var sp = so.GetIterator(); var objRefValueMethod = typeof(SerializedProperty).GetProperty("objectReferenceStringValue", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); // Iterate over the components' properties. while (sp.NextVisible(true)) { if (sp.propertyType == SerializedPropertyType.ObjectReference) { string objectReferenceStringValue = string.Empty; if (objRefValueMethod != null) { objectReferenceStringValue = (string) objRefValueMethod.GetGetMethod(true).Invoke(sp, new object[] { }); } if (sp.objectReferenceValue == null && (sp.objectReferenceInstanceIDValue != 0 || objectReferenceStringValue.StartsWith("Missing"))) { missingObjs.Add(go); missingPropertyLog.AppendLine(GetMissingPropertyLog(go, comp.GetType().Name, ObjectNames.NicifyVariableName(sp.name), context)); } } } } } if (missingObjs.Count > 0) { var sb = new StringBuilder(); if (missingScriptLog.Length > 0) { sb.AppendLine(""); sb.AppendLine(missingScriptLog.ToString()); } if (missingPropertyLog.Length > 0) { if (missingScriptLog.Length > 0) sb.AppendLine(); sb.AppendLine(""); sb.AppendLine(missingPropertyLog.ToString()); } Debug.LogError(sb.ToString()); Selection.objects = missingObjs.ToArray(); } else { Debug.Log("No missing script/property in current scene!"); } } private static IEnumerable GetSceneObjects() { // Use this method since GameObject.FindObjectsOfType will not return disabled objects. return Resources.FindObjectsOfTypeAll() .Where(go => string.IsNullOrEmpty(AssetDatabase.GetAssetPath(go)) && go.hideFlags == HideFlags.None); } private readonly static string missingScriptFormat = "Missing Script in: [{1}] {0}"; private static string GetMissingScriptLog(GameObject go, string context) { return string.Format(missingScriptFormat, GetFullPath(go), context); } private readonly static string missingPropertyFormat = "Missing Property in : [{3}] {0}. Component : {1}, Property : {2}"; private static string GetMissingPropertyLog(GameObject go, string componentName, string propertyName, string context) { return string.Format(missingPropertyFormat, GetFullPath(go), componentName, propertyName, context); } private static string GetFullPath(GameObject go) { return go.transform.parent == null ? go.name : GetFullPath(go.transform.parent.gameObject) + "/" + go.name; } }