在使用YooAsset打包时,尤其是跨项目组开发时,资源导入并不是很规范;如果预制体、场景等引用的资源没有在YooAsset中单独配置组为StaticAssetCollectorDependAssetCollector ,将会导致引用的资产造成冗余,间接导致YooAsset在Build包时内存爆炸,以及打包后场景加载缓慢;如果没成功出包(打包崩溃),更没法分析打包信息,检查是否有冗余情况。

借助Gemini,编写了下面这些脚本,协助分析项目资产引用情况,以便执行优化。

大资源文件追踪

PrefabDependencyAnalyzer.cs

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class PrefabDependencyAnalyzer : EditorWindow
{
    // 用于反向查找的数据结构:资源 -> 被哪些根对象(预制体/资源)引用
    private class ReverseAssetInfo
    {
        public string Path;
        public Object AssetObj;
        public long SizeBytes;
        public float SizeMB => SizeBytes / (1024f * 1024f);
        public List<Object> ReferencedByObjects = new List<Object>();
        public bool IsExpanded = false;
    }

    // 将 DefaultAsset 改为 Object,以支持单个文件或文件夹
    private Object targetObject; 
    private List<ReverseAssetInfo> assetDataList = new List<ReverseAssetInfo>();
    private Vector2 scrollPosition;
    private float minSizeFilterMB = 0f; // 可以过滤掉太小的文件

    [MenuItem("Tools/资源分析器/大资源追踪器 (支持单文件与文件夹)")]
    public static void ShowWindow()
    {
        var window = GetWindow<PrefabDependencyAnalyzer>("大资源追踪器");
        window.minSize = new Vector2(600, 500);
        window.Show();
    }

    private void OnGUI()
    {
        DrawHeader();
        DrawSeparator();
        DrawAssetList();
    }

    private void DrawHeader()
    {
        GUILayout.Space(10);
        GUILayout.Label("1. 选择要扫描的文件夹 或 单个资源:", EditorStyles.boldLabel);
        
        GUILayout.BeginHorizontal();
        // 允许拖入任何类型的 Object
        targetObject = EditorGUILayout.ObjectField(targetObject, typeof(Object), false, GUILayout.Width(300));
        GUILayout.EndHorizontal();

        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("2. 过滤小于该大小的资源 (MB):", GUILayout.Width(180));
        minSizeFilterMB = EditorGUILayout.FloatField(minSizeFilterMB, GUILayout.Width(50));
        
        GUILayout.FlexibleSpace();
        GUI.enabled = targetObject != null;
        if (GUILayout.Button("开始扫描大资源", GUILayout.Width(120), GUILayout.Height(25)))
        {
            AnalyzeReverseDependencies();
        }
        GUI.enabled = true;
        GUILayout.EndHorizontal();

        if (assetDataList.Count > 0)
        {
            GUILayout.Space(5);
            GUILayout.Label($"扫描结果: 找到 {assetDataList.Count} 个符合条件的外部依赖资源。", EditorStyles.helpBox);
        }
    }

    private void DrawSeparator()
    {
        GUILayout.Space(5);
        Rect rect = EditorGUILayout.GetControlRect(false, 2);
        rect.height = 1;
        EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
        GUILayout.Space(5);
    }

    private void DrawAssetList()
    {
        if (assetDataList.Count == 0)
        {
            GUILayout.Label("请选择目标并点击“开始扫描”。", EditorStyles.centeredGreyMiniLabel);
            return;
        }

        scrollPosition = GUILayout.BeginScrollView(scrollPosition);

        foreach (var assetInfo in assetDataList)
        {
            GUILayout.BeginVertical("box");
            
            // 资源标题行
            GUILayout.BeginHorizontal();
            assetInfo.IsExpanded = EditorGUILayout.Foldout(assetInfo.IsExpanded, "", true);
            EditorGUILayout.ObjectField(assetInfo.AssetObj, typeof(Object), false, GUILayout.Width(250));
            
            // 大资源高亮
            GUIStyle sizeStyle = new GUIStyle(EditorStyles.label);
            if (assetInfo.SizeMB > 10f) sizeStyle.normal.textColor = new Color(1f, 0.4f, 0.4f); // 大于10MB标红
            else if (assetInfo.SizeMB > 2f) sizeStyle.normal.textColor = new Color(1f, 0.7f, 0.2f); // 大于2MB标黄
            
            GUILayout.Label($"[ {assetInfo.SizeMB:F2} MB ]", sizeStyle, GUILayout.Width(100));
            GUILayout.Label($"被 {assetInfo.ReferencedByObjects.Count} 个对象引用", EditorStyles.miniBoldLabel);
            GUILayout.EndHorizontal();

            // 展开引用了该资源的预制体/资源列表
            if (assetInfo.IsExpanded)
            {
                EditorGUI.indentLevel++;
                GUILayout.Label("引用该资源的根对象 (点击定位):", EditorStyles.miniLabel);
                foreach (var refObj in assetInfo.ReferencedByObjects)
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(30); // 缩进
                    EditorGUILayout.ObjectField(refObj, typeof(Object), false, GUILayout.Width(250));
                    GUILayout.Label(AssetDatabase.GetAssetPath(refObj), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                EditorGUI.indentLevel--;
                GUILayout.Space(5);
            }
            GUILayout.EndVertical();
        }

        GUILayout.EndScrollView();
    }
private void AnalyzeReverseDependencies()
    {
        assetDataList.Clear();
        string targetPath = AssetDatabase.GetAssetPath(targetObject);
        
        List<string> rootPathsToAnalyze = new List<string>();

        // 判断选中的是文件夹还是单个文件
        if (AssetDatabase.IsValidFolder(targetPath))
        {
            // 【修改点】:不再局限于 "t:Prefab",而是查找文件夹下的所有资源
            string[] allGuids = AssetDatabase.FindAssets("", new[] { targetPath });
            foreach (string guid in allGuids)
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                
                // 排除子文件夹本身,只把真实的文件加入分析列表
                if (!AssetDatabase.IsValidFolder(path) && !rootPathsToAnalyze.Contains(path))
                {
                    rootPathsToAnalyze.Add(path);
                }
            }
        }
        else
        {
            // 如果是单个文件,直接把它加入分析列表
            rootPathsToAnalyze.Add(targetPath);
        }

        Dictionary<string, ReverseAssetInfo> assetDict = new Dictionary<string, ReverseAssetInfo>();

        int count = 0;
        foreach (string rootPath in rootPathsToAnalyze)
        {
            count++;
            EditorUtility.DisplayProgressBar("扫描中", $"正在分析 {Path.GetFileName(rootPath)}...", (float)count / rootPathsToAnalyze.Count);

            Object rootObj = AssetDatabase.LoadAssetAtPath<Object>(rootPath);
            string[] dependencies = AssetDatabase.GetDependencies(rootPath, true);

            foreach (string depPath in dependencies)
            {
                // 排除自己、代码和非 Assets 目录的内置资源
                if (depPath == rootPath || depPath.EndsWith(".cs") || depPath.EndsWith(".dll") || !depPath.StartsWith("Assets/"))
                    continue;

                // 如果字典里还没有这个资源,就添加它并计算大小
                if (!assetDict.ContainsKey(depPath))
                {
                    string fullPath = Path.GetFullPath(depPath);
                    if (File.Exists(fullPath))
                    {
                        long size = new FileInfo(fullPath).Length;
                        float sizeMB = size / (1024f * 1024f);

                        // 过滤掉太小的文件
                        if (sizeMB >= minSizeFilterMB)
                        {
                            assetDict[depPath] = new ReverseAssetInfo
                            {
                                Path = depPath,
                                AssetObj = AssetDatabase.LoadAssetAtPath<Object>(depPath),
                                SizeBytes = size
                            };
                        }
                    }
                }

                // 记录引用关系
                if (assetDict.ContainsKey(depPath) && !assetDict[depPath].ReferencedByObjects.Contains(rootObj))
                {
                    assetDict[depPath].ReferencedByObjects.Add(rootObj);
                }
            }
        }

        // 转换为List并按资源单体大小降序排列
        assetDataList = assetDict.Values.ToList();
        assetDataList.Sort((a, b) => b.SizeBytes.CompareTo(a.SizeBytes));
        
        EditorUtility.ClearProgressBar();
    }
}

全局反向引用查找器 (全盘排查)

GlobalReferenceFinder.cs

using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
using System.Linq;

public class GlobalReferenceFinder : EditorWindow
{
    private Object targetAsset;
    
    private List<GameObject> referencingPrefabs = new List<GameObject>();
    private List<SceneAsset> referencingSceneFiles = new List<SceneAsset>(); // 新增:引用的场景文件
    private List<GameObject> referencingSceneObjects = new List<GameObject>();
    
    private Vector2 prefabScroll;
    private Vector2 sceneFileScroll; // 新增:场景文件滚动条
    private Vector2 sceneScroll;
    
    private bool showPrefabs = true;
    private bool showSceneFiles = true; // 新增:场景文件折叠状态
    private bool showSceneObjects = true;

    [MenuItem("Tools/全局反向引用查找器 (全盘排查)")]
    public static void ShowWindow()
    {
        var window = GetWindow<GlobalReferenceFinder>("反向引用查找");
        window.minSize = new Vector2(500, 600);
        window.Show();
    }

    private void OnGUI()
    {
        DrawHeader();
        DrawSeparator();
        
        if (referencingPrefabs.Count > 0 || referencingSceneFiles.Count > 0 || referencingSceneObjects.Count > 0)
        {
            DrawResults();
        }
        else if (targetAsset != null)
        {
            GUILayout.Space(10);
            GUILayout.Label("没有找到任何预制体、场景文件或当前场景物体引用此资源。", EditorStyles.centeredGreyMiniLabel);
        }
    }

    private void DrawHeader()
    {
        GUILayout.Space(10);
        GUILayout.Label("请拖入你要排查的目标资源 (如贴图、材质、FBX等):", EditorStyles.boldLabel);
        
        GUILayout.BeginHorizontal();
        targetAsset = EditorGUILayout.ObjectField(targetAsset, typeof(Object), false, GUILayout.Height(20));
        
        GUI.enabled = targetAsset != null;
        if (GUILayout.Button("开始全盘查找", GUILayout.Width(120), GUILayout.Height(20)))
        {
            FindReferences();
        }
        GUI.enabled = true;
        GUILayout.EndHorizontal();
    }

    private void DrawSeparator()
    {
        GUILayout.Space(10);
        Rect rect = EditorGUILayout.GetControlRect(false, 2);
        rect.height = 1;
        EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
        GUILayout.Space(5);
    }

    private void DrawResults()
    {
        // 1. 预制体引用列表
        if (referencingPrefabs.Count > 0)
        {
            GUILayout.BeginHorizontal();
            showPrefabs = EditorGUILayout.Foldout(showPrefabs, $"项目预制体引用 ({referencingPrefabs.Count})", true, EditorStyles.foldoutHeader);
            GUILayout.EndHorizontal();

            if (showPrefabs)
            {
                prefabScroll = GUILayout.BeginScrollView(prefabScroll, GUILayout.MaxHeight(200));
                foreach (var prefab in referencingPrefabs)
                {
                    GUILayout.BeginHorizontal("box");
                    EditorGUILayout.ObjectField(prefab, typeof(GameObject), false, GUILayout.Width(250));
                    GUILayout.Label(AssetDatabase.GetAssetPath(prefab), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndScrollView();
            }
            GUILayout.Space(10);
        }

        // 2. 场景文件引用列表 (新增)
        if (referencingSceneFiles.Count > 0)
        {
            GUILayout.BeginHorizontal();
            showSceneFiles = EditorGUILayout.Foldout(showSceneFiles, $"项目场景文件引用 ({referencingSceneFiles.Count})", true, EditorStyles.foldoutHeader);
            GUILayout.EndHorizontal();

            if (showSceneFiles)
            {
                sceneFileScroll = GUILayout.BeginScrollView(sceneFileScroll, GUILayout.MaxHeight(150));
                foreach (var sceneFile in referencingSceneFiles)
                {
                    GUILayout.BeginHorizontal("box");
                    EditorGUILayout.ObjectField(sceneFile, typeof(SceneAsset), false, GUILayout.Width(250));
                    GUILayout.Label(AssetDatabase.GetAssetPath(sceneFile), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndScrollView();
            }
            GUILayout.Space(10);
        }

        // 3. 当前场景物体引用列表
        if (referencingSceneObjects.Count > 0)
        {
            Scene activeScene = SceneManager.GetActiveScene();
            GUILayout.BeginHorizontal();
            showSceneObjects = EditorGUILayout.Foldout(showSceneObjects, $"当前已打开场景 [{activeScene.name}] 内部引用 ({referencingSceneObjects.Count})", true, EditorStyles.foldoutHeader);
            GUILayout.EndHorizontal();

            if (showSceneObjects)
            {
                sceneScroll = GUILayout.BeginScrollView(sceneScroll);
                foreach (var go in referencingSceneObjects)
                {
                    GUILayout.BeginHorizontal("box");
                    EditorGUILayout.ObjectField(go, typeof(GameObject), true, GUILayout.Width(250));
                    GUILayout.Label(GetGameObjectPath(go), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndScrollView();
            }
        }
    }

    private void FindReferences()
    {
        referencingPrefabs.Clear();
        referencingSceneFiles.Clear();
        referencingSceneObjects.Clear();
        
        string targetPath = AssetDatabase.GetAssetPath(targetAsset);
        if (string.IsNullOrEmpty(targetPath))
        {
            Debug.LogWarning("选中的目标不是有效的工程资产。");
            return;
        }

        FindInPrefabs(targetPath);
        FindInAllSceneFiles(targetPath); // 新增:扫描所有场景文件
        FindInActiveScene(targetAsset);
        
        EditorUtility.ClearProgressBar();
    }

    private void FindInPrefabs(string targetPath)
    {
        string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab");
        int count = 0;
        
        foreach (string guid in prefabGuids)
        {
            count++;
            if (count % 50 == 0)
                EditorUtility.DisplayProgressBar("扫描工程预制体", $"正在扫描... {count}/{prefabGuids.Length}", (float)count / prefabGuids.Length);

            string prefabPath = AssetDatabase.GUIDToAssetPath(guid);
            string[] dependencies = AssetDatabase.GetDependencies(prefabPath, true);
            
            if (dependencies.Contains(targetPath))
            {
                GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
                if (prefab != null) referencingPrefabs.Add(prefab);
            }
        }
    }

    // 新增:扫描全工程所有的场景文件
    private void FindInAllSceneFiles(string targetPath)
    {
        string[] sceneGuids = AssetDatabase.FindAssets("t:Scene");
        int count = 0;

        foreach (string guid in sceneGuids)
        {
            count++;
            if (count % 10 == 0)
                EditorUtility.DisplayProgressBar("扫描项目场景文件", $"正在扫描... {count}/{sceneGuids.Length}", (float)count / sceneGuids.Length);

            string scenePath = AssetDatabase.GUIDToAssetPath(guid);
            
            // 扫描场景文件的依赖关系
            string[] dependencies = AssetDatabase.GetDependencies(scenePath, true);
            
            if (dependencies.Contains(targetPath))
            {
                SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
                if (sceneAsset != null) referencingSceneFiles.Add(sceneAsset);
            }
        }
    }

    private void FindInActiveScene(Object targetObj)
    {
        Scene activeScene = SceneManager.GetActiveScene();
        GameObject[] allGameObjects = Resources.FindObjectsOfTypeAll<GameObject>()
            .Where(go => go.scene == activeScene).ToArray();

        int count = 0;
        foreach (GameObject go in allGameObjects)
        {
            count++;
            if (count % 20 == 0)
                EditorUtility.DisplayProgressBar("扫描当前场景内部", $"正在分析... {count}/{allGameObjects.Length}", (float)count / allGameObjects.Length);

            Object[] dependencies = EditorUtility.CollectDependencies(new Object[] { go });
            
            if (dependencies.Contains(targetObj))
            {
                referencingSceneObjects.Add(go);
            }
        }
    }

    private string GetGameObjectPath(GameObject obj)
    {
        string path = obj.name;
        Transform current = obj.transform;
        while (current.parent != null)
        {
            current = current.parent;
            path = current.name + "/" + path;
        }
        return path;
    }
}

场景大资源追踪器

ActiveSceneDependencyAnalyzer.cs

using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class ActiveSceneDependencyAnalyzer : EditorWindow
{
    // 数据结构:资源 -> 被场景中的哪些 GameObject 引用
    private class ReverseSceneAssetInfo
    {
        public string Path;
        public Object AssetObj;
        public long SizeBytes;
        public float SizeMB => SizeBytes / (1024f * 1024f);
        public List<GameObject> ReferencedByGameObjects = new List<GameObject>();
        public bool IsExpanded = false;
    }

    private List<ReverseSceneAssetInfo> assetDataList = new List<ReverseSceneAssetInfo>();
    private Vector2 scrollPosition;
    private float minSizeFilterMB = 1f; // 默认过滤掉1MB以下的资源

    [MenuItem("Tools/当前场景大资源追踪器")]
    public static void ShowWindow()
    {
        var window = GetWindow<ActiveSceneDependencyAnalyzer>("场景大资源追踪");
        window.minSize = new Vector2(600, 500);
        window.Show();
    }

    private void OnGUI()
    {
        DrawHeader();
        DrawSeparator();
        DrawAssetList();
    }

    private void DrawHeader()
    {
        GUILayout.Space(10);
        Scene activeScene = SceneManager.GetActiveScene();
        GUILayout.Label($"当前分析场景: {activeScene.name} {(activeScene.isDirty ? "(未保存)" : "")}", EditorStyles.boldLabel);
        
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("过滤小于该大小的资源 (MB):", GUILayout.Width(180));
        minSizeFilterMB = EditorGUILayout.FloatField(minSizeFilterMB, GUILayout.Width(50));
        
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("扫描当前场景", GUILayout.Width(120), GUILayout.Height(25)))
        {
            AnalyzeActiveScene();
        }
        GUILayout.EndHorizontal();

        if (assetDataList.Count > 0)
        {
            GUILayout.Space(5);
            GUILayout.Label($"扫描结果: 场景中找到了 {assetDataList.Count} 个符合条件的外部依赖资源。", EditorStyles.helpBox);
        }
    }

    private void DrawSeparator()
    {
        GUILayout.Space(5);
        Rect rect = EditorGUILayout.GetControlRect(false, 2);
        rect.height = 1;
        EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
        GUILayout.Space(5);
    }

    private void DrawAssetList()
    {
        if (assetDataList.Count == 0)
        {
            GUILayout.Label("点击“扫描当前场景”开始分析。", EditorStyles.centeredGreyMiniLabel);
            return;
        }

        scrollPosition = GUILayout.BeginScrollView(scrollPosition);

        foreach (var assetInfo in assetDataList)
        {
            GUILayout.BeginVertical("box");
            
            // 资源标题行
            GUILayout.BeginHorizontal();
            assetInfo.IsExpanded = EditorGUILayout.Foldout(assetInfo.IsExpanded, "", true);
            EditorGUILayout.ObjectField(assetInfo.AssetObj, typeof(Object), false, GUILayout.Width(250));
            
            // 大资源高亮
            GUIStyle sizeStyle = new GUIStyle(EditorStyles.label);
            if (assetInfo.SizeMB > 10f) sizeStyle.normal.textColor = new Color(1f, 0.4f, 0.4f); // 大于10MB标红
            else if (assetInfo.SizeMB > 2f) sizeStyle.normal.textColor = new Color(1f, 0.7f, 0.2f); // 大于2MB标黄
            
            GUILayout.Label($"[ {assetInfo.SizeMB:F2} MB ]", sizeStyle, GUILayout.Width(100));
            GUILayout.Label($"被场景中 {assetInfo.ReferencedByGameObjects.Count} 个物体引用", EditorStyles.miniBoldLabel);
            GUILayout.EndHorizontal();

            // 展开引用了该资源的场景物体列表
            if (assetInfo.IsExpanded)
            {
                EditorGUI.indentLevel++;
                GUILayout.Label("场景中直接引用该资源的 GameObject (点击可在 Hierarchy 中高亮):", EditorStyles.miniLabel);
                foreach (var go in assetInfo.ReferencedByGameObjects)
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(30); // 缩进
                    EditorGUILayout.ObjectField(go, typeof(GameObject), true, GUILayout.Width(250));
                    
                    // 显示该物体在场景中的层级路径,方便查找
                    GUILayout.Label(GetGameObjectPath(go), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                EditorGUI.indentLevel--;
                GUILayout.Space(5);
            }
            GUILayout.EndVertical();
        }

        GUILayout.EndScrollView();
    }

    private void AnalyzeActiveScene()
    {
        assetDataList.Clear();
        Scene activeScene = SceneManager.GetActiveScene();

        // 获取当前场景中的所有 GameObject(包括隐藏的物体)
        GameObject[] allGameObjects = Resources.FindObjectsOfTypeAll<GameObject>()
            .Where(go => go.scene == activeScene).ToArray();

        Dictionary<string, ReverseSceneAssetInfo> assetDict = new Dictionary<string, ReverseSceneAssetInfo>();

        int count = 0;
        foreach (GameObject go in allGameObjects)
        {
            count++;
            // 每分析20个物体刷新一次进度条,避免频繁刷新导致界面卡顿
            if (count % 20 == 0)
            {
                EditorUtility.DisplayProgressBar("扫描场景中", $"正在分析物体: {go.name}...", (float)count / allGameObjects.Length);
            }

            // 获取该 GameObject 及其组件上引用的所有资产
            Object[] dependencies = EditorUtility.CollectDependencies(new Object[] { go });

            foreach (Object dep in dependencies)
            {
                if (dep == null) continue;

                string depPath = AssetDatabase.GetAssetPath(dep);

                // 排除:空路径(可能是内存生成的网格/材质)、脚本、以及非 Assets 目录下的 Unity 内置资源
                if (string.IsNullOrEmpty(depPath) || 
                    depPath.EndsWith(".cs") || 
                    depPath.EndsWith(".dll") || 
                    !depPath.StartsWith("Assets/"))
                    continue;

                // 如果字典里还没有这个资源,添加并计算磁盘大小
                if (!assetDict.ContainsKey(depPath))
                {
                    string fullPath = Path.GetFullPath(depPath);
                    if (File.Exists(fullPath))
                    {
                        long size = new FileInfo(fullPath).Length;
                        float sizeMB = size / (1024f * 1024f);

                        // 过滤掉太小的文件
                        if (sizeMB >= minSizeFilterMB)
                        {
                            assetDict[depPath] = new ReverseSceneAssetInfo
                            {
                                Path = depPath,
                                AssetObj = AssetDatabase.LoadAssetAtPath<Object>(depPath),
                                SizeBytes = size
                            };
                        }
                    }
                }

                // 将当前的场景 GameObject 绑定到该资源上
                if (assetDict.ContainsKey(depPath) && !assetDict[depPath].ReferencedByGameObjects.Contains(go))
                {
                    assetDict[depPath].ReferencedByGameObjects.Add(go);
                }
            }
        }

        // 转换为List并按资源单体大小降序排列
        assetDataList = assetDict.Values.ToList();
        assetDataList.Sort((a, b) => b.SizeBytes.CompareTo(a.SizeBytes));
        
        EditorUtility.ClearProgressBar();
    }

    // 辅助方法:获取 GameObject 在 Hierarchy 中的完整路径
    private string GetGameObjectPath(GameObject obj)
    {
        string path = obj.name;
        Transform current = obj.transform;
        while (current.parent != null)
        {
            current = current.parent;
            path = current.name + "/" + path;
        }
        return path;
    }
}

修复一些BUG

using UnityEditor;
using UnityEditor.SceneManagement; // 新增:用于监听编辑器场景切换
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class ActiveSceneDependencyAnalyzer : EditorWindow
{
    // 数据结构:资源 -> 被场景中的哪些 GameObject 引用
    private class ReverseSceneAssetInfo
    {
        public string Path;
        public Object AssetObj;
        public long SizeBytes;
        public float SizeMB => SizeBytes / (1024f * 1024f);
        public List<GameObject> ReferencedByGameObjects = new List<GameObject>();
        public bool IsExpanded = false;
    }

    private List<ReverseSceneAssetInfo> assetDataList = new List<ReverseSceneAssetInfo>();
    private Vector2 scrollPosition;
    private float minSizeFilterMB = 1f; // 默认过滤掉1MB以下的资源

    [MenuItem("Tools/当前场景大资源追踪器")]
    public static void ShowWindow()
    {
        var window = GetWindow<ActiveSceneDependencyAnalyzer>("场景大资源追踪");
        window.minSize = new Vector2(600, 500);
        window.Show();
    }

    // --- 新增:生命周期监听,防止场景切换报错 ---
    private void OnEnable()
    {
        EditorSceneManager.activeSceneChangedInEditMode += OnSceneChanged;
    }

    private void OnDisable()
    {
        EditorSceneManager.activeSceneChangedInEditMode -= OnSceneChanged;
    }

    private void OnSceneChanged(Scene current, Scene next)
    {
        // 场景切换时,清空上一个场景的脏数据并刷新UI
        assetDataList.Clear();
        Repaint();
    }
    // ------------------------------------------

    private void OnGUI()
    {
        DrawHeader();
        DrawSeparator();
        DrawAssetList();
    }

    private void DrawHeader()
    {
        GUILayout.Space(10);
        Scene activeScene = SceneManager.GetActiveScene();
        GUILayout.Label($"当前分析场景: {activeScene.name} {(activeScene.isDirty ? "(未保存)" : "")}", EditorStyles.boldLabel);
        
        GUILayout.Space(5);
        GUILayout.BeginHorizontal();
        GUILayout.Label("过滤小于该大小的资源 (MB):", GUILayout.Width(180));
        minSizeFilterMB = EditorGUILayout.FloatField(minSizeFilterMB, GUILayout.Width(50));
        
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("扫描当前场景", GUILayout.Width(120), GUILayout.Height(25)))
        {
            AnalyzeActiveScene();
        }
        GUILayout.EndHorizontal();

        if (assetDataList.Count > 0)
        {
            GUILayout.Space(5);
            GUILayout.Label($"扫描结果: 场景中找到了 {assetDataList.Count} 个符合条件的外部依赖资源。", EditorStyles.helpBox);
        }
    }

    private void DrawSeparator()
    {
        GUILayout.Space(5);
        Rect rect = EditorGUILayout.GetControlRect(false, 2);
        rect.height = 1;
        EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
        GUILayout.Space(5);
    }

    private void DrawAssetList()
    {
        if (assetDataList.Count == 0)
        {
            GUILayout.Label("点击“扫描当前场景”开始分析。", EditorStyles.centeredGreyMiniLabel);
            return;
        }

        scrollPosition = GUILayout.BeginScrollView(scrollPosition);

        foreach (var assetInfo in assetDataList)
        {
            GUILayout.BeginVertical("box");
            
            // 资源标题行
            GUILayout.BeginHorizontal();
            assetInfo.IsExpanded = EditorGUILayout.Foldout(assetInfo.IsExpanded, "", true);
            EditorGUILayout.ObjectField(assetInfo.AssetObj, typeof(Object), false, GUILayout.Width(250));
            
            // 大资源高亮
            GUIStyle sizeStyle = new GUIStyle(EditorStyles.label);
            if (assetInfo.SizeMB > 10f) sizeStyle.normal.textColor = new Color(1f, 0.4f, 0.4f); // 大于10MB标红
            else if (assetInfo.SizeMB > 2f) sizeStyle.normal.textColor = new Color(1f, 0.7f, 0.2f); // 大于2MB标黄
            
            GUILayout.Label($"[ {assetInfo.SizeMB:F2} MB ]", sizeStyle, GUILayout.Width(100));
            
            // 清理列表中可能因为用户手动删除而变成 null 的物体
            assetInfo.ReferencedByGameObjects.RemoveAll(go => go == null);
            
            GUILayout.Label($"被场景中 {assetInfo.ReferencedByGameObjects.Count} 个物体引用", EditorStyles.miniBoldLabel);
            GUILayout.EndHorizontal();

            // 展开引用了该资源的场景物体列表
            if (assetInfo.IsExpanded)
            {
                EditorGUI.indentLevel++;
                GUILayout.Label("场景中直接引用该资源的 GameObject (点击可在 Hierarchy 中高亮):", EditorStyles.miniLabel);
                
                foreach (var go in assetInfo.ReferencedByGameObjects)
                {
                    // 【关键修复】:防空判断。虽然上面 RemoveAll 过滤了一次,但在多线程或复杂操作下加一层判断最安全
                    if (go == null) continue; 

                    GUILayout.BeginHorizontal();
                    GUILayout.Space(30); // 缩进
                    EditorGUILayout.ObjectField(go, typeof(GameObject), true, GUILayout.Width(250));
                    
                    // 显示该物体在场景中的层级路径,方便查找
                    GUILayout.Label(GetGameObjectPath(go), EditorStyles.miniLabel);
                    GUILayout.EndHorizontal();
                }
                EditorGUI.indentLevel--;
                GUILayout.Space(5);
            }
            GUILayout.EndVertical();
        }

        GUILayout.EndScrollView();
    }

    private void AnalyzeActiveScene()
    {
        assetDataList.Clear();
        Scene activeScene = SceneManager.GetActiveScene();

        // 获取当前场景中的所有 GameObject(包括隐藏的物体)
        GameObject[] allGameObjects = Resources.FindObjectsOfTypeAll<GameObject>()
            .Where(go => go != null && go.scene == activeScene).ToArray(); // 增加防空

        Dictionary<string, ReverseSceneAssetInfo> assetDict = new Dictionary<string, ReverseSceneAssetInfo>();

        int count = 0;
        foreach (GameObject go in allGameObjects)
        {
            if (go == null) continue; // 双重防空

            count++;
            // 每分析20个物体刷新一次进度条,避免频繁刷新导致界面卡顿
            if (count % 20 == 0)
            {
                EditorUtility.DisplayProgressBar("扫描场景中", $"正在分析物体: {go.name}...", (float)count / allGameObjects.Length);
            }

            // 获取该 GameObject 及其组件上引用的所有资产
            Object[] dependencies = EditorUtility.CollectDependencies(new Object[] { go });

            foreach (Object dep in dependencies)
            {
                if (dep == null) continue;

                string depPath = AssetDatabase.GetAssetPath(dep);

                // 排除:空路径(可能是内存生成的网格/材质)、脚本、以及非 Assets 目录下的 Unity 内置资源
                if (string.IsNullOrEmpty(depPath) || 
                    depPath.EndsWith(".cs") || 
                    depPath.EndsWith(".dll") || 
                    !depPath.StartsWith("Assets/"))
                    continue;

                // 如果字典里还没有这个资源,添加并计算磁盘大小
                if (!assetDict.ContainsKey(depPath))
                {
                    string fullPath = Path.GetFullPath(depPath);
                    if (File.Exists(fullPath))
                    {
                        long size = new FileInfo(fullPath).Length;
                        float sizeMB = size / (1024f * 1024f);

                        // 过滤掉太小的文件
                        if (sizeMB >= minSizeFilterMB)
                        {
                            assetDict[depPath] = new ReverseSceneAssetInfo
                            {
                                Path = depPath,
                                AssetObj = AssetDatabase.LoadAssetAtPath<Object>(depPath),
                                SizeBytes = size
                            };
                        }
                    }
                }

                // 将当前的场景 GameObject 绑定到该资源上
                if (assetDict.ContainsKey(depPath) && !assetDict[depPath].ReferencedByGameObjects.Contains(go))
                {
                    assetDict[depPath].ReferencedByGameObjects.Add(go);
                }
            }
        }

        // 转换为List并按资源单体大小降序排列
        assetDataList = assetDict.Values.ToList();
        assetDataList.Sort((a, b) => b.SizeBytes.CompareTo(a.SizeBytes));
        
        EditorUtility.ClearProgressBar();
    }

    // 辅助方法:获取 GameObject 在 Hierarchy 中的完整路径
    private string GetGameObjectPath(GameObject obj)
    {
        if (obj == null) return "已销毁"; // 【关键修复】:防止取路径时物体已被删除
        
        string path = obj.name;
        Transform current = obj.transform;
        while (current.parent != null)
        {
            current = current.parent;
            path = current.name + "/" + path;
        }
        return path;
    }
}