天天看點

Unity3D資源加載(AssetBundle和Resource)

轉載自http://blog.csdn.net/fenrir_sun/article/details/50207909

Unity3D 裡有兩種動态加載機制:一個是Resources.Load,另外一個通過AssetBundle,其實兩者差別不大。 Resources.Load就是從一個預設打程序式包裡的AssetBundle裡加載資源,而一般AssetBundle檔案需要你自己建立,運作時 動态加載,可以指定路徑和來源的。

其實場景裡所有靜态的對象也有這麼一個加載過程,隻是Unity3D背景替你自動完成了。

詳細說一下細節概念: 

AssetBundle運作時加載: 

來自檔案就用CreateFromFile(注意這種方法隻能用于standalone程式)這是最快的加載方法 

也可以來自Memory,用CreateFromMemory(byte[]),這個byte[]可以來自檔案讀取的緩沖,www的下載下傳或者其他可能的方式。 

其實WWW的assetBundle就是内部資料讀取完後自動建立了一個assetBundle而已 

Create完以後,等于把硬碟或者網絡的一個檔案讀到記憶體一個區域,這時候隻是個AssetBundle記憶體鏡像資料塊,還沒有Assets的概念。 

Assets加載: 

用AssetBundle.Load(同Resources.Load) 這才會從AssetBundle的記憶體鏡像裡讀取并建立一個Asset對象,建立Asset對象同時也會配置設定相應記憶體用于存放(反序列化) 

異步讀取用AssetBundle.LoadAsync 

也可以一次讀取多個用AssetBundle.LoadAll 

AssetBundle的釋放: 

AssetBundle.Unload(flase)是釋放AssetBundle檔案的記憶體鏡像,不包含Load建立的Asset記憶體對象。 

AssetBundle.Unload(true)是釋放那個AssetBundle檔案記憶體鏡像和并銷毀所有用Load建立的Asset記憶體對象。

一個Prefab從assetBundle裡Load出來 裡面可能包括:Gameobject transform mesh texture material shader script和各種其他Assets。 

你 Instaniate一個Prefab,是一個對Assets進行Clone(複制)+引用結合的過程,GameObject transform 是Clone是新生成的。其他mesh / texture / material / shader 等,這其中些是純引用的關系的,包括:Texture和TerrainData,還有引用和複制同時存在的,包括:Mesh/material /PhysicMaterial。引用的Asset對象不會被複制,隻是一個簡單的指針指向已經Load的Asset對象。這種含糊的引用加克隆的混合, 大概是搞糊塗大多數人的主要原因。 

專門要提一下的是一個特殊的東西:Script Asset,看起來很奇怪,Unity裡每個Script都是一個封閉的Class定義而已,并沒有寫調用代碼,光Class的定義腳本是不會工作的。其 實Unity引擎就是那個調用代碼,Clone一個script asset等于new一個class執行個體,執行個體才會完成工作。把他挂到Unity主線程的調用鍊裡去,Class執行個體裡的OnUpdate OnStart等才會被執行。多個物體挂同一個腳本,其實就是在多個物體上挂了那個腳本類的多個執行個體而已,這樣就好了解了。在new class這個過程中,資料區是複制的,代碼區是共享的,算是一種特殊的複制+引用關系。 

你可以再Instaniate一個同樣的Prefab,還是這套mesh/texture/material/shader…,這時候會有新的GameObject等,但是不會建立新的引用對象比如Texture. 

是以你Load出來的Assets其實就是個資料源,用于生成新對象或者被引用,生成的過程可能是複制(clone)也可能是引用(指針) 

當你Destroy一個執行個體時,隻是釋放那些Clone對象,并不會釋放引用對象和Clone的資料源對象,Destroy并不知道是否還有别的object在引用那些對象。 

等到沒有任何 遊戲場景物體在用這些Assets以後,這些assets就成了沒有引用的遊離資料塊了,是UnusedAssets了,這時候就可以通過 Resources.UnloadUnusedAssets來釋放,Destroy不能完成這個任 務,AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全,除非你很清楚沒有任何 對象在用這些Assets了。 

配個圖加深了解: 

Unity3D資源加載(AssetBundle和Resource)

雖然都叫Asset,但複制的和引用的是不一樣的,這點被Unity的暗黑技術細節掩蓋了,需要自己去了解。

關于記憶體管理 

按照傳統的程式設計思維,最好的方法是:自己維護所有對象,用一個Queue來儲存所有object,不用時該Destory的,該Unload的自己處理。 

但這樣在C# .net架構底下有點沒必要,而且很麻煩。 

穩妥起見你可以這樣管理

建立時: 

先建立一個AssetBundle,無論是從www還是檔案還是memory 

用AssetBundle.load加載需要的asset 

加載完後立即AssetBundle.Unload(false),釋放AssetBundle檔案本身的記憶體鏡像,但不銷毀加載的Asset對象。(這樣你不用儲存AssetBundle的引用并且可以立即釋放一部分記憶體) 

釋放時: 

如果有Instantiate的對象,用Destroy進行銷毀 

在合适的地方調用Resources.UnloadUnusedAssets,釋放已經沒有引用的Asset. 

如果需要立即釋放記憶體加上GC.Collect(),否則記憶體未必會立即被釋放,有時候可能導緻記憶體占用過多而引發異常。 

這樣可以保證記憶體始終被及時釋放,占用量最少。也不需要對每個加載的對象進行引用。

當然這并不是唯一的方法,隻要遵循加載和釋放的原理,任何做法都是可以的。

系統在加載新場景時,所有的記憶體對象都會被自動銷毀,包括你用AssetBundle.Load加載的對象和Instaniate克隆的。但是不包括AssetBundle檔案自身的記憶體鏡像,那個必須要用Unload來釋放,用.net的術語,這種資料緩存是非托管的。

總結一下各種加載和初始化的用法: 

AssetBundle.CreateFrom…..:建立一個AssetBundle記憶體鏡像,注意同一個assetBundle檔案在沒有Unload之前不能再次被使用 

WWW.AssetBundle:同上,當然要先new一個再 yield return 然後才能使用 

AssetBundle.Load(name): 從AssetBundle讀取一個指定名稱的Asset并生成Asset記憶體對象,如果多次Load同名對象,除第一次外都隻會傳回已經生成的Asset 對象,也就是說多次Load一個Asset并不會生成多個副本(singleton)。 

Resources.Load(path&name):同上,隻是從預設的位置加載。 

Instantiate(object):Clone 一個object的完整結構,包括其所有Component和子物體(詳見官方文檔),淺Copy,并不複制所有引用類型。有個特别用法,雖然很少這樣 用,其實可以用Instantiate來完整的拷貝一個引用類型的Asset,比如Texture等,要拷貝的Texture必須類型設定為 Read/Write able。

總結一下各種釋放 

Destroy: 主要用于銷毀克隆對象,也可以用于場景内的靜态物體,不會自動釋放該對象的所有引用。雖然也可以用于Asset,但是概念不一樣要小心,如果用于銷毀從文 件加載的Asset對象會銷毀相應的資源檔案!但是如果銷毀的Asset是Copy的或者用腳本動态生成的,隻會銷毀記憶體對象。 

AssetBundle.Unload(false):釋放AssetBundle檔案記憶體鏡像 

AssetBundle.Unload(true):釋放AssetBundle檔案記憶體鏡像同時銷毀所有已經Load的Assets記憶體對象 

Reources.UnloadAsset(Object):顯式的釋放已加載的Asset對象,隻能解除安裝磁盤檔案加載的Asset對象 

Resources.UnloadUnusedAssets:用于釋放所有沒有引用的Asset對象 

GC.Collect()強制垃圾收集器立即釋放記憶體 Unity的GC功能不算好,沒把握的時候就強制調用一下。

原貼位址:http://www.cnblogs.com/88999660/archive/2013/03/15/2961663.html

資源加載,通過AssetBundle其實和通過Resources沒有本質的差別,Resources其實在unity内部也是作為一個AssetBundle來打包的。很多遊戲還需要一個從遠端伺服器下載下傳更新資源進行熱更新的需求。 

這個可以通過比較本地和遠端的資源版本号來差別是從遠端下載下傳還是從本地用戶端讀取,不同平台上本地的資源存貯位置不同,大體如下: 

UNITY_ANDROID:”jar:file://” + Application.dataPath + “!/assets/xxx.assetbundle”; 

UNITY_IPHONE:Application.dataPath + “/Raw/xxx.assetbundle”; 

UNITY_STANDALONE_WIN或者UNITY_EDITOR:”file://” + Application.dataPath + 

“/StreamingAssets/xxx.assetbundle”;

xxx為檔案名。

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> delegate <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">DelVoid</span>();
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> delegate <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">DelAsset</span> (Asset asset);
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> delegate <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">DelOnBundleLoad</span>(Object bundle);

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> bool <span class="hljs-title" style="box-sizing: border-box;">LoadAsset</span>(string resURL, DelAsset onLoad)
    {

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (serverVersionInfo[bundleName]>LocalVersionInfo[bundleName]) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//比較版本号</span>
        {
            resURL = RemotePath + bundleName;       
            version = LocalVersionInfo[bundleName];   
        }

        StartCoroutine(LoadBundle(resURL,(bundle)=>{
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (bundle)
            {
                Asset asset = bundle as Asset;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (onLoad != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>)
                    onLoad(asset);
            }
        }));
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
    }

    IEnumerator LoadBundle(string url, DelOnBundleLoad onResLoad = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>)
    {
        WWW www = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
        www = WWW.LoadFromCacheOrDownload(url, version);

        yield <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> www;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (www.error != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>)
        {
            yield <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (onResLoad != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>)
            onResLoad(www.assetBundle.mainAsset);

        www.assetBundle.Unload(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li></ul>      

這裡使用WWW.LoadFromCacheOrDownload()來下載下傳遠端用戶端(本地的資源)。這個方法具體的功能如下: 

1.Unity會先判斷本地有無”url”的檔案,如果沒有,就會根據URL去下載下傳相應的檔案。然後在本地存儲一下檔案,并記錄相應的版本号。 

2.Unity會先判斷本地有”url”的檔案,本地的版本号小于API傳入的版本号,此時Unity也會去下載下傳最新的檔案,然後覆寫本地的此檔案,然後記錄下最新的版本号。 

3.Unity會先判斷本地有”url”的檔案,本地的版本号大于等于API傳入的版本号,此時Unity會從本地直接取,并且加載相應的檔案傳回。

chche會存在本地硬碟的某個目錄下面,不在遊戲的安裝目錄中。可以通過Caching.CleanCache()删除本地下載下傳的資源。 

版本比對的方法這裡就不寫了,可以通過一個配置檔案存放各個AssetBundle的版本号,伺服器更新之後,本地用戶端擷取遠端的版本号資訊,再和本地的版本号比對,要下載下傳的下載下傳後更新本地版本号就行了。

繼續閱讀