Unity学习笔记 - Assets, Objects and Serialization
Assets和Objects
? ? Asset是存儲在硬盤上的文件,保存在Unity項目的Assets文件夾內(nèi)。比如:紋理貼圖、材質(zhì)和FBX都是Assets。一些Assets以Unity原生格式保存數(shù)據(jù),例如材質(zhì)。另一些Assets需要通過處理轉(zhuǎn)換到原生格式,例如FBX。
? ? Object是一系列序列化數(shù)據(jù),這些數(shù)據(jù)描述了具體的資源實例,這可以是Unity使用的任意類型的資源,例如mesh,sprite,audio clip或animation clip。所有的Objects都是UnityEngine.Object的子類。
? ? 大部分Object類型都是Unity內(nèi)置的,但有兩個特殊類型:
? ? 1. ScriptableObject允許開發(fā)者定義他們自己的數(shù)據(jù)類型。這些類型能夠由Unity序列化和反序列化,并且在編輯器的Inspector窗口中進(jìn)行操作。
? ? 2. MonoBehaviour提供了鏈接到MonoScript的封裝。MonoScript是Unity的內(nèi)部數(shù)據(jù)類型,其中保存了指向在具體的程序集和命名空間中的具體腳本類的引用。MonoScripte不包含任何實際可執(zhí)行的代碼。
? ? Assets和Objects之間存在一對多的關(guān)系:也就是說,Asset文件內(nèi)能夠包含一個或多個Objects。
?
內(nèi)部對象引用
? ? 所有的UnityEngine.Objects都可以引用其他的UnityEngine.Objects,被引用的Objects可以和引用的Objects位于同一個Asset文件內(nèi),也可以是由其他Asset文件導(dǎo)入的。例如,材質(zhì)對象通常有一個或多個紋理對象的引用,這些紋理對象通常都是從紋理資源文件導(dǎo)入的(例如PNG或JPG)。
? ? 當(dāng)序列化的時候,這些對象由兩部分分離的數(shù)據(jù)組成:文件的GUID和Local ID。文件的GUID標(biāo)記了存儲資源的Asset文件。Local ID是局部唯一的(也就是說,在每個Asset文件中,Local ID都是唯一的),標(biāo)記了Asset文件中的每個Object。
? ? 文件的GUID存儲在.meta文件中。這些.meta文件是Unity第一次導(dǎo)入Assets時生成的,并且和Asset存儲在同一個目錄中。下圖展示了Diffuse材質(zhì)及其.meta文件:
? ? .meta文件中包含了GUID:
? ? 打開材質(zhì)文件本身,可以看到Local ID:
? ? 如果在場景中有對象使用該材質(zhì)進(jìn)行渲染,那么打開場景文件后,就會發(fā)現(xiàn)該材質(zhì)對象由GUID以及Local ID來標(biāo)記:
?
為什么使用GUID和Local ID?
? ? GUID的功能是提供文件路徑的抽象表示。只要使用GUID來關(guān)聯(lián)具體的文件,那么文件在磁盤上的位置就無關(guān)緊要了。因此可以隨意移動文件而不需要更新引用該文件的Objects(因為這些Objects存儲的都是文件的GUID)。
? ? 由于一個Asset文件可能包含多個UnityEngine.Object資源,因此需要用Local ID來明確的標(biāo)記每個不同的Object。
? ? 如果一個Asset文件關(guān)聯(lián)的GUID丟失的話,那么所有對該Asset文件中的Objects的引用都將丟失。當(dāng).meta文件丟失時,Unity會重新生成。
? ? Unity維護(hù)了具體文件路徑與GUID的映射關(guān)系。當(dāng)一個Asset被加載或?qū)霑r,就會新增一個映射項,該映射項將Asset的文件路徑和Asset文件的GUID連接在一起。如果一個Asset的.meta文件丟失但其文件路徑?jīng)]有發(fā)生變化的話,Unity能確保重新生成的.meta中記錄的GUID是保持不變的。
? ? 如果.meta文件在Unity關(guān)閉時丟失,或者Asset文件的路徑發(fā)生了變化,但.meta文件沒有跟著一起移動的話,那么所有對該Asset文件中的Objects的引用都將丟失。舉個例子,場景中的Cube使用了我創(chuàng)建的材質(zhì)Diffuse:
Diffuse材質(zhì)及其.meta文件存儲在Assets目錄下,如果現(xiàn)在在外部移動Diffuse材質(zhì)到Assets/Temp目錄下,由于沒有同時移動其.meta文件,因此Cube對其引用就會丟失:
?
資源及其導(dǎo)入
? ? 非Unity原生資源必須導(dǎo)入進(jìn)Unity中才能使用,這是通過asset importer完成的。這些improter在資源導(dǎo)入時會被自動調(diào)用,同時你也可以用AssetImporter及其子類的API來通過代碼調(diào)整資源導(dǎo)入過程。
? ? 資源導(dǎo)入的結(jié)果是一個或多個UnityEngine.Objects。在Unity中你可以看到一個父對象包含多個子對象,例如sprite atlas:。這些對象都共享同一個GUID,因為他們的源數(shù)據(jù)來自于同一個Asset文件。Unity使用Local ID來區(qū)分他們:
? ? 資源導(dǎo)入過程包含了十分耗時的操作,例如紋理壓縮。所以如果每次打開Unity都需要執(zhí)行一遍資源導(dǎo)入過程的話將會十分低效,因此,Unity將資源導(dǎo)入的結(jié)果緩存在Library文件夾中:。具體來說,存儲在以Asset文件的GUID前兩個數(shù)字命名的文件夾中,這些文件夾位于目錄Library/metadata:
實際上即使是Unity原生資源,也會將導(dǎo)入結(jié)果存儲在對應(yīng)文件中。但是原生資源不需要很長的轉(zhuǎn)換時間或重新序列化時間。?
? ?
實例ID
? ? 盡管GUID和Local ID健壯耐用,但是GUID的比較很耗時,而在運(yùn)行時我們需要有個十分高效的系統(tǒng)。因此Unity在內(nèi)部會維護(hù)一份緩存,這份緩存將GUID和Local ID轉(zhuǎn)換成獨(dú)一無二的整數(shù),這些整數(shù)被稱為Instance ID,每當(dāng)有新的Objects添加到緩存中時,Instance ID以簡單的單調(diào)遞增的方式進(jìn)行賦值。緩存維護(hù)了Instance ID,GUID和Local ID(這兩個定義了Object的源數(shù)據(jù)在磁盤上的位置)以及Object在內(nèi)存中的實例(如果Object已經(jīng)被加載到內(nèi)存中的話)之間的映射關(guān)系。這樣UnityEngine.Objects就可以維護(hù)相互之間的引用關(guān)系。通過Instance ID可以快速找到對應(yīng)的已經(jīng)加載的Object,如果對應(yīng)的Object還沒有加載,那么就可以通過GUID和Local ID來找到Object的源數(shù)據(jù),然后加載相應(yīng)的Object。
? ? 應(yīng)用程序啟動時,項目內(nèi)置對象(比如場景中使用的對象)的數(shù)據(jù)以及在Resources文件夾中的對象的數(shù)據(jù)將被初始化到Instance ID緩存中。當(dāng)運(yùn)行時有新的資源被導(dǎo)入(比如通過腳本創(chuàng)建的Texture2D對象),以及當(dāng)從AssetBundle中加載對象時,就會在緩存中添加Instance ID項。Instance ID只有在被認(rèn)為已經(jīng)過時的情況下才會從緩存中刪除,這種情況發(fā)生在一個AssetBundle被卸載時。當(dāng)一個AssetBundle被卸載時,除了會導(dǎo)致對應(yīng)的Instance ID被認(rèn)為已經(jīng)過時,Instance ID和GUID以及Local ID之間的映射數(shù)據(jù)也會被從內(nèi)存中刪除。如果AssetBundle被重新加載的話,那么從該AssetBundle中加載的每一個對象都會創(chuàng)建一個新的Instance ID。
? ? 需要注意的是在具體平臺上的一些特定事件會導(dǎo)致Objects從內(nèi)存中被刪除。比如當(dāng)iOS上的應(yīng)用程序被掛起時,圖形資源可能會從顯存中被刪除,如果這些資源是來自一個已經(jīng)被卸載的AssetBundle,那么Unity就無法重新加載這些資源了,任何對這些資源的引用也將變得無效(例如出現(xiàn)不可見的模型(missing)使用粉色的材質(zhì)(missing)來渲染)。
?
MonoScript
? ? 一個MonoBehaviour包含了一個對MonoScript的引用,而MonoScript僅僅包含了用于定位到一個具體腳本類所需的信息,他們都不包含腳本類的可執(zhí)行代碼。
? ? 一個MonoScript中包含了三個字符串:一個程序集名,一個類名以及一個命名空間名。
? ? 當(dāng)Unity構(gòu)建項目時,會將Assets文件夾下的所有腳本文件編譯到Mono程序集中。具體來說,Unity會為在Assets文件夾中使用的每種不同的編程語言編譯一個程序集,并且會將在Assets/Plugins文件夾中的腳本單獨(dú)編譯到一個程序集中。在Assets/Plugins文件夾外的C#腳本會被編譯到Assetmbly-CSharp.dll中,在Assets/Plugins文件夾外的Java腳本會被編譯到Assembly-UnityScript.dll中,Assets/Plugins中的腳本會被編譯到Assembly-CSharp-firstpass.dll中。
? ? 這些程序集(再加上預(yù)編譯的程序集)都會被包含在最終的應(yīng)用程序中:
這些程序集就是MonoScript引用的程序集。和其他資源不同,所有程序集在應(yīng)用程序第一次啟動時會被全部加載進(jìn)來。這種方式也是為什么一個AssetBundle(或者一個Scene、一個Prefab)中不包含掛載的MonoBehaviour組件中的可執(zhí)行代碼。這種方式使得不同的MonoBehaviour可以引用共同的具體類。
?
資源生命周期
? ? 有兩種加載UnityEngine.Objects的方式:自動加載和顯示的手動加載。當(dāng)一個Instance ID被解引用,其對應(yīng)的Object當(dāng)前沒有加載到內(nèi)存中,并且Object的源數(shù)據(jù)能夠被定位到時,Object會被自動加載。Objects還能夠顯示的在腳本中手動加載,例如新建一個Texture2D或通過AssetBundle.LoadAsset方式加載一個Object。
? ? 如果一個文件GUID和Local ID沒有對應(yīng)的Instance ID,或者一個Instance ID對應(yīng)的Object沒有被加載,并且其對應(yīng)的GUID和Local ID是無效的話,那么Object就不會被加載,但是引用關(guān)系仍舊會被保留,此時在Unity編輯器中就會出現(xiàn)"(Missing)"。
? ? Objects在下面三種具體的情況下會被卸載:?
? ? 1. 當(dāng)清理未被引用的Asset時,未被引用的Objects會被自動卸載。當(dāng)場景切換時或當(dāng)調(diào)用Resources.UnloadUnusedAssets函數(shù)時會觸發(fā)清理未被引用的Asset。
? ? 2. 來自Resources文件夾的Objects在調(diào)用Resources.UnloadAsset函數(shù)時會被銷毀。但是Instance ID會被保留,所以如果在Object被銷毀后,有任何先前對該對象的引用被解引用時,Unity會重新通過Instance ID找到GUID和Local ID,然后將該對象再次加載進(jìn)來。
? ? 3. 來自AssetBundle的Objects在調(diào)用AssetBundle.Unload(true)函數(shù)時會被立即銷毀,同時也會使得Instance ID,GUID和Local ID變得無效,任何對該對象的引用也會變成"(Missing)"。之后在C#中任何對該對象的訪問都會引發(fā)"NullReferenceException"異常。如果調(diào)用AssetBundle.Unload(false),從AssetBundle加載的Objects不會被銷毀,但是Instance ID對應(yīng)的GUID和Local ID會變得無效,因此如果這些對象被從內(nèi)存中釋放的話,Unity將無法再次加載他們。
?
加載大層級對象
? ? 當(dāng)序列化Unity GameObjects(例如Prefabs)時,要記住整個層級都會被序列化。也就是說,層級中每個GameObject及其組件在序列化數(shù)據(jù)中都會被獨(dú)立的表示。因此,加載和實例化具有大層級的GameObjects時會有性能影響。
? ? 當(dāng)實例化GameObjects時,實例化一個具有大層級的GameObject和實例化多個小層級的GameObjects然后將這些GameObjects組合在一起相比,需要耗費(fèi)更多的CPU時間。盡管實例化一個大層級的GameObject不需要組合GameObjects(不需要trampolining和SendTransformChanged回調(diào))的CPU時間,但這些節(jié)約的CPU時間遠(yuǎn)遠(yuǎn)比不過讀取和反實例化大層級數(shù)據(jù)的時間。
? ? 之前提到,序列化GameObjects時,整個層級中的GameObject及其組件數(shù)據(jù)都會被序列化 --- 即使這些數(shù)據(jù)是重復(fù)的。比如一個UI中有30個一樣的Button,那么Button數(shù)據(jù)會被序列化30次。在加載時,這些數(shù)據(jù)都需要從磁盤上進(jìn)行讀取,在加載大層級的GameObjects時,文件讀取時間會消耗大量CPU時間。因此,可以把重復(fù)對象從整個層級中移出來,再單獨(dú)實例化后再組合到整個層級中。
? ??
轉(zhuǎn)載于:https://www.cnblogs.com/twjcnblog/p/5673309.html
總結(jié)
以上是生活随笔為你收集整理的Unity学习笔记 - Assets, Objects and Serialization的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ 2236 Wireless N
- 下一篇: Web安全1沙箱隔离