Unity 编辑器-查找所有未被使用的Prefab

需求

接到一个需求,将Res里所有特效相关的prefab检查一下,没有使用的移除。

分析

先拆解一下需求,如下

需求
检测筛选
移除
和其他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);
        }
    }
}

结果
Unity 编辑器-查找所有未被使用的Prefab_第1张图片

结果如图所示。每组元素第一个为查找的prefab,之后的是使用了该prefab的预设或配表。
如果一组元素只有一个对象,则这个prefab无引用。
顶部的按钮为指定功能。
PS:使用Prefab名字做的检测,未对同名Prefab做筛选判断

你可能感兴趣的:(#,编辑器拓展,Unity,unity,编辑器,游戏引擎)