AssetBundle使用方式
上一篇文章涵盖了AssetBundle基础,包括各种底层的加载API。这篇文章主要讨论AssetBundles实践中的各种问题和潜在的解决方案。
管理加载的Assets
仔细控制加载Objects大小和数量在内存敏感的环境中是非常重要的。当从活动场景中移除Objects时,Unity不会自动卸载Objects。Asset的清理会在特定时间触发,也可以手动清理。
AssetBundles自身也要小心管理。由本地存储(Unity缓冲或通过AssetBundle.LoadFromFile加载的)支持的AssetBundle拥有最小的内存开销,很少消耗超过几十kb的。但是大量的AssetBundles出现的时候,这个消耗还是会有问题的。
大多数项目允许玩家多次体验内容(如副本),那么知道何时加载和卸载AssetBundle就显得非常重要了。如果AssetBundle不恰当的卸载了,那么可能造成双份Object在内存中。不恰当的卸载AssetBundle在某些情况下可能造成奇怪的现象,比如图片纹理丢失。要弄明白这些原因,请看Assets, Objects, and serialization。
管理assets和AssetBundles最重要的是明白AssetBundle.Unload参数true和false的区别(unloadAllLoadedObjects参数)。
调用这个API会卸载AssetBundle的头部信息。unoadAllLoadedObjects参数决定了是否要卸载从AssetBundle中实例化的所有对象。如果是true,从AssetBundle创建出来的所有Objects会被立即卸载,即使是当前活动场景正在使用的Objects。
举个例子,假设材质M是从AB AssetBundle中加载的,M在当前活动场景中使用。
如果AssetBundle.Unload(true)被调用,M会从场景中移除,卸载并销毁。如果调用AssetBundle.Unload(false),AB的头部信息会被卸载,但M仍然会保存在场景中。调用AssetBundle.Unload(false)会打断M和AB间的链接。如果AB后面重新加载,会将AB中的对象的新副本加载到内存中。
如果AB重新加载一次,AssetBundle的新的头部信息拷贝会重新加载。然而M不是从这份新的AB副本中加载的。Unity不会建立M与这个新的AB拷贝链接关系。
如果调用AssetBundle.LoadAsset()去重新加载M,Unity不会将M的旧副本解释成AB的数据实例。因此,Unity会加载一份M的新拷贝,场景中会存在两份相同的M拷贝。
对大多数项目来说,这种行为是不受欢迎的。大多数项目应该使用AssetBundle.Unload(true)和采用一个函数去确保Objects没有重复。两个通用的方法是:
- 在应用程序生命周期中有明确定义的点,在这些时间点上卸载临时资源,比如两个场景切换的时候或加载画面的时候。这是最简单最通用的方法。
- 给单个Objects添加一个引用次数,当所有子Objects没有被引用的时候(引用次数为0)卸载。这就允许应用程序卸载和重新加载Objects的时候不会出现重复内存。
如果应用程序一定要调用AssetBundle.Unload(false),单个的Objects只能用下面两个方法卸载:
- 在场景和代码中清除不想要的Object,然后调用Resource.UnloadUnusedAssets。
- 加载一个没有叠加的场景。这会销毁当前场景中的所有Objects并自动调用Resources.UnloadUnusedAssets。
如果项目有明确定义的点,开发者可以在该点等待Objects的加载和卸载,比如游戏模式的切换,场景切换。这些点应该尽可能多的卸载不必要的Objects和加载新的Objects。
最简单的方式是将这些离散的东西放进场景中,再将场景和相应的依赖打包成AssetBundles。应用程序进入一个加载场景,然后把旧场景中用到AssetBundle卸载,加载新场景中用到的AssetBundle。
以上是最简单的操作步骤,但一些项目需要更复杂的AssetBundle管理。因为每个项目都是不同的,因此没有统一的AssetBundle设计方案。