聲明:本文介紹的熱更新方案是我在網上搜索到的,然后自己修改了一下,相當於是借鑒了別人的思路,加工成了自己的,在此感謝無私分享經驗的朋友們。
想要使用熱更新技術,需要規划設計好資源比較策略,資源版本,確保增加新資源后可以下載到本地,有資源更新的時候可以替換掉本地舊資源。我在前面寫了一篇“unity 打包AssetBundle”的文章,里面生成了一個資源版本文件,不多解釋了,上圖。至於怎么生成這個文件的,可以看一下我前面寫的文章。
廢話不多說。
先介紹熱更新步驟,后上代碼
步驟一、在Resources目錄下新建一個文本,名稱是bundle_list(后綴是.txt),內容如下:
{"id":0,"version":"1.0","manifest":"android","resource":{}},當然您可以根據自己項目
實際情況來設計json格式。資源服務器上也會有一份格式相同的bundle_list
步驟二、如果是第一次進入游戲,Application.persistentDataPath目錄下還沒有bundle_list文件,這
時候就需要用Resources.Load方法從Resources目錄中加載出來。否則
加載Application.persistentDataPath目錄下的bundle_list
步驟三、從資源服務器下載bundle_list文件
步驟四、獲取本地bundle_list的id和資源服務器下載的bundle_list中的id,做對比,如果前者等於后者,
則不需要更新,如果前者小於后者,則需要更新。
步驟五、分別解析出本地和資源服務器bundle_list中的資源路徑名稱,名稱相同的,對比hash值,相同
則不需要更新,反之,更新。如果資源服務器有的名稱本地沒有,則表示是新增資源,需要
下載到本地。
步驟六、把資源服務器的bundle_list覆蓋本地bundle_list。熱更新完成。
代碼:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Text; using System.IO; using LitJson; /** * 資源增量更新 * 1.檢查本地Application.persistentDataPath目錄中是否有bundle_list文件 * 2.如果沒有,則從Resources目錄中讀取bundle_list文件 * 3.從服務器上下載bundle_list文件,判斷版本是否一致,如果一致就不用更新 * 4.版本不一致,需要更新,更新 * 5.將最新的bundle_list存入Application.persistentDataPath目錄中 **/ public class BundleUpdate : MonoBehaviour { private static readonly string VERSION_FILE = "bundle_list"; private string SERVER_RES_URL = ""; private string LOCAL_RES_URL = ""; private string LOCAL_RES_PATH = ""; /// <summary> /// 本地版本json對象 /// </summary> private JsonData jdLocalFile; /// <summary> /// 服務端版本json對象 /// </summary> private JsonData jdServerFile; /// <summary> /// 本地資源名和路徑字典 /// </summary> private Dictionary<string, string> LocalBundleVersion; /// <summary> /// 服務器資源名和路徑字典 /// </summary> private Dictionary<string, string> ServerBundleVersion; /// <summary> /// 需要下載的文件List /// </summary> private List<string> NeedDownFiles; /// <summary> /// 是否需要更新本地版本文件 /// </summary> private bool NeedUpdateLocalVersionFile = false; /// <summary> /// 下載完成委托 /// </summary> /// <param name="www"></param> public delegate void HandleFinishDownload(WWW www); /// <summary> /// 本次一共需要更新的資源數 /// </summary> int totalUpdateFileCount = 0; void Start() { #if UNITY_EDITOR && UNITY_ANDROID SERVER_RES_URL = "file:///" + Application.streamingAssetsPath + "/android/"; LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/"; LOCAL_RES_PATH = Application.persistentDataPath + "/res/"; #elif UNITY_EDITOR && UNITY_IOS SERVER_RES_URL = "file://" + Application.streamingAssetsPath + "/ios/"; LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/"; LOCAL_RES_PATH = Application.persistentDataPath + "/res/"; #elif UNITY_ANDROID //安卓下需要使用www加載StreamingAssets里的文件,Streaming Assets目錄在安卓下的路徑為 "jar:file://" + Application.dataPath + "!/assets/" SERVER_RES_URL = "jar:file://" + Application.dataPath + "!/assets/" + "android/"; LOCAL_RES_URL = "jar:file://" + Application.persistentDataPath + "!/assets/" + "/res/"; //LOCAL_RES_URL = "file://" + Application.persistentDataPath + "/res/"; LOCAL_RES_PATH = Application.persistentDataPath + "/res/"; #elif UNITY_IOS SERVER_RES_URL = "http://127.0.0.1/resource/ios/" LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/"; LOCAL_RES_PATH = Application.persistentDataPath + "/res/"; #endif //初始化 LocalBundleVersion = new Dictionary<string, string>(); ServerBundleVersion = new Dictionary<string, string>(); NeedDownFiles = new List<string>(); //加載本地version配置 string tmpLocalVersion = ""; if (!File.Exists(LOCAL_RES_PATH + VERSION_FILE)) { TextAsset text = Resources.Load(VERSION_FILE) as TextAsset; tmpLocalVersion = text.text; } else { tmpLocalVersion = File.ReadAllText(LOCAL_RES_PATH + VERSION_FILE); } //保存本地的version ParseVersionFile(tmpLocalVersion, LocalBundleVersion, 0); //加載服務端version配置 StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion) { //保存服務端version ParseVersionFile(serverVersion.text, ServerBundleVersion, 1); //計算出需要重新加載的資源 CompareVersion(); //加載需要更新的資源 DownLoadRes(); })); } //依次加載需要更新的資源 private void DownLoadRes() { if (NeedDownFiles.Count == 0) { UpdateLocalVersionFile(); return; } string file = NeedDownFiles[0]; NeedDownFiles.RemoveAt(0); StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w) { //將下載的資源替換本地就的資源 ReplaceLocalRes(file, w.bytes); DownLoadRes(); })); } private void ReplaceLocalRes(string fileName, byte[] data) { try { string filePath = LOCAL_RES_PATH + fileName; if (!File.Exists(filePath)) { string p = Path.GetDirectoryName(filePath); if (!Directory.Exists(p)) Directory.CreateDirectory(p); } File.WriteAllBytes(filePath, data); } catch (System.Exception e) { Debug.Log("e is " + e.Message); } } //更新本地的version配置 private void UpdateLocalVersionFile() { if (NeedUpdateLocalVersionFile) { if (!Directory.Exists(LOCAL_RES_PATH)) Directory.CreateDirectory(LOCAL_RES_PATH); StringBuilder versions = new StringBuilder(jdServerFile.ToJson()); FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create); byte[] data = Encoding.UTF8.GetBytes(versions.ToString()); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } } private void CompareVersion() { int localVersionId; int serverVersionId; if (jdLocalFile != null && jdLocalFile.Keys.Contains("id")) localVersionId = (int)jdLocalFile["id"]; if (jdServerFile != null && jdServerFile.Keys.Contains("id")) serverVersionId = (int)jdServerFile["id"]; #if UNITY_ANDROID || UNITY_EDITOR NeedDownFiles.Add("android"); #endif #if UNITY_IOS #endif foreach (var version in ServerBundleVersion) { string fileName = version.Key; string serverHash = version.Value; //新增的資源 if (!LocalBundleVersion.ContainsKey(fileName)) { NeedDownFiles.Add(fileName); } else { //需要替換的資源 string localHash; LocalBundleVersion.TryGetValue(fileName, out localHash); if (!serverHash.Equals(localHash)) { NeedDownFiles.Add(fileName); } } } totalUpdateFileCount = NeedDownFiles.Count; //本次有更新,同時更新本地的version.txt NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0; } /// <summary> /// /// </summary> /// <param name="content"></param> /// <param name="dict"></param> /// <param name="flag">0表示本地版本文件,1表示服務器版本文件</param> private void ParseVersionFile(string content, Dictionary<string, string> dict, int flag) { if (content == null || content.Length == 0) { return; } JsonData jd = null; try { jd = JsonMapper.ToObject(content); } catch (System.Exception e) { Debug.LogError(e.Message); return; } if (flag == 0)//本地 { jdLocalFile = jd; } else if (flag == 1)//服務器 { jdServerFile = jd; } else return; //獲取資源對象 JsonData resObjs = null; if (jd.Keys.Contains("resource")) resObjs = jd["resource"]; if (resObjs != null && resObjs.IsObject && resObjs.Count > 0) { string[] resNames = new string[resObjs.Count]; resObjs.Keys.CopyTo(resNames, 0); for (int i = 0; i < resNames.Length; i++) { if (resObjs.Keys.Contains(resNames[i])) dict.Add(resNames[i], resObjs[resNames[i]].ToString()); } } } private IEnumerator DownLoad(string url, HandleFinishDownload finishFun) { WWW www = new WWW(url); yield return www; if (!string.IsNullOrEmpty(www.error)) { Debug.LogError("www.error is " + www.error); yield break; } if (finishFun != null) { finishFun(www); } www.Dispose(); } }