1. 程式人生 > >ToLua熱更新之LuaFramework框架之資源熱更新(2)

ToLua熱更新之LuaFramework框架之資源熱更新(2)

1、建立物體

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

圖:配置為本地模式

先測試個簡單的建立物體,新建一個名為go的物體,然後設定它的座標為(1,1,1)。這段程式碼雖然不涉及資源載入,但能展示“把物體新增到場景中”的過程。Main.lua的程式碼如下:

function Main()					
	local go = UnityEngine.GameObject ('go')
	go.transform.position = Vector3.one		
end

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

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

圖:坦克預設

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邏輯
function Main()					
	LuaHelper = LuaFramework.LuaHelper;
	resMgr = LuaHelper.GetResManager();
	resMgr:LoadPrefab('tank', { 'TankPrefab' }, OnLoadFinish);
end

--載入完成後的回撥--
function OnLoadFinish(objs)
	local go = UnityEngine.GameObject.Instantiate(objs[0]);
	LuaFramework.Util.Log("Finish");	
end

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

圖:動態加載出來的模型

4、載入資源的過程

只有理解了動態載入,即LoadPrefab的過程,才能算是真正的理解了熱更新。LoadPrefab為ResourceManager中定義的方法,在Assets\LuaFramework\Scripts\Manager\ResourceManager.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(包名);

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

m_LoadedAssetBundles

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

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

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

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

m_Dependencies

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

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

m_LoadRequests

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

圖:LoadAssetRequest

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

圖:兩次載入同一資源

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

圖:記錄請求2的回撥函式

簡化後的程式碼如下:

void LoadAsset(包名)
{
       If(m_LoadRequests[abName] == null)
       {
              m_LoadRequests[包名].Add(回撥函式等);
              OnLoadAsset();
       }
       else
       {
              m_LoadRequests[包名].Add(回撥函式等);
       }
}

IEnumerator OnLoadAsset(XXX)
{
       載入包
       載入資源
       foreach( request in  m_LoadRequests[包名] )
       {
              Request.回撥函式();
       }
}

5、資源熱更新

“資源熱更新”和上一篇的“程式碼熱更新”完全相同,開啟更新模式後,將新的資原始檔複製到伺服器上,框架即可自動下載更新的資源。這裡不再複述。