這是一系列文章中的第二章,覆蓋了Unity5的Assets,Resources和資源管理
本文將從Unity編輯器和運行時兩個角度出發,主要探討以下兩方面內容:Unity序列化系統內部細節以及Unity如何維護不同對象之間的強引用。另外還會討論對象與資源的技術實現差別。
譯注:除非特別說明,下文中所有的“資源”均指代“Asset”。
本文內容是理解在Unity中如何高效加載和卸載資源的基礎。正確的資源管理對縮短加載時間並減少內存占用來說至關重要。今天先為大家分享上半部分內容。
1.1. 深入理解資源和對象
在理解Unity如何確保萬無一失地管理數據之前,首先要知道Unity是如何識別並序列化數據的。首先第一點,要正確區分資源(Asset)和對象(UnityEngine.Objects)。
Asset
資源(Asset)是硬盤中的文件,存儲在Unity工程的Assets文件夾內。例如,紋理(Texture),材質(Material)和FBX文件等,它們都是資源。一些資源的數據格式是Unity原生支持的,例如材質。有些資源則需要轉換為原生的數據格式后才能被Unity使用,例如FBX文件。
UnityEngine.Object
UnityEngine.Object,或者說以大寫字母O開頭的Object——對象,代表序列化數據的集合,表示某個資源的具體實例。它可以是Unity引擎使用的任何類型的資源,例如網格,Sprite,音頻剪輯或動畫剪輯。所有的對象(Object)都是UnityEngine.Object基類的子類。
特殊的Object類型
幾乎所有的對象(Object)類型都是內建的,其中有兩種比較特殊的類型。
- ScriptableObject為開發者提供了一套便捷的系統,供開發者自定義數據類型。這些類型可以被Unity直接序列化或反序列化,並在Unity編輯器的檢視器窗口中進行操作。
- MonoBehaviour提供了鏈接MonoScript的容器。MonoScript是一種內部數據類型,Unity用它保存對某個特定程序集和命名空間中特定腳本類的引用,MonoScript本身不包含任何實際的可執行代碼。
一對多關系
資源(Asset)與對象(Object)是一種一對多的關系,即一個資源文件可能會包括多個Object。
1.2. 對象之間的引用
所有UnityEngine.Objects都可以引用其他的UnityEngine.Objects。這里“其他的Object”可能存在於相同的資源文件中,或需要從其他資源文件導入。例如,一個材質Object通常有一個或多個紋理Object的引用。這些紋理Object一般是從一個或多個紋理資源文件中導入的(例如PNG或JPG文件)。
序列化后,這些引用由兩部分數據組成:文件GUID和本地ID。文件GUID用於識別資源(Asset)文件中目標資源(Resource)的存儲位置。而本地唯一(1)的ID負責識別單個資源文件中的Object,因為一個資源文件可能會包含多個Object。
文件GUID(.meta)
文件GUID存儲於.meta文件中。Unity會在首次導入資源文件時生成.meta文件,並和資源文件一起存儲在相同的目錄中。
上述的識別和引用系統可以使用文本編輯器查看:
- 創建一個全新的Unity工程,更改編輯器設置,將Edit - Project Settings - Editor中的Version Control設為Visible Meta Files,並將Asset Serialization設為文本。
- 新建材質並向工程中導入一個紋理。將材質賦給場景中的一個立方體,保存場景。
使用文本編輯器打開這個材質對應的.meta文件。在文件頂端附近會有一行被標示為“guid”,該行定義了材質資源文件的文件GUID。
fileFormatVersion: 2
guid: 6839b719d14310c4f945de352bac3767
timeCreated: 1472566765
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:
本地ID(具體文件)
如需查看本地ID,使用文本編輯器打開材質文件,材質Object的定義大致如下:
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
... more data ...
在上面的例子中,前面有&符號的數字就是材質的本地ID。如果這個材質的Object位於一個文件GUID為“abcdefg”的資源文件中,則該材質Object的唯一識別符就是文件GUID“abcdefg”和本地ID“2100000”的組合。
1.3. 為什么要用文件GUID和本地ID?
工作流程
在Unity中,為什么要使用文件GUID和本地ID這套系統呢?答案是為穩定服務,也是為了提供一套靈活的、無關具體平台的工作流程。文件GUID提供了文件存儲位置的抽象,這樣一個文件GUID就對應一個具體的文件,這個具體的文件存儲在什么位置也就無關緊要了。因此我們才能隨意移動這個文件而不破壞所有相關Object對這個文件的引用。
任何資源(Asset)文件中都可能含有(或通過導入產生)多個UnityEngine.Object資源(Resource),因此需要一個本地ID來對其中的Object做明確區分。
如果與資源文件相關聯的文件GUID丟失,則所有對該資源文件中的Object的引用都會被破壞。這就是必須保證.meta文件具有和資源文件相同的文件名並存儲在同一目錄下的原因。注意Unity會重新生成丟失或被刪除的.meta文件。
Unity編輯器維護映射表
Unity編輯器負責維護一張文件路徑與文件GUID之間關系的映射表。只要資源文件被讀取或導入,這個映射關系就會被建立,映射會將資源的具體位置和資源的文件GUID進行關聯。Unity編輯器處於打開狀態時,假設一個文件的.meta意外丟失,並且該資源文件的路徑沒有改變,編輯器可以保證這個資源會被分配到相同的文件GUID。
如果在Unity編輯器處於關閉狀態時丟失.meta文件,或資源文件被移動但沒有移動對應的.meta文件時,所有對資源文件中的Object的引用都會丟失。
1.4. 復合資源和導入器
資源導入器
正如前面深入理解資源與對象中所說的一樣,不能被Unity直接支持的資源類型必須經過導入才可以使用——使用資源導入器來完成。這些導入器是自動調用的,您也可以使用AssetImporter在腳本中調用API及其子類。例如,在導入單獨的紋理資源例如PNG和JPG時,TextureImporter API提供了導入時要使用的相關設置的訪問。
導入過程最終的產物是一系列UnityEngine.Object。在Unity編輯器中,這些對象會具體表現為父資源下的多個子資源,例如作為Sprite Atlas導入的紋理材質,其下屬會有多個嵌套的Sprite。每一個對象都會使用相同的文件GUID,因為它們的源數據都存儲在同一個資源文件中。它們在紋理資源中的具體區分工作則使用本地ID來完成。
Library文件夾
導入過程中會將源資源轉換為匹配Unity編輯器中選定的目標平台的格式。導入過程可能會牽涉一些重量級操作,例如紋理壓縮。如果每次打開Unity編輯器時都要執行這些操作,那效率就太低了。
為了解決這一問題,我們將資源導入的結果緩存在Library文件夾中。具體就是,導入進程的結果將會存儲在以資源文件GUID頭兩位作為名稱的文件夾中。這些文件夾位於 Library/metadata/ 目錄下。各個不同的對象會被序列化后存儲在一個二進制文件中,文件使用資源文件的GUID來命名。
這對所有資源都是一樣的,不僅僅是非原生資源。只不過Unity原生支持的資源不需要對其進行轉換或序列化處理。
上半部分的內容主要介紹了資源(Asset)和對象(UnityEngine.Objects)的區別,以及文件GUID和本地ID二者的關聯和差異。下半部分將為大家介紹第三種ID:對象的實例ID,並看看這些ID對資源在內存與顯存中的加載和卸載分別有着怎樣的作用。
Footnotes(腳注)
- 在文件中,本地ID是唯一的。即在一個資源文件中,里面包含的本地ID都是不重復的。
- 在內部,這種緩存被稱為PersistentManager。實際的轉換工作在在Unity的C++ Remapper類中進行,Remapper類沒有提供任何C# API調用接口。
- 運行時創建資源的示例是在腳本中創建Texture2D對象:
var myTexture = new Texture2D(1027, 768); - 程序運行時對象並沒有被卸載卻被從內存中移除的情況通常會發生在Unity失去了對圖形內容的控制的時候。例如,當手機應用被掛起並被強制在后台運行。這種情況下,手機操作系統通常會將所有的圖形資源從GPU顯存中強行卸載。之后APP再回到前台運行時,Unity不得不重新向GPU上傳需要的材質、着色器和網格數據,以便恢復場景的正常渲染。
到此整個Unity內部資源管理與對象引用及序列化的內容就結束了,希望看完本文的你對如何合理分配Unity項目結構都有了比較清晰的概念。
原文鏈接:http://unity3d.com/cn/learn/tuto ... tion?playlist=30089
感謝Unity官方翻譯組成員“E.A.S”對本文翻譯所做的貢獻。
轉載請注明來源:Unity官方中文社區 (forum.china.unity3d.com)。請勿私自更改任何版權說明信息。
