Skip to content

Instantly share code, notes, and snippets.

@noisecrime
Last active March 21, 2024 20:50
Show Gist options
  • Save noisecrime/4622eae538222680a1e78c75a90e57d8 to your computer and use it in GitHub Desktop.
Save noisecrime/4622eae538222680a1e78c75a90e57d8 to your computer and use it in GitHub Desktop.
WIP - Simple editor script to provide options in dropdown menu when clicking on an asset in the project browser to discover what other assets/scenes reference it.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace NoiseCrimeStudios.Core.Editor.Asset.Tools
{
/// <summary>
/// Editor functions to obtain useful information about Assets in Project Browser.
/// Right-Click on asset, navigate to 'Reference Checker' then 'Use In Scene', 'Used By Objects' or 'Missing References'.
/// Based on original code by Matt "Trip" Maker from Monstrous Company :: http://monstro.us
/// http://unifycommunity.com/wiki/index.php?title=UnityAssetXrefs
/// </summary>
public class AssetsMenu_ReferenceChecker
{
#region Used In Scene
[MenuItem("Assets/Reference Checker/Used In Scene", false, 201)]
private static void SelectSceneUsesOfAsset()
{
Object cur = Selection.activeObject;
// Optionally tell the user what's going on // TODO get only main assets of above
// Object[] sceneObjects = Object.FindSceneObjectsOfType(typeof(Object)); // Depreceated
// Object[] sceneObjects = Object.FindObjectsOfType( typeof(Object) );
Object[] sceneObjects = Object.FindObjectsOfType<Object>();
List<Object> results = CollectReverseDependenciesInCurrentScene(cur);
// Debug.Log("Usage: Used In Scene: You have asked which of " + sceneObjects.Length + " assets make use of " + cur.name + ", a " + cur.GetType().ToString() + ".", cur);
// Debug.Log("Usage: Used In Scene: " + results.Count() + " uses in scene found for this asset.", cur);
// printAssetPaths(results);
Debug.Log("Reference Checker: Used In Scene: " + results.Count() + " / " + sceneObjects.Length + " found for " + cur.name + ", a " + cur.GetType().ToString() + ".", cur);
foreach ( Object result in results )
{
#if UNITY_2018_1_OR_NEWER
Object parentPrefab = PrefabUtility.GetCorrespondingObjectFromSource(result);
#else
Object parentPrefab = PrefabUtility.GetPrefabParent( result );
#endif
string parentPrefabType = parentPrefab == null ? "null" : parentPrefab.GetType().ToString();
Debug.Log(result.name + " : " + parentPrefabType, result);
}
SetSelection(results);
}
private static List<Object> CollectReverseDependenciesInCurrentScene(Object b)
{
Object[] sceneObjects = Object.FindObjectsOfType(typeof(Object)); // Object.FindSceneObjectsOfType(typeof(Object));
return sceneObjects.Where(a => ADependsOnB(a, b)).ToList();
}
#endregion
#region Used By Objects
[MenuItem("Assets/Reference Checker/Used by Objects", false, 201)]
private static void FindUsesOfSelectedAssets() { FindUsesOfSelectedAssets(false, false); }
[MenuItem("Assets/Reference Checker/Used by Objects (Select)", false, 201)]
private static void FindUsesOfSelectedAssetsHighlight() { FindUsesOfSelectedAssets(true, false); }
[MenuItem("Assets/Reference Checker/Used by Objects (Itemise)", false, 201)]
private static void FindUsesOfSelectedAssetsItemised() { FindUsesOfSelectedAssets(false, true); }
private static void FindUsesOfSelectedAssets(bool highlightResults, bool itemised)
{
// Get all selected objects, including folders and folder contents
Object[] selectedObjectArray = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
foreach ( Object selected in selectedObjectArray )
{
// Get Asset Path of selected object
string assetPath = AssetDatabase.GetAssetPath(selected);
// Check if path is to file or folder.
if ( !Directory.Exists(assetPath) )
SelectUsesOfAsset(selected, highlightResults, itemised);
}
}
private static void SelectUsesOfAsset(Object objectToSearch, bool highlightResults = false, bool itemised = false)
{
float startTime = Time.realtimeSinceStartup;
List<Object> results = CollectReverseDependencies(objectToSearch);
string title = string.Format("SelectUsesOfAsset: {0} reverse dependencies found for '{1}' in {2:F2} seconds.\n", results.Count(), objectToSearch.name, ( Time.realtimeSinceStartup - startTime ).ToString());
if ( itemised )
{
Debug.Log(title, objectToSearch);
foreach ( Object obj in results )
Debug.Log(" > " + AssetDatabase.GetAssetPath(obj), obj);
}
else
{
foreach ( Object obj in results )
title += AssetDatabase.GetAssetPath(obj) + "\n";
Debug.Log(title, objectToSearch);
}
if ( highlightResults )
SetSelection(results);
}
private static List<Object> CollectReverseDependencies(Object b)
{
return AllAssets.Where(a => ADependsOnB(a, b)).ToList();
}
private static List<Object> CollectReverseDependencies(Object[] objs)
{
List<Object> ret = new List<Object>();
foreach ( Object obj in objs )
ret.AddRange(CollectReverseDependencies(obj));
return ret;
}
#endregion
#region Select Missing References
[MenuItem("Assets/Reference Checker/Select Missing References", false, 201)]
private static void SelectMissingReferences()
{
float startTime = Time.realtimeSinceStartup;
List<Object> results = CollectMissingRefs();
string title = string.Format("SelectMissingReferences: {0} missing references found in {1:F2} seconds.\n", results.Count(), ( Time.realtimeSinceStartup - startTime ).ToString());
PrintAssetPaths(title, results);
SetSelection(results);
}
private static List<Object> CollectMissingRefs()
{
return AllAssets.Where(x => HasMissingRef(x)).ToList();
}
private static bool HasMissingRef(Object obj)
{
if ( !obj )
return false;
Object[] dependencies = EditorUtility.CollectDependencies(new Object[1] { obj });
foreach ( Object dep in dependencies )
if ( dep == null )
return true;
return false;
}
private static void PrintAssetPaths(string title, List<Object> objs)
{
foreach ( Object obj in objs )
title += AssetDatabase.GetAssetPath(obj) + "\n";
Debug.Log(title);
}
private static void SetSelection(List<Object> results)
{
if ( results.Count() > 0 )
Selection.objects = results.ToArray();
}
#endregion
#region General Methods
// everything below this line would normally be in other libraries I've made, but I've brought these versions of them into here for simplicity.
private const string ForwardSlash = "/";
private const string BackSlash = "\\";
private static List<Object> AllAssets
{
get
{
// get every single one of the files in the Assets folder.
List<FileInfo> files = DirSearch(new DirectoryInfo(Application.dataPath), "*.*");
// now make them all into Asset references.
List<Object> assetRefs = new List<Object>();
foreach ( FileInfo fi in files )
{
if ( fi.Name.StartsWith(".") )
continue; // Unity ignores dotfiles.
assetRefs.Add(AssetDatabase.LoadMainAssetAtPath(GetRelativeAssetPath(fi.FullName)));
}
return assetRefs;
}
}
private static bool ADependsOnB(Object obj, Object selectedObj)
{
if ( selectedObj == null )
return false;
//optionally, exclude self.
if ( selectedObj == obj )
return false;
Object[] dependencies = EditorUtility.CollectDependencies(new Object[1] { obj });
if ( dependencies.Length < 2 )
return false; // if there's only one, it's us.
// Debug.Log(obj.name + " has " + dependencies.Length + " dependencies", obj);
foreach ( Object dep in dependencies )
if ( dep && ( dep == selectedObj ) )
return true;
return false;
}
/// <summary>DataPath uses forward slashes on all platforms now, so replace with Backslash.</summary>
private static string FixSlashes(string s)
{
return s.Replace(BackSlash, ForwardSlash);
}
/// <summary>Convert filesystem path to Unity Asset folder relative path so it can work with UnityEditor's built-in commands.</summary>
private static string GetRelativeAssetPath(string pathName)
{
// DataPath uses forward slashes on all platforms now
return FixSlashes(pathName).Replace(Application.dataPath, "Assets");
}
// given a folder and a search filter, return a list of file references
// (in the unlikely event you have some filesystem arrangement with recursive "hard links", be aware this may not work out well for you)
private static List<FileInfo> DirSearch(DirectoryInfo d, string searchFor)
{
List<FileInfo> founditems = d.GetFiles(searchFor).ToList();
// Add (by recursing) subdirectory items.
DirectoryInfo[] dis = d.GetDirectories();
foreach ( DirectoryInfo di in dis )
founditems.AddRange(DirSearch(di, searchFor));
return ( founditems );
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment