Unity資源加載的一些問題

Unity小白,決定開始一點點積累一些相關知識。

參考:http://blog.csdn.net/swj524152416/article/details/54022282




Unity中資源動態加載的幾種方式比較
unity會自動將場景需要引用到的資源打包到安裝包裏,沒有到的不會跟進去。我們在編輯器裏看到的Asset中的文件結構只是工作於編輯器環境下的,在遊戲中unity會重新組織數據庫。這是我們一定會遇到一個需求,即動態的加載我們自己的文件,而且想維護這個文件存儲和加載的位置,並且是各種自定義的文件。
unity資源的類型:
1.Unity內置的常用asset,fbx\jpg...
2.textasset: txt、binary等,對應了它的TextAsset類,可以直接讀入文本或者二進制byte
3.scriptable object它是序列化的Object實例,例如把一個Object實例保存成scriptable object,讀入進來後就直接在內存中變成那個實例
4.asset bundle它是一個資源壓縮包,裏面包含了一堆資源
動態資源的存放
有時我需要存放一些自己的文件在磁盤上,例如我想把幾個bundle放在初始的安裝裏, unity有一個streaming asset的概念,用於提供存儲接口的訪問。我們需要在編輯器建立一個StreamingAssets名字的文件夾,把需要我們放在客戶磁盤上的動態文件放在這個文件夾下面,這樣安裝後,這些文件會放在用戶磁盤的指定位置,這個位置可以通過Application.streamingAssetsPath來得到。
Unity必須通過導入將所支持的資源序列化,生成AssetComponents後,才能被Unity使用。
GUID與fileID(本地ID)
Unity會爲每個導入到Assets目錄中的資源創建一個meta文件,文件中記錄了GUID,GUID用來記錄資源之間的引用關係。還有fileID(本地ID),用於標識資源內部的資源。資源間的依賴關係通過GUID來確定;資源內部的依賴關係使用fileID來確定。
InstanceID(實例ID)
Unity爲了在運行時,提升資源管理的效率,會在內部維護一個緩存表,負責將文件的GUID與fileID轉換成爲整數數值,這個數值在本次會話中是唯一的,稱作實例ID(InstanceID)。
程序啓動時,實例ID緩存與所有工程內建的對象(例如在場景中被引用),以及Resource文件夾下的所有對象,都會被一起初始化。如果在運行時導入了新的資源,或從AssetBundle中載入了新的對象,緩存會被更新,併爲這些對象添加相應條目。實例ID僅在失效時纔會被從緩存中移除,當提供了指定文件GUID和fileID的AssetBundle被卸載時會產生移除操作。
卸載AssetBundle會使實例ID失效,實例ID與其文件GUID和fileID之間的映射會被刪除以便節省內存。重新載入AssetBundle後,載入的每個對象都會獲得新的實例ID。
資源的生命週期
Object從內存中加載或卸載的時間點是定義好的。Object有兩種加載方式:自動加載與外部加載。當對象的實例ID與對象本身解引用,對象當前未被加載到內存中,而且可以定位到對象的源數據,此時對象會被自動加載。對象也可以外部加載,通過在腳本中創建對象或者調用資源加載API來載入對象(例如:AssetBundle.LoadAsset) 
對象加載後,Unity會嘗試修復任何可能存在的引用關係,通過將每個引用文件的GUID與FileID轉化成實例ID的方式。一旦對象的實例ID被解引用且滿足以下兩個標準時,對象會被強制加載:
實例ID引用了一個沒有被加載的對象。
實例ID在緩存中存在對應的有效GUID和本地ID。
如果文件GUID和本地ID沒有實例ID,或一個已卸載對象的實例ID引用了非法的文件GUID和本地ID,則引用本身會被保留,但實例對象不會被加載。在Unity編輯器中表現爲空引用,在運行的應用中,或場景視圖裏,空對象會以多種方式表示,取決於丟失對象的類型:網格會變得不可見,紋理呈現爲紫紅色等等。
MonoScripts
一個MonoScripts含有三個字符串:程序庫名稱,類名稱,命名空間。 
構建工程時,Unity會收集Assets文件夾中獨立的腳本文件並編譯他們,組成一個Mono程序庫。Unity會將Assets目錄中的語言分開編譯,Assets/Plugins目錄中的腳本同理。Plugin子目錄之外的C#腳本會放在Assembly-CSharp.dll中。而Plugin及其子目錄中的腳本則放置在Assembly-CSharp-firstpass.all中。 
這些程序庫會被MonoScripts所引用,並在程序第一次啓動時被加載。
Assets
爲Unity編輯器下的資源文件夾,Unity項目編輯時的所有資源都將置入此文件夾內。在編輯器下,可以使用以下方法獲得資源對象:AssetDatabase.LoadAssetAtPath("Assets/x.txt"); 
注意:此方法只能在編輯器下使用,當項目打包後,在遊戲內無法運作。參數爲包含Assets內的文件全路徑,並且需要文件後綴。Assets下的資源除特殊文件夾內,或者在會打入包內的場景中引用的資源,其餘資源不會被打入包中。
Resources
資源載入:Assets下的特殊文件夾,此文件夾內的資源將會在項目打包時,全部打入包內,並能通過以下方法獲得對象:
Resources.Load("fileName"); 
Resources.Load("fileName"); 
注意:函數內的參數爲相對於Resource目錄下的文件路徑與名稱,不包含後綴。Assets目錄下可以擁有任意路徑及數量的Resources文件夾,在運行時,Resources下的文件路徑將被合併。
例:Assets/Resources/test.txt與 Assets/TestFloder/Resources/test.png在使用Resource.Load("test")載入時,將被視爲同一資源,只會返回第一個符合名稱的對象。如果使用Resource.Load(「test」)將返回text.txt;
如果在Resources下有相同路徑及名稱的資源,使用以上方法只能獲得第一個符合查找條件的對象,使用以下方法能或得到所有符合條件的對象:
Object[] assets = Resources.LoadAll("fileName"); 
TextAsset[] assets = Resources.LoadAll("fileName"); 
相關機制
在工程進行打包後,Resource文件夾中的資源將進行加密與壓縮,打包後的程序內將不存在Resource文件夾,故無法通過路徑訪問以及更新資源。
在程序啓動時會爲Resource下的所有對象進行初始化,構建實例ID。隨着Resource內資源的數量增加,此過程耗時的增加是非線性的。故會出現程序啓動時間過長的問題,請密切留意Resource內的資源數量。
卸載資源:所有實例化後的GameObject 可以通過Destroy函數銷燬。請留意Object與GameObject之間的區別與聯繫
Object可以通過Resources中的相關Api進行卸載
Resources.UnloadAsset(Object);//卸載對應Object 
Resources.UnloadUnusedAssets();//卸載所有沒有被引用以及實例化的Object 
注意以下情況:
Object obj = Resources.Load("MyPrefab"); 
GameObject instance = Instantiate(obj) as GameObjct; 
...... 
Destroy(instance); 
Resources.UnloadUnusedAssets(); 
此時UnloadUnusedAssets將不會生效,因爲obj依然引用了MyPrefab,需要將obj = null,纔可生效。
StreamingAssets
概述:StreamingAssets文件夾爲流媒體文件夾,此文件夾內的資源將不會經過壓縮與加密,原封不動的打包進遊戲包內。在遊戲安裝時,StreamAssets文件件內的資源將根據平臺,移動到對應的文件夾內。StreamingAssets文件夾在Android與IOS平臺上爲只讀文件夾. 
你可以使用以下函數獲得不同平臺下的StreamingAssets文件夾路徑:
Application.streamingAssetsPath 
請參考以下各平臺下StreamingAssets文件夾的等價路徑,Application.dataPath爲程序安裝路徑。Android平臺下的路徑比較特殊,請留意此路徑的前綴,在一些資源讀取的方法中是不必要的(AssetBundle.LoadFromFile,下詳)
Application.dataPath+"/StreamingAssets"//Windows OR MacOS 
Application.dataPath+"/Raw" //IOS 
"jar:file://"+Application.dataPath+"!/assets/" //Android 
文件讀取:StreamingAssets文件夾下的文件在遊戲中只能通過IO Stream或者WWW的方式讀取(AssetBundle除外)
IO Stream方式
using(FileStream stream =  
File.Open(Application.streamingAssetsPath+"fileName", 
FileMode.Open)) 

//處理方法 

WWW方式(注意協議與不同平臺下路徑的區別)
using(WWW www = new WWW( 
Application.streamingAssetsPath+"fileName")) 

yield return www; 
www.text; 
www.texture; 

AssetBundle特有的同步讀取方式(注意安卓平臺下的路徑區別)
string assetbundlePath = 
#if UNITY_ANDROID 
Application.dataPath+"!/assets"; 
#else 
Application.streamingAssetsPath; 
#endif  
AssetBundle.LoadFromFile(assetbundlePath+"/name.unity3d"); 
PersistentDataPath
Application.persistentDataPath 
Unity指定的一個可讀寫的外部文件夾,該路徑因平臺及系統配置不同而不同。可以用來保存數據及文件。該目錄下的資源不會在打包時被打入包中,也不會自動被Unity導入及轉換。該文件夾只能通過IO Stream以及WWW的方式進行資源加載。
WWW載入資源:WWW是一個Unity封裝的網絡下載模塊,支持Http以及file兩種URL協議,並會嘗試將資源轉換成Unity能使用的AssetsComponents(如果資源是Unity不支持的格式,則只能取出byte[])。WWW加載是異步方法。
byte[] bytes = WWW.bytes; 
string text = WWW.text; 
Texture2D texture = WWW.texture; 
MovieTexture movie = WWW.movie; 
AssetBundle assetbundle = WWW.assetBundle; 
AudioClip audioClip = WWW.audioClip; 
new WWW:每次new WWW時,Unity都會啓用一個線程去進行下載。通過此方式讀取或者下載資源,會在內存中生成WebStream,WebStream爲下載文件轉換後的內容,佔用內存較大。使用WWW.Dispose將終止仍在加載過程中的進程,並釋放掉內存中的WebStream
WWW.LoadFromCacheOrDownload
int version = 1; 
WWW.LoadFromCacheOrDownload(PathURL+"/fileName",version); 
使用此方式加載,將先從硬盤上的存儲區域查找是否有對應的資源,再驗證本地Version與傳入值之間的關係,如果傳入的Version>本地,則從傳入的URL地址下載資源,並緩存到硬盤,替換掉現有資源,如果傳入Version<=本地,則直接從本地讀取資源;如果本地沒有存儲資源,則下載資源。此方法的存儲路徑無法設定以及訪問。使用此方法載入資源,不會在內存中生成 WebStream(其實已經將WebStream保存在本地),如果硬盤空間不夠進行存儲,將自動使用new WWW方法加載,並在內存中生成WebStream。在本地存儲中,使用fileName作爲標識符,所以更換URL地址而不更改文件名,將不會造成緩存資源的變更。 
保存的路徑無法更改,也沒有接口去獲取此路徑
AssetBundle: AssetBundle是Unity支持的一種文件儲存格式,也是Unity官方推薦的資源存儲與更新方式,它可以對資源(Asset)進行壓縮,分組打包,動態加載,以及實現熱更新,但是AssetBundle無法對Unity腳本進行熱更新,因爲其需要在打包時進行編譯。
AssetBundle加載
加載方式

之前已經提及,不再詳細說明,使用WWW 或者 AssetBundle相關API加載,其中AssetBundle的API只能進行本地加載。
AssetBundle.LoadfromMemory(byte[] bytes)
此API是一個例外,用來對加密的Assetbundle進行讀取,可以結合WWW使用。
壓縮LZMA(Ziv-Markov chain algorithm)格式
Unity打包成AssetBundle時的默認格式,會將序列化數據壓縮成LZMA流,使用時需要整體解包。優點是打包後體積小,缺點是解包時間長,且佔用內存。
LZ4格式:5.3新版本添加的壓縮格式,壓縮率不及LZMA,但是不需要整體解壓。LZ4是基於chunk的算法,加載對象時只有響應的chunk會被解壓。
壓縮格式在打包時通過AssetBundleOption參數選擇
AssetBundle加載後會在內存中生成AssetBundle的序列化架構的佔用,一般來說遠遠小於資源本身,除非包含複雜的序列化信息(複雜多層級關係或複雜靜態數據的prefab等)
AssetBundle.Unload(bool unloadAllLoadedObjects);AssetBundle只有唯一的一個卸載函數,傳入的參數用來選擇是否將已經從此AssetBundle中加載的資源一起卸載。另外,已經從AssetBundle中加載的資源可以通過Resources.UnloadAsset(Object)卸載。如果想通過Resources.UnloadUnusedAssets()卸載從AssetBundle加載的資源,一定要先將AssetBundle卸載後才能生效。