Unity AssetBundle 冗余检测与资源分析
来源:互联网 发布:dnf补丁制作软件 编辑:程序博客网 时间:2024/06/06 01:43
原因
在使用 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 冗余检测与资源分析
- unity-AssetBundle资源冗余检测
- AssetBundle资源冗余检测
- Unity AssetBundle打包与资源更新
- Unity AssetBundle打包与资源更新
- Unity 资源打包Assetbundle
- Unity 资源打包Assetbundle
- Unity 资源打包Assetbundle
- Unity资源打包Assetbundle
- Unity 5.x AssetBundle零冗余解决方案
- Unity 5.x AssetBundle零冗余解决方案
- Unity资源打包之Assetbundle
- Unity之资源打包Assetbundle
- Unity资源解决方案之AssetBundle
- Unity资源打包之Assetbundle
- unity AssetBundle 加载资源 笔记
- Unity创建Assetbundle与加载
- Unity 3D Assetbundle 资源分类
- 微软用12年时间让量子计算走进现实:是时候开发量子程序了
- 【shiro】--- 自定义realm
- C++基础(11)
- 大数据开源处理工具汇总
- Solr Facet
- Unity AssetBundle 冗余检测与资源分析
- hdu5971 Wrestling Match (判断是否为二分图)
- Java反射机制详解
- PAT 甲级 1052. Linked List Sorting (25)
- require源码分析之require
- shiro学习随笔【四】session过期报 org.apache.shiro.session.UnknownSessionException: There is no session with id
- 15算法课程 28. Implement strStr()
- JavaScript事件
- 【shiro】--- spring整合shiro