需求
接到一个需求,将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做筛选判断