Unity 热更新之AssetBundle

这几天刚好看到了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:

 

[csharp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;font-size:14px;color:#660000;">Caching.CleanCache();  
  2.   string SavePath = Application.dataPath + "/StreamingAssets/" ;  
  3.  Object sharedAsset = AssetDatabase.LoadMainAssetAtPath("Assets/models/textures/texture_Orc_Cleaver.png");  
  4.         BuildPipeline.BuildAssetBundle(sharedAsset, null, SavePath + sharedAsset.name + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);  
  5.     AssetDatabase.Refresh();</span>  

2.打包成.unity3d:

 Caching.CleanCache();// 在本地测试的情况下,你可能会频繁的打包生成Assetbundle,如果忘记改版本号的话可能会读取之前的缓存,可能就会看不到新的效果,所以我建议在bunild Assetbundle的时候强制清空一下缓存。

[csharp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;font-size:14px;color:#660000;"string[] path = { "Assets/Test.unity" };  
  2.         BuildPipeline.BuildPlayer(path, "Test.unity3d", BuildTarget.StandaloneWindows, BuildOptions.BuildAdditionalStreamedScenes);  
  3.     AssetDatabase.Refresh();</span>  



资源下载:


从服务器加载AssetBundle(.assetbundle):

 

[csharp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;font-size:14px;color:#333333;">private IEnumerator LoadMainCacheGameObject(string path)  
  2.  {  
  3.        
  4.         WWW www = WWW.LoadFromCacheOrDownload(path, 1);  
  5.         yield return www;  
  6.         if (!System.String.IsNullOrEmpty(www.error))  
  7.         {  
  8.             Debug.Log(www.error);  
  9.         }  
  10.         else  
  11.         {  
  12.             Object main = www.assetBundle.mainAsset;  
  13.             GameObject.Instantiate(main);  
  14.         }  
  15.   }</span>  


      这里只要你把path要是远程服务器所下载的文件地址,比如,我分别试过用jsp和PHP搭建了一个服务器,然后把要下载的文件放到起目录下,然后把这个文件的地址传进来就行!

从本地加载AssetBundle(.assetbundle):

       同样这里只要你把path改为本地文件的地址传进来就行!

  注意:

在这里要注意一点的就是 WWW www = WWW.LoadFromCacheOrDownload(path, Version)的第二个参数Version版本号。这里说说从服务器下载时吧,当版本号为1文件已经缓存到本地,此时将Version改成2 那么它又会重新去服务器下载,如果还保持版本号为1那么它会从本地的缓存读取。

解决资源间的依赖关系:

      当你有多个模型用到同一个材质或者一张纹理时,材质还好,资源大小比较小,可是如果事贴图的话,那重复打包就可能占很多的内存,这时候你要单独给纹理打包成一个AssetBUndle,否者它将会被打包多次,其简单做法如下:

       

[csharp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;font-size:14px;color:#660000;"> Caching.CleanCache();          
  2.         string SavePath =  Application.dataPath + "/StreamingAssets/;  
  3.         BuildPipeline.PushAssetDependencies();  
  4.         // 共享资源texture_Orc_Cleaver.png   
  5.         Object sharedAsset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Textures/  
  6. texture_Orc_Cleaver.png  
  7. ");  
  8.         BuildPipeline.BuildAssetBundle(sharedAsset, null, SavePath + sharedAsset.name + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);  
  9.          
  10.       // Prefab1,引用了texture_Orc_Cleaver.png   
  11.         BuildPipeline.PushAssetDependencies();  
  12.         Object p1Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/Prefabs/P1.prefab");  
  13.         BuildPipeline.BuildAssetBundle(p1Asset, null, SavePath + p1Asset.name + ".assetbundle",  BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
  14.         BuildPipeline.PopAssetDependencies();  
  15.   
  16.         // Prefab2,引用了texture_Orc_Cleaver.png   
  17.         BuildPipeline.PushAssetDependencies();  
  18.         Object p2Asset = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/PreFabs/P2.prefab");  
  19.         BuildPipeline.BuildAssetBundle(p2Asset, null, SavePath + p2Asset.name + ".assetbundle",  BuildAssetBundleOptions.CollectDependencies,       BuildTarget.StandaloneWindows);  
  20.         BuildPipeline.PopAssetDependencies();  
  21.   
  22.         BuildPipeline.PopAssetDependencies();  
  23.   
  24.         EditorUtility.DisplayDialog("""打包完成!""OK");  
  25.         AssetDatabase.Refresh();</span>  


     

[csharp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;"><span style="font-size:18px;color: rgb(51, 0, 51);">    </span><span style="font-size:14px;color:#660000;">OnGUI()  
  2.       {  
  3.         public static readonly string PathURL = "file://" + Application.dataPath + "/StreamingAssets/";  
  4.         if (GUI.Button(new Rect(0f, 30f, 100f, 20f), "Load Share Res"))  
  5.         {  
  6.             StartCoroutine(Load(PathURL +"texture_Orc_Cleaver  
  7. .assetbundle", 1));  
  8.         }  
  9.   
  10.         if (GUI.Button(new Rect(0f, 60f, 100f, 20f), "Load And Instantiate Prefab"))  
  11.         {  
  12.             StartCoroutine(LoadAndInstantiate(PathURL +"P1.assetbundle", 1));  
  13.             StartCoroutine(LoadAndInstantiate(PathURL +"P2.assetbundle", 1));  
  14.         }  
  15.     }  
  16.   
  17.   
  18.     // 加载  
  19.     IEnumerator Load(string url, int version)  
  20.     {  
  21.         WWW www = WWW.LoadFromCacheOrDownload(url, version);  
  22.         yield return www;  
  23.          www.assetBundle.Unload(false);  
  24.     }  
  25.   
  26.   
  27.     // 加载并实例化  
  28.     IEnumerator LoadAndInstantiate(string url, int version)  
  29.     {  
  30.         WWW www = WWW.LoadFromCacheOrDownload(url, version);  
  31.         yield return www;  
  32.   
  33.   
  34.         if (!System.String.IsNullOrEmpty(www.error))  
  35.         {  
  36.             Debug.Log(www.error);  
  37.         }  
  38.         else  
  39.         {  
  40.             Object main = www.assetBundle.mainAsset;  
  41.             GameObject.Instantiate(main);  
  42.         }  
  43.         www.assetBundle.Unload(false);  
  44.     }</span></span>  

       从上面的代码可以看出来,如果很依赖关系比较复杂的话,不仅分析起来麻烦而且代码量也会很多,但是在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()。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值