[Unity編輯器擴展基礎總結] 第5章 序列化對象 SerializedObject


第5章 序列化對象 SerializedObject

 

Unity可以將資源序列化成特殊的格式來使用,這是在Unity中使用對象的基礎。在Unity官方手冊中有“SerializedObject”的詳細信息。http://docs.unity3d.com/Manual/script-Serialization.html

5.1 SerializedObject簡介

SerializedObject是將數據結構或對象轉換為Unity便於存儲和處理的格式。Unity的很多內置功能都在使用序列化,比如保存和加載數據,監視器窗口,預設

SerializedObject與在Unity上處理的所有對象都有關系。如果沒有Seri​​alizedObject,就無法創建通常要處理的資源(材質,紋理,動畫剪輯等)。

UnityEngine.Object和SerializedObject之間的關系

    在Unity編輯器中,所有對象(UnityEngine.Object)都會被轉換為SerializedObject並進行處理。當我們在Inspector中編輯組件的值時,不是在編輯Component組件的實例,而是在編輯SerializedObject的實例。

    在Unity編輯器中或者在編輯器擴展中,所有對象的操作應該盡可能用SerializedObject。這是因為SerializedObject不僅能處理序列化的數據,而且還能處理UndoSelection的操作。

撤消處理 Undo
當使用SerializedObject編輯值時,撤消過程可以自動注冊。如果直接編輯UnityEngine.Object實例,則必須自己實現撤消過程。有關撤消的更多信息,請參見以后的第12章。
選擇處理 Selection
當我們在項目窗口中選擇資源時,它會立即對其進行反序列化以獲取UnityEngine.Object的實例,並在Inspector中顯示它的值。這樣處理的主要用處是可以允許我們在選擇多個對象的時候同時進行編輯。

資源和SerializedObject之間的關系

將UnityEngine.Object保存為資源時,會將其另存為二進制YAML文本數據,而SerializedObject負責這些序列化。

要將UnityEngine.Object保存為資源,必須將其先轉為SerializedObject。然后,轉換后的SerializedObject嘗試創建資源和.meta文件。

資源和.meta文件之間的關系

SerializedObject會創建兩個文件,一個資源文件和一個.meta文件。資源文件是真實對象的序列化版本,而.meta文件保存導入的相關設置等。

參考以下代碼:

using UnityEditor;
using UnityEngine; public class EditorTest : ScriptableObject { [InitializeOnLoadMethod] static void CheckPropertyPaths() { var so = new SerializedObject(Texture2D.whiteTexture); var pop = so.GetIterator(); while (pop.NextVisible(true)) { Debug.Log(pop.propertyPath); } } }

備注:[InitializeOnLoadMethod] Editor文件夾下,添加了InitializeOnLoad特性后,其構造方法會自動執行,測試結果是,每次修改這個類的內容,就會重新執行一遍構造方法。可以在構造方法中執行一些操作,來控制Editor模式下的代碼執行。

 日志中顯示的內容如下:

 

正如我們所看到的,當轉換為SerializedObject時,Texture2D對象也帶有很多導入相關的設置。將Texture2D保存為資源時,是無法將這些設置寫入紋理文件的(jpg或png),因此只能將其寫入.meta文件,也就是元數據。

同樣,導入資源時,會從資源和.meta文件(如果沒有.meta文件,默認情況下會自動生成)生成SerializedObject,並將其轉換為UnityEngine.Object。

可序列化的類變量

判斷UnityEngine.Object的派生類(我們經常使用的MonoBehaviour,ScriptableObject,Editor,EditorWindow等)中字段是否可以序列化要看一下幾點

  • 必須是公共變量或具有Seri​​alizeField屬性的字段
  • 必須是Unity支持可序列化的類型(sbyte,short,int,long,byte,ushort,uint,ulong,float,double,bool,char,string,UnityEngine.Object,具有Seri​​alizable屬性和結構的類等)

以下兩點要重點說明一下

  • 變量不能帶static,const,readonly修飾符
  • 不是抽象類

我們經常看到一些書中說把需要在監視器中展示的字段設成公共變量,這只是為了方便非程序員更容易理解,而使其成為公共變量只是序列化的條件之一,有時候給私有變量添加SerializeField屬性是更加合理的解決方案。

using UnityEngine;

public class EditorTest : MonoBehaviour { [SerializeField] private string m_str; public string str { get { return m_str; } set { m_str = value; } } }

 從外部訪問帶有Seri​​alizeField屬性的字段時,需要通過SerializedObject訪問。

 

5.2 如何使用SerializedObject

從SerializedObject中獲取參數

序列化后的數據字段可以通過SerializedProperty來檢索,就像本章開頭那樣,使用迭代器來遍歷操作所有屬性。

using UnityEditor;
using UnityEngine; public class EditorTest : ScriptableObject { [InitializeOnLoadMethod] static void CheckPropertyPaths() { var so = new SerializedObject(Texture2D.whiteTexture); var pop = so.GetIterator(); while (pop.NextVisible(true)) { Debug.Log(pop.propertyPath); } } }

我們還可以獲取指定路徑的SerializedProperty。

例如,要獲取”Vector3類型變量的值時“

var seObj = 類實例
var serializedObject = new SerializedObject(seObj); serializedObject.FindProperty ("position").vector3Value;

當我們想要獲取A類中的變量類型為B的字符串變量值時

[System.Serializable]
public class B { [SerializeField] 
string bar; }
public class A : MonoBehaviour
{
    [SerializeField] B m_b;
}
var m_a = /* 獲取a的實例 */;
var serializedObject = new SerializedObject(m_a); serializedObject.FindProperty ("m_b.bar").stringValue;

當我們想獲取數組中對應索引的值時

serializedObject.FindProperty("數組變量").GetArrayElementAtIndex(1);

獲取變量並更新最新數據

SerializedObject在內部進行緩存,如果在實例化時已被緩存,則將從緩存中取出SerializedObject。

例如,如果在編輯器窗口內和檢視器內分別為一個對象生成一個SerializedObject,並且不同步兩個SerializedObjects,那么其中一個可能會使用舊信息進行更新。

因此,如果一個對象有兩個SerializedObjects,則兩個SerializedObjects應該始終保持最新狀態,以使其中一個不會因信息沒及時更新而過期。

Unity提供了兩個API來解決此問題。

Update

從內部緩存中獲取最新數據。在訪問SerializedObject之前先對其進行更新,以使其保持最新狀態。

using UnityEditor;
using UnityEngine; public class EditorTest : Editor { public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("name")); } }

ApplyModifiedProperties

將更改應用到內部緩存,使其保持最新。通過ApplyModifiedProperties應用更改,這樣將此視為一組。

除非有特定的條件可以應用更改,否則將Update放在方法的第一行上,並將ApplyModifiedProperties放在方法的最后一行。

using UnityEditor;
using UnityEngine; public class EditorTest : Editor { public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("name")); //其他處理
serializedObject.ApplyModifiedProperties(); } }

 

5.3 在一個SerializedObject中處理多個UnityEngine.Objects

 我們可以簡單地通過在SerializedObject構造函數中傳遞一個數組來同時處理多個UnityEngine.Objects的情況。但是,只能將相同的類型作為參數傳遞。如果將不同類型的對象作為參數傳遞,會導致鍵值映射不匹配,從而發生錯誤。

Rigidbody[] rigidbodies = /* 獲取組件 */;

var serializedObject = new SerializedObject(rigidbodies); serializedObject.FindProperty ("m_UseGravity").boolValue = true;

 

5.4 獲取屬性名稱

要想訪問SerializedProperty,我們首先需要知道屬性的路徑。如果要訪問MonoBehaviour組件,則可以通過查看腳本文件輕松地找到對應屬性路徑。在Unity端實現的組件以及與UnityEngine.Object相關的屬性名稱中都包含m_,由於m_在檢查器中會被省略並顯示為屬性名稱,因此很難知道實際屬性。另外,在檢查器中顯示的屬性名稱可能與實際的屬性名稱並不匹配。

 

有兩種獲取屬性名稱的主要方法:

SerializedObject.GetIterator

可以使用迭代器把所有屬性的名稱打印出來,文章開頭有介紹。

在文本編輯器中查看資源

如果目標是組件,可以在設置中將預設體“Asset Serialization”設置為“Force Text“,然后在文本編輯器中打開預制件。

這樣就可以看到YAML格式的數據,其中列出了屬性名稱。

 

 我們也可以使用UnityEditorInternal命名空間中的InternalEditorUtility.SaveToSerializedFileAndForget將UnityEngine.Object另存為資源。

using UnityEngine;
using UnityEditorInternal; using UnityEditor; public class EditorTest : MonoBehaviour { void Start () { var rigidbody = GetComponent<Rigidbody> (); InternalEditorUtility.SaveToSerializedFileAndForget(new Object[]{ rigidbody }, "Rigidbody.yml", true); } }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM