浅谈Unity的资源管理部分

发表于2017-05-18
评论1 6.3k浏览

作者:张昊


前言


Unity资源管理部分的功能很强大,会帮你做好很多东西,但是由于Unity设计目标是超级通用性,所以很多时候你要更多去了解其原理,才能保证使用的正确性和高效性,否则很容易掉坑里。


这里我结合以前看过的Unity4.2的源代码和部分文档,浅谈下Unity的资源管理部分的一些东西。常言道:“庖丁解牛,恢恢乎”,个人认为源代码是唯一没有二义性的文档。


3种资源读取方式

1. Resources:支持编辑器模式和发布模式 

2. AssetDatabase :仅仅支持编辑器模式

3. AssetBundle: 支持编辑器模式和发布模式


接下来,让我们看下这3种资源获取方式的优缺点。


Resources

你可以认为,unity给你提供了一个特定的包Resources。在编辑器模式下,它以文件夹的形式存在,你可以随意放置里边的散文件。在打包过程中,unity则自动帮你把Resources文件夹的所有东西打包。

优点

开发时绝对的方便。

缺点

Resources文件夹下的任何一点点修改,都会导致Resources包的重打。


AssetDatabase

优点

AssetDatabase 在编辑器模式下超级方便。

缺点

在正式发布版不能用,要用AssetBundle来替换。


AssetBundle

优点

你可以根据自己的项目实际情况自定义打包粒度,很好的支持热更新,就连unity 4.0里被大家吐的最多的槽点——资源依赖问题在unity 5.0以上也被完美的解决了。

缺点

AssetBundle从第一天出生就不是给开发环境用的,如果要修改贴图,每一张都得打包再测试。(信不信愤怒的美术同学过来找你,呵呵。)

    

3种资源获取方式的优缺点总结

1. Resources :适合开发模式,不适合热更。

2. AssetDatabase:仅仅支持编辑器模式,在发布版本要用AssetBundle 取代。

3. AssetBundle:很好的支持热更,适合发布模式,但开发模式千万别用。



资源读取异步的支持

unity引擎内部加载异步管理器(C++类: PreloadManager )负责处理所有的异步加载请求(C++类:PreloadLevelOperation,继承自C++ 类AsyncOperation 


注意

1. ResourceRequest  和 AssetBundleRequest 继承自C#的  AsyncOperation,此  AsyncOperation非彼AsyncOperation也。 


2. C#的  AsyncOperation和 C++ 的 AsyncOpera tion名字相同,但这是两个类,C#的单向关联C++。


Application.backgroundLoadingPriority 就是操作的 PreloadManager。网络上有些文章说unity的异步机制是伪多线程,这是不实的。PreloadManager的确是多线程的。


建议

包装下unity的异步操作,用一个优先级桶排队来push引擎异步指令。


每次按照优先级从上述的指令桶中pop,1个或几个指令,按照指令创建AsyncOperation给引擎,当这些AsyncOperation都执行完,再重复操作。这样既可以抑制创建AsyncOperation风暴,又可以做到自定义优先级。


不建议

不要短时间push给unity太多的异步操作,这样会加重unity的负担,同时也不利于我们控制资源的加载优先级。



AssetBundle 爬坑记


这里我重点说下AssetBundle的对外提供的api以及原理。 (AssetBundle的坑点很多,好多人都掉入过。)


Asset是一堆文件的集合,我们可以选择把几个文件(Asset)打包成一个Bundle。Bundle的文件内容包括 所有Asset文件信息索引列表和所有Asset 的data。


索引列表 (伪代码)


你可认为,一个 AssetBundle文件的格式形如(伪代码)


加载一个 Asset:


那如何加载一个AssetBundle ?

Unity 给你提供了4种方式( 注意,坑点来了)


第 1 种方式

第 1 种方式,看着很土。但我告诉你,它只加载文件索引部分到内存,很小的内存啊!你实际加载asset的时候,它才从文件中IO对应的数据块。


第 2 种方式

第 2 种方式,看着很爽(还有个从内存流创建的异步接口)。因为灵活,你给它传递个数据流就可以了,你可以做自定义bundle 加密等等。(你想怎么折腾怎么折腾)


但是没有文件依托,所有的bundle数据是展到内存里的哈哈)等于用内存做了文件的事情!用这种方式再去加载asset的时候会比第一种方式快,代价是牺牲了大量的内存。


第 3 种方式

第 3 种方式,利用http从网络上下载bundle数据,然后将其展到内存里,这和方式二处理文件内存方式类似。


这种方式比较适合网页游戏的开发,或用来做下载。如果用来做游戏运行时的bundle,问题和二一样。


第 4 种方式

第 4 种方式,非常不灵活,它集成了三的下载部分。加载bundle也是只加载文件列表(和一类似),注意版本号大于上次的版本号才会下载更新。url可以是网络地址,也可以是本机地址。


它在使用内存上和一类似,但是函数不够原子,集成了太多的东西。很难用,所以一般被一取代。


4.2的源码,前边两个为二、三服务,最后一个为一、四服务。嘿嘿...)


做手游开发,内存是金贵的。使用二和三的同学,小心进坑阿。)如果你自定义加密资源的需求强烈过内存的占用,你可以把bundle的粒度打得足够小,调用方式二,然后及时的卸载bundle。


有人说,使用第一种方式固然省,但如何做资源压缩和文件内容加密呢?

Unity 5.4可以帮你不压缩文件列表,只压缩Asset的数据。强烈建议Unity在LoadAsset提供口子,允许我们这群码农能够自定义数据过滤器。这样我就可以作自定义加解密等。


使用 AssetBundle 还要注意Bundle的加载依赖,否则会有资源丢失的问题。这块Unity 5.0改进的非常好,并且网上文章很多,这里就不详细介绍了。



资源卸载


卸载第 1 种

当我第一次看见这玩意的时候非常兴奋,因为觉得终于可以单点式释放资源了,无限大地图要求的平滑释放资源的接口有了。


此函数只能释放资源文件,非GameObject文件,否则会报“UnloadAsset may only be used on individual assets and can not be used on GameObject's / Components or AssetBundles”。


另外,我之前在unity5.2.1里测试,发现用Resources.load 加进来的Texture,如果你调用了此函数卸载,然后想通过load再加载回来,是加载不回来的了,编辑器模式下还要重启Unity编辑器才能重新加载此Texture,后来我就再也没用过它了。


大家可以在最新的unity版本测试下此函数。


卸载第 2 种

这个函数才是unity希望你调用的卸载资源的函数。因为unity是基于资源引用的卸载策略。(越过引用的暴力卸载函数显然有些任性了)


Resources.UnloadUnusedAssets( ) 这个函数返回的AsyncOperation,你可能觉得是在异步处理。开始我也觉得很不可思议,因为这玩意在多线程里怎么写,各种状态都会乱啊!我还很有兴致的扒了下4.2的源码,发现这真的是很奇特的事情。


多线程部分的函数为空函数, 主线程同步的函数里做了所有的事情,然后进度从0瞬间跳到1。这和主线程调了个函数不是一样吗?


是不一样的。AsyncOperation是在PreloadManager线程中排队执行的,所以UnloadUnusedAssets放在异步的主线程同步函数里执行,是为了之前队列中的AsyncOperation全部执行完毕再执行UnloadUnusedAssets。


这样防止同时加载和删除的情况,unity在这点做的十分巧妙。也所以说UnloadUnusedAssets不是立即执行的。


这函数很耗,主要开销在两方面,标记资源依赖(MarkAllDependencies)和资源释放。之前我在手机上作过测试,在cache所有资源的情况下,每帧调用UnloadUnusedAssets,所有开销100%是GC.MarkDependencies,红米note2 的 fps只有7~8。所以Resources.UnloadUnusedAssets的调用频率不要太高,而且过一段时间必须要调一次,否则资源放不掉,内存会爆。



尾声


很多人说,unity对新手友好。这话没错,但想用好unity却那么容易的。


侯捷先生在《深入浅出MFC》的书里写到:“勿在浮沙筑高台”,末了我想引用这句话。真正要想把东西做精,基础、内功、 掌控原理才是最重要的。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引