using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEditor.SceneManagement; using UnityEditor.UI; using UnityEngine; using Object = UnityEngine.Object; public class NoReferenceWindow : EditorWindow { #if UNITY_EDITOR private enum AssetType { None, Prefab, Material, Scene, Asset, Lua, Image, FBX, Controller, Animation, } private class FileInfo { public string path; public AssetType assetType; public Object asset; } private static string[] ASSET_FILE_ROOT = { "Assets/Resources", "Assets/GameAssets", "Assets/Scene", "Assets/StreamingAssets" }; // 过滤文件夹 private static string[] FilterDirectory = { "Assets/Resources/UI/GenAtlas" }; private Dictionary allFileDic = null; // key:luaPath,value:luaContent private Dictionary luaFileContentDic = null; private List assetDependsList = null; // 未被引用资源 private Dictionary> noReferenceAssetDic = null; private Vector2 ve2; private bool initComplete = false; // 筛选文件夹 private List siftPathList = new List(); // 筛选文件夹中未使用的文件 private Dictionary> noReferenceSiftAssetDic = null; private bool isAll = true; private void Awake() { siftPathList.Add("Assets/GameAssets"); } // 绘制编辑器窗口内容 private void OnGUI() { EditorGUILayout.Space(); if (initComplete == false) { return; } if (noReferenceAssetDic == null) { InitAllFileDic(); return; } DrawSift(); ve2 = EditorGUILayout.BeginScrollView(ve2); var tmpNoRefernceDic = noReferenceAssetDic; if (!isAll) { tmpNoRefernceDic = noReferenceSiftAssetDic; } foreach (var assetType in tmpNoRefernceDic.Keys) { List infos = tmpNoRefernceDic[assetType]; if (infos.Count == 0) continue; EditorGUILayout.LabelField(assetType.ToString(), EditorStyles.boldLabel); for (int i = 0; i < infos.Count; i++) { CreateNpReferenceItem(infos[i]); } if (GUILayout.Button("一键删除", GUILayout.Height(30))) { DeleteAllAsset(assetType); } EditorGUILayout.Space(); } EditorGUILayout.EndScrollView(); EditorGUILayout.Space(); } [MenuItem("项目工具/资源检查/检查未被引用的资源", false, 10)] private static void OpenNoReferenceWindow() { NoReferenceWindow window = (NoReferenceWindow)EditorWindow.GetWindow(typeof(NoReferenceWindow), true, "未被引用资源列表"); window.InitAllFileDic(); } public void RefreshInfo() { InitAssetDependDic(); InitLuaFileContentDic(); InitNoReferenceAssetDic(); initComplete = true; } private bool IsInFilterDirectory(string assetPath) { for (int i = 0; i < FilterDirectory.Length; i++) { if (assetPath.Contains(FilterDirectory[i])) { return true; } } return false; } /// /// 找出目标路径下的所有资源 /// private void InitAllFileDic() { Debug.Log("开始扫描"); long t = System.DateTime.Now.Ticks; allFileDic = new Dictionary(); string[] guids = AssetDatabase.FindAssets("", ASSET_FILE_ROOT); for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (EditorUtility.DisplayCancelableProgressBar($"资源加载中:({i}/{guids.Length})", path, (float)i / (float)guids.Length)) { EditorUtility.ClearProgressBar(); Close(); return; } if (IsInFilterDirectory(path)) continue; AssetType assetType = GetAssetTypeByPath(path); if (assetType == AssetType.None) continue; FileInfo info = new FileInfo(); info.path = path; info.assetType = assetType; info.asset = AssetDatabase.LoadAssetAtPath(path); allFileDic.Add(path, info); } EditorUtility.ClearProgressBar(); Debug.Log("资源扫描完成"); RefreshInfo(); Debug.Log($"耗时:{(System.DateTime.Now.Ticks - t) / 10000} 毫秒"); } /// /// 找出资源的依赖,并添加到依赖列表中 /// private void InitAssetDependDic() { assetDependsList = new List(); List infos = new List(allFileDic.Values); if (infos.Count == 0) return; for (int i = 0; i < infos.Count; i++) { FileInfo info = infos[i]; if (info.assetType == AssetType.Lua || info.assetType == AssetType.Image) continue; if (EditorUtility.DisplayCancelableProgressBar("扫描资源依赖关系...", $"({i}/{infos.Count})\n path:{info.path}", (float)i / (float)infos.Count)) { EditorUtility.ClearProgressBar(); Close(); return; } string[] depends = AssetDatabase.GetDependencies(info.path, false); for (int j = 0; j < depends.Length; j++) { string dependPath = depends[j]; FileInfo dependInfo = FindFileInfo(dependPath); // 依赖项为空或者是自己不需要添加 if (dependInfo == null || dependInfo == info) continue; if (!assetDependsList.Contains(dependInfo)) { assetDependsList.Add(dependInfo); } } } Debug.Log("扫描资源依赖关系完成"); EditorUtility.ClearProgressBar(); } /// /// 读取lua文件内容,并保存到列表中(由于项目是用lua热更新的,配置表最终也是生成lua文件,所以lua文件中会配置了引用资源的名称) /// private void InitLuaFileContentDic() { luaFileContentDic = new Dictionary(); foreach (var info in allFileDic.Values) { if (info.assetType == AssetType.Lua) { luaFileContentDic.Add(info.path, File.ReadAllText(info.path)); } } } // 初始化未被引用资源 private void InitNoReferenceAssetDic() { noReferenceAssetDic = new Dictionary>(); foreach (var info in allFileDic.Values) { if (info.assetType == AssetType.Lua) continue; // 判断资源是否被引用(依赖列表中的引用,lua脚本内容列表中的引用) // 被其他资源引用 if (CheckOtherAssetReference(info.path)) continue; // 被lua脚本引用 if (CheckLuaReference(info.path)) continue; AddNoReferenceAsset(info); } Debug.Log("未被引用资源添加完成"); } private FileInfo FindFileInfo(string assetPath) { if (!allFileDic.ContainsKey(assetPath)) return null; return allFileDic[assetPath]; } private void AddNoReferenceAsset(FileInfo info) { List list = null; if (!noReferenceAssetDic.TryGetValue(info.assetType, out list)) { list = new List(); noReferenceAssetDic.Add(info.assetType, list); } list.Add(info); } private void RemoveNoReferenceAsset(Dictionary> noReferenceAsset, string path) { FileInfo info = FindFileInfo(path); if (info == null) return; List list = null; if (noReferenceAsset.TryGetValue(info.assetType, out list)) { list.Remove(info); } } private AssetType GetAssetTypeByPath(string assetPath) { string extension = Path.GetExtension(assetPath); switch (extension.ToLower()) { case ".prefab": return AssetType.Prefab; case ".mat": return AssetType.Material; case ".unity": return AssetType.Scene; case ".asset": return AssetType.Asset; case ".lua": return AssetType.Lua; case ".png": return AssetType.Image; case ".jpg": return AssetType.Image; case ".fbx": return AssetType.FBX; case ".controller": return AssetType.Controller; case ".anim": return AssetType.Animation; } return AssetType.None; } /// /// 检查被其他资源引用 /// /// /// private bool CheckOtherAssetReference(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { Debug.LogError("CheckOtherAssetReference error: assetPath is null"); return false; } FileInfo info = FindFileInfo(assetPath); if (info == null) return false; if (info.assetType == AssetType.Scene) { return EditorSceneManager.GetActiveScene().name.Equals(Path.GetFileNameWithoutExtension(assetPath)); } foreach (var dependInfo in assetDependsList) { if (dependInfo == info) { return true; } } return false; } // 检查被lua脚本引用 private bool CheckLuaReference(string assetPath) { string fileName = Path.GetFileNameWithoutExtension(assetPath); if (string.IsNullOrEmpty(fileName)) { return false; } foreach (var content in luaFileContentDic.Values) { if (Regex.IsMatch(content, fileName + "\"") || Regex.IsMatch(content, fileName + "\'")) { return true; } } return false; } private void DeleteAsset(FileInfo info, bool refresh = false) { RemoveDepends(info); RemoveNoReferenceAsset(noReferenceAssetDic, info.path); if (!isAll && noReferenceSiftAssetDic != null) RemoveNoReferenceAsset(noReferenceSiftAssetDic, info.path); allFileDic.Remove(info.path); AssetDatabase.DeleteAsset(info.path); if (refresh) AssetDatabase.Refresh(); } // 删除资源 private void DeleteAllAsset(AssetType assetType) { List infos = noReferenceAssetDic[assetType]; for (int i = infos.Count - 1; i >= 0; i--) { DeleteAsset(infos[i]); } AssetDatabase.Refresh(); } // 移除依赖数据 private void RemoveDepends(FileInfo info) { if (assetDependsList.Contains(info)) { assetDependsList.Remove(info); } } // 初始化筛选文件中未被引用资源 private void RefreshNoReferenceSiltAssetDic() { noReferenceSiftAssetDic = new Dictionary>(); foreach (var info in allFileDic.Values) { if (info.assetType == AssetType.Lua) continue; if (!CheckInSiltDirectory(info.path)) continue; // 判断资源是否被引用(依赖列表中的引用,lua脚本内容列表中的引用) // 被其他资源引用 if (CheckOtherAssetReference(info.path)) continue; // 被lua脚本引用 if (CheckLuaReference(info.path)) continue; AddNoReferenceSiltAsset(info); } } private bool CheckInSiltDirectory(string assetPath) { for (int i = 0; i < siftPathList.Count; i++) { string path = siftPathList[i]; if (string.IsNullOrEmpty(path)) continue; if (!Directory.Exists(path)) continue; if (Regex.IsMatch(assetPath, path)) { return true; } } return false; } private void AddNoReferenceSiltAsset(FileInfo info) { List list = null; if (!noReferenceSiftAssetDic.TryGetValue(info.assetType, out list)) { list = new List(); noReferenceSiftAssetDic.Add(info.assetType, list); } list.Add(info); } #region // ---------------------- 绘制相关 ------------------------- // private void CreateNpReferenceItem(FileInfo info) { GUILayout.BeginHorizontal(); // var obj = AssetDatabase.LoadAssetAtPath(info.path); EditorGUILayout.ObjectField(info.asset, typeof(UnityEngine.Object)); // string assetName = Path.GetFileNameWithoutExtension(info.path); // EditorGUILayout.DelayedTextField(assetName); // if (GUILayout.Button("定位",GUILayout.Width(70))) // { // var obj = AssetDatabase.LoadAssetAtPath(info.path); // if(obj != null) // EditorGUIUtility.PingObject(obj); // } if (GUILayout.Button("删除资源", GUILayout.Width(70))) { DeleteAsset(info, true); } GUILayout.EndHorizontal(); } private void DrawSift() { EditorGUILayout.LabelField("*注意:有些资源仅靠代码加载,注意区分。", EditorStyles.boldLabel, GUILayout.Height(30)); EditorGUILayout.LabelField("筛选列表", EditorStyles.boldLabel, GUILayout.Height(30)); for (int i = 0; i < siftPathList.Count; i++) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Path:", GUILayout.Width(50)); siftPathList[i] = EditorGUILayout.TextField(siftPathList[i]); if (GUILayout.Button("x", GUILayout.Width(20))) { siftPathList.RemoveAt(i); } EditorGUILayout.EndHorizontal(); } if (GUILayout.Button("+", GUILayout.Height(20))) { siftPathList.Add(string.Empty); } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("筛选资源", GUILayout.Height(40))) { string noExistDirectoryStr = ""; for (int i = 0; i < siftPathList.Count; i++) { string path = siftPathList[i]; if (string.IsNullOrEmpty(path)) continue; if (!Directory.Exists(path)) { noExistDirectoryStr = noExistDirectoryStr + path + "\r\n"; continue; } } if (noExistDirectoryStr != "") { EditorUtility.DisplayDialog("提示", "文件夹不存在:\r\n" + noExistDirectoryStr, "ok"); } RefreshNoReferenceSiltAssetDic(); isAll = false; } if (GUILayout.Button("全部资源", GUILayout.Height(40))) { isAll = true; } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.Space(); } #endregion // ---------------------- 绘制相关 ------------------------- // #endif }