Created
June 28, 2022 07:32
-
-
Save mousedoc/036c24f91d6b1bc39ee67b2e5f9f3333 to your computer and use it in GitHub Desktop.
Unity Missing Reference Finder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text; | |
using UnityEditor; | |
using UnityEditor.SceneManagement; | |
using UnityEngine; | |
/// <summary> | |
/// Utility script for finding missing references to objects. | |
/// </summary> | |
public class MissingRefFinder | |
{ | |
private const string RootMenu = "Tools/Utility/Missing References Finder/"; | |
/// <summary> | |
/// Finds all missing references to objects in the currently loaded scene. | |
/// </summary> | |
[MenuItem(RootMenu + "Search in scene", false, 50)] | |
public static void FindMissingReferencesInCurrentScene() | |
{ | |
var sceneObjects = GetSceneObjects(); | |
FindMissingReferences(EditorSceneManager.GetActiveScene().path, sceneObjects); | |
} | |
/// <summary> | |
/// 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. | |
/// </summary> | |
[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(); | |
} | |
} | |
/// <summary> | |
/// Finds all missing references to objects in assets (objects from the project window). | |
/// </summary> | |
[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<GameObject> gameObjects) | |
{ | |
if (gameObjects == null) | |
return; | |
var missingObjs = new List<GameObject>(); | |
var missingScriptLog = new StringBuilder(); | |
var missingPropertyLog = new StringBuilder(); | |
foreach (var go in gameObjects) | |
{ | |
var comps = go.GetComponents<Component>(); | |
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("<Missing Script List>"); | |
sb.AppendLine(missingScriptLog.ToString()); | |
} | |
if (missingPropertyLog.Length > 0) | |
{ | |
if (missingScriptLog.Length > 0) | |
sb.AppendLine(); | |
sb.AppendLine("<Missing Property List>"); | |
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<GameObject> GetSceneObjects() | |
{ | |
// Use this method since GameObject.FindObjectsOfType will not return disabled objects. | |
return Resources.FindObjectsOfTypeAll<GameObject>() | |
.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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment