Related Posts Plugin for WordPress, Blogger...

6-31 Using AssetBundles Natively And UnityWebRequest

本章要來介紹在Unity使用AssetBundle的四種方法。

  • AssetBundle.LoadFromMemory
  • AssetBundle.LoadFromFile
  • WWW.LoadfromCacheOrDownload
  • UnityWebRequest’s DownloadHandlerAssetBundle (Unity 5.3 or newer)

第一種是透過記憶體載入AssetBundle,比方說從網路上下載的檔案是bytes陣列,可以直接使用LoadFromMemoryAsync轉換成AssetBundle。Unity官方不建議使用此API,原理上至少會佔用三倍AssetBundle的容量於記憶體內。

第二種是從本地載入AssetBundle。如AssetBundle為LZ4壓縮格式,則只會載入 AssetBundle 的檔頭,其餘的資料仍留存在硬碟上,將會基於「按需求 (on-demand)」的模式來載入。

第三種是從網路上下載AssetBundle後,在本地緩存並載入。*注意:從Unity 2017.1開始,第三種方法只是包裝自UnityWebRequest。 因此,使用Unity 2017.1或更高版本的開發人員應改用UnityWebRequest。* 與UnityWebRequest不同,每次調用此API都會產生一個新的工作線程。 因此,在移動設備等內存有限的平台上,每次只能使用此API下載一個AssetBundle,以避免內存高峰。如果需要下載超過5個AssetBundle,請使用代碼創建和管理下載隊列,以確保只有少量AssetBundle下載正在同時運行。

第四種是從Unity 5.3以後才支援的功能,用來處理從網路上下載的AssetBundle。使用DownloadHandlerAssetBundle下載時,下載的數據會傳輸到固定大小的緩衝區,然後根據不同的配置方式,決定將緩衝的數據放到暫存空間或AssetBundle緩存空間。 以上操作都以native-code形式進行,消除了擴展heap的風險。

上述解說來自這篇官方文檔,也推薦大家閱讀這篇文檔,會獲得非常大的幫助:

第一種方式:AssetBundle.LoadFromMemory


第二種方式:AssetBundle.LoadFromFile


第三種方式:WWW.LoadfromCacheOrDownload

針對LoadfromCacheOrDownload,再提一個可添加的參數CRC,這是用來檢查網路下載的檔案是否有錯誤的檢查碼。

Unity產生AssetBundle的時候,也會附加一個manifest文件。如不清楚mainfest文件,可以參考之前寫的文章。
6-30 AssetBundle Option And Manifest File
https://3dactionrpg.blogspot.com/2018/06/6-30-assetbundle-option-and-manifest.html

其中就有一個欄位是該AssetBundle的CRC檢查碼。


第四種方式:UnityWebRequest’s DownloadHandlerAssetBundle

需要注意的是,經由UnityWebRequest下載的AssetBundle並不會存到本地的暫存區,跟WWW. LoadfromCacheOrDownload不一樣。主要理念是讓使用者自己管理下載後的AssetBundle,可以透過request.downloadHandler.data取得資料流的byte陣列,再使用File.WriteAllBytes寫入到本地的儲存體內。

以下提供四種方法的測試用程式碼,我統一都寫在LoadAssetBundle.cs腳本中,並都含有註解,請大家自行參考嘍~


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.Networking;

public class LoadAssetBundle : MonoBehaviour {

 [SerializeField] string loadPath;
 [SerializeField] string assetName;
 [SerializeField] Transform assetTransform;
 [SerializeField] string urlPath;

 void Start () {
//  LoadFromMemory ();
//  StartCoroutine (LoadFromMemoryAsync());
//  LoadFromFile ();
//  StartCoroutine (LoadFromFileAsync());
//  StartCoroutine (LoadFromCacheOrDownload());
  StartCoroutine (LoadFromUnityWebRequest());
 }

 // 第一種方式(同步)
 void LoadFromMemory(){
  // 加載AssetBundle
  AssetBundle assetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(loadPath));
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 // 第一種方式(異步)
 IEnumerator LoadFromMemoryAsync(){
  // 異步加載
  AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync (File.ReadAllBytes(loadPath));
  // 等待加載完成
  yield return request;
  AssetBundle assetBundle = request.assetBundle;
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 // 第二種方式(同步)
 void LoadFromFile(){
  // 加載AssetBundle
  AssetBundle assetBundle = AssetBundle.LoadFromFile(loadPath);
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 // 第二種方式(異步)
 IEnumerator LoadFromFileAsync(){
  // 異步加載
  AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync (loadPath);
  // 等待加載完成
  yield return request;
  AssetBundle assetBundle = request.assetBundle;
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 // 第三種方式
 IEnumerator LoadFromCacheOrDownload(){
  // 先檢查Cache是否正常
  while (Caching.ready == false) {
   // 暫停一個Frame
   yield return null;
  }
  // 使用WWW下載資源,參數中的1為版本號
  // 可使用版本號決定是否更新Cache中的AssetBundle
  WWW www = WWW.LoadFromCacheOrDownload (urlPath, 1);
  yield return www;
  // 如下載時發生問題,會記錄於error中
  if (string.IsNullOrEmpty(www.error) == false) {
   Debug.Log (www.error);
   // 跳出Corutine
   yield break;
  }
  AssetBundle assetBundle = www.assetBundle;
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 // 第四種方式(用來取代WWW方法,需引用UnityEngine.Networking)
 IEnumerator LoadFromUnityWebRequest(){
  // 使用UnityWebRequest不會有緩存,需要自行管理下載後的AssetBundle
  UnityWebRequest request = UnityWebRequest.GetAssetBundle(urlPath);
  yield return request.SendWebRequest ();
  // 取得AssetBundle(方法1)
  AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(request);
  // 取得AssetBundle(方法2)
//  AssetBundle assetBundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
  // 從AssetBundle取得想要的資源
  GameObject Building_a = assetBundle.LoadAsset(assetName);
  // 產生資源
  Instantiate(Building_a, assetTransform.position, assetTransform.rotation);
 }

 void SaveToLocal(UnityWebRequest request, string savePath){
  File.WriteAllBytes (savePath, request.downloadHandler.data);
 }
}


撰寫好以後,將LoadAssetBundle放進一個Empty GameObject中,並指定相關參數。

接著來測試看看吧,比較特別需要注意的是,如果在遊戲執行才開始從Server上下載的話,就會出現如下圖的情況。進入遊戲後,場景中沒有任何東西。

過一段時間,下載完成後才會出現。這樣的話當然是非正常狀態,建議大家應要在遊戲場景載入前將檔案下載到本地端。


留言