Unity中資源打包成Assetsbundle的資料整理


最近在研究Unity中關於資源打包的東西,網上看了一堆資料,這里做個整合,說整合,其實也就是Ctrl-C + Ctrl-V,不是原創

首先為了尊重原創,先貼出原創者的文章地址:

http://blog.csdn.net/kenkao/article/details/24290063

http://blog.csdn.net/janeky/article/details/17652021

http://blog.csdn.net/janeky/article/details/17666409

http://blog.csdn.net/janeky/article/details/25923151

 

 

本文原創版權歸 csdn ken老 所有,轉載請詳細注明原創作者及出處,以示尊重!

作者:ken老

原文:http://blog.csdn.net/janeky/article/details/17652021

如果這篇文章對你有幫助,敬請關注作者《Unity手游之路》系列教程。

 

在手游的運營過程中,更新資源是比不可少的。資源管理第一步是資源打包。傳統的打包可以將所有物件制成預設Prefab,打包成場景。今天我們來一起學習官方推薦的Assetbundle,它是Unity(Pro)提供的資源打包策略。利用AssetBundle,可以將幾乎所有的資源都打包封裝,便於客戶端更新下載新的資源。

(轉載請注明原文出處http://blog.csdn.net/janeky/article/details/17652021)

  • 創建AssetBundle

1.創建一個空的Prefab,命名Cube,然后創建一個Cube,將其拉到剛創建好的Prefab
2.新建一個腳本ExportAssetBundles.cs(代碼來自官方文檔),保存在Asset/Editor目錄下

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. //在Unity編輯器中添加菜單  
  2. [MenuItem("Assets/Build AssetBundle From Selection")]  
  3. static void ExportResourceRGB2()  
  4. {  
  5.     // 打開保存面板,獲得用戶選擇的路徑  
  6.     string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "assetbundle");  
  7.   
  8.     if (path.Length != 0)  
  9.     {  
  10.         // 選擇的要保存的對象  
  11.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);  
  12.         //打包  
  13.         BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.StandaloneWindows);  
  14.     }  
  15. }  
這時我們將看到Asset下面出現Build AssetBundle From Selection和Build Scene
3.選中預設Cube,運行Build AssetBundle From Selection。這時會彈出一個保存框,將其命名為cube.unity3d(這里為了測試方便,放在c盤。實際項目中,我們是需要將他們放在web服務器,供所有客戶端下載更新)
4.新建一個場景scene1.unity,上面放置幾個模型,然后保存

5.選中該場景,在之前的ExportAssetBundles.cs腳本中添加打包場景的函數,運行Assets->Build Scene,保存為scene1.unity3d(這里為了測試方便,也放在c盤)

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. [MenuItem("Assets/Save Scene")]  
  2. static void ExportScene()  
  3. {  
  4.       // 打開保存面板,獲得用戶選擇的路徑  
  5.     string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "unity3d");  
  6.   
  7.     if (path.Length != 0)  
  8.     {  
  9.         // 選擇的要保存的對象  
  10.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);  
  11.         string[] scenes = {"Assets/scene1.unity"};  
  12.         //打包  
  13.         BuildPipeline.BuildPlayer(scenes,path,BuildTarget.StandaloneWindows,BuildOptions.BuildAdditionalStreamedScenes);  
  14.     }  
  15. }  

注意事項
a.AssetBundle的保存后綴名可以是assetbundle或者unity3d
b.BuildAssetBundle要根據不同的平台單獨打包,BuildTarget參數指定平台,如果不指定,默認的webplayer

  • 加載AssetBundle

我們通過一個簡單的代碼來演示如何加載assetbundle,包括加載普通asset和場景。

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. using System;  
  2. using UnityEngine;  
  3. using System.Collections;  
  4.   
  5. public class Load: MonoBehaviour  
  6. {  
  7.     private string BundleURL = "file:///C:/cube.assetbundle";  
  8.     private string SceneURL = "file:///C:/scene1.unity3d";  
  9.   
  10.     void Start()  
  11.     {  
  12.         //BundleURL = "file//"+Application.dataPath+"/cube.assetbundle";  
  13.         Debug.Log(BundleURL);  
  14.         StartCoroutine(DownloadAssetAndScene());  
  15.     }  
  16.   
  17.     IEnumerator DownloadAssetAndScene()  
  18.     {  
  19.         //下載assetbundle,加載Cube  
  20.         using (WWW asset = new WWW(BundleURL))  
  21.         {  
  22.             yield return asset;  
  23.             AssetBundle bundle = asset.assetBundle;  
  24.             Instantiate(bundle.Load("Cube"));  
  25.             bundle.Unload(false);  
  26.             yield return new WaitForSeconds(5);  
  27.         }  
  28.         //下載場景,加載場景  
  29.         using (WWW scene = new WWW(SceneURL))  
  30.         {  
  31.             yield return scene;  
  32.             AssetBundle bundle = scene.assetBundle;  
  33.             Application.LoadLevel("scene1");  
  34.         }  
  35.          
  36.     }  
  37. }  

注意事項
a.LoadFromCacheOrDownload 可以指定版本,如果本地版本是新的,將不會從服務器讀取
b.如果是多個資源打包在一起,我們要通過bundle.Load(),加載特定的資源
c.掛載在模型上的腳本也可以一起打包,但是保證腳本在原目錄也要存在,否則加載出來無法運行。關於如何更新腳本,我將放在以后的章節中闡述。

  • AssetBundle依賴關系

如果一個公共對象被多個對象依賴,我們打包的時候,可以有兩種選取。一種是比較省事的,就是將這個公共對象打包到每個對象中。這樣會有很多弊端:內存被浪費了;加入公共對象改變了,每個依賴對象都得重新打包。AssetBundle提供了依賴關系打包。我們通過一個簡單的例子來學習

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. //啟用交叉引用,用於所有跟隨的資源包文件,直到我們調用PopAssetDependencies  
  2.     BuildPipeline.PushAssetDependencies();  
  3.   
  4.     var options =  
  5.         BuildAssetBundleOptions.CollectDependencies |  
  6.         BuildAssetBundleOptions.CompleteAssets;  
  7.   
  8.   
  9.     //所有后續資源將共享這一資源包中的內容,由你來確保共享的資源包是否在其他資源載入之前載入  
  10.     BuildPipeline.BuildAssetBundle(  
  11.         AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"),  
  12.         null, "Shared.unity3d", options);  
  13.   
  14.   
  15.         //這個文件將共享這些資源,但是后續的資源包將無法繼續共享它  
  16.     BuildPipeline.PushAssetDependencies();  
  17.     BuildPipeline.BuildAssetBundle(  
  18.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"),  
  19.         null, "Lerpz.unity3d", options);  
  20.     BuildPipeline.PopAssetDependencies();  
  21.   
  22.   
  23.     這個文件將共享這些資源,但是后續的資源包將無法繼續共享它  
  24.     BuildPipeline.PushAssetDependencies();  
  25.     BuildPipeline.BuildAssetBundle(  
  26.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"),  
  27.         null, "explosive.unity3d", options);  
  28.     BuildPipeline.PopAssetDependencies();  
  29.   
  30.   
  31.     BuildPipeline.PopAssetDependencies();  
我們在程序加載的時候必須保證先加載公共對象。否則,只能是在各個對象加載成功后,再通過程序手動添加進來,比較繁瑣。在實際項目中,由於是團隊開發,對象間的依賴關系通常會比較凌亂,最好在開發周期就定好相關的規范約束,方便管理。
  • 總結

這一節的內容偏實際操作,官方文檔和雨松的blog中已經有更加詳細介紹。如果大家還有不明白的地方,可以結合文檔再實際操作一下。后面的章節,我將花更多的時間介紹核心的內容:資源的增量更新,和代碼程序的更新。

  • 源碼

http://pan.baidu.com/s/1i3BOAPn

  • 參考資料

1.http://www.xuanyusong.com/
2.http://game.ceeger.com/Manual/DownloadingAssetBundles.html

 

來源:http://www.im286.com/thread-11924518-1-1.html

 

Assetbundle 是Unity Pro提供提供的功能,它可以把多個游戲對象或者資源二進制文件封裝到Assetbundle中,提供了封裝與解包的方法使用起來很便利。

1.預設

Assetbundle可以將Prefab封裝起來,這是多么方便啊! 而且我也強烈建議大家將Prefab封裝成Assetbundle,因為Prefab可以將游戲對象身上帶的游戲游戲組件、游戲腳本、材質都封裝在一起。當從服務器上將Assetbundle下載以后直接Instantiate就可以放入游戲中。

試想一下,如果只能將原始的二進制資源文件放在服務器上下載,當資源文件下載完畢后,需要動態的創建游戲對象、然后動態的將腳本綁定在游戲對象、動態的將貼圖賦予游戲對象等等各種動態的操作。。所以強烈建議使用Prefa,不解釋!!!!!

另外,我在舉個例子,因為模型有可能會帶很多動畫文件,那么這樣一組模型資源就可能是多個FBX 文件 和 若干png貼圖文件 材質文件。這時我只需要把原始模型放入Prefab中,它就會包含這個模型的所有組件、甚至包括它的動畫資源、貼圖。那么如下圖所示,Mode就是模型的Prefab文件,那么我僅僅只需要把Mode這個預設打包成Assetbundle即可。 當我在服務器上下載這個Assetbundle並且載入游戲中就可以直接使用了,切換動畫、換貼圖都可以。。

2.二進制文件

也並不是Assetbundle中全都要用預設,Assetbundle它也可以將二進制文件直接封裝在里面,比如圖片、聲音、文本信息等等。

3.場景文件

在Unity中可以將一個場景保存在Scene中,Scene就會包含這個場景中的所有,那能不能把Scene也封裝成Assetbundle中?答案是能,但是它不能在移動平台上用,因為移動平台上是不能更新腳本的,換句話來說就是即使將腳本綁定在Prefab中,然后下載Assetbundle后,所有腳本是不會執行的,后面說另外一種巧妙用法。

4.移動平台

上面MOMO已經將Assetbundle 的使用原理大致介紹了一下 ,我們在談談移動平台。腳本不能更新是移動平台下最大的傷,這就意味着開發者無法繞過App store和 google Play這種在線商店升級應用程序。唯一能做到的就是更新資源、舉個例子,游戲中在處理版本升級時,一般會有個大版本號和一個小版本號,大版本號就是 2.0、3.0這種 版本需要在AppStore中更新,大版本主要是升級游戲腳本,然后當小版本號,比如2.0.1 或2.0.2這種只是更新游戲中的資源,通過自己游戲的服務器就可以完成,通過Assetbundle在自己服務器上下載,然后適應在游戲中。如果非要更新腳本,或不得不更新腳本那么只能在Appstore或者google Play去更新大版本。

移動平台上不能更新腳本,那么Prefab上綁定的腳本怎么辦?在任何平台上都可以把腳本添加到Prefab上,然后打包成Assetbundle,只有移動平台上有點特殊,比如將Test.cs這條腳本綁定在Prefab中,最后程序通過服務器下載這個Assetbundle ,當載入工程中這條腳本是不會被執行的。

但是如果本地工程有Test.cs這條腳本,那么Unity會自動將這條腳本綁定在下載的Prefab中,並且他們執行的非常好。如果本地工程中沒有Test.cs這條腳本,那么Prefab上的腳本是永遠都不會執行的。有時我們會在腳本中寫一些Public的變量,有可能不同的Prefab上綁定的是相同的腳本,只是Inspector 腳本中的public參數不同。別擔心這一點Assetbundle 中的Prefab也是沒問題,所以說只要大版本中的腳本沒問題,在小版本中只更新游戲資源是一點問題都么有的。

5.移動優化

之前我們說過可以將游戲中的某個游戲對象封裝成Assetbundle,也可以將游戲中的整個場景也封裝成Assetbundle。但是我認為需要巧妙的使用封裝場景,因為場景中肯定有很多公用的模型,如果打包場景的話那么內存與size就是 公用模型的size * N個場景,想想其實挺恐怖的。其實我們可以巧妙的使用,首先把場景中公用的部分和私有的部分統統放入Unity, 然后烘培整個場景。 當場景烘培完畢后把公用的模型部分在拿出去,場景只只保留私有的模型。還可以做一個工具將公用模型在場景中的坐標保存在XML中(每個場景文件會對應一個公用模型的XML信息),最后在將公用的模型分別封裝在別的Assetbundle中。

服務器上提供每個場景的Assetbundle ,和公用模型的Assetbundle,一般公用模型的Assetbundle可以放在常駐內存中(可能使用頻繁、根據項目的不同而定)場景Assetbundle下載完畢后,現載入場景然后在根據場景對應的XML信息將公用模型部分動態的在添加到場景中,這樣就完成了一個場景的構建。

6.總結

對游戲中所有資源進行打包,比如按類型分為五個大部分 界面,模型,特效,聲音,場景,腳本。

界面部分:

公用資源包(可復用的資源包)和 每個界面獨有得資源包(不可復用的資源包)統一使用Prefab 打包成.assetbundle 二進制格式。

模型部分:

按角色分類,統一使用Prefab 打包成.assetbundle 二進制格式。 模型部分包括模型文件與動畫文件,每一個模型文件對應一組動畫文件。(如果模型需要換裝還需提供對應換裝的模型與貼圖) ,因為unity4的重定向動畫不支持動態加載,所以目前不需要考慮 不同大小 不同規格 不同性別 的模型重定向動畫。

特效部分: 統一使用Prefab 打包成.assetbundle 二進制格式。

聲音部分: 統一使用Prefab 打包成.assetbundle 二進制格式。

場景部分:場景和前面的有點區別,場景需要導出烘培的光信息並且只能烘培場景之上永遠不動的模型,但是這些永遠不動的模型有可能會同時在多個場景中使用,所以場景烘培完畢后要把重復使用的對象刪除,(運行游戲在動態的加載進來)場景中只保留該場景中永遠不會變的模型,以及烘培的光照信息。 打包場景后會生成.unity3D 二進制格式,它和 assetbundle 打包方式是不同的。(另外,也可以考慮 json xml 二進制 來動態組裝場景)。

腳本部分:如果Prefab上是帶腳本打包Assetbundle的話 腳本是不會被運行的(移動平台), 但是unity有一個技巧,Prefab上的腳本 如果本地有的話它會把本地的同名腳本綁定在Prefab對象上,它會很好的執行。

Prefab打包技巧: Prefab打包時自身是不占多少空間的 <=1KB 但是Prefab上是可以關聯 這五大部分 “界面,模型,特效,聲音,場景,腳本”以及在Hierarchy視圖中 坐標/縮放/旋轉。 關聯這些信息以后就會很大,所以為了避免資源的浪費盡量避免Prefab重復關聯。

一個prefab下面可以同時關聯多個游戲對象 ,這里舉個例子如果你的 Prefab下面放了一個模型 它的大小可能是500k ,在 Prefab下面放了十個完全相同模型 它的大小可能是501k 。 如果Prefab下面放了兩個不同的模型,它的大小可能就會是 500k x 2 的size ,也就是說Prefab與關聯的數量是無關的 。

加密部分: assetbundle 是可以轉換成 字節數組 ,客戶端與服務器約定一組解密 字節數組的算法就可以實現資源加密。

大版本升級:

unity的版本升級其實主要是升級主程序中的腳本。 因為所有的資源都是assetbundle 和 .unity3d 這些資源放在本地或者服務器 解包的方式是完全一樣,所以理論上我們的主程序包的大小可以做到很小,可以很好設置把多少資源放在包里 或者把所少資源放在服務器上。在運行的時候服務端應該把所有 assetbundle 和 .unity3d的資源文件的下載地址列表返回給客戶端。

小版本升級:

小版本升級也就是更新資源,因為不能更新腳本, 在登陸的時候服務端應該把所有 assetbundle 和 .unity3d的資源文件的下載地址列表返回給客戶端。

還有個需要考慮的地方,比如現在大版本是2.0.0 ,小版本已經是2.0.5 ,用戶的手機上是一個1.5.0的包。 此時用戶在打開游戲的時候 應當強制它去appstore中去下載大版本2.0.0 ,當用戶下載完畢后登陸游戲,此時服務器告訴客戶端現在已經是2.0.5的小版本了,這時候客戶端去下載對應小版本的所有 assetbundle 和 .unity3d文件地址列表。

增量更新:理論上增量更新是可行的。因為unity不能更新腳本,所以在處理增量更新的話 需要在代碼中做可以兼容增量更新的可能。



因為Assetbundle這塊的代碼比較多,我還是決定分成兩篇文章來寫,這篇文章先說原理、下篇文章說代碼。歡迎大家來討論!

前幾天我和Unity鑫哥聊天,他告訴我IOS上是無法運行時更新腳本、但是Android上是可以運行時更新腳本,我回家也試了一下但是沒能成功,后來我考慮即使成功了項目中我也不打算那么做,因為這樣Android和IOS 做起來的差別就太多了, 另外Unity商店中有一個處理運行時更新腳本的插件 unityLua 大家可以去研究研究。

 

 

上一次我們學習了如何將資源進行打包。這次就可以用上場了,我們來探討一下手游資源的增量更新策略。注意哦,只是資源哦。關於代碼的更新,我們稍后再來研究。理論上這個方案可以使用各種靜態資源的更新,不僅僅是assetbundle打包的。

(轉載請注明原文地址http://blog.csdn.net/janeky/article/details/17666409

  • 原理

現在的手游安裝有幾種方式。一種是安裝的時候就把程序和資源安裝到本地。另外一種是只安裝程序和少量的必要資源,然后在啟動的時候再把缺少的資源下載完整。手游一般不建議和傳統頁游一樣,在運行過程中加載資源,那樣做會導致用戶體驗會比較差些。上述的兩種安裝模式,在更新資源上本質都是相同的。都是比較服務器資源的版本和本地資源的版本,以確定哪些資源要下載(包括需要更新的和新增的)。

  • 實踐

        1.資源打包。
資源打包之前,要先規划好資源之間的相互依賴關系。把一些共性的東西抽取出來,盡量減少不必要的耦合。一些比較好的做法有,所有物件盡可能做成Prefab,場景上的東西越少越好,“一切都是動態加載”。
        2.生成文件MD5
關於文件的MD5,這里就不詳細描述了。大家可以簡單理解它為一個文件的狀態標記。如果文件有更改,那么它的md5一定是改變的,單純的移動文件是不會更改的。md5驗證還可以起到安全驗證的作用,保證本地文件不被篡改。舉個例子,我們經常從網上上下載軟件時,一般都會給出一個md5值,你下載后,對比一下已下載文件的md5值,就可以知道文件有沒有被篡改。在版本發布時,我們需要對所有打包好的文件計算md5值,然后保存在一個配置文件中。關於這部分的工作,我之前寫過一個可視化小工具(https://github.com/kenro/File_Md5_Generator),現在分享給大家。如果大家覺得有用,記得打星哦:)
        3.版本比較
先加載本地的version.txt,將結果緩存起來。下載服務器的version.txt,與本地的version進行比較,篩選出需要更新和新增的資源
        4.下載資源
依次下載更新的資源,如果本地已經有舊資源,則替換之,否則就新建保存起來

        5.更新本地版本配置文件version.txt

用服務器的version.txt替換掉本地的version.txt。這樣做是為了確保下次啟動的時候,不會再重復更新了。

        6.從本地加載assetbundle進行測試顯示。

這里將一個模型制成Prefab,打包成assetbundle。程序從本地加載后,顯示在場景中

        7.更新服務器的assetbundle,重新生成版本號文件。

        8.重復6的步驟

我們可以驗證,我們的程序不用任何改動,資源已經實現了更新。場景中顯示的已經是最新的模型了。

 

關於上述的流程,我寫了一個小的演示demo。我這里沒有用到web服務器,而是將本地的另外一個文件夾作為資源服務器目錄。這里的目錄只針對windows下的版本進行測試。如果要在手機平台上,需要記得更新相關的路徑。

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.Collections.Generic;  
  4. using System.Text;  
  5. using System.IO;  
  6.   
  7. public class ResUpdate : MonoBehaviour  
  8. {  
  9.     public static readonly string VERSION_FILE = "version.txt";  
  10.     public static readonly string LOCAL_RES_URL = "file://" + Application.dataPath + "/Res/";  
  11.     public static readonly string SERVER_RES_URL = "file:///C:/Res/";  
  12.     public static readonly string LOCAL_RES_PATH = Application.dataPath + "/Res/";  
  13.   
  14.     private Dictionary<string, string> LocalResVersion;  
  15.     private Dictionary<string, string> ServerResVersion;  
  16.     private List<string> NeedDownFiles;  
  17.     private bool NeedUpdateLocalVersionFile = false;  
  18.   
  19.     void Start()  
  20.     {  
  21.         //初始化  
  22.         LocalResVersion = new Dictionary<string, string>();  
  23.         ServerResVersion = new Dictionary<string, string>();  
  24.         NeedDownFiles = new List<string>();  
  25.   
  26.         //加載本地version配置  
  27.         StartCoroutine(DownLoad(LOCAL_RES_URL + VERSION_FILE, delegate(WWW localVersion)  
  28.         {  
  29.             //保存本地的version  
  30.             ParseVersionFile(localVersion.text, LocalResVersion);  
  31.             //加載服務端version配置  
  32.             StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate(WWW serverVersion)  
  33.             {  
  34.                 //保存服務端version  
  35.                 ParseVersionFile(serverVersion.text, ServerResVersion);  
  36.                 //計算出需要重新加載的資源  
  37.                 CompareVersion();  
  38.                 //加載需要更新的資源  
  39.                 DownLoadRes();  
  40.             }));  
  41.   
  42.         }));  
  43.     }  
  44.   
  45.     //依次加載需要更新的資源  
  46.     private void DownLoadRes()  
  47.     {  
  48.         if (NeedDownFiles.Count == 0)  
  49.         {  
  50.             UpdateLocalVersionFile();  
  51.             return;  
  52.         }  
  53.   
  54.         string file = NeedDownFiles[0];  
  55.         NeedDownFiles.RemoveAt(0);  
  56.   
  57.         StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate(WWW w)  
  58.         {  
  59.             //將下載的資源替換本地就的資源  
  60.             ReplaceLocalRes(file, w.bytes);  
  61.             DownLoadRes();  
  62.         }));  
  63.     }  
  64.   
  65.     private void ReplaceLocalRes(string fileName, byte[] data)  
  66.     {  
  67.         string filePath = LOCAL_RES_PATH + fileName;  
  68.         FileStream stream = new FileStream(LOCAL_RES_PATH + fileName, FileMode.Create);  
  69.         stream.Write(data, 0, data.Length);  
  70.         stream.Flush();  
  71.         stream.Close();  
  72.     }  
  73.   
  74.     //顯示資源  
  75.     private IEnumerator Show()  
  76.     {  
  77.         WWW asset = new WWW(LOCAL_RES_URL + "cube.assetbundle");  
  78.         yield return asset;  
  79.         AssetBundle bundle = asset.assetBundle;  
  80.         Instantiate(bundle.Load("Cube"));  
  81.         bundle.Unload(false);  
  82.     }  
  83.   
  84.     //更新本地的version配置  
  85.     private void UpdateLocalVersionFile()  
  86.     {  
  87.         if (NeedUpdateLocalVersionFile)  
  88.         {  
  89.             StringBuilder versions = new StringBuilder();  
  90.             foreach (var item in ServerResVersion)  
  91.             {  
  92.                 versions.Append(item.Key).Append(",").Append(item.Value).Append("\n");  
  93.             }  
  94.   
  95.             FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);  
  96.             byte[] data = Encoding.UTF8.GetBytes(versions.ToString());  
  97.             stream.Write(data, 0, data.Length);  
  98.             stream.Flush();  
  99.             stream.Close();  
  100.         }  
  101.         //加載顯示對象  
  102.         StartCoroutine(Show());  
  103.     }  
  104.   
  105.     private void CompareVersion()  
  106.     {  
  107.         foreach (var version in ServerResVersion)  
  108.         {  
  109.             string fileName = version.Key;  
  110.             string serverMd5 = version.Value;  
  111.             //新增的資源  
  112.             if (!LocalResVersion.ContainsKey(fileName))  
  113.             {  
  114.                 NeedDownFiles.Add(fileName);  
  115.             }  
  116.             else  
  117.             {  
  118.                 //需要替換的資源  
  119.                 string localMd5;  
  120.                 LocalResVersion.TryGetValue(fileName, out localMd5);  
  121.                 if (!serverMd5.Equals(localMd5))  
  122.                 {  
  123.                     NeedDownFiles.Add(fileName);  
  124.                 }  
  125.             }  
  126.         }  
  127.         //本次有更新,同時更新本地的version.txt  
  128.         NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;  
  129.     }  
  130.   
  131.     private void ParseVersionFile(string content, Dictionary<string, string> dict)  
  132.     {  
  133.         if (content == null || content.Length == 0)  
  134.         {  
  135.             return;  
  136.         }  
  137.         string[] items = content.Split(new char[] { '\n' });  
  138.         foreach (string item in items)  
  139.         {  
  140.             string[] info = item.Split(new char[] { ',' });  
  141.             if (info != null && info.Length == 2)  
  142.             {  
  143.                 dict.Add(info[0], info[1]);  
  144.             }  
  145.         }  
  146.   
  147.     }  
  148.   
  149.     private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)  
  150.     {  
  151.         WWW www = new WWW(url);  
  152.         yield return www;  
  153.         if (finishFun != null)  
  154.         {  
  155.             finishFun(www);  
  156.         }  
  157.         www.Dispose();  
  158.     }  
  159.   
  160.     public delegate void HandleFinishDownload(WWW www);  
  161. }  

 

  • 總結

資源更新的原理,本質上都是相似的。我之前也從事過頁游的開發,資源更新流程也類似。所以技術的本質是掌握思維方式,平台和語言都是永遠在變的。我們最后歸納一下流程:比較服務端的資源版本和本地的資源版本,找出需要更新的資源,然后依次下載。如果大家有更好的策略,歡迎分享探討 ken@iamcoding.com。

  • 源碼

http://pan.baidu.com/s/1mgNnR8O

  • 參考資料

Unity3d官網文檔

 

 

 

之前我們已經學過手機游戲的資源熱更新策略了。在實際手游的開發運營中,我們需要經常修復bug,增加新玩法。這些通常都涉及到代碼的更新。unity游戲代碼的更新比較復雜,也存在不同的更新策略,各有優缺點,在不同的平台上做法也不盡相同。這里主要談一些比較常用的策略和各大手機平台上的策略。大家有更好的思路,歡迎探討。

(轉載請注明出處 http://blog.csdn.net/janeky/article/details/25923151

  • 反射

大部分編程語言都是支持反射的,利用反射,可以動態去加載所需的程序。C#也是同樣可以用反射來實現。要實現代碼的更新,我們在項目初期就要做好規划,將一些容易變更的業務邏輯代碼獨立划分。每次更新時,將代碼打包成dll,再打包成資源文件。程序啟動時,檢查更新到客戶端,客戶端通過反射重新加載代碼運行。下面通過一個簡單的demo來演示。

1.在vs中新建一個代碼庫工程,命名為test
2.添加幾個類Scirpt,Scirpt2,Data
3.將這個項目生成DLL,test.dll
4.新建一個unity項目,將DLL倒入到Asset,改名為test.bytes,不然可能會報錯
5.利用我們之前實現過的打包腳本,將test.bytes打包成test.assetbundle。
6.創建CodeUpdate.cs腳本,用於加載代碼資源,反射調用。
7.為了驗證代碼更新后,可以直接加載使用,我們可以更改一下Data.cs的代碼,重復以上過程,可以看到,更新了代碼打包后,我們重新運行游戲,就可以看到效果

Data.cs

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. public class Data  
  2. {  
  3.     private int attr;  
  4.   
  5.     public Data()  
  6.     {  
  7.         attr = 2;  
  8.     }  
  9.   
  10.     public override string ToString()  
  11.     {  
  12.         return attr.ToString();  
  13.     }  
  14. }  

Script.cs

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. public class Script: MonoBehaviour  
  2. {  
  3.     void Start()  
  4.     {  
  5.         Debug.Log("------------------I am script 1");  
  6.         Data data = new Data();  
  7.         Debug.Log("-------------" + data.ToString());  
  8.     }  
  9. }  

CodeUpdate.cs

[csharp]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
 
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System;  
  4.   
  5. public class CodeUpdate : MonoBehaviour {  
  6.       
  7.     private static readonly string DLL_URL = "file:///c:/test.assetbundle";  
  8.   
  9.     void Start () {  
  10.         StartCoroutine(loadDllScript());  
  11.     }  
  12.       
  13.     private IEnumerator loadDllScript()  
  14.     {  
  15.         WWW www = new WWW(DLL_URL);  
  16.         yield return www;  
  17.         AssetBundle bundle = www.assetBundle;  
  18.         TextAsset asset = bundle.Load("test",typeof(TextAsset)) as TextAsset;  
  19.           
  20.         System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(asset.bytes);  
  21.         Type script1 = assembly.GetType("Script");  
  22.         GameObject obj = new GameObject();  
  23.         obj.AddComponent(script1);  
  24.           
  25.         Type script2 = assembly.GetType("Script2");  
  26.         obj.AddComponent(script2);  
  27.     }  
  28. }  
  • 完整安裝包更新

大部分的app更新都是采用完整包更新。在程序啟動的時候,檢查服務器的最新版本,如果比本地的版本要新,就下載服務器的版本,重新安裝替換本地的程序。在IOS平台上,是由App Store來統一管理的。客戶端程序只需檢查版本,跳轉到app store頁面即可。android 平台的更新更靈活,略微復雜。在判斷版本號,確定要更新后,直接就可以下載服務器的最新的apk文件,安裝替換本地的。這里就不演示代碼了。大家先理清楚思路,流程,就容易實現了。

  • LUA腳本更新

LUA一直是一種很神奇的腳本語言,無處不在,服務端,客戶端,大型機,嵌入式設備都能看到它的蹤影。雖然Unity3d官方不支持Lua腳本,但是已經有人寫了c#版本的lua解析器了。我們可以將業務代碼用Lua來實現。每次要更新代碼的時候,只要將lua當做資源文件更新到客戶端,運行即可。

C#版 Lua,有很多個版本,這里選擇雲風他們公司開源的UniLua,大家可以去Githunb下載

https://github.com/xebecnan/UniLua/wiki

  • IOS平台

比較遺憾,IOS是一個封閉的平台,所以它對app程序監管比較嚴格,一般情況下不運行熱更新,每次版本更新都需要提交審核。所以涉及到手游代碼的更新,都是采用完整包更新。LUA腳本更新的方式,有朋友試過說可以(他們一般是在程序上線一段時間后才使用Lua更新)。但是也存在風險的,如果被蘋果發現,是屬於違規的。這里不建議使用。

  • Android平台

目前比較通用的方式是用代碼dll反射更新機制。我們在實際過程中,將穩定不變的底層代碼單獨規划,用作游戲的主程序。全部業務邏輯代碼發布時候,打包成dll,制成資源文件。客戶端下載后,反射加載。只有當底層主程序要更新是,才單獨下載主程序的apk文件,重新安裝替換。平時的代碼更新,可以隨意更新代碼dll

  • 總結

上面說的幾種方式,各有優缺點。在不同的平台上策略也不盡相同。說一下我的經驗:一般是優先發布android版本,有問題隨時熱更新代碼調試。待版本穩定后,發布ios越獄版本。全部穩定后,最后才發布app store。眾所周知,app store的審查周期比較長,有可能他們員工去休個假,幾個星期才審核通過:)。每次審核不通過,又得重新修改提交審查,又是漫長的等待。在游戲界,時間就是生命。我們盡量在android平台上調試版本。

ps.大家有什么好的Unity3d技術點想討論的,歡迎告知,我今后將會多多寫一下大家比較感興趣的實戰內容。

最后祝大家工作順利,項目大賣~。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM