在使用YooAsset打包时,尤其是跨项目组开发时,资源导入并不是很规范;如果预制体、场景等引用的资源没有在YooAsset中单独配置组为StaticAssetCollector、DependAssetCollector ,将会导致引用的资产造成冗余,间接导致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;
}
}
评论