1. 程式人生 > >Unity3D 關於資源載入(Resources和AssetBundle)和記憶體管

Unity3D 關於資源載入(Resources和AssetBundle)和記憶體管

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了。 
配個圖加深理解: 
這裡寫圖片描述

 
雖然都叫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功能不算好,沒把握的時候就強制呼叫一下。

資源載入,通過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的版本號,伺服器更新之後,本地客戶端獲取遠端的版本號資訊,再和本地的版本號比對,要下載的下載後更新本地版本號就行了。