前端资源管理-前端资源缓存
AssetBundle资源管理流程:
资源资源包
动态内部调用
动态外部调用
Resources 是编辑器环境中项目窗口中的一个文件夹。 可以使用Resources类来调用里面的资源。 打包之后这个文件夹就不存在了,统一生成assets资源。
要使用 AssetBundle,首先将资源打包成一个 assetbundle 文件,然后在本地或网络服务器上动态加载该文件。
1、外部资源导入
外部导入的资源主要包括图片、模型(可能包括动画)、音频等,每种类型的文件类型很多,比如图片包括png、psd、jpeg、tga等,在导入这些文件时,Unity会处理将它们转化成自己的数据格式YAML,保存在Library下,同时在Assets下在资源文件所在位置生成元文件,记录其GUID和一些设置信息,比如FBX文件的设置,以及图片导入后用户配置的Sprite信息。
包含子资源的文件除了GUID外,其子资源还会分配一些FileID,或者LocalID前端资源管理,GUID + FileID在整个Assets下是唯一的。 每个 GUID 都与 Library 下的文件夹一一对应。 在 Library 下添加文件的后缀为 . 在文件的数据字段中)。 通常我们会遇到奇怪的资源问题或者报错问题。 ReimportAll的原理是重新生成Library下的Unity数据格式。
这里比较复杂的是FBX。 导入FBX后,Unity会在Assets下将FBX显示为不可编辑的Prefab。 它的图标是Prefab图标加一个白色方块,里面有SubAssets,也就是子资源。 这些资源如 Mesh 、 AnimationClip 、 Avatar ,显示在不可编辑的 Prefab 下方。
2.内部资源的创建
所有内部创建的资源都是序列化的资源,包括Prefab、Material、Shader、Scene、AnimatorController、OverrideController。 这些文件基本上是用来保存状态信息或者配置信息的。
“序列化”简单来说就是将对象的状态保存在存储设备上,类似于XML或者游戏存档; 反序列化就是从存储设备中读取并恢复对象的状态,这正是Prefab和scene(格式为YAML)的原理。 在Unity中设置文本为ForceText后,用记事本打开这些序列化文件,可以看到这些对象之间的关系,比如引用关系,Inspector面板上的属性等,遵循YAML的语法规则。
深入了解Unity序列化系统后,就可以轻松解决如何查找Prefab使用了哪些组件等问题了。 资源引用的是什么Prefab? 如何实现Prefab嵌套? 等待。 想要深入了解这部分前端资源管理,必然会用到AssetDataBase下面的方法,研究Library文件夹下的文件是什么,研究AssetBundlde的打包过程,阅读官网的YAML ClassID Reference。
3、资源的打包
打包就是生成AssetBundle。 5.X及以上版本流程如下(4.X手动告别,王者荣耀已经从4.6.9升级到5.X),首先用户指定要打包的资源和要放置的资源in Which AssetBundle类型,那么Unity会收集用户指定资源的依赖资源,并跟随这些用户指定资源,然后生成各个AssetBundle的资源对应关系。 然后进入打包阶段,Unity会把Library下的资源以自己的数据格式取出来添加到AssetBundle中,并做一些数据移除操作,比如移除编辑器中用到的数据信息。
我们经常会遇到编辑器下的效果和打包出来的效果不一致的情况。 这个时候我们一般会重新导入。 其实原因是Unity在生成AssetBundle的时候,获取的是Library下已经生成的数据。
包装也涉及很多技术难点。 如何删除冗余资源? 如何规划AssetBundle的粒度? 如何加快打包速度? 如何对AssetBundle进行加密以防止其被UnityStudio轻易转储? 这里的每个问题都可以挑出来写一篇论文,本文不再展开。
4.资源加载
一个好的资源加载系统一般都有三层Cache。 第一层是AssetBundle的Cache,第二层是Asset(或Resource)层,最后一层是GameObject/Material等序列化资源的实例。 我们经常听到的资源对象池(区别于Class的对象池)其实就是第三层。
加载一个资源,第一步是加载资源所在的AssetBundle。 要加载整个AssetBundle,首先要加载AssetBundle所依赖的AssetBundle。 没有必要先加载它,只要在 LoadAsset 之前加载所有依赖的 AssetBundle 即可。 加载就可以了(这里的加载其实就是打开)。 第二部分是从 AssetBundle 加载资源。 单个资源可以直接LoadAsset。 如果包含大量子资源,则需要缓存,避免每次加载LoadAllSubAssets。 一次加载缓存速度更快,比如雪碧。 释放它。 最后一步,如果你加载的资源是序列化类型(GameObject、Material等),那么你加载的资源是只读的,需要一个Instantiate才能使用(最好配合对象池来实现)避免频繁的创建和销毁。未来的费用)。 如果是Texture、Audio,则跳过第三步,直接使用。
上面说的加载可以分为同步加载和异步加载。 异步加载过程可以通过回调、轮询、Coroutine等方式完成,这里不再赘述。 没有完美的加载方案,适合项目的才是最好的。 比如MOBA这种比较即时性的,比如释放技能,马上看到特效,不能使用异步加载,只能使用同步+预加载。 在MMO游戏中,玩家不太关心周围角色的技能释放。 可以使用异步。 逻辑层可以在加载过程中先改变数据,资源加载完成后再修正位置。
很多人在demo或者开发阶段习惯把资源放在Resources下,直接使用Resources.Load(Async)接口加载。 优点是不需要打开AssetBundle,没有冗余资源,缺点是Unity官博说的比较全面,启动慢(创建ResourceManager表),不能直接更新资源,默认是LZMA格式,加载速度慢(5.x某个版本以后,apk整体可以使用LZ4格式),加密难度大等等。
更多的人选择了AssetBundle,虽然在细粒度的情况下AssetBundle的打包速度极慢,虽然资源容易冗余,虽然依赖加载容易出错,虽然Bundle卸载容易造成空指针……在除了认真攻克这几个问题,还有一些纠结的问题,比如第一次启动游戏时要不要复制AssetBundle到PersistentDataPath? 在不同的 AssetBundle 粒度下是 Unload(true) 还是 Unload(false)? 以上问题解决或解决后,AssetBundle的好处就很明显了。 热更新修复BUG(尤其是使用Lua作为脚本的游戏),无需更换安装包即可更新资源,更快的加载速度,直接偏移读取和加密等。
5.资源的卸载
一个很棘手的问题,想象两个极端,如果AssetBundle粒度很细,每个AssetBundle只包含一个资源,那么LoadAsset后直接Unload(false)。 另一个极端是AssetBundle的粒度非常粗。 所有资源都放在一个AssetBundle中,所以AssetBundle无法卸载。 当然,这个庞大的AssetBundle并不需要在游戏运行时多次加载。 对比关系如下表所示:
AssetBundle 的粒度很细(每个资源一个 AssetBundle),也很粗(一个 AssetBundle 包含所有资源)
I/O开销多次加载/卸载(取决于Asset缓存时间),一次Load发热严重,永久,后续IO开销相对较小
内存开销小,加载后立即Unload大,常驻AssetBundle
patch size小,Patch只包含变化的资源,每次都需要完整更新AssetBundle。
重要数据加密加密成本小,基本不能加密(Offest或加密TextAsset)
我们的项目一般介于两者之间,所以设计粒度要适合项目。 Unload一般有两种方式。 一种是自己做引用计数,要求上层开发严格按照提供的接口Get和Release保证正确的引用关系,然后周期性扫描引用计数为0的Asset卸载Asset(序列化resources cannot be Unloaded. ), 同理卸载AssetBundle。 另一种方法是粗粒度管理,使用WeakReference,适合小型AssetBundle,使用Resources.UnloadUnusedAssets定期清理。