Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大。 Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时 动态加载,可以指定路径和来源的。
开发时候用Resources.Load较简单(放在Resources目录下),主要看发布时候使用的AssetBundle(在StreamAssets目录下)及从服务器下载。
U3D用AssetBundle来动态下载和加载资源,资源包括纹理,模型,动画,声音,场景,文本TextAsset资源(lua数据)等。
各种asset可以打包为assetBundle,上传到服务器。
下载assetBundle并将包含的asset加载到游戏中使用。
一.资源制作过程:
1)BuildPipeline.BuildAssetBundle实现。
二.下载和加载过程:
下载分为缓存机制和非缓存机制:
非缓存机制下下载后的assetBundle不会进入unity引擎特定的缓存区。
非缓存机制:
var www: WWW = new WWW(url);
yield www;
缓存机制:
缓存机制下,会将下载过的assetBundle放置到特定的缓存区下。可以避免重复下载。
var ww: WWW = WWW.LoadFromCacheorDownload(url, 5);
yield www;
下载过程也会对www指向web stream文件存在内存结构,只有www = null或www.dispose()时候将www及www指向的web stream 内存结构卸载掉
。
其中下载下来的www文件数据,unity会用额外decompression buffer来解压存放,当解压出来后还会保留一个decompression buffer,其它的decompression buffer会被清理掉,目的是为了提高内存重用,提高性能。这层逻辑不用管,u3d引擎会自己维护。
加载过程:
1.加载assetBundle到内存
当把assetBundle文件从服务器端下载到本地后,需要将assetBundle加载到内存中并创建assetBundle文件内存对象。
有三种方式加载assetBundle:
1)www.assetBundle属性。
var ww: WWW = WWW.LoadFromCacheorDownload(url, 5);
yield www;
if(www.error != null)
{
return;
}
var myLoadAssetBundle = www.assetBundle; // 通过属性访问来加载一个assetBundle文件对象到内存中。
2)AssetBundle.CreateFromFile
支持从磁盘中创建一个assetBundle到内存中,这个方法仅支持非压缩格式的assetBundle,即创建assetBundle时必须使用UncompressedAssetBundle选项。
3)AssetBundle.CreateFromMemory
可以从内存数据流创建assetBundle内存对象,例如先用解密算法解密资源,然后通过该函数在内存中构建assetBundle内存对象。
2.从assetBundle内存中加载asset资源
asset-Object:原始资源
1)AssetBundle.Load通过名字将asset同步的加载到内存中。
AssetBundle.Load(name): 从AssetBundle读取一个指定名称的Asset并生成Asset内存对象,如果多次Load同名对象,除第一次外都只会返回已经生成的Asset 对象,也就是说多次Load一个Asset并不会生成多个副本(singleton)。
Resources.Load(path&name):同上,只是从默认的位置加载。
2)AssetBundle.LoadAsync通过名字将asset异步的加载到内存中。
AssetBundleRequest request = bundle.LoadAsync("myObject", typeof(GameObject));
yield return request;
GameObject obj = request.asset as GameObject;
3)AssetBundle.LoadAll 将所有assets加载到内存中。
gameobjec类型,clone-object拷贝asset
加载出来的asset,一般使用GameObject.Instantiate接口来实例化出GameObject供U3D直接使用,实例化时候纹理材质一般共享一份,gameObject的位置大小缩放有每个对象自己的数据
。
Instantiate(object):Clone 一个object的完整结构,包括其所有Component和子物体(详见官方文档),浅Copy,并不复制所有引用类型(mesh / texture / material / shader/terrainData/脚本代码
等),值类型transform/脚本实例和脚本数据等可以复制。有个特别用法,虽然很少这样 用,其实可以用Instantiate来完整的拷贝一个引用类型的Asset,比如Texture等,要拷贝的Texture必须类型设置为 Read/Write able。
拓展,动态生成游戏对象的方式:
一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细 节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部assets 都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会包含加载引用 assets的操作,导致第一次加载的lag。AssetBundle.Load是更好的方式,没有lag性能开销,但是也要load后instantiate出来asset使用。
非gameObject类型的资源不需要instantiate,但也有原始资源:
例如:
从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
obj1.renderer.material.mainTexture = AssetBundle1.Load("wall") as Texture;
把obj2的主贴图也指向同一个Texture Asset
obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
释放时候要:
AssetBundle1.Unload(false);//那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
Destroy(obj1);//obj1被释放,但并不会释放刚才Load的Texture
Destroy(obj2);//obj2被释放,但也不会释放刚才Load的Texture
Resources.UnloadUnusedAssets();//这时候刚才load的Texture Asset释放了,因为没有任何引用了
3.卸载assetBundle
(1) 卸载实例化
Instantiate
出来的asset使用:
GameObject.Destroy(),
GameObject.DestroyImmediate()。释放掉clone-asset拷贝的非引用资源,对引用资源对象和对asset原始资源的引用计算减1。非gameObject资源,也会对它关联的资源引用计算减去1(如纹理)。
(2) 卸载asset原始资源
1)用assetBundle.Unload(true)卸载。
2)用
Resource.UnloadUnuseAssets()
来卸载。该接口会卸载掉没有被使用的asset(该原始asset的引用为0),作用效果不仅仅是assetBundle的,而是整个系统的(因为AssetBundle加载asset到游戏引擎中使用时候也归Resource管理),有较大耗性能。
Resources.UnloadAsset(Obj)释放指定的Asset对象资源,只能卸载磁盘文件加载的Asset对象
,不能释放从assetBundle加载的。
(3) 卸载assetBundle内存对象
assetBundle.Unload(false);
只是将assetBundle内存对象卸载掉及减少web stream的引用计数,减少www引用计数,由它加载出来的asset并不受影响。
assetBundle.Unload(true);会将assetBundle内存对象,减少www引用计数,和由它加载出来的assets一起卸载掉。
(4)卸载www内存对象
www = null
或www.dispose()时候,web stream在assetBundle释放后,www释放后,引用计数为0,则GC会将
web stream内存结构卸载掉。
(5)如果要立即释放,则调用
GC.Collect()
否则内存未必会立即被释放,有时候可能导致内存占用过多而引发异常。
系统在加载新场景时,所有的内存对象都会被自动销毁,包括你用AssetBundle.Load加载的对象和Instaniate克隆的,因为这些是非托管的,需要用上述方式释放掉。
游戏进行的过程中尽量减少对GameObject的Instantiate()和Destroy()调用,提升CPU和内存性能,使用游戏对象池,对于暂时不需要的对象用scale = 0,移到屏幕外面,或setActive(false)即可。