Unity3D熱更新實戰(zhàn)演練

作者:朔宇
大家好,給大家介紹一下,封面是我老婆。

前兩篇文章中,我們了解了Unity中的熱更新及熱更新方案Tolua的使用方法,在本文中,我們將會通過一個具體的案例,來了解Unity中熱更新的使用方法。
完整的Tolua熱更新框架讀者可以在基于uGUI+tolua的簡單游戲框架下載:https://github.com/jarjin/LuaFramework_UGUI,并且學(xué)習(xí)。本文中的案例及代碼旨在為讀者提供熱更新思路。
熱更新流程
在unity中,C#部分無法熱更新,我們需要熱更的只有l(wèi)ua代碼部分。項目完成并上線運營時,我們需要部署資源配置列表到服務(wù)器,接下來我們便可以將lua代碼及相關(guān)資源上傳至服務(wù)器。當(dāng)游戲運行時首先需要從服務(wù)器上的資源配置列表中按照我們規(guī)定的查詢方式(如MD5/CRC)來查詢其資源是否為最新,若有新的資源便可更新至本地,更新完成后,啟動游戲。 我們可以從下圖來了解具體的流程:

Demo
和之前一樣,為了讓讀者更清晰的了解unity熱更新,我們通過一個實際的案例來進(jìn)行學(xué)習(xí)和探討。
案例使用的是在Unity3D_ToLua下篇中的跳一跳項目,在文章最后會提供github地址供讀者下載,讀者可配合項目來閱讀本文
在學(xué)習(xí)熱更新之前,我們在之前跳一跳項目的文章基礎(chǔ)上增加一些元素,如下圖所示,我們在開始界面增加一些圖片資源,以進(jìn)行圖片資源的打包:

接下來我們分步研究tolua熱更新
注意:我們的熱更新主要流程以及各種管理器調(diào)用最好使用c#來進(jìn)行編寫,因為這些部分一般來說不會去做更新,使用lua會造成不必要的性能損耗
1.打包資源
我們首先要使用AssetBundle把我們所需的lua文件及資源文件這兩部分打包到StreaminAssets文件夾,打包邏輯代碼在Packager.cs中。
StreaminAssets目錄會隨著Unity最終生成包原目錄打包出去,游戲客戶端可以通過代碼讀取到里面的資源,并且把里面的資源復(fù)制到玩家的手機本地存儲里面 。?
代碼如下,我們根據(jù)不同平臺,把這兩部分的資源進(jìn)行打包:
[MenuItem("LuaFrameWork/Build iPhone Resource", false, 100)]
??? public static void BuildiPhoneResource()
??? {
??????? BuildTarget target;
??????? target = BuildTarget.iOS;
??????? BuildAssetResource(target);
??? }
??? [MenuItem("LuaFrameWork/Build Android Resource", false, 101)]
??? public static void BuildAndroidResource()
??? {
??????? BuildAssetResource(BuildTarget.Android);
??? }
??? [MenuItem("LuaFrameWork/Build Windows Resource", false, 102)]
??? public static void BuildWindowsResource()
??? {
??????? BuildAssetResource(BuildTarget.StandaloneWindows);
??? }
?
然后我們查看效果,菜單欄已經(jīng)又了LuaFrameWork的菜單選項

我們依照需要打包的平臺來選擇,在Project面板中,打包之前的文件目錄如下圖所示

打包后可以看到,生成了StreaminAssets目錄,以及目錄下的打包及依賴文件。
然后我們來看打包模塊的主要邏輯代碼:
public static void BuildAssetResource(BuildTarget target)
{
??? if (Directory.Exists(Tools.DataPath))
??? {
??????? Directory.Delete(Tools.DataPath, true);
??? }
??? string streamPath = Application.streamingAssetsPath;
??? if (Directory.Exists(streamPath))
??? {
??????? Directory.Delete(streamPath, true);
??? }
??? Directory.CreateDirectory(streamPath);
??? AssetDatabase.Refresh();
??? maps.Clear();
??? HandleLuaBundle();
??? HandleResourcesBundle();
??? string resPath = "Assets/" + AppConst.AssetDir;
??? BuildPipeline.BuildAssetBundles(resPath, maps.ToArray(), BuildAssetBundleOptions.None, target);
??? BuildFileIndex();
??? string streamDir = Application.dataPath + "/" + AppConst.LuaTempDir;
??? if (Directory.Exists(streamDir)) Directory.Delete(streamDir, true);
??? AssetDatabase.Refresh();
}
?
這部分代碼中,我們主要看HandleLuaBundle() 和 HandleResourcesBundle()
在HandleResourcesBundle()中,我們對Resources下的素材資源按照Unity的規(guī)則進(jìn)行打包成assetbundle文件:
static void HandleResourcesBundle()
{
??? string resPath = AppDataPath + "/" + AppConst.AssetDir + "/";
??? if (!Directory.Exists(resPath)) Directory.CreateDirectory(resPath);
?
??? AddBuildMap("Prefabs" + AppConst.ExtName, "*.prefab", "Assets/Resources/Prefabs");
?
??? AddBuildMap("Textures" + AppConst.ExtName, "*.jpg", "Assets/Resources/Textures");
??? AddBuildMap("Textures" + AppConst.ExtName, "*.png", "Assets/Resources/Textures");
}
?
HandleLuaBundle()中我們主要做了這幾件事:
(1)在StreamingAssets目錄下面新建lua目錄,用于存放編碼后的lua文件。
(2)遍歷Lua目錄下面所有的lua文件,并且根據(jù)目錄結(jié)構(gòu)創(chuàng)建相應(yīng)目錄樹。 這兩部分代碼過長,可以直接下載github中的項目查看。
(3)將需要的包的文件,統(tǒng)一遍歷計算出MD5,生成到files.txt里面。下面是計算MD5的流程。
public static string md5file(string file)
{
??? try
??? {
??????? FileStream fs = new FileStream(file, FileMode.Open);
??????? System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
??????? byte[] retVal = md5.ComputeHash(fs);
??????? fs.Close();
?
??????? StringBuilder sb = new StringBuilder();
??????? for (int i = 0; i < retVal.Length; i++)
??????? {
??????????? sb.Append(retVal[i].ToString("x2"));
??????? }
??????? return sb.ToString();
??? }
??? catch (Exception ex)
??? {
??????? throw new Exception("md5file() fail, error:" + ex.Message);
??? }
}
for (int i = 0; i < files.Count; i++)
{
??? string file = files[i];
??? string ext = Path.GetExtension(file);
??? if (file.EndsWith(".meta") || file.Contains(".DS_Store")) continue;
??? string md5 = Tools.md5file(file);
??? string value = file.Replace(resPath, string.Empty);
??? sw.WriteLine(value + "|" + md5);
}
?
到這里,我們的打包流程就結(jié)束了,接下來就是更新的流程說明。
2.資源更新
更新邏輯代碼我們放在了GameManager.cs文件中
我們直接來看更新邏輯中的主要部分
IEnumerator OnUpdateResource()
?這這個函數(shù)中我們同樣分步驟來說明:
(1)初始化操作,查找更新地址。
(2)請求更新列表文件files.txt
(3)分析Web服務(wù)器上的files.txt文件內(nèi)容,遍歷檢查本地的文件結(jié)構(gòu)是否完整、MD5是否匹配。
(4)如果MD5不匹配,或本地文件不存在,就創(chuàng)建下載請求。并協(xié)程下載文件(這里我們可以使用線程來下載)
(5)更新完成
最后,在熱更新完成后,我們在GameManager.cs中啟動lua管理器沒執(zhí)行l(wèi)ua邏輯代碼
public void OnResourceInited()
??? {
??????? ResManager.Initialize(AppConst.AssetDir, delegate() {
??????????? Debug.Log("Initialize OK!!!");
??????????? this.OnInitialize();
??????? });
??? }
?
??? void OnInitialize()
??? {
?? LuaManager.Instance.LuaClient.luaState.DoFile("Login.lua");
??????? LuaManager.Instance.LuaClient.CallFunc("Login.Awake", this.gameObject);
??? }
?
具體代碼可以下載GitHub中的項目查看。
到此為止,我們的熱更新流程已經(jīng)全部結(jié)束了,接下來的工作就是解包AssetBundle文件及依賴文件,然后啟動lua虛擬機,運行程序。項目中的解包工作由ResourceManager.cs來負(fù)責(zé)。
然后我們來看具體的效果: 首先我們將生成好的一系列文件上傳至服務(wù)器(這里我上傳到阿里云對象存儲,讀者可以根據(jù)自己的需求和現(xiàn)有的資源上傳,不過要記得更改AppConst.cs中的WebUrl地址)

接下來我們刪除我們的lua文件及圖片資源文件以及StreaminAssets目錄

然后我們生成新的AssetBundle資源


點擊運行后,我們再來查看當(dāng)前的StreaminAssets及AssetBundle


我們lua熱更新相關(guān)的文章到此也告一段落,熱更新流程和普通的更新流程并沒有什么不同,但因為我們使用tolua進(jìn)行邏輯的編寫,更新下來的lua代碼不需要編譯便可以執(zhí)行,這也是熱更新的基本思想及其實現(xiàn)的目的。
這里提示讀者,本文中涉及代碼只是精簡的框架邏輯,主要作為引導(dǎo)及教學(xué)用途,如需要在實際項目中使用希望讀者可以根據(jù)自己的需求進(jìn)行擴展,如需在商業(yè)項目中使用,可以使用:https://github.com/jarjin/LuaFramework_UGUI
本文資源地址:https://github.com/Etnly/ToLuaDemo2
最后,感謝ToLua作者阿蒙及LuaFramework作者駿擎【CP】為Unity3D熱更新作出的貢獻(xiàn)。
想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問?http://levelpp.com/? ? ? ?????
游戲開發(fā)攪基QQ群:869551769? ? ? ? ?????
微信公眾號:皮皮關(guān)