使用方法
1.在Project中选中需要查找的资源文件,邮件点击“Find References”

2.等待第一步分析完成,会出现如下界面

通过上面面板可以看出当前资源的引用关系,包括资源名字、路径等;双击其中某个资源可以锁定在Project中的位置。
实现逻辑
a.资源引用数据类ReferenceFinderData
主要功能是把分析的资源通过字典存储并写入本地缓存中“”“./Library/ReferenceFinderCache”,完整代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor;
using UnityEngine;
namespace ColaFramework.ToolKit
{
public class ReferenceFinderData
{
//缓存路径
private const string CACHE_PATH = "Library/ReferenceFinderCache";
//资源引用信息字典
public Dictionary<string, AssetDescription> assetDict = new Dictionary<string, AssetDescription>();
//收集资源引用信息并更新缓存
public void CollectDependenciesInfo()
{
try
{
ReadFromCache();
var allAssets = AssetDatabase.GetAllAssetPaths();
int totalCount = allAssets.Length;
for (int i = 0; i < allAssets.Length; i++)
{
//每遍历100个Asset,更新一下进度条,同时对进度条的取消操作进行处理
if ((i % 100 == 0) && EditorUtility.DisplayCancelableProgressBar("Refresh", string.Format("Collecting {0} assets", i), (float)i / totalCount))
{
EditorUtility.ClearProgressBar();
return;
}
if (File.Exists(allAssets[i]))
ImportAsset(allAssets[i]);
if (i % 2000 == 0)
GC.Collect();
}
//将信息写入缓存
EditorUtility.DisplayCancelableProgressBar("Refresh", "Write to cache", 1f);
WriteToChache();
//生成引用数据
EditorUtility.DisplayCancelableProgressBar("Refresh", "Generating asset reference info", 1f);
UpdateReferenceInfo();
EditorUtility.ClearProgressBar();
}
catch (Exception e)
{
Debug.LogError(e);
EditorUtility.ClearProgressBar();
}
}
//通过依赖信息更新引用信息
private void UpdateReferenceInfo()
{
foreach (var asset in assetDict)
{
foreach (var assetGuid in asset.Value.dependencies)
{
assetDict[assetGuid].references.Add(asset.Key);
}
}
}
//生成并加入引用信息
private void ImportAsset(string path)
{
//通过path获取guid进行储存
string guid = AssetDatabase.AssetPathToGUID(path);
//获取该资源的最后修改时间,用于之后的修改判断
Hash128 assetDependencyHash = AssetDatabase.GetAssetDependencyHash(path);
//如果assetDict没包含该guid或包含了修改时间不一样则需要更新
if (!assetDict.ContainsKey(guid) || assetDict[guid].assetDependencyHash != assetDependencyHash)
{
//将每个资源的直接依赖资源转化为guid进行储存
var guids = AssetDatabase.GetDependencies(path, false).
Select(p => AssetDatabase.AssetPathToGUID(p)).
ToList();
//生成asset依赖信息,被引用需要在所有的asset依赖信息生成完后才能生成
AssetDescription ad = new AssetDescription();
ad.name = Path.GetFileNameWithoutExtension(path);
ad.path = path;
ad.type = AssetDatabase.GetMainAssetTypeAtPath(path).ToString();
ad.assetDependencyHash = assetDependencyHash;
ad.dependencies = guids;
if (assetDict.ContainsKey(guid))
assetDict[guid] = ad;
else
assetDict.Add(guid, ad);
}
}
//读取缓存信息
public bool ReadFromCache()
{
assetDict.Clear();
if (File.Exists(CACHE_PATH))
{
var serializedGuid = new List<string>();
var serializedDependencyHash = new List<Hash128>();
var serializedDenpendencies = new List<int[]>();
//反序列化数据
using (FileStream fs = File.OpenRead(CACHE_PATH))
{
BinaryFormatter bf = new BinaryFormatter();
EditorUtility.DisplayCancelableProgressBar("Import Cache", "Reading Cache", 0);
serializedGuid = (List<string>)bf.Deserialize(fs);
serializedDependencyHash = (List<Hash128>)bf.Deserialize(fs);
serializedDenpendencies = (List<int[]>)bf.Deserialize(fs);
EditorUtility.ClearProgressBar();
}
for (int i = 0; i < serializedGuid.Count; ++i)
{
string path = AssetDatabase.GUIDToAssetPath(serializedGuid[i]);
if (!string.IsNullOrEmpty(path))
{
var ad = new AssetDescription();
ad.name = Path.GetFileNameWithoutExtension(path);
ad.path = path;
ad.type = AssetDatabase.GetMainAssetTypeAtPath(path).ToString();
ad.assetDependencyHash = serializedDependencyHash[i];
assetDict.Add(serializedGuid[i], ad);
}
}
for (int i = 0; i < serializedGuid.Count; ++i)
{
string guid = serializedGuid[i];
if (assetDict.ContainsKey(guid))
{
var guids = serializedDenpendencies[i].
Select(index => serializedGuid[index]).
Where(g => assetDict.ContainsKey(g)).
ToList();
assetDict[guid].dependencies = guids;
}
}
UpdateReferenceInfo();
return true;
}
return false;
}
public void DeletaData()
{
if (File.Exists(CACHE_PATH))
File.Delete(CACHE_PATH);
}
//写入缓存
private void WriteToChache()
{
if (File.Exists(CACHE_PATH))
File.Delete(CACHE_PATH);
var serializedGuid = new List<string>();
var serializedDependencyHash = new List<Hash128>();
var serializedDenpendencies = new List<int[]>();
//辅助映射字典
var guidIndex = new Dictionary<string, int>();
//序列化
using (FileStream fs = File.OpenWrite(CACHE_PATH))
{
foreach (var pair in assetDict)
{
guidIndex.Add(pair.Key, guidIndex.Count);
serializedGuid.Add(pair.Key);
serializedDependencyHash.Add(pair.Value.assetDependencyHash);
}
foreach (var guid in serializedGuid)
{
int[] indexes = assetDict[guid].dependencies.Select(s => guidIndex[s]).ToArray();
serializedDenpendencies.Add(indexes);
}
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, serializedGuid);
bf.Serialize(fs, serializedDependencyHash);
bf.Serialize(fs, serializedDenpendencies);
}
}
//更新引用信息状态
public void UpdateAssetState(string guid)
{
AssetDescription ad;
if (assetDict.TryGetValue(guid, out ad) && ad.state != AssetState.NODATA)
{
if (File.Exists(ad.path))
{
//修改时间与记录的不同为修改过的资源
if (ad.assetDependencyHash != AssetDatabase.GetAssetDependencyHash(ad.path))
{
ad.state = AssetState.CHANGED;
}
else
{
//默认为普通资源
ad.state = AssetState.NORMAL;
}
}
//不存在为丢失
else
{
ad.state = AssetState.MISSING;
}
}
//字典中没有该数据
else if (!assetDict.TryGetValue(guid, out ad))
{
string path = AssetDatabase.GUIDToAssetPath(guid);
ad = new AssetDescription();
ad.name = Path.GetFileNameWithoutExtension(path);
ad.path = path;
ad.type = AssetDatabase.GetMainAssetTypeAtPath(path).ToString();
ad.state = AssetState.NODATA;
assetDict.Add(guid, ad);
}
}
//根据引用信息状态获取状态描述
public static string GetInfoByState(AssetState state)
{
if (state == AssetState.CHANGED)
{
return "<color=#F0672AFF>Changed</color>";
}
else if (state == AssetState.MISSING)
{
return "<color=#FF0000FF>Missing</color>";
}
else if (state == AssetState.NODATA)
{
return "<color=#FFE300FF>No Data</color>";
}
return "Normal";
}
public class AssetDescription
{
public string name = "";
public string path = "";
public string type = "";
public Hash128 assetDependencyHash;
public List<string> dependencies = new List<string>();
public List<string> references = new List<string>();
public AssetState state = AssetState.NORMAL;
}
public enum AssetState
{
NORMAL,
CHANGED,
MISSING,
NODATA,
}
}
}
b.资源引用树类(AssetTreeView)
主要把资源名字、路径、类型、文件状态显示在资源面板中,继承TreeView类,下图中选在的信息是通次此类封装的。

通过图标+名称、路径、状态、资源路径的顺序放进数组中并生成队列
//生成ColumnHeader
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
{
var columns = new[]
{
//图标+名称
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Name"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 200,
minWidth = 60,
autoResize = false,
allowToggleVisibility = false,
canSort = false
},
//路径
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Path"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 360,
minWidth = 60,
autoResize = false,
allowToggleVisibility = false,
canSort = false
},
//状态
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("State"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 60,
minWidth = 60,
autoResize = false,
allowToggleVisibility = true,
canSort = false
},
//资源类型
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Type"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 30,
minWidth = 60,
autoResize = false,
allowToggleVisibility = true,
canSort = false
},
};
var state = new MultiColumnHeaderState(columns);
return state;
}
绘制列表中每项内容
//绘制列表中的每项内容
void CellGUI(Rect cellRect, AssetViewItem item, MyColumns column, ref RowGUIArgs args)
{
CenterRectUsingSingleLineHeight(ref cellRect);
switch (column)
{
case MyColumns.Name:
{
var iconRect = cellRect;
iconRect.x += GetContentIndent(item);
iconRect.width = kIconWidth;
if (iconRect.x < cellRect.xMax)
{
var icon = GetIcon(item.data.path);
if (icon != null)
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
}
args.rowRect = cellRect;
base.RowGUI(args);
}
break;
case MyColumns.Path:
{
GUI.Label(cellRect, item.data.path);
}
break;
case MyColumns.State:
{
GUI.Label(cellRect, ReferenceFinderData.GetInfoByState(item.data.state), stateGUIStyle);
}
break;
case MyColumns.Type:
{
GUI.Label(cellRect, item.data.type);
}
break;
default:
break;
}
}
完整代码如下:
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
namespace ColaFramework.ToolKit
{
//带数据的TreeViewItem
public class AssetViewItem : TreeViewItem
{
public ReferenceFinderData.AssetDescription data;
}
//资源引用树
public class AssetTreeView : TreeView
{
//图标宽度
const float kIconWidth = 18f;
//列表高度
const float kRowHeights = 20f;
public AssetViewItem assetRoot;
private GUIStyle stateGUIStyle = new GUIStyle { richText = true, alignment = TextAnchor.MiddleCenter };
//列信息
enum MyColumns
{
Name,
Path,
State,
Type,
}
public AssetTreeView(TreeViewState state, MultiColumnHeader multicolumnHeader) : base(state, multicolumnHeader)
{
rowHeight = kRowHeights;
columnIndexForTreeFoldouts = 0;
showAlternatingRowBackgrounds = true;
showBorder = false;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
extraSpaceBeforeIconAndLabel = kIconWidth;
}
//响应双击事件
protected override void DoubleClickedItem(int id)
{
var item = (AssetViewItem)FindItem(id, rootItem);
//在ProjectWindow中高亮双击资源
if (item != null)
{
var assetObject = AssetDatabase.LoadAssetAtPath(item.data.path, typeof(UnityEngine.Object));
EditorUtility.FocusProjectWindow();
Selection.activeObject = assetObject;
EditorGUIUtility.PingObject(assetObject);
}
}
//生成ColumnHeader
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
{
var columns = new[]
{
//图标+名称
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Name"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 200,
minWidth = 60,
autoResize = false,
allowToggleVisibility = false,
canSort = false
},
//路径
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Path"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 360,
minWidth = 60,
autoResize = false,
allowToggleVisibility = false,
canSort = false
},
//状态
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("State"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 60,
minWidth = 60,
autoResize = false,
allowToggleVisibility = true,
canSort = false
},
//资源类型
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Type"),
headerTextAlignment = TextAlignment.Center,
sortedAscending = false,
width = 30,
minWidth = 60,
autoResize = false,
allowToggleVisibility = true,
canSort = false
},
};
var state = new MultiColumnHeaderState(columns);
return state;
}
protected override TreeViewItem BuildRoot()
{
return assetRoot;
}
protected override void RowGUI(RowGUIArgs args)
{
var item = (AssetViewItem)args.item;
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
}
//绘制列表中的每项内容
void CellGUI(Rect cellRect, AssetViewItem item, MyColumns column, ref RowGUIArgs args)
{
CenterRectUsingSingleLineHeight(ref cellRect);
switch (column)
{
case MyColumns.Name:
{
var iconRect = cellRect;
iconRect.x += GetContentIndent(item);
iconRect.width = kIconWidth;
if (iconRect.x < cellRect.xMax)
{
var icon = GetIcon(item.data.path);
if (icon != null)
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
}
args.rowRect = cellRect;
base.RowGUI(args);
}
break;
case MyColumns.Path:
{
GUI.Label(cellRect, item.data.path);
}
break;
case MyColumns.State:
{
GUI.Label(cellRect, ReferenceFinderData.GetInfoByState(item.data.state), stateGUIStyle);
}
break;
case MyColumns.Type:
{
GUI.Label(cellRect, item.data.type);
}
break;
default:
break;
}
}
//根据资源信息获取资源图标
private Texture2D GetIcon(string path)
{
Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Object));
if (obj != null)
{
Texture2D icon = AssetPreview.GetMiniThumbnail(obj);
if (icon == null)
icon = AssetPreview.GetMiniTypeThumbnail(obj.GetType());
return icon;
}
return null;
}
}
}
c.资源引用窗口(ReferenceFinderWindow)
主要实现window面板的绘制,继承EditorWindow,如果本地有缓存优先读取缓存。
更新选中资源列表
//更新选中资源列表
private void UpdateSelectedAssets()
{
selectedAssetGuid.Clear();
foreach (var obj in Selection.objects)
{
string path = AssetDatabase.GetAssetPath(obj);
//如果是文件夹
if (Directory.Exists(path))
{
string[] folder = new string[] { path };
//将文件夹下所有资源作为选择资源
string[] guids = AssetDatabase.FindAssets(null, folder);
foreach (var guid in guids)
{
if (!selectedAssetGuid.Contains(guid) &&
!Directory.Exists(AssetDatabase.GUIDToAssetPath(guid)))
{
selectedAssetGuid.Add(guid);
}
}
}
//如果是文件资源
else
{
string guid = AssetDatabase.AssetPathToGUID(path);
selectedAssetGuid.Add(guid);
}
}
needUpdateAssetTree = true;
}
完整代码:
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
namespace ColaFramework.ToolKit
{
public class ReferenceFinderWindow : EditorWindow
{
//依赖模式的key
const string isDependPrefKey = "ReferenceFinderData_IsDepend";
//是否需要更新信息状态的key
const string needUpdateStatePrefKey = "ReferenceFinderData_needUpdateState";
private static ReferenceFinderData data = new ReferenceFinderData();
private static bool initializedData = false;
private bool isDepend = false;
private bool needUpdateState = true;
private bool needUpdateAssetTree = false;
private bool initializedGUIStyle = false;
//工具栏按钮样式
private GUIStyle toolbarButtonGUIStyle;
//工具栏样式
private GUIStyle toolbarGUIStyle;
//选中资源列表
private List<string> selectedAssetGuid = new List<string>();
private AssetTreeView m_AssetTreeView;
[SerializeField]
private TreeViewState m_TreeViewState;
//查找资源引用信息
[MenuItem("Assets/Find References")]
static void FindRef()
{
InitDataIfNeeded();
OpenWindow();
ReferenceFinderWindow window = GetWindow<ReferenceFinderWindow>();
window.UpdateSelectedAssets();
}
//打开窗口
[MenuItem("Window/Refrence Finder", false, 1000)]
static void OpenWindow()
{
ReferenceFinderWindow window = GetWindow<ReferenceFinderWindow>();
window.wantsMouseMove = false;
window.titleContent = new GUIContent("Ref Finder");
window.Show();
window.Focus();
}
//初始化数据
static void InitDataIfNeeded()
{
if (!initializedData)
{
//初始化数据
if (!data.ReadFromCache())
{
data.CollectDependenciesInfo();
}
initializedData = true;
}
}
//初始化GUIStyle
void InitGUIStyleIfNeeded()
{
if (!initializedGUIStyle)
{
toolbarButtonGUIStyle = new GUIStyle("ToolbarButton");
toolbarGUIStyle = new GUIStyle("Toolbar");
initializedGUIStyle = true;
}
}
//更新选中资源列表
private void UpdateSelectedAssets()
{
selectedAssetGuid.Clear();
foreach (var obj in Selection.objects)
{
string path = AssetDatabase.GetAssetPath(obj);
//如果是文件夹
if (Directory.Exists(path))
{
string[] folder = new string[] { path };
//将文件夹下所有资源作为选择资源
string[] guids = AssetDatabase.FindAssets(null, folder);
foreach (var guid in guids)
{
if (!selectedAssetGuid.Contains(guid) &&
!Directory.Exists(AssetDatabase.GUIDToAssetPath(guid)))
{
selectedAssetGuid.Add(guid);
}
}
}
//如果是文件资源
else
{
string guid = AssetDatabase.AssetPathToGUID(path);
selectedAssetGuid.Add(guid);
}
}
needUpdateAssetTree = true;
}
//通过选中资源列表更新TreeView
private void UpdateAssetTree()
{
if (needUpdateAssetTree && selectedAssetGuid.Count != 0)
{
var root = SelectedAssetGuidToRootItem(selectedAssetGuid);
if (m_AssetTreeView == null)
{
//初始化TreeView
if (m_TreeViewState == null)
m_TreeViewState = new TreeViewState();
var headerState = AssetTreeView.CreateDefaultMultiColumnHeaderState(position.width);
var multiColumnHeader = new MultiColumnHeader(headerState);
m_AssetTreeView = new AssetTreeView(m_TreeViewState, multiColumnHeader);
}
m_AssetTreeView.assetRoot = root;
m_AssetTreeView.CollapseAll();
m_AssetTreeView.Reload();
needUpdateAssetTree = false;
}
}
private void OnEnable()
{
isDepend = PlayerPrefs.GetInt(isDependPrefKey, 0) == 1;
needUpdateState = PlayerPrefs.GetInt(needUpdateStatePrefKey, 1) == 1;
}
private void OnGUI()
{
InitGUIStyleIfNeeded();
DrawOptionBar();
UpdateAssetTree();
if (m_AssetTreeView != null)
{
//绘制Treeview
m_AssetTreeView.OnGUI(new Rect(0, toolbarGUIStyle.fixedHeight, position.width, position.height - toolbarGUIStyle.fixedHeight));
}
}
//绘制上条
public void DrawOptionBar()
{
EditorGUILayout.BeginHorizontal(toolbarGUIStyle);
if (GUILayout.Button("Delete Data", toolbarButtonGUIStyle))
{
data.DeletaData();
}
//刷新数据
if (GUILayout.Button("Refresh Data", toolbarButtonGUIStyle))
{
data.CollectDependenciesInfo();
needUpdateAssetTree = true;
EditorGUIUtility.ExitGUI();
}
//修改模式
bool PreIsDepend = isDepend;
isDepend = GUILayout.Toggle(isDepend, isDepend ? "Model(Depend)" : "Model(Reference)", toolbarButtonGUIStyle, GUILayout.Width(100));
if (PreIsDepend != isDepend)
{
OnModelSelect();
}
//是否需要更新状态
bool PreNeedUpdateState = needUpdateState;
needUpdateState = GUILayout.Toggle(needUpdateState, "Need Update State", toolbarButtonGUIStyle);
if (PreNeedUpdateState != needUpdateState)
{
PlayerPrefs.SetInt(needUpdateStatePrefKey, needUpdateState ? 1 : 0);
}
GUILayout.FlexibleSpace();
//扩展
if (GUILayout.Button("Expand", toolbarButtonGUIStyle))
{
if (m_AssetTreeView != null) m_AssetTreeView.ExpandAll();
}
//折叠
if (GUILayout.Button("Collapse", toolbarButtonGUIStyle))
{
if (m_AssetTreeView != null) m_AssetTreeView.CollapseAll();
}
EditorGUILayout.EndHorizontal();
}
private void OnModelSelect()
{
needUpdateAssetTree = true;
PlayerPrefs.SetInt(isDependPrefKey, isDepend ? 1 : 0);
}
//生成root相关
private HashSet<string> updatedAssetSet = new HashSet<string>();
//通过选择资源列表生成TreeView的根节点
private AssetViewItem SelectedAssetGuidToRootItem(List<string> selectedAssetGuid)
{
updatedAssetSet.Clear();
int elementCount = 0;
var root = new AssetViewItem { id = elementCount, depth = -1, displayName = "Root", data = null };
int depth = 0;
foreach (var childGuid in selectedAssetGuid)
{
root.AddChild(CreateTree(childGuid, ref elementCount, depth));
}
updatedAssetSet.Clear();
return root;
}
//通过每个节点的数据生成子节点
private AssetViewItem CreateTree(string guid, ref int elementCount, int _depth)
{
if (needUpdateState && !updatedAssetSet.Contains(guid))
{
data.UpdateAssetState(guid);
updatedAssetSet.Add(guid);
}
++elementCount;
var referenceData = data.assetDict[guid];
var root = new AssetViewItem { id = elementCount, displayName = referenceData.name, data = referenceData, depth = _depth };
var childGuids = isDepend ? referenceData.dependencies : referenceData.references;
foreach (var childGuid in childGuids)
{
root.AddChild(CreateTree(childGuid, ref elementCount, _depth + 1));
}
return root;
}
}
}
说明:
以上代码去放到Editor文件夹下面。
网友评论