Unity-腳本序列化
序列化是將數(shù)據(jù)結(jié)構(gòu)或?qū)ο鬆顟B(tài)轉(zhuǎn)換為 Unity 可存儲(chǔ)并在以后可重構(gòu)的格式的自動(dòng)過程。Unity 的一些內(nèi)置功能會(huì)使用序列化,比如保存和加載、Inspector 窗口、實(shí)例化和預(yù)制件等功能。請(qǐng)參閱有關(guān)內(nèi)置序列化使用情況的文檔,了解所有相關(guān)的背景詳情。
數(shù)據(jù)在 Unity 項(xiàng)目中的組織方式會(huì)影響 Unity 序列化該數(shù)據(jù)的方式,并會(huì)對(duì)項(xiàng)目的性能產(chǎn)生重大影響。以下是有關(guān) Unity 中的序列化以及如何針對(duì)其進(jìn)行項(xiàng)目?jī)?yōu)化的一些準(zhǔn)則。
另請(qǐng)參閱以下相關(guān)文檔:序列化錯(cuò)誤、自定義序列化和內(nèi)置序列化。
了解熱重載
熱重載
熱重載是在 Editor 打開的狀態(tài)下創(chuàng)建或編輯腳本并立即應(yīng)用腳本行為的過程。無(wú)需重新啟動(dòng)游戲和 Editor 即可使更改生效。
更改并保存腳本時(shí),Unity 會(huì)熱重載所有當(dāng)前加載的腳本數(shù)據(jù)。它首先將所有可序列化變量存儲(chǔ)在所有加載的腳本中,并在加載腳本后恢復(fù)它們。熱重載后,所有不可序列化的數(shù)據(jù)都將丟失。
保存和加載
Unity 使用序列化技術(shù)從計(jì)算機(jī)的硬盤驅(qū)動(dòng)器加載和保存場(chǎng)景、資源和?AssetBundle。這包括保存在您自己的腳本 API 對(duì)象(如?MonoBehaviour?組件和?ScriptableObject)中的數(shù)據(jù)。
Unity Editor 中的許多功能都建立在核心序列化系統(tǒng)之上。對(duì)于序列化要特別注意的兩點(diǎn)是?Inspector 窗口和熱重載。
Inspector 窗口
在?Inspector 窗口中查看或更改游戲?qū)ο蟮慕M件字段的值時(shí),Unity 會(huì)序列化此數(shù)據(jù),然后在 Inspector 窗口中顯示數(shù)據(jù)。Inspector 窗口在顯示字段值時(shí)不與 Unity Scripting API 通信。
如果在腳本中使用屬性,則在 Inspector 窗口中查看或更改值時(shí),絕不會(huì)調(diào)用任何屬性 getter 和 setter,因?yàn)?Unity 會(huì)直接序列化 Inspector 窗口字段。這意味著:當(dāng) Inspector 窗口中的字段值表示腳本屬性時(shí),對(duì) Inspector 窗口中值的更改不會(huì)調(diào)用腳本中的任何屬性 getter 和 setter
序列化規(guī)則
Unity 中的序列化程序在實(shí)時(shí)游戲環(huán)境中運(yùn)行。這對(duì)性能有重大影響。因此,Unity 中的序列化與其他編程環(huán)境中的序列化具有不同的行為。下面列出了一些關(guān)于如何在 Unity 中使用序列化的技巧。
如何確保腳本中的字段被序列化
確保其符合以下條件:
為?
public
,或具有?SerializeField?屬性非?
static
非?
const
非?
readonly
具有可序列化的?
fieldtype
(請(qǐng)參閱下面的可序列化的簡(jiǎn)單字段類型。)
可序列化的簡(jiǎn)單字段類型
具有?
Serializable
?屬性的自定義非抽象、非泛型類
(請(qǐng)參閱下面的如何確保自定義類可序列化。)具有?
Serializable
?屬性的自定義結(jié)構(gòu)對(duì)從?UnityEngine.Object?派生的對(duì)象的引用
原始數(shù)據(jù)類型(
int
、float
、double
、bool
、string
?等)枚舉類型
某些 Unity 內(nèi)置類型:
Vector2
、Vector3
、Vector4
、Rect
、Quaternion
、Matrix4x4
、Color
、Color32
、LayerMask
、AnimationCurve
、Gradient
、RectOffset
、GUIStyle
可序列化的容器字段類型
可序列化的簡(jiǎn)單字段類型的數(shù)組
可序列化的簡(jiǎn)單字段類型的?
List<T>
注意:Unity 不支持多級(jí)類型(多維數(shù)組、交錯(cuò)數(shù)組和嵌套容器類型)的序列化。
如果要序列化這些類型,可使用兩種方法:將嵌套類型包裝在類或結(jié)構(gòu)中,或使用序列化回調(diào)?ISerializationCallbackReceiver?執(zhí)行自定義序列化。有關(guān)更多信息,請(qǐng)參閱自定義序列化的文檔。
如何確保自定義類可序列化
確保其符合以下條件:
具有?Serializable?屬性
非抽象
非靜態(tài)
非泛型(但可繼承自泛型類)
要確保自定義類或結(jié)構(gòu)的字段被序列化,請(qǐng)參閱上面的如何確保腳本中的字段被序列化。
序列化程序何時(shí)可能出現(xiàn)意外行為?
自定義類的行為類似于結(jié)構(gòu)
對(duì)于不是從?UnityEngine.Object?派生的自定義類,Unity 以內(nèi)聯(lián)方式按值對(duì)它們進(jìn)行序列化,類似于結(jié)構(gòu)的序列化方式。如果在多個(gè)不同的字段中存儲(chǔ)對(duì)自定義類的實(shí)例的引用,則在序列化時(shí)它們將成為單獨(dú)的對(duì)象。然后,當(dāng) Unity 反序列化這些字段時(shí),它們將包含具有相同數(shù)據(jù)的不同對(duì)象。
需要序列化具有引用的復(fù)雜對(duì)象圖時(shí),不要讓 Unity 自動(dòng)序列化對(duì)象。相反,應(yīng)使用?ISerializationCallbackReceiver
?手動(dòng)序列化它們。這樣可以防止 Unity 從對(duì)象引用創(chuàng)建多個(gè)對(duì)象。有關(guān)更多信息,請(qǐng)參閱?ISerializationCallbackReceiver?的文檔。
這種情況僅適用于自定義類。Unity 以“內(nèi)聯(lián)”方式對(duì)自定義類進(jìn)行序列化,因?yàn)檫@些類的數(shù)據(jù)會(huì)成為使用這些類的?MonoBehaviour?或?ScriptableObject?的完整序列化數(shù)據(jù)的一部分。當(dāng)字段引用?UnityEngine.Object?派生的某個(gè)類(例如?public Camera myCamera
)時(shí),Unity 會(huì)序列化對(duì)攝像機(jī)?UnityEngine.Object
?的實(shí)際引用。如果腳本派生自?MonoBehaviour
?或?ScriptableObject
(二者均從?UnityEngine.Object
?派生而來(lái)),在腳本的實(shí)例中也適用相同的序列化原則。
自定義類不支持?null
思考在反序列化一個(gè)使用以下腳本的?MonoBehaviour
?時(shí)進(jìn)行了多少次分配。
出現(xiàn)一次以下分配并不奇怪:Test
?對(duì)象的一次分配。另外,出現(xiàn)兩次以下分配也不奇怪:Test
?對(duì)象的一次分配和?Trouble
?對(duì)象的一次分配。
但是,Unity 實(shí)際上進(jìn)行超過一千次的分配。序列化程序不支持 null。如果序列化程序?qū)δ硞€(gè)對(duì)象進(jìn)行序列化,并且一個(gè)字段為 null,則 Unity 將實(shí)例化該類型的新對(duì)象,并對(duì)該對(duì)象進(jìn)行序列化。顯然,這可能導(dǎo)致無(wú)限循環(huán),因此存在七級(jí)的深度限制。達(dá)到該限制時(shí),Unity 停止序列化具有自定義類、結(jié)構(gòu)、列表或數(shù)組類型的字段。
由于 Unity 的子系統(tǒng)很多都是在序列化系統(tǒng)之上構(gòu)建的,因此?Test MonoBehaviour
?的這種異常大型的序列化流會(huì)導(dǎo)致所有這些子系統(tǒng)的執(zhí)行速度低于必要速度。
不支持多態(tài)
如果具有?public Animal[] animals
?并輸入一個(gè)?Dog
、一個(gè)?Cat
?和一個(gè)?Giraffe
?的實(shí)例,則在序列化之后有三個(gè)?Animal
?實(shí)例。
解決此限制的一種方法是認(rèn)識(shí)到它僅適用于內(nèi)聯(lián)序列化的自定義類。對(duì)其他?UnityEngine.Objects
?的引用被序列化為實(shí)際引用,對(duì)于這些情況,多態(tài)確實(shí)有效。可創(chuàng)建一個(gè)?ScriptableObject
?派生類或另一個(gè)?MonoBehaviour
?派生類,并引用該類。這樣做的缺點(diǎn)是需要在某處存儲(chǔ)該?Monobehaviour
?或腳本化對(duì)象,并且無(wú)法有效地對(duì)其進(jìn)行內(nèi)聯(lián)序列化。
設(shè)置這些限制的原因是序列化系統(tǒng)的核心基礎(chǔ)之一是提前知道對(duì)象的數(shù)據(jù)流布局;它取決于類的字段類型,而不是存儲(chǔ)在字段內(nèi)的具體內(nèi)容。
提示
序列化的優(yōu)化應(yīng)用
您可以組織數(shù)據(jù)來(lái)確保從 Unity 的序列化獲得最佳使用效果。
組織數(shù)據(jù)的目的是讓 Unity 序列化盡可能小的數(shù)據(jù)集。這樣做的主要目的不是為了節(jié)省計(jì)算機(jī)硬盤驅(qū)動(dòng)器上的空間,而是為了確保您可以保持與項(xiàng)目以前版本的向后兼容性。如果使用大型的序列化數(shù)據(jù)集,那么在開發(fā)后期保持向后兼容性會(huì)變得更加困難。
組織數(shù)據(jù)時(shí)確保絕不會(huì)讓 Unity 序列化重復(fù)的數(shù)據(jù)或緩存的數(shù)據(jù)。否則會(huì)導(dǎo)致向后兼容性出現(xiàn)嚴(yán)重問題:存在著很高的錯(cuò)誤風(fēng)險(xiǎn),因?yàn)閿?shù)據(jù)太容易失去同步。
避免使用嵌套的遞歸結(jié)構(gòu)引用其他類。序列化結(jié)構(gòu)的布局總是必須相同;獨(dú)立于數(shù)據(jù),僅依賴于腳本中公開的內(nèi)容。引用其他對(duì)象的唯一方法是通過?UnityEngine.Object?派生的類。這些類是完全獨(dú)立的;它們只互相引用,沒有嵌入內(nèi)容。
使 Editor 代碼可熱重載
重新加載腳本時(shí),Unity 會(huì)在所有加載的腳本中序列化并存儲(chǔ)所有變量。重新加載腳本后,Unity 會(huì)將它們恢復(fù)為序列化前的原始值。
重新加載腳本時(shí),Unity 會(huì)恢復(fù)滿足序列化要求的所有變量(包括私有變量),即使變量沒有?SerializeField?屬性也是如此。在某些情況下,需要特意防止恢復(fù)私有變量:例如,如果您希望從腳本重新加載后引用為 null。在這種情況下,請(qǐng)使用?NonSerialized?屬性。
Unity 絕不會(huì)恢復(fù)靜態(tài)變量,因此對(duì)于重新加載腳本后需要保留的狀態(tài),不要使用靜態(tài)變量。