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.
Unity Missing Reference Finder
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