关于 Unity 中引用查询的研究
博主目前所知道的 Unity 引用查询的方式有两种:
- 将 Unity 的序列化模式设置为 Force Text, 通过文本比对的方式查询引用情况
- 通过收集复合资源的依赖情况反向转换为引用情况, 直接进行查询
文本方式
这种查询方式可以自己实现也可以借助工具实现
手动实现
这里就以最简单的方式举例 (只说明思路)
先收集全部的复合资源
var guids = AssetDatabase.FindAssets("t:Prefab t:Material t:Scriptableobject t:Scene t:AnimatorController t:Textasset", new string[] {"Assets"});
依次加载全部文本
rawData.text = File.ReadAllText(assetFullPath);
获得要查询资源的 GUID
guidRules = Selection.assetGUIDs
依次进行比对
1
2
3
4
5
6
7foreach (var guidRule in guidRules)
{
if (Regex.IsMatch(rawData.text, guidRule))
{
results.Add(guidRule, rawData.fullPath);
}
}
最后汇总结果就可以了, 其中关键的点在于要使用多线程来加快读取 text 以及遍历查询的速度, 否则效率将会非常低
【总结】
在较大的项目中, 初始化需要 33s 时间, 而且每次初始化的时间差异很大, 有时会长达 60s 以上
查询 1 个文件的耗时 0.7s
但是随着文件增多, 需要的查询时间也会随之上升, 查询 10 个文件时需要 6s
!110个文件
查询 20 个文件时需要 16s
【适用情况】
由于每次修改 C# 代码都会导致缓存清空, 因此适用于关卡, 美术和优化等基本不会修改代码的人员
制作缓存时, 基本都会使用
OnPostprocesser
这个方法, 从而会加长资源导入的时间, 如果项目中有大量的此方法, 便会导致资源导入非常缓慢由于查询数增多时, 时间消耗也会大幅度增加, 因此只适用于人工查询, 不适合批量查询
使用 Ripgrep 软件
使用 Ripgrep 软件的查询功能实现引用查询
Ripgrep 是命令行下一个基于行的搜索工具, 使用 Rust 开发, 可以在多平台下运行, RipGrep 官方号称比其它类似工具在搜索速度上快上 N 倍, VSCode 的搜索功能默认就是用的 Ripgrep
使用 VSCode 执行搜索时在任务管理器中就可以看到
软件下载地址: Ripgrep 开源地址
将软件导入到 Unity 中, 编写代码使用 Process
类, 设置好参数, 启动软件查询即可
推荐将参数写为可配置的方式, 这样使用过程中需要调整参数时可以直接外部调整
另外 .exe 程序和 .ignore 文件都可以直接放到项目中
在较大的项目中, 每次查询需要消耗 6 ~ 10s
【适用情况】
没有了初始化操作带来的时间消耗, 单次查询的时间大幅度缩短, 适用于程序等经常修改代码的人员
由于无法做缓存, 因此也只适用于人工查询, 不适用于批量查询
由于没有使用
OnPostprocesser
方法, 也带来一个好处, 不会对资源导入速度造成任何影响
逆向依赖方式
Unity 目前提供了 AssetDatabase.GetDependencies()
和 EditorUtility.CollectDependencies()
两个方法收集复合类资源的依赖, 通过将依赖关系转化为引用关系, 便可以实现引用情况的查询 (编辑器中可以直接: 资源右键 => Select Dependencies)
【注】由于使用了 Unity 内部的引用关系来做查询, 直接通过代码调用资源接口进行加载的资源将无法被统计
先说一下两个接口的差异
AssetDatabase.GetDependencies()
收集的依赖包含无效引用EditorUtility.CollectDependencies()
收集的依赖不包含无效引用
无效引用指的就是 Unity 为了方便开发者而做的一些引用缓存; 以材质球为例, 在切换 Shader 的时候, Unity 并不会将之前 Shader 的相关序列化信息删除, 旧的纹理引用依旧会序列化保存下来
更详细的差异可以看这里: 详细差异
下面说一下实现思路:
- 得到全部的复合资源
1 | var sprites = AssetDatabase.FindAssets("t:SpriteAtlas"); |
- 获取全部依赖
使用 AssetDatabase.GetDependencies()
, 因为其可以直接传递路径, 另一个方法需要传递物体, 多一个加载过程的耗时
1 | foreach (var key in typeFilter.Keys) |
- 将依赖关系转换为引用关系, 有了引用关系的字典就可以直接查询了
1 | foreach (var guid in dependencies.Keys) |
这个方法的耗时主要在构建依赖关系上, 经测试每次构建需要耗时 70s 以上
而将依赖关系转换为引用关系仅花费了 1s 左右
查询耗时连 1ms 都不到
【适用情况】
因为每次构建字典的时间过长, 不适合经常使用, 多适用于批量查询, 全局查询
也可以将字典做成缓存, 使用
OnPostprocesser
来做, 虽然会对资源导入速度有影响, 但是就不必每次都进行字典的构建, 借助字典的查询友好, 可快速查询引用, 资源商店中的 FR2 便是使用了这个思路
总结
思路 | 方案 | 初始化耗时 | 单次查询耗时 | 多次查询耗时 | 是否适合批量查询 |
---|---|---|---|---|---|
文本比对 | 手动实现逻辑 | 33s ~ 2 min (耗时不稳定) | 0.7s | 高于线性 | 不适合 |
文本比对 | Ripgrep | 无 | 6 ~ 10s | 线性 | 不适合 |
逆向依赖 | 逆向依赖 | 72s 左右 (耗时稳定) | ~0ms [字典: O(1)] | 线性 | 适合 |