聲明:本文介紹的熱更新方案是我在網上搜索到的,然後自己修改了一下,相當於是借鑑了別人的思路,加工成了自己的,在此感謝無私分享經驗的朋友們。
想要使用熱更新技術,需要規劃設計好資源比較策略,資源版本,確保增加新資源後可以下載到本地,有資源更新的時候可以替換掉本地舊資源。我在前面寫了一篇“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();
}
}
以上是提供了一個熱更新思路,水平有限,有寫的不對和需要優化的地方,還請不吝賜教!