文章目录

  • 前言
  • 一、AssetBundle是什么?
    • 1.定义
    • 2.为什么需要AssetBundle
    • 3.AssetBundle内部格式
  • 二、AssetBundle打包
    • 1.设置资源AB名称
    • 2.资源打包
    • 3.资源压缩格式
    • 4.分包策略
    • 5.避免资源冗余
    • 6.AB包加密
  • 三、AssetBundle资源加载
    • 1.各平台资源文件目录介绍
    • 2.加载、卸载资源API
    • 3.内存分析
  • 总结

前言

本文主要记录笔者学习Unity资源管理的过程。


一、AssetBundle是什么?

1.定义

AssetBundle即资源包,它可以把多个游戏对象或者资源以二进制形式保存到Assetbundle文件中。AssetBundle中的单个资源称为Asset。
Assetbundle支持所有unity可识别的格式:比如预制体、模型、声音、贴图、材质、Shader、场景、lua脚本等。

2.为什么需要AssetBundle

1)将资源打包放到远程服务器上,需要的时候在进行下载。可以减小安装包大小,提升首包的下载速度。
2)可以进行资源和业务逻辑的热更新。
3)可以按需加载和释放 Asset ,减少内存压力。

3.AssetBundle内部格式

AssetBundle 包含了两个部分:数据头以及数据段。数据头内包含了 AssetBundle 的元数据信息,比如它的标识符、压缩类型、manifest 等等。
这里的 manifest 是一个以 Object 名称作为键的查找表,用以指定 AssetBundle 中给定 Object 的位置。数据段内包含了序列化 Asset 生成的原始数据,内容会依据压缩算法变化。

一个AssetBundle本质上是将一些对象组合成一个序列化文件,根据是普通包(normal bundle)还是场景包(scene bundle),Assetbundle的数据文件展开略有不同。

二、AssetBundle打包

1)可以使用Asset Bundle Browser插件进行打包。
2)Unity提供了一套API来实现打包功能。

1.设置资源AB名称

可以在Inspector窗口下手动设置资源的AB名,和变体(Variant)名。

变体的作用参考:
AssetBundle 变体与压缩
Unity 变体探秘

设置AB名称示例代码:
首先清除之前设置的AB名称,避免产生不必要的资源也打包
然后批量设置资源AB名,和变体名。这里是根据资源名称设置AB名。

    public static void ClearAssetBundlesName(){string[] oldAssetBundleNames = AssetDatabase.GetAllAssetBundleNames();for (int j = 0; j < oldAssetBundleNames.Length; j++){EditorUtility.DisplayProgressBar("清除AssetName名称", "正在清除AssetName名称中...", 1f * j / oldAssetBundleNames.Length);AssetDatabase.RemoveAssetBundleName(oldAssetBundleNames[j], true);}EditorUtility.ClearProgressBar();}public static void SetAssetBundlesName(){string fullPath = Application.dataPath + "/GameRes/";    //将Assets/GameRes/文件夹下的所有资源进行打包if (Directory.Exists(fullPath)){EditorUtility.DisplayProgressBar("设置AssetName名称", "正在设置AssetName名称中...", 0f);   //显示进程加载条DirectoryInfo dir = new DirectoryInfo(fullPath);    //获取目录信息FileInfo[] files = dir.GetFiles("*", SearchOption.TopDirectoryOnly);  //获取所有的文件信息for (var i = 0; i < files.Length; ++i){FileInfo fileInfo = files[i];EditorUtility.DisplayProgressBar("设置AssetName名称", "正在设置AssetName名称中...", 1f * i / files.Length);if (!fileInfo.Name.EndsWith(".meta"))   //判断去除掉扩展名为“.meta”的文件{string basePath = "Assets" + fileInfo.FullName.Substring(Application.dataPath.Length);  //编辑器下路径Assets/..string assetName = fileInfo.FullName.Substring(fullPath.Length);  //预设的Assetbundle名字,带上一级目录名称assetName = assetName.Substring(0, assetName.LastIndexOf('.')); //名称要去除扩展名assetName = assetName.Replace('\\', '/');   //注意此处的斜线一定要改成反斜线,否则不能设置名称AssetImporter importer = AssetImporter.GetAtPath(basePath);if (importer && importer.assetBundleName != assetName){importer.assetBundleName = "ui/" + assetName;  //资源的AssetBundleName名称importer.assetBundleVariant = "normal";  //设置资源的变体名称}}}EditorUtility.ClearProgressBar();   //清除进度条}}

2.资源打包

资源打包代码示例:

 static void CreateAllAssetBundles(){Caching.ClearCache();   //清除AssetBundle缓存string uiAssetBundlesPath = ClientConfig.GetResPath;    //获取需要打包的资源目录if (!Directory.Exists(uiAssetBundlesPath)){Directory.CreateDirectory(uiAssetBundlesPath);}//第三个参数BuildTarget参数用来选择针对的平台,因为AB包在不同平台下是不兼容的。BuildPipeline.BuildAssetBundles(uiAssetBundlesPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);//清除manifest文件int value = 1;string[] strFileName = Directory.GetFiles(uiAssetBundlesPath, "*.manifest", SearchOption.AllDirectories);foreach (var item in strFileName){File.Delete(item);EditorUtility.DisplayProgressBar("清除manifest文件", "正在清除manifest文件...", 1f * value / strFileName.Length);value++;}EditorUtility.ClearProgressBar();   //清除进度条}

注意:如果A资源依赖了B资源,且B没有设置AB名,则B会被打进A所在的包中。
若B被多个资源引用,则B重复打进多个AB包,造成资源冗余。
依赖详情看官方文档

各种ID
序列化后,资源用GUID和Local ID管理。
GUID对应Asset。GUID存在.meta文件中。提供了文件特定位置的抽象。是一种映射。无需关心资源在磁盘上的存放位置。
Local ID对应Asset内的每一个Object。(Asset中)

虽然GUID和Local ID比较好用,但是毕竟因为存在磁盘上,读取比较耗时。因此Unity缓存一个instance ID对应Object,通过instance ID快速找到Object。instance ID是一种快速获取对象实例的ID,包含着对GUID和Local ID的引用。解析instance ID可以快速返回instance表示的已加载对象,如果为加载目标对象,则可以将文件GUID和Local ID解析为对象源数据,从而允许Unity即时加载对象。每次AB包重新加载时,都会为每个对象创建新的instance ID。

3.资源压缩格式

打包的时候支持两种压缩格式:
1)BuildAssetBundleOptions.None 默认LZMA压缩(流压缩)
流压缩在处理整个数据块时使用同一个字典。优点是压缩率高,压出来的包体小。缺点是只支持顺序读取,加载的时候需要一次解压整个包(造成卡顿和额外内存占用)。

2)BuildAssetBundleOptions.ChunkBasedCompression 使用LZ4(块压缩)
将原始数据分成大小相同的子块并单独压缩。包体相对LZMA较大。优点是支持实时解压,随机读取。

推荐使用LZ4压缩。当然如果不在意包体大小的话,也可以选择不压缩。使用BuildAssetBundleOptions.UncompressedAssetBundle ,读取速度最快。

4.分包策略

1)将公共依赖的资源打包到一个公共AssetBundle中,独立的资源打包到一个AssetBundle中。
2)将相同类型的资源(Shader、Atlas、Prefab、Material、Scene、动画、声音、特效等)打包成一个AssetBundle。
3)地图、Monster、Npc、角色、坐骑、动作、模型等根据配置进行打包 。
4)经常更新的资源打包到一个AssetBundle中。
5)按照生命周期,同一个UI界面的资源打包到一个AssetBundle中。

打包时需要注意AB包整体数量和单个AB包的大小。
按照有经验的前辈的说法,一个AB包的大小控制在1~2M左右比较合适。

AB包数量较多,包内资源较少 AB包数量较少,包内资源较多
加载一个AB包到内存的时间短,玩家不会有卡顿感,但每个资源实际上加载时间变长。 加载一个AB包到内存的时间较长,玩家会有卡顿感,但之后包内的每个资源加载很快。
热更新灵活,要更新下载的包体较小。 热更新不灵活,要更新下载的包体较大。
IO次数过多,增大了硬件设备耗能和发热压力。 IO次数不多,硬件压力小。

5.避免资源冗余

引用自:鹅厂程序小哥 Unity用户手册-AssetBundle
建立三个对应关系的字典:
Dictionary<string, Bundle> m_BundleDic = new Dictionary<string, Bundle>();
Dictionary<string, Asset> m_AssetDic = new Dictionary<string, Asset>();
Dictionary<string, string> m_AssetMapBundleDic = new Dictionary<string, string>();
m_BundleDic:维护了BundleName-Bundle对象对应关系的字典,其中Bundle对象中,维护了当前Bundle中所有的资源列表AssetList。
m_AssetDic:维护了AssetName-Asset对象对应关系的字典
m_AssetMapBundleDic:维护了AssetName-BundleName对应关系的字典
具体操作步骤:
1.创建一个HashSet,确保所有的资源Asset在Bundle中是唯一的,不产生冗余。
2.遍历m_BundleDic中每一个Bundle对象的AssetList;
1)如果不在HashSet中,把Asset加入到HashSet中,否则,执行删除冗余操作;
2)如果HashSet已经存在相同的Asset时,通过m_AssetMapBundleDic字典获得当前Asset对应的BundleName;
3. 如果当前Asset对应的BundleName在m_BundleDic中,表示多个ab包依赖了这个Asset,从对应的Bundle的AssetList中移除这个Asset。
4.如果当前Asset对应的BundleName不在m_BundleDic中,表示虽然多个ab包依赖了这个Asset,但是其中有一些并不需要打到ab包中,这时,需要把对应的Bundle从m_AssetMapBundleDic中移除。
5.代码实现:TODO

6.AB包加密

TODO

三、AssetBundle资源加载

1.各平台资源文件目录介绍

1)Resources
Unity编辑器下目录
全部资源都会被压缩,转化成二进制。打包后该路径不存在,不可写也不可读。
只能使用Resources.Load加载资源。
2) Application.dataPath:
 在Unity下对应为:/Assets
 在iOS下对应为:/var/containers/Bundle/Application/appsandbox/xxx.app/Data 此目录是只读的。
 在Android下对应为:/data/app/package name-1/base.apk APK程序包路径。
3)Application.StreamingAssets
在Unity下对应为:/Assets/StreamingAssets,该目录下全部资源原封不动打包。
在Android下对应为:jar:file:///data/app/xxx.apk!/assets。使用"Application.streamingAssetsPath"进行访问。
在iOS下对应为:Application/…/xxx.app/Data/Raw。使用"file://{Application.streamingAssetsPath}“进行访问
在移动平台下,是只读的,不能写入数据,其他平台下可以使用System.File类进行读写。
在任意平台都可以使用AssetBundle.LoadFromFile来从此文件夹读取加载AB包。
4)Application.persistentDataPath
对应的是应用持久化数据存储文件夹路径。应用更新、覆盖安装时,这里的数据都不会被清除。(可读可写
在Unity下对应为:/该Unity项目文件夹路径。
在Android下对应为:/…/data/应用名/files。
在iOS下对应为:Application/…/Documents。
我们一般将下载的AssetBundle存放于此。移动平台可以使用”{Application.persistentDataPath}/{Application.productName}/"进行访问,非移动平台直接使用Application.persistentDataPath即可访问。
5)Application.temporaryCachePath
iOS会自动将persistentDataPath路径下的文件上传到iCloud,会占用用户的iCloud空间。如果persistentDataPath路径下的文件过多,苹果审核可能被拒,所以,iOS平台,有些数据得放temporaryCachePath路径下。

2.加载、卸载资源API

加载资源的时候必须先加载它依赖的资源,否则会出现引用丢失异常。
可以通过总manifest文件获取AB包的依赖关系,预加载依赖的AB包。

加载AB的API

AssetBundle.LoadFromFile(path):同步加载,path为本地路径
AssetBundle.LoadFromFileAsync(path):异步加载
AssetBundle.LoadFromMemory(byte[] binary):从字节数组加载,binary为目标ab二进制流
AssetBundle.LoadFromMemoryAsync(byte[] binary):异步加载
AssetBundle.LoadFromStream(Stream stream):从流中加载AB包
AssetBundle.LoadFromStreamAsync(Stream stream):异步加载
WWW.assetBundle:www加载(已过时)
UnityWebRequest.GetAssetBundle(string uri):url为ab文件路径,可为本地,也可为云端,

从AB中加载具体的Asset的API

assetBundle.LoadAsset<T>(name):T为目标资产类型,name为资产名称,会返回一个T实例
assetBundle.LoadAssetAsync<T>(string name):异步加载
assetBundle.LoadAsset(name,type):name为资产名,type为资产类型
assetBundle.LoadAssetAsync(name,type):异步加载
assetBundle.LoadAllAssets<T>():T为目标资产类型,会返回一个assetBundle中所有T类型资产数组
assetBundle.LoadAllAssets():加载assetBundle中所有资产,返回一个assetBundle中所有资产数组(object)

卸载AB和Asset的API

Resources.UnloadAsset(obj) : 释放指定资源
Resources.UnloadUnusedAssets():再切场景或者内存峰值的时候调用,卸载没有被引用的资源。
assetBundle.Unload(false):释放AB自己,不会影响已经加载的资源。
assetBundle.Unload(true):不但会释放自己,还会释放所有从AB加载的资源。
AssetBundle.UnloadAllAssetBundles(bool unloadAllObjects):卸载所有资源,消耗较大,不建议调用。

注意:assetbundle.Unload(bool unloadAllLoadedObjects);
unloadAlLoadedlObjects:表示是否卸载所有加载的资源。
参数为false时,AssetBundle内的文件内存镜像会被释放,实例化的物体还都保持完好。简单的说就是断开了AssetBundle内存镜像和实例之间的联系。
如果再次实例化对象,也不会返回以前初例化过的AssetBundle内存镜像,而是重新实例化一个新的AssetBundle内存镜像,那么这样就出现了冗余,同样的资源,内存中会出现多份。

参数为true时,就简单多了,卸载AssetBundle,并且删除被引用的资源。如果这时AssetBundle中有资源在场景中被引用,则会出现资源丢失的情况。这种卸载方式,最为彻底,完全从内存移除,缺点是你需要一套机制(目前流行的是引用计数),来关注是不是还有资源引用,会不会引起异常。

AssetBundle.UnloadAllAssetBundles(bool unloadAllObjects):unloadAllObjects:是否卸载所有加载的资源,如果为true,则会卸载所有资源,包括正在使用着(被依赖)的资源。如果为false,则会卸载未被依赖的资源,被其他资源依赖的资源不会被卸载。

3.内存分析

分析资源在内存中的生命周期。先贴出两张很经典的图。

可以看到,AB包先要从硬盘或者网络中加载并解压到内存中,产生AssetBundle文件内存镜像(紫色部分)。
通过assetBundle.Load将单个资源加载到内存中(虚线框绿色部分)。
有的资源通过复制,需要实例化(GameObject),有的资源通过引用(shader等)。
GameObject资源通过Destroy释放。
Resources.UnloadAsset(obj) , 释放绿色区域指定资源 。
Resources.UnloadUnusedAssets(),释放绿色区域没有被引用的资源。
调用assetbundle.Unload(false)会卸载AssetBundle文件内存镜像镜像,其加载的Asset不会被释放。
调用assetbundle.Unload(true)会卸载AssetBundle文件内存镜像镜像,其加载的Asset也会被释放。容易造成引用丢失(场景中出现粉红色)。

总结:
1)对于加载完后不再需要主动和被动依赖加载的资源,在加载完成后调用AssetBundel.Unload(false),立即释放掉AssetBundle资源,当资源使用完毕,再用Resources.UnloadAsset()或Resources.UnloadUnusedAssets()释放资源内存。
2)Resources.UnloadUnusedAssets()有较大消耗,尽量减少调用次数。
3)不能调用AssetBundle.Unload(false)后再调用AssetBundle.Unload(true).
4)不再使用的GameObject直接Destroy即可。
5)对于shader、通用图集等常驻且需要保留依赖关系的资源,在合适时机加载进来,不调用AssetBundle.Unload()接口。
6)对于界面等存在明确生命周期,又可能动态加载的资源,在生命周期结束后调用AssetBundle.Unload(true),将全部资源一起释放。
7)AssetBundle.Unload(true)在使用中,最好的做法是给创建出来的实例都添加计数,当计数不为0时,表示场景或代码中仍有引用,而当计数为0时,表示没有引用了,这样就可以放心大胆的AssetBundle.Unload(true)了。

具体内存分析可参考:Unity5-ABSystem(五):AssetBundle内存


总结

本文只是粗糙的记录了笔者在学习Unity资源管理过程中遇到的一些知识点,具体每一个知识点有待深剖。
如有错误,希望大家在评论区指出,一起学习进步。

参考链接:
Unity官方文档
王江荣-Unity-Asset简介
Unity5-ABSystem AssetBundle原理
UWA-你应该知道的AssetBundle管理机制
烟雨-AssetBundle热更新完整工作流与知识点解析
Unity——浅谈AB包(AssetBundle)
林新发-3种资源加载方式

Unity AssetBundle学习笔记相关推荐

  1. Unity Shader 学习笔记(33) 全局光照(GI)、反射探针、线性空间和伽马空间、高动态范围(HDR)

    Unity Shader 学习笔记(33) 全局光照(GI).反射探针.线性空间和伽马空间.高动态范围(HDR) 参考书籍:<Unity Shader 入门精要> [<Real-Ti ...

  2. 【Unity ASE学习笔记】

    Unity ASE学习笔记 一.工具比较 二.ASE插件工具下载 三.ASE界面 主要工作区详解 四.节点 常用节点概览 4.1.贴图节点 4.2.常数节点 4.3.四则运算(+ - * /) 4.4 ...

  3. Unity DOTS 学习笔记1 - ECS 0.50介绍和安装

    Unity DOTS 学习笔记1 - ECS 0.50介绍和安装 为什么学习这个技术 ECS的全称为Entity Component System,是最早由暴雪在GDC2017上提出的一个新的游戏设计 ...

  4. Unity 2D 学习笔记:游戏实例Sunnyland

    Unity 2D 学习笔记:游戏实例Sunnyland 01安装软件&导入素材 02编辑素材&Tilemap 03图层layer&角色建立 04角色移动 05角色方向& ...

  5. Unity Shader 学习笔记(3)URP渲染管线带阴影PBR-Shader模板(ASE优化版本)

    此 Shader 已经不是最新版本,最新版本见本专栏的第四篇文章: Unity Shader 学习笔记(4) 材质面板截图: 功能实现(URP渲染管线下): PBR材质.投射和接收阴影. 代码展示: ...

  6. Unity Shader 学习笔记(27)渲染轮廓线(描边)方法、卡通风格渲染、素描风格渲染

    Unity Shader 学习笔记(27)渲染轮廓线(描边)方法.卡通风格渲染.素描风格渲染 参考书籍:<Unity Shader 入门精要> 渲染轮廓线(描边) 五种方法: 基于观察角度 ...

  7. Unity DOTS 学习笔记2 - 面向数据设计的基本概念(上)

    上一章,我们安装了ECS套件,也进行了一些介绍,但是比较笼统.没有一些基础知识储备,很难开始编写代码.本章首先翻译和整理了部分Unity官方的DOTS知识,需要对面向数据有更深刻的认识. DOD知识准 ...

  8. 【Unity】Unity Shader学习笔记(二)渲染管线

    文章目录 渲染管线(Randering Pipeline) 渲染流程 可编程渲染管线 应用阶段 把数据加载到显存中 设置渲染状态 调用DrawCall 几何阶段.光栅化阶段 渲染管线(Randerin ...

  9. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板

    写在之前 Shader变体.Shader属性定义技巧.自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用 ...

最新文章

  1. 极米亮相CES展 首推3000元内1080p无屏电视
  2. 【深度解析】FPGA四大设计要点
  3. P3605 [USACO17JAN]Promotion Counting晋升者计数
  4. NumPy-快速处理数据
  5. python bottle web框架上传静态文件与加载静态文件
  6. oracle常用的监控,oracle常用性能监控及优化语句
  7. NOJ --138 找球号(二)
  8. 08年冬季足协代表VS三水喜健友谊赛
  9. 浅析VB For Each.Next语句
  10. 大数据分析需掌握哪些方面
  11. hervorgehen ( aus ... )
  12. URI和URL的区别和关联
  13. 领取敬业福或新春红包
  14. strongSwan之ipsec.secrets配置手册
  15. android room 分页,Android官方ORM数据库Room技术解决方案简介(一)
  16. 目标级联分析法( Analytical Target Cascading , ATC )理论matlab程序
  17. 苹果开发者注册了邓白氏编码不能用,查询时提示该组织不存在怎么办?
  18. PDF文档用什么软件打开?
  19. 大数据开发工程师目录
  20. 2004年高考数学压轴题(利用对数齐次化或主元法解决)

热门文章

  1. 更多的 Ubuntu 手机即将上市
  2. mssql 数据库审计账户_mssql 数据库所有者
  3. 在中国程序员还是青春饭吗?35岁危机?头发见光,工资不涨?亲身经历!(内容太过真实)
  4. 这样用糯米API,老板再也不叽歪!
  5. Html+Vue实现五子棋游戏(单机版)
  6. 供应链厂商信息-2014
  7. Firefox – 一个开源的浏览器
  8. LED屏显示驱动简述与类型
  9. Dynamically Loaded (DL)
  10. 词袋模型BoW和词集模型SoW比较