Skip to content

Instantly share code, notes, and snippets.

@mousedoc
Created June 28, 2022 07:32
Show Gist options
  • Save mousedoc/036c24f91d6b1bc39ee67b2e5f9f3333 to your computer and use it in GitHub Desktop.
Save mousedoc/036c24f91d6b1bc39ee67b2e5f9f3333 to your computer and use it in GitHub Desktop.

Revisions

  1. mousedoc created this gist Jun 28, 2022.
    158 changes: 158 additions & 0 deletions MissingRefFinder.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    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;
    }
    }