Unity 基于 Xlua 與 AssetBundle 的熱更新實(shí)現(xiàn)

最近實(shí)現(xiàn)了 Unity 的熱更新葵袭,這里記錄一下實(shí)現(xiàn)的過程尸饺。


熱更新第二個(gè)關(guān)卡演示

引入 Xlua 框架

熱更新大致可以分為代碼的更新與資源的更新。在代碼更新這部分沈堡,我引入了 XLua 框架静陈,使用 Lua 編寫游戲邏輯。需要更新游戲邏輯時(shí),下載新的 Lua 文件鲸拥,并在運(yùn)行中加載即可拐格。

//在游戲主邏輯啟動(dòng)時(shí),新建LuaEnv刑赶,并添加自定義Loader禁荒,讀取指定lua文件
luaEnv = new LuaEnv();
luaEnv.AddLoader((ref string filePath) =>
{
    //luaNameToFilePath是一個(gè)Dictionary,鍵值分別為lua文件名與lua文件所在的路徑角撞,在新建LuaEnv前初始化
    if(luaNameToFilePath.ContainsKey(filePath))
    {
        string realPath = luaNameToFilePath[filePath];
        filePath = realPath;
        //讀取并返回特定lua文件的所有字節(jié)
        return File.ReadAllBytes(realPath);
    }
    return null;
});

注意以上代碼僅能在編輯器模式下運(yùn)行呛伴,在游戲編譯后,Lua 文件會(huì)打包進(jìn)入 AssetBundle谒所,上文中的自定義 Loader 需要修改為從 AssetBundle 加載 Lua 文件热康。

XLua 的詳細(xì)使用方法,Lua 與 C# 間的通信可以參考官方文檔:XLua: 騰訊開源劣领,基于Unity姐军、Lua的熱更新技術(shù),本文不做過多介紹尖淘。
Unity 中的游戲邏輯通常寫在 Start奕锌,FixedUpdateUpdate村生,LateUpdate 這幾個(gè)方法中惊暴,因此我在 Lua 的主邏輯代碼中,聲明了同名 Function趁桃,并在 C# 端訪問并調(diào)用辽话,這樣就可以只在 Lua 端編寫大部分游戲邏輯。

startFunction = luaEnv.Global.Get<LuaStart>("Start");
fixedUpdateFunction = luaEnv.Global.Get<LuaUpdate>("FixedUpdate");
updateFunction = luaEnv.Global.Get<LuaUpdate>("Update");
lateUpdate = luaEnv.Global.Get<LuaUpdate>("LateUpdate");

private void FixedUpdate()
{
    if(fixedUpdateFunction != null)
    {
        fixedUpdateFunction(Time.fixedDeltaTime);
    }
}
//....其他方法類似

動(dòng)態(tài)創(chuàng)建場(chǎng)景中物體

場(chǎng)景中的物體應(yīng)當(dāng)在運(yùn)行過程中創(chuàng)建卫病,這樣需要更新場(chǎng)景中的部分資源時(shí)油啤,只需要更新對(duì)應(yīng)的資源即可。如果所有物體直接保存在場(chǎng)景中蟀苛,那么對(duì)場(chǎng)景的任何更改都需要更新整個(gè)場(chǎng)景文件益咬。

因?yàn)樵擁?xiàng)目主要是為了了解熱更新的原理,為了節(jié)省工作量帜平,我僅將場(chǎng)景中的部分可互動(dòng)物體(可收集的金幣幽告,鉆石)信息序列化為 Json 文件,并在運(yùn)行過程中動(dòng)態(tài)創(chuàng)建罕模。

打包 AssetBundle

構(gòu)建 AssetBundle 需要使用 BuildPipeline.BuildAssetBundles 方法评腺,需要注意該方法在 UnityEditor 命名空間中帘瞭,該命名空間中的類在編譯后是無法使用的淑掌,因此最好拓展編輯器,并在編輯器拓展腳本中調(diào)用該方法蝶念。

打包 Lua 文件時(shí)的注意事項(xiàng)

另外還有一點(diǎn)需要注意抛腕,Unity 無法識(shí)別后綴為 .Lua 的文件芋绸,需要將 Lua 文件的后綴該為 .txt.bytes ,讓 Unity 將代碼識(shí)別為 TextAsset担敌,才能正確打包進(jìn) AssetBundle 并在運(yùn)行中讀取摔敛。如果在編寫 Lua 代碼時(shí)就使用 .txt.bytes 后綴,很多 Lua 編輯器的功能就無法使用了全封,為了開發(fā)方便马昙,我在編輯器腳本中添加了額外的方法,在構(gòu)建 AssetBundle 時(shí)刹悴,將所有 .Lua 文件復(fù)制到 Unity 中的 Assets 目錄并更改后綴為 .bytes行楞。這樣就可以正常使用 Lua 編輯器進(jìn)行開發(fā),同時(shí)又不影響 AssetBundle 打包土匀。

總 Manifest 文件

在構(gòu)建 AssetBundle 后子房,除了會(huì)生成用戶指定的 AssetBundle,Unity 還會(huì)自動(dòng)生成一個(gè)額外的 AssetBundle就轧,默認(rèn)情況下該 AssetBundle 與輸出路徑最內(nèi)層文件夾名相同证杭,例如我設(shè)定的輸出路徑為:E:\guyu\projects\unity\UnityJumpJump\jump-jump\Assets\StreamingAssets,在該路徑下就會(huì)出現(xiàn) StreamingAssets 以及 StreamingAssets.manifest 文件妒御。

總Manifest文件.png

在該 AssetBundle 中包含名為"assetbundlemanifest"的總 Manifest 文件解愤,總 Manifest 文件記錄了所有 AssetBundle 間的依賴關(guān)系,在運(yùn)行中加載 AssetBundle 時(shí)需要先從該 AssetBundle 讀取 assetbundlemanifest 文件乎莉,確定所有需要加載的 AssetBundle琢歇。在與服務(wù)器簡(jiǎn)歷鏈接并判斷哪些資源需要熱更新時(shí),也需要從本地與服務(wù)器讀取該 AssetBundle梦鉴。

為了保證在運(yùn)行中可以快速找到該 AssetBundle李茫,我在拓展編輯器中添加了代碼,將該 AssetBundle 重命名為“ResourceMap”肥橙,服務(wù)器中也使用相同的名稱魄宏。

運(yùn)行中加載 AssetBundle

AssetBundle 的存放路徑

存放 AssetBundle 的路徑主要有兩個(gè):Application.streamingAssetsPathApplication.persistentDataPath

Application-streamingAssetsPath是流媒體文件路徑存筏,在編輯器下對(duì)應(yīng)的路徑為 Assets/StreamingAssets宠互,該路徑下的文件會(huì)在游戲打包的過程中會(huì)一同打進(jìn)包里,但是在運(yùn)行過程中,該路徑下的文件只能讀取灼伤,不能寫入动遭,通常將游戲初始需要的 AssetBundle 放在該路徑下,在運(yùn)行過程中拷貝到 persistentDataPath券册。
需要注意的是,在安卓平臺(tái)無法直接使用訪問文件的方式從該路徑下讀取文件,需要使用 Networking.UnityWebRequest 訪問烁焙。

Application-persistentDataPath 為可持續(xù)化數(shù)據(jù)路徑航邢,該路徑下允許讀取與寫入文件,通常將游戲存檔文件骄蝇,更新的 AssetBundle 文件存放在該路徑下膳殷,需要注意的是,該路徑只有在游戲運(yùn)行時(shí)才會(huì)存在九火,在編輯器下是不存在的赚窃。這個(gè)路徑的操作就簡(jiǎn)單很多了,直接使用文件操作的方法就去可以在該路徑下讀寫文件岔激。

資源加載模塊

加載資源時(shí)需要避免內(nèi)存中存在重復(fù)的資源考榨,因此使用單例模式實(shí)現(xiàn)資源加載模塊。
資源加載的邏輯是先在 persistentDataPath 下查找 AssetBundle 文件鹦倚,如果文件不存在河质,則在 streamingAssetsPath 下查找,并將文件復(fù)制到 persistentDataPath震叙。
加載資源成功后掀鹅,不論是 AssetBundle 還是 Asset,都應(yīng)當(dāng)緩存在內(nèi)存中媒楼,再次請(qǐng)求時(shí)乐尊,直接從緩存中返回,這樣也可以避免 AssetBundle 重復(fù)加載的問題划址。

從 AssetBundle 中加載場(chǎng)景

從 AssetBundle 中加載場(chǎng)景有些特殊扔嵌,只需要將場(chǎng)景文件所在的 AssetBundle 加載到內(nèi)存中,就可以直接調(diào)用切換場(chǎng)景的方法夺颤,并不需要通過 AssetBundle.LoadAsset 方法從 AssetBundle 中加載場(chǎng)景文件痢缎。

UnityWebRequest assetBundle = UnityWebRequestAssetBundle.GetAssetBundle(path);
yield return assetBundle.SendWebRequest();
AssetBundle scenes = DownloadHandlerAssetBundle.GetContent(assetBundle);
//場(chǎng)景所在AssetBundle的緩存
loadedScenesBundle = scenes;
//封裝的切換場(chǎng)景方法,會(huì)在切換場(chǎng)景前加載場(chǎng)景依賴的資源
StageAssetsLoader.StageSwitcher(sceneName);

資源更新

服務(wù)器配置

該項(xiàng)目中我使用 node.Js 搭建了一個(gè)本地服務(wù)器世澜,啟動(dòng)后監(jiān)聽 8888 端口独旷,服務(wù)器只包含兩個(gè)功能:

  1. 根據(jù)請(qǐng)求 url 中的平臺(tái)和資源名,返回服務(wù)器中的指定文件寥裂;
  2. 如果請(qǐng)求 url 以“/ServerMD5_”開頭嵌洼,則返回對(duì)應(yīng)平臺(tái) ResourceMap(上文提到的包含總 manifest 文件的 AssetBundle) 的 MD5 碼;

客戶端

客戶端在啟動(dòng)時(shí)連接服務(wù)器封恰,獲取服務(wù)器上 ResourceMap 文件的 MD5 碼麻养,同時(shí)與本地 ResourceMap 文件的 MD5 碼進(jìn)行比較,如果兩者一致诺舔,則表示客戶端與服務(wù)器的資源版本一致鳖昌,不需要更新备畦,可以直接進(jìn)入游戲。
如果兩者不一致遗遵,則從服務(wù)器請(qǐng)求 ResourceMap 文件并讀取其中的 assetbundlemanifest萍恕,與本地 assetbundlemanifest 進(jìn)行對(duì)比逸嘀,篩選需要更新的 AssetBundle车要,從服務(wù)器下載指定的 AssetBundle,完成資源更新崭倘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翼岁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子司光,更是在濱河造成了極大的恐慌琅坡,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件残家,死亡現(xiàn)場(chǎng)離奇詭異榆俺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坞淮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門茴晋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人回窘,你說我怎么就攤上這事诺擅。” “怎么了啡直?”我有些...
    開封第一講書人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵烁涌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我酒觅,道長(zhǎng)撮执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任舷丹,我火速辦了婚禮二打,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掂榔。我一直安慰自己继效,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開白布装获。 她就那樣靜靜地躺著瑞信,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穴豫。 梳的紋絲不亂的頭發(fā)上凡简,一...
    開封第一講書人閱讀 49,802評(píng)論 1 290
  • 那天逼友,我揣著相機(jī)與錄音,去河邊找鬼秤涩。 笑死帜乞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筐眷。 我是一名探鬼主播黎烈,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匀谣!你這毒婦竟也來了照棋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤武翎,失蹤者是張志新(化名)和其女友劉穎烈炭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宝恶,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡符隙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垫毙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霹疫。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖露久,靈堂內(nèi)的尸體忽然破棺而出更米,到底是詐尸還是另有隱情,我是刑警寧澤毫痕,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布征峦,位于F島的核電站,受9級(jí)特大地震影響消请,放射性物質(zhì)發(fā)生泄漏栏笆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一臊泰、第九天 我趴在偏房一處隱蔽的房頂上張望蛉加。 院中可真熱鬧,春花似錦缸逃、人聲如沸针饥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)丁眼。三九已至,卻和暖如春昭殉,著一層夾襖步出監(jiān)牢的瞬間苞七,已是汗流浹背藐守。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹂风,地道東北人卢厂。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惠啄,于是被迫代替她去往敵國(guó)和親慎恒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容