詳談 Unity3D AssetBundle 資源加載,結合實際項目開發實例


第一次搞資源更新方面,這里只說更新,加載,AssetBundle資源加載,談談自己的理解,以及自己在項目中遇到的那些神坑,現在回想一下,真的是自己跪着過來的,說多了,都是淚。

我這邊是安卓AssetBundle資源加載。歡迎拍磚

 

一.Unity中各個目錄

我這里說的是移動平台(安卓舉例),讀,寫。所謂讀,就是你出大版本的包之后,這個只讀的話,就一輩子就這些東西了,不會改變了,不會有其他資源來覆蓋或者增加啦。

可寫,就是可以加東西進去唄。可能是自己太笨,一開始沒怎么注意這意思。竟然往StreamingAssets去實現資源更新(天啦擼)。

 

Application.StreamingAssetsPath,

StreamingAssets目錄必須在Assets根目錄下,該目錄下所有資源也會被打包到游戲里,不同於Resources目錄,該目錄下的資源不會進行壓縮,同樣是只讀不可寫的。
這里的只可讀,不可寫,就是除了出大版本的包(重新下載),這里面的東西永遠不會變。

  各平台StreamingAssets路徑打印:
  Win:E:/myProj/Assets/StreamingAssets
  Mac : /myProj/Assets/StreamingAssets
  Andorid:jar:file:///data/app/com.myCompany.myProj-1/base.apk!/assets
  iOS: /var/containers/Application/E5543D66-83F3-476D-8A8F-49D5332B3763/myProj.app/Data/Raw

Application.PersistentDataPath

   應用程序安裝后才會出現。該目錄獨特之處在於是可讀,可寫的,所以我們一般將下載的AssetBundle存放於此。
  各平台PersistentDataPath路徑打印:
  Win:C:/Users/lodypig/Appdata/LocalLow/myCompany/myProj
  Mac : /Users/lodypig/Library/Application Support/myCompany/myProj
  Andorid:/data/data/com.myCompany.myProj/files
  iOS: /var/mobile/Containers/Data/Appliction/A112252D-6B0E-459Z-9D49-CD3EAC6D47D/Documents

Application.DataPath

  應用程序目錄,即Assets目錄。使用Appliction.dataPath訪問。只讀不可寫
  各平台DataPath路徑:
  Win:E:/myProj/Assets
  Mac : /myProj/Assets/
  Andorid:/data/app/com.myCompany.myProj-1/base.apk!
  iOS: /var/containers/Application/E5543D66-83F3-476D-8A8F-49D5332B3763/myProj.app/Data

綜上,也就是說,要實現資源更新,你只有把資源下載到Application.PersistentDataPath目錄下才可實現資源更新(增加或者替換),其他目錄不可能實現。

 

二.Unity游戲加載的資源是如何分配

首先你得有一個資源服務器(FTP為例),因為StreamingAssets目錄是只讀的,我們想要實現熱更新,StreamingAssets

目錄里面的東西一旦第一個版本打出APK的包之后,這里的東西將永遠不會變(只讀)。由於PersistentDataPath目錄是可讀可寫的,

所以游戲下載資源都會下載到這里面。這樣就實現了資源的熱更新。

 

注解:綠色的代表流動,可以不斷可以改變的資源。紅色線代表,讀取persistent目錄沒有的情況下,讀StreamingAssets目錄,所以,是永遠不變的資源。(除非你去重新下載一個apk的包,就不是熱更了)

三.如何加載本地的資源

 首先優先判斷PersistentDataPath目錄下的資源是否存在,因為服務器上的資源都是下載到這里的,最新的資源通過下載到這里並且覆蓋,這里的資源

能保持跟服務器一致。(雨松之前講的UnityAssetBundle例子就是通過加載服務器上的,那個只是一個小案例,不能每次用哪下載到哪,每次都要下載,

這種方式是很不好用的,就第一次用的時候如果資源與服務器不一致,就下載到本地中,即PersistentDataPath目錄。)

因為每個游戲一開始出大版本的時候,都會附帶大量資源,就是放在StreamingAssets目錄,所以,這里存放大量資源。這樣減少下載的次數。

其實,換一種說法,PersistentDataPath完全是給StreamingAssets的補丁目錄,我是這樣理解的。當然,在項目運用中,都需要優先最先資源判斷。

 四.遇到的那些神神神.....坑

   (1) 不要以為在PC端可以加載的路徑,安卓也可以用。

  我這邊因為涉及到WWW加載,貼出我的。

  這里主要是通過www 如何加載PersistentDataPath和StreamingAssets這兩個目錄。

  Application.PersistentDataPath:

    

 /// <summary>
    /// 天吶,一個Per目錄,還兩種方法加載。這真的是最后我找出來最完美的,可以加載的,之前還有N種版本,就不提了,網上有,我是經過實測,Unity5.3.4版本
    /// 加載PC上安卓平台,加載PC上Standalone,加載安卓真機(APK包),這三個,都是可以加載的(WWW加載),
    /// </summary>
    public static string PERSISTENT_PATH_DATABASE //= LGameConfig.LOCAL_URL_PREFIX + Application.persistentDataPath + "/DataBase/";
    {
        get
        {
            #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
                    return   "file:///"+ Application.persistentDataPath + "/Test/";
            #else
	                      return   "file://"+ Application.persistentDataPath + "/Test/";
            #endif
        }
    }

  Application.StreamingAssets:

public static string STREAMING_PATH_DATABASE
    {
        //這樣寫,因為安卓Unity平台是Application.isMobilePlatform==false, 而宏定義中又  ==UNITY_ANDROID。
         //因為我們項目中是需要同時在PC下安卓平台和PC下 Standard平台,哈哈
        get
        {
            if (!Application.isMobilePlatform)
            {
                return "file://" + Application.dataPath + "/StreamingAssets" + "/DataBase/";
            }
            else
            {
                return
#if UNITY_ANDROID
        Application.streamingAssetsPath+  "/DataBase/";
#else
      "file://" + Application.dataPath + "/StreamingAssets" + "/DataBase/";
#endif
            }
        }

  這個加載地址,真的是精華,可能自己太笨,就是搞這個WWW加載安卓StreamingAssets目錄,花了大把時間,因為網上,加載方式真的是尼瑪一萬種,要噴一下,這些人,不實際打到APK測一下,我MDGB呀,坑的我好慘。

 

 (2)不要以為在PC端可以用的方法,在安卓也可以用。

   

      安卓上跟其他平台不一樣,安裝后,這些文件實際上是在一個Jar壓縮包里,所以不能直接用讀取文件的函數去讀,而要用WWW方式
      讀取的代碼(假設名為"文件.txt") 

 (3) 安卓資源路徑加載,下載問題,真的是這次做AssetBundle最大的障礙。

(4)通過FTP和CDN下載資源的時候對應的 后綴是不同的。

  FTP下載 后面用   "/"  ,即可。

  CDN下載 后面用  "//" ,即可。

(5)不同的加載方式,加載的路徑也是不同的。

具體我就不說了。

(6)記得加 加載文件的后綴名

安卓上跟其他平台不一樣,安裝后,這些文件實際上是在一個Jar壓縮包里,所以不能直接用讀取文件的函數去讀,而要用WWW方式
1.讀取的代碼(假設名為"文件.txt") 

(7)加載方式,First In PersistentDataPath,Then StreamingAssets

 IEnumerator LoadAnouncementText()
    {
        string strUrl = GetTxtPerststentUrl(anouncementText);
        WWW www = new WWW(strUrl);
        yield return www;
        if (www.error == null)
        {
            mAnoucementText = ConvertByteToString(www.bytes);
        }
        else if (www.isDone)
        {
            string strPerUrl = GetTxtStreamUrl(anouncementText);
            www = new WWW(strPerUrl);
            yield return www;
            if (www.error == null)
            {
                mAnoucementText = ConvertByteToString(www.bytes);
            }
            else if (www.isDone)
            {
                Debug.LogError("下載當前表出錯"  + www.error.ToString());
            }
        }
    }

string GetTxtStreamUrl(string name)
    {
        return STREAMING_PATH_DATABASE + name + ".txt";
    }

    string GetTxtPerststentUrl(string name)
    {
        return PERSISTENT_PATH_DATABASE + name + ".txt";
    }
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!補充!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
補充:對於上面的路徑問題大家可能有些困惑。
貼出最詳細全面的路徑問題,經過測試,完全沒問題(安卓,PC都可以用,實際項目中使用)

 string GetScenePath(string fileName)
    {
       string path =DataUrl.GetFilePersistentUrl(fileName)+".unity3d";
       // string path= DataUrl.LOCAL_URL_PREFIX + Application.dataPath + "/StreamingAssets/" + "Scene_Main" + ".unity3d";
        //讀取Per目錄的時候不需要加prefix,但是讀取Streaming目錄時候需加上prefix
        bool isPersistentDataPath = System.IO.File.Exists(path);
        if (!isPersistentDataPath)
        {
            path =  DataUrl.GetFileStreamingUrl(fileName)+ ".unity3d";
            if (!System.IO.File.Exists(path))
            {
                Debug.LogError("Per,Stream目錄 scene bundle都不存在");
                return null;
            }
        }

        return DataUrl.LOCAL_URL_PREFIX+ path;
    }

DataUrl.cs
public static string GetFilePersistentUrl(string path)
    {
        return Application.persistentDataPath+"/" + path;
    }

    public static string GetFileStreamingUrl(string path)
    {
        return Application.streamingAssetsPath+"/" + path;
    }
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
    public static readonly string LOCAL_URL_PREFIX = "file:///";
#else
    public static readonly string LOCAL_URL_PREFIX="file://";
#endif





 

  

  

  

 五.AssetBundle 加載方式

(轉自:https://blog.uwa4d.com/archives/ABTheory.html)

1.用法

AssetBundle加載資源分為兩步,第一步是獲取AssetBundle對象;第二步是通過該AssetBundle對象加載需要的資源。

  第一步:獲取AssetBundle對象(可以分為以下兩種)

      ①先獲取WWW對象,再通過WWW.assetbundle來獲取AssetBundle對象。

        public WWW(string url);

        記載bundle文件並獲取WWW對象,完成后會在內存中創建較大的WebStream(解壓后的內容,通常為原Bundle文件的4~5倍,紋理資源可能更大),因此后續的AssetBundle.load可以直接在內存中進行。

            public static WWW LoadFromCacheOrDownload(string url,int version,unit crc = 0);

      加載Bundle文件並獲取WWW對象,同時將解壓形式的Bundle內容存入磁盤中作為緩存(如果該Bundle已經在緩存中,則省去這一步),完成后只會在內存中創建較小的SerializedFile,而后續的AssetBundle.load 需要通過IO從磁盤中的緩存中獲取。

        綜上兩種方式,直接使用WWW.AssetBundle獲取AssetBundle對象。

      ②直接獲取AssetBundle。

        public static AssetBundle LoadFromFile(string path);

        通過未壓縮的Bundle文件,同步創建AssetBundle對象,這是最快的創建方式。創建完后只會在內存中創建較小的SerializedFile,

而后續的AssetBundle.Load需要通過IO從磁盤中獲取。

        public static AssetBundleCreateRequest LoadFromMemoryAsync(byte[] binary);

        通過Bundle的二進制數據,異步創建AssetBundle對象。完成后會在內存中創建較大的WebStream。調用時,Bundle的解壓是異步的。

        public static LoadromMemory

        上述方式的同步版本.    

  第二步:從AssetBundle加載資源的常用API    

        public T LoadAsset<T>(string name) where T: Object

 

2.Load assetBundle 區別

    new WWW  vs WWW.LoadFromCacheOrDownLoad

     ①前者的優勢

      前者的Load操作在內存中進行,相比后者的IO操作開銷更小

      不形成緩存文件,而后者則需要額外的磁盤空間存放緩存

    ②前者的劣勢

      每次加載都涉及到解壓的操作,而后者在第二次加載時就省去了解壓的開銷

      在內存中會有較大的WebStream,而后者在內存中通常只有較小的SerializedFile。

六.內存分析   

    WebStream:在使用new WWW或LoadFromMemory時產生,內存開銷較大

    SerializedFile:內存開銷通常較小,但是一般磁盤中存儲資源,需要IO操作。

    建議:

       AssetBundle文件的大小不超過1MB,因為在普遍情況下Bundle加載時間與其大小並非呈線性關系,過大的Bundle可能引起較大的加載開銷。

       由於WWW對象的加載是異步的,因此逐個加載容易出現CPU空閑的情況,此時建議適當同時加載多個對象,以增加CPU使用率,同時

加快加載的完成。

    卸載:

       場景物體(GameObject):這類物件可通過Destroy函數進行卸載。

      資源(包括Prefab):除了Prefab以外,資源文件可以通過三種方式來卸載

                1)Resource.UnLoadAsset 卸載指定的資源,CPU開銷小

                2)Resource.UnLoadUnusedAssets:一次卸載所有未被引用的資源,CPU開銷大。

                3)AssetBundle.UnLoad(true)在卸載AssetBundle對象時,所有該資源引用的資源也一起卸載,因為該方法容易造成資源丟失,不建議經常使用。unload(false),只卸載該資源。

                4)WWW對象,調用對象的Dispose函數或將其置為null即可。

                5)WebStream:在卸載WWW對象以及對應的AssetBundle對象后,這部分內存即會被引擎自動卸載。

                6)SerializedFile:卸載AssetBundle后,這部分內存會被引擎自動卸載。

        注意:

          在通過AssetBundle.unload(false)卸載AssetBundle對象后,如果重新創建該對象並加載之前加載過的資源到內存時,會出現冗余,即兩份相同的資源。      

          被腳本的靜態變量引用的資源,在調用resource.unloadUnusedAssets,並不會被卸載。

 

        推薦:

          ①對於需要常駐內存的Bundle文件來說,優先考慮減少內存占用,因此對於存放非Prefab資源(紋理)的Bundle文件,可以考慮使用LoadFromCacheOrDownLoad或LoadFromFile加載,從而避免WebStream常駐內存。對於存放較多Prefab資源的Bundle,則考慮使用WWW加載。因為這類Bundle用WWW.LoadFromCacheOrDownLoad加載時產生的SerializedFile可能會比WWW產生的WebStream更大。

          ②對於加載完后卸載Bundle文件,分兩種情況,優先考慮速度(加載場景時),優先考慮流暢度(游戲進行時)

            加載場景的情況下,需要注意的是避免WWW對象的逐個加載導致的CPU空閑,優先考慮使用加載速度較快的LoadFromCacheDownLoad或LoadFromFile。

            游戲進行時,需要避免使用同步操作引起卡頓,因此考慮使用WWW配合LoadAsync進行平滑的資源加載。

            盡量避免游戲進行時調用Resource.UnLoadUnusedAssets().因為該接口開銷較大,容易造成卡頓,可以嘗試使用

Resource.UnLoad(obj)來逐個進行卸載,以保證游戲流暢度。

                


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM