1. 项目概述
如果你在Unity项目开发中,经历过项目体积随着版本迭代像吹气球一样膨胀,或者打包时发现构建包里有大量你确信已经不再使用的材质、贴图、预制体,那么你肯定对“项目清理”这件事又爱又恨。手动排查?耗时耗力,还容易误删。Unity自带的工具?功能有限,难以应对复杂的引用关系,尤其是像Addressables这样的现代资源管理系统。这正是Unity-Dependencies-Hunter(以下简称Dependencies Hunter)诞生的背景。它是一款专门用于在Unity项目中查找和清理未引用资产的工具,核心目标就是帮你精准定位那些“僵尸资产”,从而优化项目结构、减小构建体积、提升团队协作效率。
简单来说,Dependencies Hunter就像一个专业的“项目资产审计员”。它不生产内容,只做资产的搬运工和清算师。通过深度扫描项目内所有资产的依赖关系图,它能告诉你哪些文件是真正被场景、脚本、或其他资产引用的,哪些是孤零零躺在文件夹里吃灰的。对于任何规模超过原型的Unity项目,无论是手游、PC游戏还是VR应用,定期使用这类工具进行资产清理,都是一项至关重要的性能优化和工程管理实践。接下来,我将结合自己多次使用Dependencies Hunter的经验,深入解析其工作原理、详细操作步骤,并重点分享那些官方文档里不会写的“坑”和解决方案。
2. 核心原理与工作流程拆解
理解Dependencies Hunter如何工作,是避免误操作和正确解读结果的关键。它的核心逻辑并不复杂,但细节决定成败。
2.1 依赖关系图的构建
工具启动后,首先会调用AssetDatabase.GetAllAssetPaths()获取项目Assets目录下所有资产的完整路径列表。这构成了扫描的“全集”。接着,对于这个列表中的每一个资产,工具会调用AssetDatabase.GetDependencies(string assetPath, bool recursive)方法。这个方法返回的是指定资产所直接或间接依赖的所有其他资产的路径列表。
这里有一个关键点:AssetDatabase.GetDependencies反映的是Unity序列化系统能识别到的引用。这包括:
- 序列化字段引用:如
public GameObject prefab;在Inspector中拖拽的引用。 - 资源内嵌引用:如Material中引用的Texture,Prefab中引用的Mesh和Material。
- ScriptableObject中的数据引用。
通过遍历所有资产并收集它们的依赖,工具就在内存中构建了一张巨大的、有向的“资产依赖关系图”。在这个图中,每个资产是一个节点,如果资产A依赖于资产B,就有一条从A指向B的边。
2.2 “未引用”资产的判定
构建好依赖图后,判定“未引用”资产的逻辑就变得直观了:在图中,没有任何其他节点的边指向它的节点,就是未被引用的资产。更技术化的说法是:入度(In-Degree)为0的节点。
但是,这个“入度为0”需要经过几层过滤:
- 忽略特定路径:用户可以通过正则表达式(RegExp)设置忽略模式,比如忽略所有
Editor/、Plugins/、Resources/文件夹下的资产。这些文件夹通常包含运行时不会打包的编辑器脚本、第三方库或需要动态加载的资源。 - Addressables检测:如果开启了“Detect Addressables”选项,工具会额外检查资产是否在Addressables资源组中注册。已注册的Addressables资产会被视为“已引用”,即使它们在传统的依赖图中没有入边。因为Addressables系统会在运行时通过标签或地址来加载它们,这是一种动态引用,静态分析无法捕获。
- AssetReference扫描:这是一个增强选项。默认情况下,
AssetDatabase.GetDependencies无法识别序列化为AssetReference类型的字段。开启“ScanForAssetReferences”后,工具会以文本方式解析资产文件(如.prefab, .asset),查找AssetReference的GUID,从而将这些引用加入依赖图。这会使扫描速度变慢,但引用关系更完整。
注意:工具无法检测“字符串路径引用”或“运行时动态加载”。例如,通过
Resources.Load(“路径/资源名”)或AssetBundle.LoadAsset加载的资源,在编辑器的静态分析中是完全不可见的。这类资源如果存放在Resources文件夹或被打进AssetBundle,需要你手动将其添加到忽略列表,或者依靠Addressables检测来“保护”它们。
3. 安装与基础配置详解
工欲善其事,必先利其器。正确的安装和初始配置能避免很多后续麻烦。
3.1 两种安装方式的选择与实操
方式一:通过UPM(Unity Package Manager)安装(推荐)这是最干净、最便于管理的方式,尤其适合团队项目。
- 在Unity编辑器中,打开Window > Package Manager。
- 点击左上角的“+”按钮,选择“Add package from git URL...”。
- 在弹出的输入框中,粘贴Dependencies Hunter的Git仓库地址:
https://github.com/AlexeyPerov/Unity-Dependencies-Hunter.git。 - 点击Add。Unity会自动下载并导入该包到项目的Packages目录下。
优点:非项目资产,不污染Assets目录;易于更新(只需在Package Manager中更新);依赖关系清晰。缺点:需要网络连接;对于内网开发环境可能不便。
方式二:直接复制C#脚本(传统方式)
- 访问项目的GitHub页面,找到根目录下的
DependenciesHunter.cs文件。 - 点击Raw按钮查看原始文件,复制全部代码。
- 在你的Unity项目的
Assets目录下,创建一个名为Editor的文件夹(如果不存在)。 - 在
Editor文件夹内,创建一个新的C#脚本,命名为DependenciesHunter.cs,将复制的代码粘贴进去。
优点:离线可用;直接嵌入项目,无需管理包。缺点:代码成为项目资产,更新麻烦;如果项目中有多个版本容易冲突。
实操心得:对于长期项目,我强烈推荐使用UPM方式。它不仅管理方便,更重要的是,当工具更新时,你可以清晰地看到版本变化。而直接复制脚本的方式,可能在Unity编辑器版本升级后,因为API变化而导致脚本编译报错,你需要自己手动查找修复,比较折腾。
3.2 首次运行与窗口布局解析
安装完成后,通过菜单栏Tools > Dependencies Hunter即可打开主窗口。这个窗口是工具的核心交互界面,布局清晰但功能密集。
窗口主要分为以下几个区域:
- 顶部控制区:包含“Analyze Project”(分析项目)按钮和“Analysis Settings”(分析设置)折叠菜单。这是操作的起点。
- 结果列表区:分析完成后,这里会以表格形式展示资产。默认只显示“Unreferenced Assets”(未引用资产)。每一列包括:资产名、路径、类型、大小、引用数。你可以点击列标题进行排序,例如按“Size”排序能快速找到占用空间最大的“僵尸资产”。
- 底部操作区:提供“Select All”(全选)、“Delete Selected”(删除选中项)等批量操作按钮,以及显示选中资产总大小和总数的统计信息。
首次打开时,建议先不要急着点击“Analyze”,而是点开“Analysis Settings”进行一番配置,这能节省大量后续筛选时间。
3.3 关键配置项:忽略模式(Ignore Patterns)
这是最重要的配置,没有之一。合理的忽略模式能让你聚焦于真正需要清理的资产,避免误报干扰。
忽略模式使用正则表达式(RegExp)来匹配资产路径。工具内置了一些默认模式,但你需要根据自己项目的结构进行定制。
如何设置:在“Analysis Settings”中,找到“Ignore Patterns”列表。你可以直接在此添加、编辑或删除正则表达式。更推荐的做法是点击“Create Ignore Patterns Asset”按钮,这会在Assets/Editor/下创建一个名为DependenciesHunterIgnorePatterns.asset的设置文件。这样做的好处是配置可以随项目版本管理,团队共享。
常用正则表达式示例:
.*/Editor/.*:忽略所有Editor文件夹下的内容。编辑器脚本、扩展工具等不应被打包。.*/Plugins/.*:忽略Plugins文件夹。通常存放原生插件(.dll, .so, .bundle)。.*\.asmdef$:忽略所有程序集定义文件。这些是代码组织文件,非资源。.*/Resources/.*:谨慎使用!Resources文件夹内的资源虽然可能未被直接引用,但可以通过Resources.Load动态加载。如果你确定某些Resources下的资源确实无用,可以不忽略;如果不确定,最好忽略,或者清理前仔细审查。.*/StreamingAssets/.*:忽略StreamingAssets文件夹。这里的文件会原样复制到构建包,供运行时读取。.*/TextMesh Pro/.*:如果你使用了TextMesh Pro,其资源包内的字体、材质等通常不应被清理。.*\.cs$:忽略所有C#脚本文件。
避坑指南:正则表达式中的
.匹配任意字符,*表示前一个字符出现0次或多次,.*组合起来就是匹配任意长度的任意字符串。$表示字符串结尾。添加模式后,建议先进行一次快速扫描,观察结果列表是否如预期般过滤了相关路径。这是一个迭代调整的过程。
4. 深度扫描:Addressables与AssetReference处理
现代Unity项目大量使用Addressables系统进行资源热更和内存管理,这使得传统的依赖分析面临挑战。Dependencies Hunter对此提供了专门的支持,但需要正确理解和使用。
4.1 启用Addressables检测
在“Analysis Settings”中,勾选“Detect Addressables”选项。
这个选项做了什么?勾选后,工具在分析时会额外读取项目的Addressables配置(通常位于Assets/AddressableAssetsData下)。任何在Addressables组中注册了的资产,即使它在AssetDatabase的依赖图中没有被任何其他资产引用,也会被工具标记为“已引用”,从而不会出现在“未引用资产”的结果列表中。
为什么需要这个选项?Addressables的本质是“动态引用”。一个预制体可能没有被任何场景或资源直接引用,但它被添加到了名为“UI”的Addressables组,并设置了“UI_Popup”的地址。游戏运行时,代码通过Addressables.LoadAssetAsync<GameObject>(“UI_Popup”)来加载它。对于静态分析工具来说,这种通过字符串建立的关联是不可见的。如果不开启此选项,这个预制体就会被误判为“未引用”而建议删除,导致运行时加载失败。
重要提示:开启此选项后,扫描时间会显著增加,因为工具需要解析Addressables的配置文件。对于大型项目,请耐心等待。
4.2 扫描AssetReference字段(进阶选项)
在“Analysis Settings”中,还有一个“Scan For AssetReferences”选项。这个功能更底层,也更容易让人困惑。
AssetReference是什么?AssetReference是Addressables系统提供的一个序列化类型。你可以在MonoBehaviour或ScriptableObject中声明一个public AssetReference myRef;字段,然后在Inspector中像拖拽普通引用一样,为其指定一个资产。与直接引用public GameObject prefab;不同,AssetReference存储的是资产的GUID和地址,它本身不阻止资产被构建时剥离,而是通过Addressables系统来管理加载。
这个选项解决了什么问题?默认情况下,AssetDatabase.GetDependencies无法识别AssetReference类型的字段。也就是说,如果一个预制体A的唯一引用,是来自脚本B中的一个AssetReference字段,那么在不开启此选项时,预制体A会被判定为“未引用”。
开启后的工作原理:当勾选“Scan For AssetReferences”后,Dependencies Hunter会改变扫描策略。它不再完全依赖AssetDatabase.GetDependencies,而是会以文本形式读取资产文件(如.prefab, .unity, .asset),搜索其中序列化的AssetReference字段所对应的GUID,然后将这个GUID对应的资产加入到依赖关系中。
代价与决策:
- 扫描速度:文本解析比API调用慢得多。对于拥有成千上万个预制体和脚本化对象的项目,扫描时间可能从几分钟延长到十几分钟甚至更久。
- 准确性:理论上更准确,能捕获更多隐藏的引用。
我的建议是:不要默认开启这个选项。首先进行常规扫描(不勾选此选项)并清理。如果在清理后,运行时发现某些通过AssetReference引用的资产丢失了,再开启此选项进行一次“终极验证”扫描。在大多数项目中,如果规范地使用了Addressables,重要的AssetReference资产通常也会被加入到Addressables组中,从而被“Detect Addressables”选项保护。因此,“Scan For AssetReferences”更像是一个兜底的安全检查。
5. 结果分析与安全删除操作流程
扫描完成后,面对可能成百上千条的“未引用资产”列表,如何安全、高效地处理是关键。鲁莽的删除会导致项目损坏。
5.1 解读结果列表
结果列表的每一列都提供了重要信息:
- Name/Path:资产名称和完整路径。这是定位资产的主要依据。
- Type:资产类型(Texture, Material, Prefab等)。类型可以帮助你快速判断。例如,一堆“TextAsset”可能是临时导入的JSON或XML配置文件,而“Material”则需谨慎,可能被代码动态创建引用。
- Size:资产在磁盘上的大小(通常是未压缩的大小)。按此列排序,优先处理那些占用空间巨大的资产(如高清纹理、音频文件),清理它们能获得最显著的体积优化效果。
- Refs:引用计数。对于“未引用资产”视图,这里始终是0。如果你关闭了“Show Unreferenced Assets Only”,则会显示所有资产的引用数。
5.2 三步审查法:避免误删
直接点击“Delete Selected”是高风险行为。请遵循以下审查流程:
第一步:类型与路径筛选
- 审查Resources和StreamingAssets:如果之前没有在忽略模式中添加这些路径,现在要格外小心。列表中的Resources资产,你需要回忆或搜索代码中是否有对应的
Resources.Load调用。 - 关注脚本化对象(ScriptableObject)和配置表:这些资产可能被代码通过路径或名称动态加载。检查其文件名是否在代码的常量定义或配置文件中出现。
- 忽略编辑器相关资产:扩展名为
.cs,.asmdef,.editor(编辑器脚本)等的文件通常可以安全忽略或加入忽略列表。
第二步:样本抽查与验证在结果列表中,右键点击某个你怀疑的资产(比如一个材质球),选择“[DH] Find References in Project”。这个上下文菜单功能是Dependencies Hunter另一个强大之处。
- 如果工具弹窗显示“No references found”,并且你确认它不在Addressables中,那么它很可能是安全的。
- 如果工具找到了引用者,但主扫描却没发现?这可能是“Scan For AssetReferences”未开启导致的漏报。此时你应该将这个资产从删除列表中排除,并考虑是否需要开启该选项重新扫描。
第三步:备份与试删除
- 版本控制提交:在执行任何删除操作前,确保当前所有更改已提交到Git、SVN或Perforce等版本控制系统。这是最重要的安全网。
- 选择性备份:对于仍然不确定但占用空间大的资产,可以手动将其从Assets目录移动到一个临时文件夹(如
_ToDeleteBackup)外部。不要只是重命名或移到Assets内的另一个位置,因为Unity的meta文件引用可能依然存在。 - 分批删除:不要一次性全选删除。可以按类型或按文件夹分批操作。例如,先删除所有确认为未使用的纹理,测试游戏运行无误后,再删除材质,以此类推。
- 运行测试:删除一批资产后,务必在编辑器中运行游戏,遍历主要功能场景,检查是否有贴图丢失(紫色)、预制体引用丢失(显示为“Missing”)或运行时加载错误。
5.3 使用“查找引用”功能进行逆向侦查
除了从“未引用”列表入手,你还可以主动侦查特定资产。在Project窗口选中一个或多个资产,右键选择“[DH] Find References in Project”,会打开一个“Selected Assets”窗口,详细列出选中资产被哪些其他资产所引用。
这个功能的典型应用场景:
- 疑惑:你觉得某个老旧的模型Prefab应该没用了,但不敢删。
- 操作:选中该Prefab,使用“查找引用”。
- 结果:发现它被一个名为“OldLevelDesign”的场景引用,而这个场景在构建设置中早已被移除。
- 结论:你可以安全地删除这个Prefab,或者连同那个废弃场景一起清理。
这个功能是理解项目资产关联关系的利器,尤其适合在接手遗留项目时进行架构梳理。
6. 常见问题、报错与解决方案实录
在实际使用中,你几乎一定会遇到下面这些问题。这里记录了我踩过的坑和总结的解决方案。
6.1 扫描过程卡住或异常缓慢
现象:点击“Analyze Project”后,进度条缓慢蠕动,编辑器无响应,甚至卡死。原因与解决方案:
- 项目资产量巨大:这是最主要的原因。首次扫描或长时间未扫描后,工具需要构建完整的依赖图。
- 方案:耐心等待。对于超大型项目(超过10GB资产),首次扫描可能需要30分钟以上。可以尝试在非工作时间进行。
- 开启了“Scan For AssetReferences”:如前所述,此选项会大幅降低扫描速度。
- 方案:除非必要,否则关闭此选项进行常规扫描。
- 磁盘I/O或杀毒软件干扰:工具需要频繁读取磁盘上的资产文件。
- 方案:将Unity项目目录添加到杀毒软件的排除列表。使用SSD硬盘能显著提升速度。
- 内存不足:构建大型依赖图会消耗大量内存。
- 方案:关闭不必要的编辑器窗口和应用,增加系统虚拟内存。如果项目过大,考虑分模块扫描(通过忽略模式排除已清理的模块)。
6.2 扫描结果不准确(误报/漏报)
现象:工具报告某个资产未引用,但你确信它在游戏中被使用了;或者反过来,某个明显无用的资产却没被扫出来。原因与解决方案:
| 问题类型 | 可能原因 | 解决方案 |
|---|---|---|
| 误报 (False Positive) 资产被标记为未引用,但实际有用。 | 1.动态加载:通过Resources.Load,AssetBundle.LoadAsset,Addressables.LoadAssetAsync加载。2.代码生成引用:在运行时通过 Resources.Load或Addressables加载,路径是字符串拼接的。3.Shader或Graph中引用:在Shader Graph或VFX Graph中引用的纹理,依赖分析可能不完整。 4.AssetReference未扫描:资产仅被 AssetReference字段引用,且未开启对应选项。 | 1. 确保资产位于Resources文件夹或已被添加到Addressables组,并开启“Detect Addressables”。2. 将资产路径或其父文件夹添加到忽略模式。 3. 手动检查Shader/Graph,或将此类资产加入忽略列表。 4. 开启“Scan For AssetReferences”重新扫描,或手动将资产移出删除列表。 |
| 漏报 (False Negative) 资产实际无用,但未被标记。 | 1.循环引用:资产A引用B,B又引用A(可能通过复杂的中间链),形成闭环,导致两者在依赖图中都有入度。 2.编辑器脚本引用:资产被某个编辑器工具类引用,但该工具类本身不参与运行时。 3.忽略模式过于宽泛:设置的忽略正则表达式匹配了不该忽略的资产路径。 | 1. 工具通常能处理简单循环引用。对于复杂情况,需要手动审查。可以尝试临时移除其中一个资产,看另一个是否会变成未引用。 2. 检查引用该资产的脚本是否在 Editor文件夹下。如果是,可以放心清理该资产,或将该编辑器脚本也加入清理列表。3. 复查并收紧忽略模式的正则表达式。 |
6.3 删除资产后引发编译错误或运行时错误
现象:删除资产后,Unity控制台报错(如脚本编译错误),或游戏运行时出现粉色材质、Missing预制体。原因与解决方案:
- 删除了被脚本直接引用的资产:例如,一个
public Material defaultMat;字段在Inspector中引用了某个材质球,你删除了这个材质球。- 预防:删除前,使用右键的“[DH] Find References in Project”功能检查资产是否被任何脚本(.cs文件)引用。
- 补救:从版本控制中恢复被删除的资产,或在Inspector中为丢失的引用重新赋值。
- 删除了Shader或Compute Shader文件:导致使用该Shader的材质变粉。
- 预防:对Shader、Compute Shader、HLSL文件等保持最高警惕。除非你百分百确定所有使用它的材质都已废弃。
- 补救:恢复Shader文件,或为受影响的材质重新指定Shader。
- 删除了Addressables中注册的资产:但游戏运行时需要加载它。
- 预防:务必开启“Detect Addressables”选项,并确保Addressables配置本身是正确的。
- 补救:恢复资产,或从Addressables组中移除该资产的条目。
6.4 工具窗口无法打开或功能缺失
现象:菜单中没有“Dependencies Hunter”选项,或窗口打开是空的/报错。原因与解决方案:
- 脚本编译错误:如果项目中有其他脚本错误,可能导致编辑器工具无法正常加载。
- 方案:解决所有编译器错误,重启Unity编辑器。
- 安装位置错误:手动复制的
DependenciesHunter.cs脚本没有放在Assets目录下的任意一个Editor文件夹内。- 方案:确保脚本路径类似于
Assets/Editor/DependenciesHunter.cs或Assets/MyTools/Editor/DependenciesHunter.cs。只有放在Editor文件夹下的脚本才能在编辑器中运行。
- 方案:确保脚本路径类似于
- Unity版本兼容性问题:虽然工具兼容性较好,但极旧的Unity版本可能缺少某些API。
- 方案:检查GitHub仓库的Issues或说明,确认支持的Unity版本。考虑升级Unity或寻找旧版本的工具分支。
7. 高级技巧与集成到工作流
将Dependencies Hunter从“偶尔使用的清理工具”升级为“项目健康守护流程”的一部分,能带来长期收益。
7.1 创建自定义扫描预设
对于大型项目,不同的模块或阶段可能需要不同的扫描策略。你可以通过脚本扩展来创建一键扫描预设。
- 在
Assets/Editor/下创建一个新脚本,例如DependencyScanPresets.cs。 - 利用
DependenciesHunter类的静态方法或反射调用,来预设参数并启动扫描。 - 示例:你可以创建一个“快速扫描”菜单,它忽略所有测试资源和第三方插件;再创建一个“深度扫描”菜单,它包含所有资源但开启Addressables检测。
这需要一定的编辑器脚本编写能力,但一旦设置好,能为团队提供极大的便利。
7.2 与CI/CD管道集成
在团队开发中,可以在每次打包前或每日构建时,自动运行Dependencies Hunter扫描,并将结果报告(如未引用资产列表及其总大小)输出为日志或发送到协作平台(如Slack、钉钉)。
思路:
- 使用Unity命令行模式 (
-batchmode -quit) 执行一个编辑器脚本。 - 在该脚本中,调用Dependencies Hunter的扫描API,获取结果。
- 将结果(资产路径、大小)格式化为文本或JSON。
- 如果未引用资产的总大小超过某个阈值(例如100MB),则令构建失败或发出警告。
这样可以防止未被注意到的资源悄悄增大包体,把优化工作左移。
7.3 处理特殊资产类型
- Terrain Data(地形数据):地形资源与其关联的细节纹理、树木原型等资产的引用关系有时比较隐蔽。Dependencies Hunter有一个实验性的“Scan Terrain Data”选项(可能在代码中,UI未直接暴露),可以更彻底地扫描地形引用。如果你大量使用地形,可以查阅源码确认。
- 动画控制器(Animator Controller)和动画片段(Animation Clip):它们的引用关系通常能被正确捕获。但要注意,状态机中通过脚本控制的条件或事件,其关联的资源可能无法被静态分析。
- ScriptableObject数据资产:这是误删的重灾区。务必确认这些数据资产是否被代码通过
Resources.Load或Addressables加载,或者是否被其他ScriptableObject引用。最好的实践是为重要的数据资产建立清晰的加载路径和架构,避免隐式引用。
使用Dependencies Hunter的最高境界,不是等项目臃肿了才来一次大扫除,而是将其作为日常开发习惯的一部分。在每次提交新资源、每次重构旧模块后,都花几分钟扫描一下相关目录,及时清理产生的“垃圾资产”。这就像每天整理办公桌,虽然琐碎,但能长期保持高效和清爽。工具本身是冰冷的,但结合清晰的工作流程和团队规范,它就能成为保障项目代码库和资源库健康的强大助力。