需求
接到一个需求,将Res里所有特效相关的prefab检查一下,没有使用的移除。
分析
先拆解一下需求,如下
代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEditor;
using Object = UnityEngine.Object;public class SearchUseing :EditorWindow
{private Vector2 _pos = Vector2.zero;static string[] fileExtensions = { ".cs", ".js", ".txt" };//你查询的文件格式 如 .json 等private static List<Object> _objs = new List<Object>();private static Dictionary<string, List<string>> _referenceCache = new Dictionary<string, List<string>>();private static Dictionary<string, Object> _allUnUsedDic = new Dictionary<string, Object>();private static Dictionary<string, List<string>> _resultDic = new Dictionary<string, List<string>>();private static void ShowWindow(){var window = (SearchUseing)EditorWindow.GetWindow(typeof(SearchUseing));window.titleContent = new GUIContent("Object List");window.Show();}private void OnGUI(){// 在 EditorWindow 中使用 GUILayout 绘制 UI 元素GUILayout.BeginHorizontal();GUILayout.Label("选中的Asset列表", EditorStyles.boldLabel);if (GUILayout.Button("导出空引用的文件路径")){string selectedFolderPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);string folderName = System.IO.Path.GetFileName(selectedFolderPath);Output(_resultDic,folderName,true);}if (GUILayout.Button("导出所有文件及依赖路径")){string selectedFolderPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);string folderName = System.IO.Path.GetFileName(selectedFolderPath);Output(_resultDic,folderName,false);}if (GUILayout.Button("移动空引用prefab")){MoveAll();}GUILayout.EndHorizontal();Rect rect = new Rect(10, 20, position.width - 20, position.height - 30);GUILayout.BeginArea(rect);if (_allUnUsedDic is { Count: > 0 }){_pos = GUILayout.BeginScrollView(_pos);foreach (var kv in _resultDic){EditorGUILayout.ObjectField(AssetDatabase.LoadMainAssetAtPath(kv.Key), typeof(Object), false);foreach (var s in kv.Value){EditorGUILayout.ObjectField(AssetDatabase.LoadMainAssetAtPath(s), typeof(Object), false);}GUILayout.Space(20);}GUILayout.EndScrollView();}GUILayout.EndArea();}[MenuItem("Assets/工具/查找空引用的Prefab")]public static void SearchAll(){_resultDic = new Dictionary<string, List<string>>();GetAllDependency();foreach (var folderPath in Selection.GetFiltered(typeof(DefaultAsset), SelectionMode.Assets)){var assetPath = AssetDatabase.GetAssetPath(folderPath);var assets = AssetDatabase.FindAssets("t:Prefab", new[] { assetPath });foreach (var asset in assets){var assetFilePath = AssetDatabase.GUIDToAssetPath(asset);_resultDic.Add(assetFilePath,new List<string>());ReferenceFilter(assetFilePath);_allUnUsedDic.TryAdd(assetFilePath,AssetDatabase.LoadMainAssetAtPath(assetFilePath));}}SearchInAssets(_allUnUsedDic);if (_resultDic is { Count: > 0 }){ShowWindow();}}//导出显示结果的txt文件private static void Output(Dictionary<string,List<string>> dic,string fileName,bool isNull){string filePath = $"temporaryCacheRes/Dic_{fileName}{(isNull ? "1" : "2")}.txt"; // 文件保存路径,可以自己指定using (StreamWriter streamWriter = new StreamWriter(filePath)){foreach (KeyValuePair<string, List<string>> kvp in dic){if (isNull&&kvp.Value.Count == 0 || !isNull){streamWriter.WriteLine(kvp.Key); // 写入Key值List<string> items = kvp.Value; foreach (string item in items){streamWriter.WriteLine("\t" + item); // 写入Value值,使用tab键缩进}streamWriter.WriteLine("\n" ); }}AssetDatabase.Refresh();}}private static void GetAllDependency(){_referenceCache = new Dictionary<string, List<string>>();_allUnUsedDic = new Dictionary<string, Object>();_objs = new List<Object>();var guids = AssetDatabase.FindAssets("");foreach (var guid in guids){var assetPath = AssetDatabase.GUIDToAssetPath(guid);var dependencies = AssetDatabase.GetDependencies(assetPath, false);foreach (var dependency in dependencies){if (_referenceCache.ContainsKey(dependency)){if (!_referenceCache[dependency].Contains(assetPath)){_referenceCache[dependency].Add(assetPath);}}else{_referenceCache[dependency] = new List<string>() { assetPath };}}}}private static void ReferenceFilter(string path){if (_referenceCache.ContainsKey(path)){foreach (var reference in _referenceCache[path]){Debug.Log(reference, AssetDatabase.LoadMainAssetAtPath(reference));var prefabObj = AssetDatabase.LoadAssetAtPath<Object>(reference);if (!_objs.Contains(prefabObj)){_objs.Add(prefabObj);}}_resultDic[path] = _referenceCache[path];}else{_allUnUsedDic.TryAdd(path,AssetDatabase.LoadMainAssetAtPath(path));Debug.LogWarning($"{path} 没有直接引用");}}private static void MoveAll(){foreach (var kv in _allUnUsedDic){Debug.Log(kv.Value.name);MovePrefabToFolder(kv.Key, kv.Value.name);_resultDic.Remove(kv.Key);}AssetDatabase.Refresh();}//移动到指定路径static void MovePrefabToFolder(string path,string prefabName){string targetFolderPath = "Assets/Art/Effect/Temp/";CreateFolderIfNotExists("Assets/Art/Effect","Temp");if (!AssetDatabase.IsValidFolder(targetFolderPath)){Debug.LogWarning("Invalid folder path: " + targetFolderPath);return;}// 组装新的Prefab路径并移动到指定文件夹string newPrefabPath = targetFolderPath + prefabName + ".prefab";AssetDatabase.MoveAsset(path, newPrefabPath);AssetDatabase.SaveAssets();Debug.Log("Prefab " + prefabName+ " moved to " + newPrefabPath);}// public static void SerachInAssets()// {// var path = AssetDatabase.GetAssetPath(Selection.activeObject);// var obj = AssetDatabase.LoadMainAssetAtPath(path);// Debug.Log(obj.name);// // SearchInAssets(obj.name,path);// // SearchInTxtFiles(obj.name);// }//在Assets文件夹中进行搜索static void SearchInAssets(Dictionary<string,Object> dic){string[] paths = AssetDatabase.GetAllAssetPaths();foreach (string path in paths){// if (!path.StartsWith("Assets/Game") && !path.StartsWith("Assets/Res/Table/effect"))// {// continue;// }if (!path.StartsWith("Assets/Res/Table/effect")) //写你的筛选条件,比如我这里项目中不会直接使用字符串加载,只会在指定路径下查配表文件{continue;}// if (path.StartsWith("Assets/StreamingAssets")||path.StartsWith("Assets/Temp"))// {// continue;// }if (AssetDatabase.IsValidFolder(path)){continue;}if (IgnoreFile(Path.GetFileName(path))){continue;}if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(GameObject)){//如果是prefab 则不检测(ReferenceFilter已经检测过prefab)continue;}string text = File.ReadAllText(path);List<string> tempRemoveList = new List<string>(); foreach (var kv in dic){if (_resultDic.ContainsKey(kv.Key)){var tempList = _resultDic[kv.Key] ??= new List<string>();var name = kv.Value.name;if (text.Contains(name) && path!=kv.Key ){Debug.Log("Found text in asset: " + path );tempList.Add(path);_resultDic[kv.Key] = tempList;tempRemoveList.Add(kv.Key);}}else{Debug.Log($"当前遍历的路径不在result内,查查为啥");}}foreach (var s in tempRemoveList){_allUnUsedDic.Remove(s);}}Debug.Log("searchend");}//在txt文件中进行搜索static void SearchInTxtFiles(string searchText){DirectoryInfo directory = new DirectoryInfo(Application.dataPath);FileInfo[] files = directory.GetFiles("*.*", SearchOption.AllDirectories).Where(f => fileExtensions.Contains(f.Extension.ToLower())).ToArray();foreach (FileInfo file in files){string text = File.ReadAllText(file.FullName);if (text.Contains(searchText)){Debug.Log("Found text in txt file: " + file.FullName);}}Debug.Log("searchend");}static bool IgnoreFile(string fileName){return fileName == "TextSearchEditor.cs";}private void OnDestroy(){_referenceCache.Clear(); }static void CreateFolderIfNotExists(string path,string name){// 检查文件夹是否存在if (!AssetDatabase.IsValidFolder(path+"/"+name)){// 创建文件夹AssetDatabase.CreateFolder(path, name);AssetDatabase.Refresh();Debug.Log("Folder Create at " + path+"/"+name);}else{Debug.Log("Folder already exists at " + path);}}
}
结果
结果如图所示。每组元素第一个为查找的prefab,之后的是使用了该prefab的预设或配表。
如果一组元素只有一个对象,则这个prefab无引用。
顶部的按钮为指定功能。
PS:使用Prefab名字做的检测,未对同名Prefab做筛选判断