unity資源加載和卸載(腳本加載卸載,資源序列化後的結構,bundle內的序列化結構)

轉自:https://www.cnblogs.com/zblade/p/11095338.html

 

一、概要

在瞭解unity的資源管理方式之後,接下來細談一下Unity的資源是如何從磁盤中加載到運行時的內存中,以及又是如何被卸載的。這部分較爲繁瑣,可能會寫較多的過程。

二、腳本資源的加載和卸載

在unity中的腳本資源,大體可以分爲C++編譯的引擎dll文件,c#編譯的dll文件,lua腳本文件(基於lua熱更的方式下)。

2.1 dll文件的加載和卸載

在工程的Library/ScriptAssemblies文件夾下,會有當前工程非引擎相關的dll文件列表:

在遊戲啓動的時候,會執行Assembly.Load的操作,將這些dll文件加載進進程中(editor類相關的dll不會被加載)。

能夠加載,自然也能在運行時被卸載,所以目前一種熱更新方案ILRuntime就是對dll文件進行加載,熱更新,卸載,加載最新的dll這樣的方式進行操作。這種熱更新方式主要是針對Assembly-CSharp.dll/Assembly-CSharp-firstpass.dll進行操作。
如果不是主動進行卸載,那麼這些被加載的dll文件,在遊戲進程中,是不會被卸載釋放的,只有在遊戲進程結束的時候,纔會被系統從內存中卸載出去。

2.2 lua文件的加載和卸載

lua由於腳本文件的屬性,可以被當做類文本文件進行熱更新,同時在遊戲啓動的時候纔開啓一個Lua虛擬機,在Lua虛擬機中才執行lua文件的require相關操作。
這類文件的加載,其實質就是將這部分代碼讀入到lua虛擬機的全局緩存中,而所謂的卸載,就是將這部分緩存置爲nil,和上面的dll文件的加載和卸載含義有一些差異。    

三、非腳本資源的加載和卸載

非腳本資源,纔是整個遊戲進程中需要處理的主要部分,會伴隨整個遊戲進程,直到遊戲進程結束。
個人對unity對資源的加載過程的理解,其本質就是一個反序列化的過程。

3.1 Serialization and Instance

unity在序列化的時候,對於每個組件,也是單獨逐個的執行序列化的操作的,其序列化信息的關鍵信息是文件本身的fileID, 以及依賴文件的fileID 和guid.
對應的,在unity的Instance操作中,unity會爲該GameObject創建一個唯一的InstanceID, 在進程內部會緩存這樣一個InstanceID <-> gameobject的映射關係表,同時 fileID/GUID/LocalID 會對應的映射到該文件的源文件存儲位置。這個InstanceID具有唯一性,當InstanceID創建完成後,如果object沒有被load,則會觸發unity執行一次資源的load,基於fileID/Guid/local id來執行object的加載。
實際的遊戲運行中,並不會直接依賴fileID/GUID來執行文件的映射,而是會將這兩個ID轉換成一個新的ID,所以在實際運行的遊戲中是看不到fileID/GUID相關文件的。

3.2 InstanceID的創建和失效

在遊戲啓動的時候,會將啓動場景以及Resources目錄下的資源逐個創建對應的InstanceID, 放入到緩存中,這部分InstanceID的創建耗時會隨着Resources目錄包含資源的增多而增大,所以儘量減小這部分資源的數目。
在遊戲啓動完成後,後續按需加載的Object,都會對應的創建InstanceID, 在卸載該資源的時候,會對應使這個映射關係失效,後續重新加載該object的時候,是會被重新創建對應的InstanceID, 先前創建的InstanceID是不會被重新定位到新加載的Object的。

3.3 AssetBundle中文件的加載

現在在Unity中主要的資源管理是基於AssetBundle的管理,那麼運行時,是如何從bundle中加載出想要的Object的?

3.3.1 Bundle文件的組成

在Unity中,bundle會被分爲兩種大類,場景Bundle和非場景Bundle,利用unity自帶的WebExtract 和 Binary2Text兩個工具,是可以解壓bundle爲文本文件的。
場景Bundle在使用WebExtract解壓後,會得到兩類文件:

  • BuildPlayer-sceneName: 場景序列化文件,也就是hierarchy序列化的結果
  • BuildPlayer-sceneName.sharedasset: 場景依賴的文件

普通bundle在使用WebExtract解壓後,會得到兩類文件:

  • CAB-GuidString: 該二進制文件爲該bundle的序列化文件,以及可能包含的具體Object文件
  • CAB-GuidString.resS:如果包含這個文件,則上面的文件目前是不能轉換成txt文件的

以轉換成功的bundle的序列化文件爲參照,可以分析主要包含以下幾個部分:   

1) External References:

   

可以理解爲依賴的外部assetbundle,這兒並不是依賴的bundle的名字,而是類似的cab-guidstring的形式,可見unity內部對於bundle的相互依賴處理,是基於這樣一套的命名來進行管理的。    

2) object map:

當前bundle包含的object的信息map:

3) bundle頭文件: AssetBundle

4) bundle包含的object的詳細序列化信息       

3.3.2 Bundle文件的加載過程

分析完bundle的組成後,接下來分析從bundle中加載Object的過程,這兒以LZ4的壓縮爲標準,基於AssetBundle.LoadFromFile(Async)做爲接口。 在加載Object的時候,會首先觸發加載該object所在的bundle,如果該bundle有依賴bundle,那麼需要先加載該bundle的依賴bundle,Unity並不會自動加載依賴bundle.

unity在加載bundle的時候,會先加載該bundle的序列化文件,也就是前面說到的External References/Object map/bundle頭文件,然後基於得到序列化信息,進一步從bundle的object序列化信息中加載對應的object。

如果該object有多個依賴的資源,unity會在內部自動從該bundle或者依賴bundle中將依賴資源加載出來,然後執行資源的裝載,最終返回一份實例到內存中,完成InstanceID 和 gameobject的映射。

3.3.3 Bundle文件中對script的加載

如果bundle中的object上有對應的script,那麼在構建Bundle的時候,會爲這個script構建一份特殊的資源:MonoScript,對應的存入到bundle中,monoscript這種資源,並沒有包含實際的運行時的代碼,而是存儲這個腳本的assembly name, namespace name and class name,在裝配該Object的時候,由於dll已經提前裝載,所以會自動的索引到裝載的assembly/namespace/class name腳本,然後裝配到該object上。

3.4 資源的卸載

在不考慮資源計數管理的情況下,當資源的引用爲空的時候,是可以執行資源的卸載的。對於資源的卸載,分爲Resources資源和bundle資源:

3.4.1 Resources資源

1)可以調用Resources.UnloadAsset接口來卸載資源,使用該接口後,下次再加載該Object,會重新建立舊InstanceID和該Object的映射關係

2) 在執行場景切換的時候,如果選擇 non-additively mode,這時候會自動的觸發Resources.UnloadUnusedAsset 

3.4.2 Bundle資源

bundle的卸載,有AssetBundle.Unload(true)/Unload(false)兩種:

1)Unload(true): 將bundle從內存中卸載,同時將從bundle中加載的所有資源都卸載掉

2) Unload(false): 將bundle從內存中卸載,從bundle中已經加載的資源會被保留,如果再重新加載該bundle,對應的不會重新構建bundle和資源的映射關係,以前加載的資源就容易造成內存泄露。

目前推薦自己計數管理,只調用Unload(true)

四、Resources的使用

Unity的官方文檔:

4.1. Best Practices for the Resources System

Don't use it. several reasons:

1) 使用resources使得精細化細粒度的內存管理更困難

2) 不恰當的使用Resources目錄會增大遊戲的啓動時間以及build時間
而且隨着Resources目錄中文件的增加,對其中資源的管理會越來越困難

3) Resources目錄無法根據平臺定製資源內容(除非在build的時候重新拷貝指定的資源到Resource目錄)

4) Resources目錄無法提供熱更新

4.2. Proper uses of the Resources system

在某些情況下,Resources目錄還是可以使用:

1) rapidly prototype開發階段

2) generally required throughout a project's lifetime

3) Not memory-intensive

4) Not prone to patching, or does not vary across platforms or devices

5) Used for minimal bootstrapping 某些和平臺無關的配置文件,不佔用較大內存,可以放入到Resources目錄中

4.3. Serialization of Resources

在build的時候,Resources目錄下的Assets/Objects會被序列化到一個序列化文件中。在這個文件中,包含了元數據以及索引信息,類似於AssetBundle。

索引信息其實就是一個序列化的查找樹,用來定位資源名字到資源的file guid和local ID, 同時用於查找這個資源本身。

在大部分的平臺上,查找樹是BST, 其構建的時間複雜度爲O(nlog(n)), 構建的時間會隨着n的增大而增大(資源數越多構建時間越久)

在遊戲啓動的時候,這個BST樹構建的過程是無法跳過的,如果Resources下的資源數目超過10,000,在低端手機上其佔用的啓動時間會達到幾秒。而事實上這些資源索引並不是全部需要預先加載的,這會降低遊戲的性能。

五、總結

簡要的闡述了整個資源加載和卸載的流程,對於AssetBundle的使用和管理,屬於新的分類內容,在後續再細談。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章