1.名詞解釋:
資源包:點擊 LuaFramework | Build XXX(平台名) Resource,框架會自動將自定義指定的資源打包到StreamingAssets文件夾,這個文件夾下的unity3d文件就是資源包,它是一種u3d自己的壓縮格式,也被稱為AssetBundle包。

資源:資源經過打包成為資源包,如果在游戲里想用資源包里的內容的話,就需要先加載資源包到內存,然后解壓這個資源包,獲取資源包里包含的部分資源。
2.熱更新涉及文件夾

資源目錄:apk文件(安卓安裝文件)其實也是一種壓縮格式,安裝apk之后,安卓系統會為游戲自動創建一個資源目錄,用來存放apk解壓出來的東西。需要特別注意的一點是,資源目錄里的東西是只能讀不能寫的,也就是說,我們不能隨意地從網上下載文件替換資源目錄里的資源,所以我們需要一個可讀可寫的目錄以便我們用下載的資源替換原資源實現熱更新。由於lua文件可以當作一種資源,所以lua里的代碼也是可以熱更新的。資源目錄路徑通過Util.AppContentPath()獲取,安卓上大概是這樣的:jar:file///data/app.com/com.XXX.XXX-1/base.apk!/assets/(root之后才能看到),用編輯器的話,是工程所在文件夾下的StreamingAssets。

數據目錄:安卓創建的用來存放游戲數據用的,可以隨意讀寫。第一次打開游戲的時候,把資源包從資源目錄復制到這里,此后每次啟動游戲都會比較這里的文件和網絡上的資源包,如果不一樣的話,就下載下來替換這里的資源包。數據目錄路徑通過Util.DataPath獲取,安卓上大概這樣data/user/0/com.XXX.XXX-1/files/mygamename/(root之后才能看到),用編輯器的話,會創建在C盤。
網絡資源目錄:存放游戲資源的網址,游戲開啟后,程序會從網絡資源地址下載一些更新的文件到數據目錄。版本控制是通過files.txt實現的,里面存放着資源文件的名稱和md5碼。程序會先下載“網絡資源地址”上的files.txt,然后與“數據目錄”中文件的md5碼做比較,更新有變化的文件。
3.熱更新的三個過程:
復制資源:將“游戲資源目錄”的內容復制到“數據目錄中”(只有在第一次打開游戲的時候會有此過程)
GameManager.OnExtractResource
1 IEnumerator OnExtractResource() { 2 string dataPath = Util.DataPath; //數據目錄 3 string resPath = Util.AppContentPath(); //游戲包資源目錄 4 5 if (Directory.Exists(dataPath)) Directory.Delete(dataPath, true); 6 Directory.CreateDirectory(dataPath); 7 8 string infile = resPath + "files.txt"; 9 string outfile = dataPath + "files.txt"; 10 if (File.Exists(outfile)) File.Delete(outfile); 11 12 string message = "正在解包文件:>files.txt"; 13 Debug.Log(infile); 14 Debug.Log(outfile); 15 if (Application.platform == RuntimePlatform.Android) { 16 WWW www = new WWW(infile); 17 yield return www; 18 19 if (www.isDone) { 20 File.WriteAllBytes(outfile, www.bytes); 21 } 22 yield return 0; 23 } else File.Copy(infile, outfile, true); 24 yield return new WaitForEndOfFrame(); 25 26 //釋放所有文件到數據目錄 27 string[] files = File.ReadAllLines(outfile); 28 foreach (var file in files) { 29 string[] fs = file.Split('|'); 30 infile = resPath + fs[0]; // 31 outfile = dataPath + fs[0]; 32 33 message = "正在解包文件:>" + fs[0]; 34 Debug.Log("正在解包文件:>" + infile); 35 facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message); 36 37 string dir = Path.GetDirectoryName(outfile); 38 if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); 39 40 if (Application.platform == RuntimePlatform.Android) { 41 WWW www = new WWW(infile); 42 yield return www; 43 44 if (www.isDone) { 45 File.WriteAllBytes(outfile, www.bytes); 46 } 47 yield return 0; 48 } else { 49 if (File.Exists(outfile)) { 50 File.Delete(outfile); 51 } 52 File.Copy(infile, outfile, true); 53 } 54 yield return new WaitForEndOfFrame(); 55 } 56 message = "解包完成!!!"; 57 facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message); 58 yield return new WaitForSeconds(0.1f); 59 60 message = string.Empty; 61 //釋放完成,開始啟動更新資源 62 StartCoroutine(OnUpdateResource()); 63 }
下載資源:從網絡比對下載資源
GameManager.OnUpdateResource
1 IEnumerator OnUpdateResource() { 2 if (!AppConst.UpdateMode) { 3 OnResourceInited(); 4 yield break; 5 } 6 string dataPath = Util.DataPath; //數據目錄 7 string url = AppConst.WebUrl; 8 string message = string.Empty; 9 string random = DateTime.Now.ToString("yyyymmddhhmmss"); 10 string listUrl = url + "files.txt?v=" + random; 11 Debug.LogWarning("LoadUpdate---->>>" + listUrl); 12 13 WWW www = new WWW(listUrl); yield return www; 14 if (www.error != null) { 15 OnUpdateFailed(string.Empty); 16 yield break; 17 } 18 if (!Directory.Exists(dataPath)) { 19 Directory.CreateDirectory(dataPath); 20 } 21 File.WriteAllBytes(dataPath + "files.txt", www.bytes); 22 string filesText = www.text; 23 string[] files = filesText.Split('\n'); 24 25 for (int i = 0; i < files.Length; i++) { 26 if (string.IsNullOrEmpty(files[i])) continue; 27 string[] keyValue = files[i].Split('|'); 28 string f = keyValue[0]; 29 string localfile = (dataPath + f).Trim(); 30 string path = Path.GetDirectoryName(localfile); 31 if (!Directory.Exists(path)) { 32 Directory.CreateDirectory(path); 33 } 34 string fileUrl = url + f + "?v=" + random; 35 bool canUpdate = !File.Exists(localfile); 36 if (!canUpdate) { 37 string remoteMd5 = keyValue[1].Trim(); 38 string localMd5 = Util.md5file(localfile); 39 canUpdate = !remoteMd5.Equals(localMd5); 40 if (canUpdate) File.Delete(localfile); 41 } 42 if (canUpdate) { //本地缺少文件 43 Debug.Log(fileUrl); 44 message = "downloading>>" + fileUrl; 45 facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message); 46 /* 47 www = new WWW(fileUrl); yield return www; 48 if (www.error != null) { 49 OnUpdateFailed(path); // 50 yield break; 51 } 52 File.WriteAllBytes(localfile, www.bytes); 53 */ 54 //這里都是資源文件,用線程下載 55 BeginDownload(fileUrl, localfile); 56 while (!(IsDownOK(localfile))) { yield return new WaitForEndOfFrame(); } 57 } 58 } 59 yield return new WaitForEndOfFrame(); 60 61 message = "更新完成!!"; 62 facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message); 63 64 OnResourceInited(); 65 }
加載資源:加載資源包內的資源到內存
PanelManager.CreatePanel
1 public void CreatePanel(string name, LuaFunction func = null) { 2 string assetName = name + "Panel"; 3 string abName = name.ToLower() + AppConst.ExtName; 4 5 ResManager.LoadPrefab(abName, assetName, delegate(UnityEngine.Object[] objs) { 6 if (objs.Length == 0) return; 7 // Get the asset. 8 GameObject prefab = objs[0] as GameObject; 9 10 if (Parent.FindChild(name) != null || prefab == null) { 11 return; 12 } 13 GameObject go = Instantiate(prefab) as GameObject; 14 go.name = assetName; 15 go.layer = LayerMask.NameToLayer("Default"); 16 go.transform.SetParent(Parent); 17 go.transform.localScale = Vector3.one; 18 go.transform.localPosition = Vector3.zero; 19 go.AddComponent<LuaBehaviour>(); 20 21 if (func != null) func.Call(go); 22 Debug.LogWarning("CreatePanel::>> " + name + " " + prefab); 23 }); 24 }
4.可更新的loading界面
前提:
1)如果想更新,則必須把loading界面打包進資源包
2)在第一次打開游戲的時候,下載資源階段之前,還沒有loading界面的資源包,但是此刻無疑是必須顯示loading的
3)生成loading界面的兩種過程:
使用Resource.Load(不可熱更新)
1 public void CreateLoadingPanelFromResource() 2 { 3 _MemoryPoolManager_ memMgr = AppFacade.Instance.GetManager<_MemoryPoolManager_>(ManagerName._Pool_); 4 5 Debug.Log("--------------------Resources加載loading--------------------------------"); 6 7 memMgr.AddPrefabPoolRS("LoadingPanel", 1, 1, true, null); 8 // memMgr.AddPrefabPoolAB ("loading", "LoadingPanel", 1, 1, true, null); 9 10 Transform loadingPanel = _MemoryPoolManager_.Spawn("LoadingPanel", true); 11 loadingPanel.SetParent(GameObject.Find("2DERoot/Resolution").transform); 12 loadingPanel.localPosition = Vector3.zero; 13 loadingPanel.localScale = new Vector3(1, 1, 1); 14 15 _slider = loadingPanel.FindChild("LoadingSlider").GetComponent<Slider>(); 16 _text = loadingPanel.FindChild("LoadingSlider/Text").GetComponent<Text>(); 17 _text.gameObject.SetActive(true); 18 _text.transform.localPosition = Vector3.zero; 19 _slider.value = 5; 20 _text.text = "准備啟動游戲"; 21 _isStartupLoading = true; 22 23 KeyFrameWork.MVC.SendEvent(FWConst.E_ShowLog, "Resources load finish");
使用加載AssetBundle資源包的方式
1 public IEnumerator CreateLoadingPanelFromAssetBundle() 2 { 3 string url = Util.GetRelativePath() + "loading.unity3d"; 4 Debug.Log("--------------------AssetBundle加載Loading--------------------------------" + url); 5 6 7 WWW www = WWW.LoadFromCacheOrDownload(url, 0, 0); 8 9 yield return www; 10 11 AssetBundle bundle = www.assetBundle; 12 GameObject asset = bundle.LoadAsset<GameObject>("LoadingPanel"); 13 Transform loadingPanel = Instantiate<GameObject>(asset).GetComponent<Transform>(); 14 15 //memMgr.AddPrefabPoolRS("LoadingPanel", 1, 1, true, null); 16 //Transform loadingPanel = _MemoryPoolManager_.Spawn ("LoadingPanel", true); 17 18 19 20 loadingPanel.SetParent(GameObject.Find("2DERoot/Resolution").transform); 21 loadingPanel.localPosition = Vector3.zero; 22 loadingPanel.localScale = new Vector3(1, 1, 1); 23 loadingPanel.name = "LoadingPanel(Clone)001"; 24 25 26 _slider = loadingPanel.FindChild("LoadingSlider").GetComponent<Slider>(); 27 _text = loadingPanel.FindChild("LoadingSlider/Text").GetComponent<Text>(); 28 _text.gameObject.SetActive(true); 29 _slider.value = 5; 30 31 _isStartupLoading = true; 32 33 //加載loading完成后再開始lua,保證lua能順利接管loading 34 AppFacade.Instance.StartUp(); //啟動游戲 35 36 KeyFrameWork.MVC.SendEvent(FWConst.E_ShowLog, "AssetBundle load finish"); 37 38 }
在游戲開始的時候使用判斷(Directory.Exists(Util.DataPath) && Directory.Exists(Util.DataPath + "lua/") && File.Exists(Util.DataPath + "files.txt")來確定當前是否是第一次開啟游戲,如果是,則使用Resource.Load,如果不是則使用加載AssetBundle資源包的方式。這里需要着重說下加載AssetBundle資源包時的順序問題,由於ResourceManager需要Manifest關聯文件(用以確定各個資源包之間的引用關系)進行初始化,所以ResourceManager初始化必在下載資源之后,而下載資源時無疑是需要顯示loading的,故順序是:加載AssetBundle資源包-》下載資源-》ResourceManager初始化。在這個順序下,雖然ResourceManager有加載AssetBundle資源包的接口,但是不能用。而加載AssetBundle資源包是需要異步加載的,也就是說,顯示loading需要1秒左右的時間(根據具體loading資源和手機硬件而定),若是需要在lua里接管loading(通過Find函數找到loading界面然后在lua里做一個引用),則必須在loading資源包加載完畢后才能執行lua接管loading的代碼