Unity 3D里有两种动态加载机制:
一是Resources.Load;
一是通过AssetBundle;
其实两者本质上没有什么区别。Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时动态加载,可以指定路径和来源的。其实场景里所有静态的对象也有这么一个加载过程,只是Unity后台替你自动完成了。
在一些大型的网络游戏,或者加载比较多的一些场景时,如果要等待所有模型,贴图等各种资源文件加载完毕才能执行游戏,对用户将会是一个很头大的事情。所以就需要用到动态加载,即AssetBundles。比如玩家在进入游戏时先加载一些周围的场景文件,这样不仅可以提高速度还可以减少内存资源的消耗。
AssetBundles是可以把unity3d中你所创建的文件或任何资源导出的一种文件格式,这些文件导出后使用的是一种特定的文件格式(.Unity3d),这些特定格式的文件能在需要的时候加载到场景中。而这些特定的文件格式可以是模型,贴图,声音文件甚至是场景文件,它们是先前就被设计好的文件,所以很容易就可以被下载到你所建立的游戏或场景中来
AssetBundles 可以是任意类型的文件只要是unity3d能识别的资源文件格式,识别主要是以文件扩展名为准,比如.prefab等等。当然如果你想包含自定义的二进制文件,需要命名这些文件为以".bytes"为后缀,Unity将会把这些文件导成TextAssets。
有三个类方法可以用来构建资源包:
BuildPipeline.BuildAssetBundle,
BuildPipeline.BuildStreamedSceneAssetBundle
BuildPipeline.BuildAssetBundleExplicitAssetNames.
当使用上面的三个函数做完准备工作,我们现在就需要去下载这些资源然后加载它们。
推荐使用WWW.LoadFromCacheOrDownload方法用来下载资源。当下载完成你就可以重新得到该资源的相关属性,例如:
function Start ()
{
var www = WWW.LoadFromCacheOrDownload ("http://myserver.com/myassetBundle.unity3d",
5);
yield www;
if (www.error != null)
{
Debug.Log (www.error);
return;
}
var myLoadedAssetBundle = www.assetBundle;
var asset = myLoadedAssetBundle.mainAsset;
}
下载完以后,等于把硬盘或者网络的一个文件读到内存一个区域,这时候只是个AssetBundle内存镜像数据块,还没有Assets的概念。就可以使用这三个不同的函数来加载物体了:
AssetBundle.Load
AssetBundle.LoadAsync
AssetBundle.LoadAll
使用AssetBundle.Unload这个函数可以卸载加载好的资源,这个函数有一个布尔值的参数是用来告诉Unity是否要卸载所有的数据(包含加载的资源对象)或者只是已经下载过的被压缩好的资源数据。如果要在资源包中从你的应用程序要使用一些对象或者你想释放一些内存,可以传递false这个值来卸载已经压缩好了的文件从你的内存中(即.unity3d文件)。如果要完全卸载所有的资源数据,需要传递true这个值,这将会销毁所有的资源包器中加载的资源。
AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。
下载了资源,也加载好了,那么就该在场景中使用Instantiate函数去实例化它了。
Instantiate一个Prefab,是一个对Assets进行Clone(复制)+引用结合的过程,GameObject transform 是Clone是新生成的。其他mesh / texture / material / shader 等,这其中些是纯引用的关系的,包括:Texture和TerrainData,还有引用和复制同时存在的,包括:Mesh/material/PhysicMaterial。引用的Asset对象不会被复制,只是一个简单的指针指向已经Load的Asset对象。
Unity里每个Script都是一个封闭的Class定义而已,并没有写调用代码,光Class的定义脚本是不会工作的。其实Unity引擎就是那个调用代码,Clone一个script asset等于new一个class实例,实例才会完成工作。把他挂到Unity主线程的调用链里去,Class实例里的OnUpdate OnStart等才会被执行。多个物体挂同一个脚本,其实就是在多个物体上挂了那个脚本类的多个实例而已,这样就好理解了。在new class这个过程中,数据区是复制的,代码区是共享的,算是一种特殊的复制+引用关系。
当你Destroy一个实例时,只是释放那些Clone对象,并不会释放引用对象和Clone的数据源对象,Destroy并不知道是否还有别的object在引用那些对象。等到没有任何游戏场景物体在用这些Assets以后,这些assets就成了没有引用的游离数据块了,是UnusedAssets了,这时候就可以通过Resources.UnloadUnusedAssets来释放,Destroy不能完成这个任务,AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全,除非你很清楚没有任何对象在用这些Assets了。
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
// Start a download of the given URL
// 开始从指定路径下载
WWW www = WWW.LoadFromCacheOrDownload (url, 1);
// Wait for download to complete
// 等待下载完成
yield return www;
// Load and retrieve the AssetBundle
// 加载并取回资源包
AssetBundle bundle = www.assetBundle;
// Load the TextAsset object
// 加载文本资源对象
GameObject go = bundle.Load("myGameObject", typeof(GameObject)) as GameObject;
// Instantiate the GameObject
// 实例化该对象
Instantiate(go);
}
Unity不提供自动的一个可以取回所有被下载资源的列表。所以需要我们在脚本中要建立这些资源对象的信息和它们的路径以便我们去查找。
资源包可以作为TextAssets包含脚本但是不会实际执行代码。如果想要在资源包包含用来执行应用程序的代码,需要预先编译,然后使用Mono Reflection class来加载(注意:Reflection在iOS平台不可用)。可以在任何版本的C#IDE编辑器(如:Monodevelop, Visual Studio)或者使用mono/.net 文档编辑器。
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
// Start a download of the given URL
WWW www = WWW.LoadFromCacheOrDownload (url, 1);
// Wait for download to complete
yield return www;
// Load and retrieve the AssetBundle
AssetBundle bundle = www.assetBundle;
// Load the TextAsset object
TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;
// Load the assembly and get a type (class) from it
var assembly = System.Reflection.Assembly.Load(txt.bytes);
var type = assembly.GetType("MyClassDerivedFromMonoBehaviour");
// Instantiate a GameObject and add a component with the loaded class
GameObject go = new GameObject();
go.AddComponent(type);
}
一个常见的错误:
从某个AssetBundle里Load了一个prefab并克隆之:obj = Instantiate (AssetBundle1.Load('MyPrefab”);
这个prefab比如是个npc,然后你不需要他的时候你用了:Destroy(obj);你以为就释放干净了。其实这时候只是释放了Clone对象,通过Load加载的所有引用、非引用Assets对象全都静静静的躺在内存里。这种情况应该在Destroy以后用:AssetBundle1.Unload(true),彻底释放干净。如果这个AssetBundle1是要反复读取的 不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets()把所有和这个npc有关的Asset都销毁。
当然如果这个NPC也是要频繁创建 销毁的那就应该让那些Assets呆在内存里以加速游戏体验。由此可以解释另一个之前有人提过的话题:为什么第一次Instantiate一个Prefab的时候都会卡一下,因为在你第一次Instantiate之前,相应的Asset对象还没有被创建,要加载系统内置的AssetBundle并创建Assets,第一次以后你虽然Destroy了,但Prefab的Assets对象都还在内存里,所以就很快了。
资源加载和释放例子:
从磁盘读取一个1.unity3d文件到内存并建立一个AssetBundle1对象
AssetBundle AssetBundle1 = AssetBundle.CreateFromFile("1.unity3d");
从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
obj1.renderer.material.mainTexture = AssetBundle1.Load("wall") as Texture;
把obj2的主贴图也指向同一个Texture Asset
obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
Texture是引用对象,永远不会有自动复制的情况出现(除非你真需要,用代码自己实现copy),只会是创建和添加引用
继续:
AssetBundle1.Unload(true) 那obj1和obj2都变成黑的了,因为指向的Texture Asset没了
如果:
AssetBundle1.Unload(false) 那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
继续:
Destroy(obj1),//obj1被释放,但并不会释放刚才Load的Texture
如果这时候:
Resources.UnloadUnusedAssets();
不会有任何内存释放 因为Texture asset还被obj2用着
如果:
Destroy(obj2)
obj2被释放,但也不会释放刚才Load的Texture
继续:
Resources.UnloadUnusedAssets();
这时候刚才load的Texture Asset释放了,因为没有任何引用了
最后CG.Collect();
强制立即释放内存。
如何加载一堆大图片轮流显示又不爆掉?
不考虑AssetBundle,直接用www读图片文件的话等于是直接创建了一个Texture Asset。
TLlist fileList;
int n=0;
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
Texture tex = obj.mainTexture;
obj.mainTexture = image.texture;
n = (n>=fileList.Length-1)?0:n;
Resources.UnloadAsset(tex);
}
文档下载地址:http://download.csdn.net/detail/tanmengwen/5262100