前言:
这几天刚好看到了Unity内存管理部分,所以写下来让以后自己可以复习下,同时也是分享给大家,看看大家觉得哪里不足,再进行更完善。 说起AssetBundle,大家可能都不陌生吧,它的最大的好处就是热更新!用的到热更新的地方一般是大型的网络游戏,因为他的资源大小可能有2G(大概的数字)以上,这时候AssetBundle就起到很大作用!其他地方能不用它尽量不要用,因为这个用起来很麻烦且有些地方还有局限!比如:
. 资源之间的依赖关系上解决起来颇为让人抓狂,下面会给出讲解。
. 如果场景中肯定有很多公用的模型,打包场景的话那么内存与size就是公用模型的size * N个场景,在这里有个解决的办法:
(1).首先把场景中公用的部分和私有的部分统统放入Unity;
(2)然后烘培整个场景。 当场景烘培完毕后把公用的模型部分在拿出去,场景只只保留私有的模型。还可以做一个工具将公用模型在场景中的坐标保存在Json/XML/二进制文件中(每个场景文件会对应一个公用模型的Json信息),再将公用的模型分别封装在别的Assetbundle中;
(3)最后服务器上提供每个场景的Assetbundle ,和公用模型的Assetbundle,一般公用模型的Assetbundle可以放在常驻内存中(可能使用频繁、根据项目的不同而定)场景Assetbundle下载完毕后,现载入场景然后在根据场景对应的XML信息将公用模型部分动态的在添加到场景中,这样就完成了一个场景的构建。
这里会给出网上有现成的例子,主要讲的是如何把场景里的物体记录Json/XML/二进制文件中去,和去解析文件信息! 链接:http://www.xuanyusong.com/archives/1901和
http://www.xuanyusong.com/archives/1919
. 在移动平台下Assetbundle里面放的脚本是不会被执行的,在手机上将Assetbundle下载到本地后,加载进游戏中Prefab会自动在本地找它身上挂着的脚本,他是根据脚本的名来寻找,如果本地有这条脚本的话,Prefab会把这个脚本重新绑定在自身,并且会把打包前的参数传递进来。如果本地没有,身上挂的条脚本永远都不会被执行。所以动态加载就有一定的局限性。
进入主题:
在这里我会通过简单的小实例,进行资源打包和本地和远程服务器进行动态加载AssetBundle这两个方面来讲解,最后会讲解下上面的资源的依赖和版本上要注意一些的细节!
首选你得会把资源打包.unity3d或者.assetbundle,可能大家会问,这两种文件有什么区别?其实没什么区别,编码格式是相同的,也就是说这两种后缀名其实对应的是同一种文件,因为在打包的的时候,后缀名是自己自定义的!
一般来说assetbundle是按不同平台设置来导出资源的,而.unity3d则只在PC平台导出使用。现在就从这两方面说下怎打包AssetBundle.
资源打包:
这里的资源打包,你可以在运行时手动单个打包,或者批量打包又或者几个打包在一起,也可以在编辑下进行同样的操作,为了简单展示功能,笔者在这进行的是运行时打包,编辑下打包请见连接地址:http://www.xuanyusong.com/archives/2405/ 如有疑惑者,请在下方留言。
1.打包成.assetbundle:
Caching.CleanCache();
string SavePath = Application.dataPath + "/StreamingAssets/" ;
Object sharedAsset = AssetDatabase.LoadMainAssetAtPath("Assets/models/textures/texture_Orc_Cleaver.png");
BuildPipeline.BuildAssetBundle(sharedAsset, null, SavePath + sharedAsset.name + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);
AssetDatabase.Refresh();
2.打包成.unity3d:
Caching.CleanCache();// 在本地测试的情况下,你可能会频繁的打包生成Assetbundle,如果忘记改版本号的话可能会读取之前的缓存,可能就会看不到新的效果,所以我建议在bunild Assetbundle的时候强制清空一下缓存。
string[] path = { "Assets/Test.unity" };
BuildPipeline.BuildPlayer(path, "Test.unity3d", BuildTarget.StandaloneWindows, BuildOptions.BuildAdditionalStreamedScenes);
AssetDatabase.Refresh();
资源下载:
从服务器加载AssetBundle(.assetbundle):
private IEnumerator LoadMainCacheGameObject(string path)
{
WWW www = WWW.LoadFromCacheOrDownload(path, 1);
yield return www;
if (!System.String.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
}
else
{
Object main = www.assetBundle.mainAsset;
GameObject.Instantiate(main);
}
}
这里只要你把path要是远程服务器所下载的文件地址,比如,我分别试过用jsp和php搭建了一个服务器,然后把要下载的文件放到起目录下,然后把这个文件的地址传进来就行!
从本地加载AssetBundle(.assetbundle):
同样这里只要你把path改为本地文件的地址传进来就行!
注意:
在这里要注意一点的就是 WWW www = WWW.LoadFromCacheOrDownload(path, Version)的第二个参数Version版本号。这里说说从服务器下载时吧,当版本号为1文件已经缓存到本地,此时将Version改成2 那么它又会重新去服务器下载,如果还保持版本号为1那么它会从本地的缓存读取。
解决资源间的依赖关系:
当你有多个模型用到同一个材质或者一张纹理时,材质还好,资源大小比较小,可是如果事贴图的话,那重复打包就可能占很多的内存,这时候你要单独给纹理打包成一个AssetBUndle,否者它将会被打包多次,其简单做法如下:
Caching.CleanCache();
string SavePath = Application.dataPath + "/StreamingAssets/;
BuildPipeline.PushAssetDependencies();
// 共享资源texture_Orc_Cleaver.png
Object sharedAsset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Textures/
texture_Orc_Cleaver.png
");
BuildPipeline.BuildAssetBundle(sharedAsset, null, SavePath + sharedAsset.name + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);
// Prefab1,引用了texture_Orc_Cleaver.png
BuildPipeline.PushAssetDependencies();
Object p1Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Prefabs/P1.prefab");
BuildPipeline.BuildAssetBundle(p1Asset, null, SavePath + p1Asset.name + ".assetbundle", BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
BuildPipeline.PopAssetDependencies();
// Prefab2,引用了texture_Orc_Cleaver.png
BuildPipeline.PushAssetDependencies();
Object p2Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/PreFabs/P2.prefab");
BuildPipeline.BuildAssetBundle(p2Asset, null, SavePath + p2Asset.name + ".assetbundle", BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
BuildPipeline.PopAssetDependencies();
BuildPipeline.PopAssetDependencies();
EditorUtility.DisplayDialog("", "打包完成!", "OK");
AssetDatabase.Refresh();
OnGUI()
{
public static readonly string PathURL = "file://" + Application.dataPath + "/StreamingAssets/";
if (GUI.Button(new Rect(0f, 30f, 100f, 20f), "Load Share Res"))
{
StartCoroutine(Load(PathURL +"texture_Orc_Cleaver
.assetbundle", 1));
}
if (GUI.Button(new Rect(0f, 60f, 100f, 20f), "Load And Instantiate Prefab"))
{
StartCoroutine(LoadAndInstantiate(PathURL +"P1.assetbundle", 1));
StartCoroutine(LoadAndInstantiate(PathURL +"P2.assetbundle", 1));
}
}
// 加载
IEnumerator Load(string url, int version)
{
WWW www = WWW.LoadFromCacheOrDownload(url, version);
yield return www;
www.assetBundle.Unload(false);
}
// 加载并实例化
IEnumerator LoadAndInstantiate(string url, int version)
{
WWW www = WWW.LoadFromCacheOrDownload(url, version);
yield return www;
if (!System.String.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
}
else
{
Object main = www.assetBundle.mainAsset;
GameObject.Instantiate(main);
}
www.assetBundle.Unload(false);
}
从上面的代码可以看出来,如果很依赖关系比较复杂的话,不仅分析起来麻烦而且代码量也会很多,但是在2015Unity发布会上,有一位开发者提出了这样一个解决方法:链接地址:http://forum.china.unity3d.com/thread-7197-1-1.html 就是利用构建有向图,找到入度为0和入度大于2的节点,然后在深度优先遍历,可是貌似实现起来蛮困难的,话说我大一学的算法现在都忘得差不多了,在这里就不实现了。
最后的最后:
AssetBundle的四种加载方式的对比:
1.new www() 是通过一个路径进行下载(无论是服务器路径还是本地路径下载操作都一样)但是bundle只能保存在内存中,也就是退出游戏在进入还得重新下,同时AssetBundle默认是压缩格式的,每次下载的时候都需解压缩,这样就会使得其加载比较缓慢。
2.WWW.LoadFromCacheDownload() 页游和网游比较适合,第一次下载解压缩后都会保存在Cache缓存中,这样下次下载就不需解压缩了,速度很快,但是很占用内存,同时他是个异步操作,在有些情况下会有错,比如用户打开某个界面时,UI某个节点下 load模型 。可是用户手比较快,把界面关了。 这样异步加载回来如果不处理肯定就出错了。所以类似这样的地方异步都要处理, 但是同步就没这问题。
3.AssetBundle.CreateFromMemory() 这个是从内存中下载,一般来说在文件需要加密和解密的时候会用。
4.AssetBundle.CreateFromFile() 这个加载速度很快,同时也是个同步操作。
所以建议大家使用AssetBundle.CreateFromFile()。