Unity AssetBundle 冗余检测与资源分析
2017-09-30 13:10
465 查看
原因
在使用 Unity 进行开发项目时,通常使用 AssetBundle 来进行资源打包,虽然在 Unity 5.x 版本里提供了更加智能的依赖自动管理,即如果依赖的资源没有显式设置 AssetBundle 名称,那么就会被隐式地打包到同一个 AssetBundle 包里面。而如果已经设置的话,那么就会自动生成依赖关系。那么当被依赖的资源没有独立打包时,而此时又存在两个或以上 AssetBundle 依赖此资源的话,这个资源就会被同时打包到这些 AssetBundle 包里面,造成资源冗余,增大 AssetBundle 包的体积,增加游戏加载 AssetBundle 时所需的内存。
于是,检测 AssetBundle 资源的冗余,才好方便对其进行优化。检测冗余可以在未打包前对将要打包的资源做分析,但是这无法完全保证打包之后的 AssetBundle 完全无冗余,一是分析时无法保证正确无冗余,二是引用的内置资源无法剔除冗余,所以对打包之后的 AssetBundle 包进行检测才真正检查到所有的冗余。
优点
通过查找 AssetBundle 里冗余的资源,就能方便对其进行优化。优化之后,AssetBundle 包的大小也会相应的变小,作为初始包的话,包体也会变小。另外,游戏运行时加载 AssetBundle 时所占用的内存也会降低。而资源分析,能够发现到资源的错误设置引起的各种问题,方便纠正。实现过程
检测 AssetBundle 资源的冗余,要分两种情况,一种是非场景打包的 AssetBundle 文件,一种是场景打包的 AssetBundle 文件。这两种类型的 AssetBundle 文件存储的方式有所不同,加载用的 API 也不同,所以分两种情况来处理。非场景打包的 AssetBundle
问题一:如何取出每个 AssetBundle 文件里面的所有资源?对于显式设置 AssetBundle 名称的资源,可以通过 API 来直接获取,比如:一个材质引用了一张贴图,对材质设置 AssetBundle 名称,如下所示:
使用
AssetBundle.LoadAllAssets来获取所有的资源,结果如下:
可以看到,只能得到有设置 AssetBundle 名称的资源对象,无法直接获取到所有的资源对象。
解决:我们知道贴图资源是被材质所引用了,那么只要获取材质对象,然后通过材质的接口就可以获取到贴图对象了,如下所示:
可以看到,贴图对象在材质的
mainTexture属性,着色器对象在材质的
shader属性上。这种方式可以获取到所引用的对象,但是太繁琐,需要对每个类进行处理,我们可以学习 Unity 编辑器检视器窗口的处理,把每个可序列化的对象都通过
SerializedObject来进行处理。具体流程伪代码如下:
public static void AnalyzeObjectReference(AssetBundleFileInfo info, Object o) { var serializedObject = new SerializedObject(o); var it = serializedObject.GetIterator(); while (it.NextVisible(true)) { // 如果是引用类型的属性,则递归查询 if (it.propertyType == SerializedPropertyType.ObjectReference && it.objectReferenceValue != null) { AnalyzeObjectReference(info, it.objectReferenceValue); } } }
这样就可以查询到所有被依赖的资源。
额外情况:对于
AssetDatabase.AddObjectToAsset方式合并多个对象到一个资产的话,并且没有任何其他对象进行引用的话,是无法获取得到的,比如
AnimatorController组件,里面关联的动画片段文件无法用
SerializedObject方式来获取得到,其检视器窗口也是空空的,如下所示:
对于这种情况,只能特定处理,通过其外部接口去获取引用的对象。还好这种情况比较少,目前也就
AnimatorController组件如此。
问题二:如何确定资源的唯一性?
我们知道在编辑器下的话,每个资源都有个
GUID唯一标识符,但是这个标识符没有直接保存到 AssetBundle 里面,而是经过 Unity 计算过后的一个唯一值,通过解包工具可以看到:
其中的
Path ID就是资源的标识符,但没有提供 API 可以直接访问这个变量,就无法知道资源是否冗余,因为资源重名太常见了,不能仅因为相同的资源名称就认为是冗余。
解决:在进行尝试的过程中,发现可以通过获取
Local Identfier In File的方式来获取得到,这个属性在检视器的
Debug模式下,如下所示:
对资源的
SerializedObject对象进行设置
Debug模式,代码如下:
public static void AnalyzeObjectReference(AssetBundleFileInfo info, Object o) { var serializedObject = new SerializedObject(o); if (inspectorMode == null) { // 反射获取模式属性 inspectorMode = typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance); } inspectorMode.SetValue(serializedObject, InspectorMode.Debug, null); var it = serializedObject.GetIterator(); while (it.NextVisible(true)) { if (it.propertyType == SerializedPropertyType.ObjectReference && it.objectReferenceValue != null) { AnalyzeObjectReference(info, it.objectReferenceValue); } } }
获取唯一标识符的方式如下:
可以看到
m_LocalIdentfierInFile属性值就是
Path ID值,就可以确定资源的唯一性。
问题三:如何获取 AssetBundle 之间的依赖关系?
解决:如果使用的是 Unity5 自动生成的
AssetBundleManifest依赖关系记录文件的话,那么直接使用
AssetBundleManifest的 API 接口就可以获取每个 AssetBundle 包的依赖关系了。
allDepends = assetBundleManifest.GetAllDependencies(bundle)
如果是自己维护的依赖关系文件的话,那么只要实现自己的加载方式即可,类似代码如下:
public static void MyAnalyzeCustomDepend() { AssetBundleFilesAnalyze.analyzeCustomDepend = directoryPath => { List<AssetBundleFileInfo> infos = new List<AssetBundleFileInfo>(); // 添加每个 AssetBundle 信息 AssetBundleFileInfo info = new AssetBundleFileInfo { //name = bundle, //path = path, //rootPath = directoryPath, //size = new FileInfo(path).Length, //directDepends = assetBundleManifest.GetDirectDependencies(bundle), //allDepends = assetBundleManifest.GetAllDependencies(bundle) }; infos.Add(info); return infos; }; }
如果都不是的话,那么就会加载文件夹下的所有 AssetBundle 文件,不分析依赖关系,只分析资源冗余。
场景打包的 AssetBundle
问题四:如何分析场景里的资源?场景打包的 AssetBundle 无法像普通 AssetBundle 那样去加载分析,普通的 AssetBundle 可以在编辑器下,不进入播放模式,直接进行使用
ab.LoadAllAssets接口去加载分析。但场景打包的 AssetBundle 无法使用这个接口,它只能在播放模式下,加载 AssetBundle 文件,使用
SceneManager.LoadScene方式去加载场景。而且无法获取到场景里资源的唯一标识符,解包工具可以看到:
这里打包进场景的贴图就是之前使用到的贴图文件,但是在这里的
Path ID只是在场景里的顺序索引而已。
解决:故不能检测冗余,只能做资源分析。在播放模式下,一个接一个地加载 AssetBundle 文件,载入场景,伪代码如下:
private void LoadNextBundleScene() { BundleSceneInfo info = m_BundleSceneInfos.Peek(); info.ab = AssetBundle.LoadFromFile(info.fileInfo.path); SceneManager.LoadScene(info.sceneName, LoadSceneMode.Additive); } private IEnumerator AnalyzeBundleScene(Scene scene) { BundleSceneInfo info = m_BundleSceneInfos.Peek(); AssetBundleFilesAnalyze.AnalyzeObjectReference(info.fileInfo, RenderSettings.skybox); GameObject[] gos = scene.GetRootGameObjects(); foreach (var go in gos) { AssetBundleFilesAnalyze.AnalyzeObjectComponent(info.fileInfo, go); } AssetBundleFilesAnalyze.AnalyzeObjectsCompleted(info.fileInfo); SceneManager.SetActiveScene(defaultScene); info.ab.Unload(true); info.ab = null; SceneManager.UnloadScene(scene); }
最好是场景打包的 AssetBundle 单独进行分析,这样不会干扰非场景打包的 AssetBundle 分析,使用的代码开关如下:
AssetBundleFilesAnalyze.analyzeOnlyScene = true;
即可在播放模式下,只分析场景资源。
资源的分析
在对 AssetBundle 文件进行加载分析的时候,可以获取到资源对象,在这里主要分析比较会引起性能问题的资源,比如:Mesh、Texture、AnimationClip、AudioClip等。分析的方法,主要通过资源对象的接口和SerializedObject序列化方式获取属性,比如:纹理的分析代码如下:
private static List<KeyValuePair<string, object>> AnalyzeTexture2D(Texture2D tex, SerializedObject serializedObject) { var propertys = new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("宽度", tex.width), new KeyValuePair<string, object>("高度", tex.height), new KeyValuePair<string, object>("格式", tex.format.ToString()), new KeyValuePair<string, object>("MipMap功能", tex.mipmapCount > 1 ? "True" : "False") }; var property = serializedObject.FindProperty("m_IsReadable"); propertys.Add(new KeyValuePair<string, object>("Read/Write", property.boolValue.ToString())); property = serializedObject.FindProperty("m_CompleteImageSize"); propertys.Add(new KeyValuePair<string, object>("内存占用", property.intValue)); return propertys; }
其他资源的分析也是类似如此。
资源的导出
既然可以获取到资源对象,那么就可以将某些资源导出来,这在分析其他 Unity 项目的时候比较有用,目前实现了纹理的导出,默认不开启功能,开启的代码如下:AssetBundleFilesAnalyze.analyzeExport = true;
开启后,在分析的时候,会将资源自动保存到以
Export结尾的同名目录下。
输出最终结果
在分析完毕之后,输出最终结果为 Excel 报表。使用 Excel 可以实现跳转链接的效果,也可以实现多个分页报告的效果。第一页为 AssetBundle 文件列表,显示所有的 AssetBundle 文件,以及每个 AssetBundle 文件的大小,依赖的 AssetBundle 数量,存在的冗余资源数量,以及包含各类型资源的数量。点击表格的 AssetBundle 名称,即可跳转到第二页相对应的所包含资源信息。
第二页为每个 AssetBundle 文件所包含的具体资源信息,以及所依赖的 AssetBundle 文件列表,和被依赖的 AssetBundle 文件列表。若此 AssetBundle 包含冗余资源,则资源名称会以红色进行显示。点击资源名称,即可跳转到第三页相对应的资源信息,如果是具体分析的资源,如:Mesh、Texture2D、Material、AnimationClip、AudioClip类型的话,会跳转到相应的资源类型分页。
第三页为所有资源的列表,以及资源类型,被包含的 AssetBundle 文件数量和具体的文件名称。
第四页为网格资源列表,以及顶点数、面数、子网格数、网格压缩、Read/Write等参数信息。
第五页为纹理资源列表,以及宽度、高度、格式、MipMap功能、Read/Write、内存占用等参数信息。
第六页为材质资源列表,以及依赖Shader、依赖纹理等参数信息。
第七页为动画片段资源列表,以及总曲线数、Constant曲线数、Dense曲线数、Stream曲线数、事件数、内存占用等参数信息。
第八页为音频片段资源列表,以及加载方式、预加载、频率、长度、格式等参数信息。
每一页都可以对每一列进行排序或查找定位,方便直接定位到有问题的资源,如下图所示:
使用说明
将插件包导入到工程,打包 AssetBundle 之后,调用检测的接口,如下所示:/// <summary> /// 分析打印 AssetBundle /// </summary> /// <param name="bundlePath">AssetBundle 文件所在文件夹路径</param> /// <param name="outputPath">Excel 报告文件保存路径</param> /// <param name="completed">分析打印完毕后的回调</param> public static void AnalyzePrint(string bundlePath, string outputPath, UnityAction completed = null)
传入所需的参数即可,等待输出报告。另外注意一点,打包完 AssetBundle 就立即检测,这样才能在分析 AssetBundle 的时候,获取到正确的自定义脚本类信息,才能分析完全。过后再检测的话,自定义的脚本类可能被其他人所修改,那么就无法分析正确。Unity 5.4+ 支持场景资源分析,Unity 4.X ~ Unity 5.3 只支持非场景资源分析。
源码地址
https://github.com/akof1314/AssetBundleReporter相关文章推荐
- Unity打包AssetBundle自动分析资源依赖关系(包括UGUI图集打包)
- Unity 资源解决方案之AssetBundle (3)
- Unity 3D Assetbundle 资源分类
- 关于Unity中从服务器下载资源压缩包AssetBundle的步骤
- Unity_AssetBundle 动态资源加载
- Unity5 AssetBundle系列——资源加载卸载以及AssetBundleManifest的使用
- Unity之资源打包Assetbundle
- unity AssetBundle 加载资源 笔记
- Unity的资源加载以及AssetBundle的一些坑
- Unity调用AssetBundle资源
- Unity中 BundleAsset资源的打包和解析加载
- 能解开assetbundle格式资源的UnityStudio工具
- Unity 5.x AssetBundle零冗余解决方案
- Unity 3D资源解决方案之AssetBundle简介
- Unity资源热更之AssetBundle(1)———基本介绍
- Unity资源热更之AssetBundle(1)———基本介绍
- 【unity】AssetBundle共享资源及依赖资源打包
- Unity资源热更之AssetBundle(3)———新版本AssetBundle