Unity3D熱更新LuaFramework入門實戰(2)——資源熱更新

熱更新涉及資源熱更新和代碼熱更新(其實lua代碼也是資源),那接下來看看如何動態載入一個模型,然後熱更成其他素材。這一部分涉及資源打包、動態創建資源等內容。

By 羅培羽 (微博 @羅培羽)

2016年6月

1、創建物體

為了調試的方便,筆者先將框架配置為本地模式,待測試熱更新時再改成更新模式。

圖:配置為本地模式

先測試個簡單的創建物體,新建一個名為go的物體,然後設置它的坐標為(1,1,1)。這段代碼雖然不涉及資源載入,但能展示「把物體添加到場景中」的過程。Main.lua的代碼如下:

function Main()tttttntlocal go = UnityEngine.GameObject (go)ntgo.transform.position = Vector3.onettnendn

圖:動態創建一個名為go的空物體

要熱更新資源,便需要製作資源。這裡製作一個名為tankPrefab的坦克模型預設,然後存到Assets/Tank目錄下。接下來對它做打包,然後動態載入。

圖:坦克預設

廣告時間:這個坦克模型是來自筆者即將出版的一本書《Unity3D網路遊戲實戰》。該書通過一個完整的多人坦克對戰實例,詳細介紹網路遊戲開發過程中涉及到的知識和技巧。書中還介紹了服務端框架、客戶端網路模塊、UI系統的架構等內容。相信透過本書,讀者能夠掌握Unity3D網路遊戲開發的大部分知識,也能夠從框架設計中了解商業遊戲的設計思路,感謝大家支持。

2、資源打包

LuaFramework在打包方面並沒有做太多的工作,我們需要手動打包。打開Assets/LuaFramework/Editor/Packager.cs,按照示例的寫法,加上下面這一行:將Assets/Tank目錄下的所有預設(.prefab)打包成名為tank的包。

圖:修改打包代碼

點擊「Build Windows Resource」,即可在StreamingAssets中看到打包好的文件。

圖:坦克資源的包文件

如下圖所示,Unity3D資源包裡面包含多個資源,就像一個壓縮文件一樣。在動態載入的時候,便需要有載入包文件、或取包中的資源兩步操作(框架已經幫我們做好了這部分工作,直接調用API即可)。

圖:Unity3D的資源包

3、動態載入模型

編寫如下lua代碼(main.lua),使用框架提供的資源管理器(resMgr)載入tank包的TankPrefab文件,載入完成後回調OnLoadFinish方法。在OnLoadFinish中使用Instantiate實例化對象。

--主入口函數。從這裡開始lua邏輯nfunction Main()tttttntLuaHelper = LuaFramework.LuaHelper;ntresMgr = LuaHelper.GetResManager();ntresMgr:LoadPrefab(tank, { TankPrefab }, OnLoadFinish);nendnn--載入完成後的回調--nfunction OnLoadFinish(objs)ntlocal go = UnityEngine.GameObject.Instantiate(objs[0]);ntLuaFramework.Util.Log("Finish");tnendn

完成後運行遊戲,即可看到動態載入出來的模型。

圖:動態載入出來的模型

4、載入資源的過程

只有理解了動態載入,即LoadPrefab的過程,才能算是真正的理解了熱更新。LoadPrefab為ResourceManager中定義的方法,在AssetsLuaFrameworkScriptsManagerResourceManager.cs中實現,建議配合代碼看下面的解釋。

LoadPrefab的流程如下所示,先是判定當前是否正在載入該資源包,如果沒有則調用OnLoadAsset載入資源包、然後解包獲取資源、調用回調函數。

圖:LoadPrefab的流程

ResourceManager類定義了m_AssetBundleManifest、m_Dependencies、m_LoadedAssetBundles、m_LoadRequests這4個變數,只要理解了這幾個變數的用途,也就能夠理解了資源載入的全過程了。這4個變數的類型如下:

圖:ResourceManager定義的幾個變數

m_AssetBundleManifest

理解m_AssetBundleManifest之前,需要先理解Unity3D的依賴打包。前面的tank.unity3D中,坦克的預設、坦克的貼圖等資源都被打包到一起,沒有依賴關係(只要打包時不給貼圖單獨打包,Unity3D會自動將預設相關的資源都打包進來)。如下圖所示。

圖:前面載入坦克製作的資源包

假如有兩個坦克預設共用一套貼圖,如果像上面那樣打包,每個坦克預設各自包含一份貼圖,資源會比較大。更好的辦法是將公共貼圖放到一個包里,每個坦克預設不再包含貼圖(如下圖)。這種打包方式下,載入TankPrefab前,需要先載入依賴包common.unity3D,坦克預設才能找到貼圖。

圖:依賴打包

打包後,Unity3D會產生一個名為AssetBundle.manifest的文件(框架會將該文件放在StreamingAssets中),該文件包含所有包的依賴信息。所以在載入資源前需要先載入這個文件,m_AssetBundleManifest便是指向這個包的變數。相關代碼如下:

圖:m_AssetBundleManifest便是指向AssetBundle.manifest的變數

載入這個包後,便可以使用下面的語句獲取某個包所依賴的所有包名,然後載入它們。

string[] dependencies = m_AssetBundleManifest.GetAllDependencies(包名);n

註:更多Unity3D的依賴打包的解釋,可以參見這篇文章:

liweizhaolili.blog.163.com

m_LoadedAssetBundles

字典類型的m_LoadedAssetBundles保存了所有已經載入資源包。如果某個包已經被載入過,那下次需要用到它時,直接從字典中取出即可,減少重複載入。簡化後的代碼如下:

IEnumerator OnLoadAsset(XXX)n{n AssetBundleInfo bundle = GetLoadedAssetBundle(XXX); n if(!bundle)n bundle = OnLoadAssetBundle(名字); n 載入資源n 回調函數處理n}n

其中GetLoadedAssetBundle方法會判斷資源包是否存在於m_LoadedAssetBundles中,並返回資源包。OnLoadAssetBundle為重新載入資源包的方法。

載入資源包後,只需通過 bundle.LoadAssetAsync(資源名,類型)便可載入所需的資源。

m_Dependencies

m_Dependencies記錄了所有已載入資源的依賴包,以便在GetLoadedAssetBundle方法中判斷資源包是否被完全載入(主體包和所有依賴包都被載入才算完成載入)。簡化後的代碼如下:

IEnumerator OnLoadAssetBundle(包名)n{n //獲取依賴包n string[] dependencies = m_AssetBundleManifest.GetAllDependencies(abName);n m_Dependencies.Add(abName, dependencies); //更新依賴表n //載入依賴包n for (int i = 0; i < dependencies.Length; i++)n OnLoadAssetBundle(XXX) n //然後載入主體資源n download = http://WWW.LoadFromCacheOrDownload(包名)n //更新載入表n m_LoadedAssetBundles.Add(XXX)n}n

AssetBundleInfo GetLoadedAssetBundle(包名)n {n //判斷載入表n AssetBundleInfo bundle = m_LoadedAssetBundles[包名];n if (bundle == null) return null;n //判斷依賴包n foreach (string 依賴包名 in m_Dependencies[包名])n {n if (m_LoadedAssetBundles[依賴包名]== null) n return null;n }n return bundle;n}n

m_LoadRequests

m_LoadRequests是一個<string, List<LoadAssetRequest>>類型的字典,LoadAssetRequest的定義如下,它用於非同步載入後的回調。

圖:LoadAssetRequest

由於採用非同步載入,載入資源的過程中,程序有可能發起同一個請求,多次載入似乎有些浪費,如下圖所示。

圖:兩次載入同一資源

更好的辦法是,在收到第2次請求時先做判斷,如果該資源正在載入,那先記錄請求2的回調函數,待資源載入完成,調用所有請求該資源的回調函數,如下圖所示。m_LoadRequests便記錄每種資源的請求,使程序可以判斷該資源是否正在載入,並從m_LoadRequests獲取各個資源請求的回調函數。

圖:記錄請求2的回調函數

簡化後的代碼如下:

void LoadAsset(包名)n{n If(m_LoadRequests[abName] == null)n {n m_LoadRequests[包名].Add(回調函數等);n OnLoadAsset();n }n elsen {n m_LoadRequests[包名].Add(回調函數等);n }n}nnIEnumerator OnLoadAsset(XXX)n{n 載入包n 載入資源n foreach( request in m_LoadRequests[包名] )n {n Request.回調函數();n }n}n

5、資源熱更新

「資源熱更新」和上一篇的「代碼熱更新」完全相同,開啟更新模式後,將新的資源文件複製到伺服器上,框架即可自動下載更新的資源。這裡不再複述。

筆者也是剛剛接觸LuaFramework不久,想看看是否適合於工作室接下來的項目,文章錯誤之處在所難免,請大家多加包涵。繼續是廣告時間:

《十六年的長度,記錄中國獨立遊戲》筆者的一篇文章,盤點了1998年以來我國一些獨立遊戲,以及獨立遊戲作者的經歷。相信對有志於開發獨立遊戲的讀者有所幫助。

games.sina.com.cn/zl/du


推薦閱讀:

【補遺】基於體積的大氣散射Shader
聚焦9.8直播 | UI製作大師初養成!
Unity手游開發札記——使用Fast Shadow Receiver優化渲染效率
從零開始學基於ARKit的Unity3d遊戲開發系列18
Unity3D程序腳本反編譯分析與加密

TAG:Unity游戏引擎 | Lua编程 | 游戏引擎 |