查找了
Script Serialization
http://docs.unity3d.com/Manual/script-Serialization.html
自定義序列化及例子:
http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnBeforeSerialize.html
和Unity 聖典
在這博友 的基礎上 再總結下Unity 的序列化
序列化的作用是: 可以顯示變數(類,結構等)在inspector(檢視面板)上.如圖
當Unity在執行時候的能在inspector(檢視面板)上看到變數(類,結構等)時候,Unity已經發生了序列化;
(當你在inspector上操作資料的時候,Unity就會把這些資料序列化為檔案,當遊戲在執行的時候,Unity會反序列化這些檔案來賦值給執行的物件,類似別的程式設計中通過建構函式初始化物件效果是一樣的)
Unity觸發序列化的時候不僅僅是再inspector上修改資料,還有在 繼承了MonoBehaviour的遊戲物件通過克隆instantiate()方法的時候,它也會反序列化和序列化.
Unity序列化的關鍵字是 Serializable 和 SerializeField.
Serializable: 用於 自定義的,非 abstract 的類. 結構體等 , 使這型別也能序列化
SerializeField: 用於 非public 型別(如private), 使非public 型別也能序列化
Serializable 作用 序列化的attribute,是為了利用序列化的技術
準備用於序列化的物件必須設定 [System.Serializable] 標籤,該標籤指示一個類可以序列化。
便於在網路中傳輸和儲存
這個標籤是類可以被序列化的特性,表示這個類可以被序列化。
什麼叫序列化?
我們都知道物件是暫時儲存在記憶體中的,不能用U盤考走了,有時為了使用介質轉移物件,並且把物件的狀態保持下來,就需要把物件儲存下來,這個過程就叫做序列化,通俗點,就是把人的魂(物件)收伏成一個石子(可傳輸的介質)
什麼叫反序列化?
就是再把介質中的東西還原成物件,把石子還原成人的過程。
在進行這些操作的時候都需要這個可以被序列化,要能被序列化,就得給類頭加[Serializable]特性。
通常網路程式為了傳輸安全才這麼做。
簡介
序列化是指將物件例項的狀態儲存到儲存媒體的過程。在此過程中,先將物件的公共欄位和私有欄位以及類的名稱(包括類所在的程式集)轉換為位元組流,然後再把位元組流寫入資料流。在隨後對物件進行反序列化時,將創建出與原物件完全相同的副本。
在面向物件的環境中實現序列化機制時,必須在易用性和靈活性之間進行一些權衡。只要您對此過程有足夠的控制能力,就可以使該過程在很大程度上自動進行。例如,簡單的二進位制序列化不能滿足需要,或者,由於特定原因需要確定類中那些欄位需要序列化。以下各部分將探討 .NET 框架提供的可靠的序列化機制,並著重介紹使您可以根據需要自定義序列化過程的一些重要功能。
持久儲存
我們經常需要將物件的欄位值儲存到磁碟中,並在以後檢索此資料。儘管不使用序列化也能完成這項工作,但這種方法通常很繁瑣而且容易出錯,並且在需要跟蹤物件的層次結構時,會變得越來越複雜。可以想象一下編寫包含大量物件的大型業務應用程式的情形,程式設計師不得不為每一個物件編寫程式碼,以便將欄位和屬性儲存至磁碟以及從磁碟還原這些欄位和屬性。序列化提供了輕鬆實現這個目標的快捷方法。
公共語言執行時 (CLR) 管理物件在記憶體中的分佈,.NET 框架則通過使用反射提供自動的序列化機制。物件序列化後,類的名稱、程式集以及類例項的所有資料成員均被寫入儲存媒體中。物件通常用成員變數來儲存對其他例項的引用。類序列化後,序列化引擎將跟蹤所有已序列化的引用物件,以確保同一物件不被序列化多次。.NET 框架所提供的序列化體系結構可以自動正確處理物件圖表和迴圈引用。對物件圖表的唯一要求是,由正在進行序列化的物件所引用的所有物件都必須標記為 Serializable(請參閱基本序列化)。否則,當序列化程式試圖序列化未標記的物件時將會出現異常。
當反序列化已序列化的類時,將重新建立該類,並自動還原所有資料成員的值。
按值封送
物件僅在建立物件的應用程式域中有效。除非物件是從 MarshalByRefObject 派生得到或標記為 Serializable,否則,任何將物件作為引數傳遞或將其作為結果返回的嘗試都將失敗。如果物件標記為 Serializable,則該物件將被自動序列化,並從一個應用程式域傳輸至另一個應用程式域,然後進行反序列化,從而在第二個應用程式域中產生出該物件的一個精確副本。此過程通常稱為按值封送。
如果物件是從 MarshalByRefObject 派生得到,則從一個應用程式域傳遞至另一個應用程式域的是物件引用,而不是物件本身。也可以將從 MarshalByRefObject 派生得到的物件標記為 Serializable。遠端使用此物件時,負責進行序列化並已預先配置為 SurrogateSelector 的格式化程式將控制序列化過程,並用一個代理替換所有從 MarshalByRefObject 派生得到的物件。如果沒有預先配置為 SurrogateSelector,序列化體系結構將遵從下面的標準序列化規則(請參閱序列化過程的步驟)。
基本序列化
要使一個類可序列化,最簡單的方法是使用 Serializable 屬性對它進行標記,如下所示:
[Serializable] public class MyObject { public int n1 = 0; public int n2 = 0; public String str = null; }
以下程式碼片段說明了如何將此類的一個例項序列化為一個檔案:
MyObject obj = new MyObject(); obj.n1 = 1; obj.n2 = 24; obj.str = "一些字串"; IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None); formatter.Serialize(stream, obj); stream.Close();
本例使用二進位制格式化程式進行序列化。您只需建立一個要使用的流和格式化程式的例項,然後呼叫格式化程式的 Serialize 方法。流和要序列化的物件例項作為引數提供給此呼叫。類中的所有成員變數(甚至標記為 private 的變數)都將被序列化,但這一點在本例中未明確體現出來。在這一點上,二進位制序列化不同於只序列化公共欄位的 XML 序列化程式。
將物件還原到它以前的狀態也非常容易。首先,建立格式化程式和流以進行讀取,然後讓格式化程式對物件進行反序列化。以下程式碼片段說明了如何進行此操作。
IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read); MyObject obj = (MyObject) formatter.Deserialize(fromStream); stream.Close(); // 下面是證明 Console.WriteLine("n1: {0}", obj.n1); Console.WriteLine("n2: {0}", obj.n2); Console.WriteLine("str: {0}", obj.str);
上面所使用的 BinaryFormatter 效率很高,能生成非常緊湊的位元組流。所有使用此格式化程式序列化的物件也可使用它進行反序列化,對於序列化將在 .NET 平臺上進行反序列化的物件,此格式化程式無疑是一個理想工具。需要注意的是,對物件進行反序列化時並不呼叫建構函式。對反序列化新增這項約束,是出於效能方面的考慮。但是,這違反了物件編寫者通常採用的一些執行時約定,因此,開發人員在將物件標記為可序列化時,應確保考慮了這一特殊約定。
如果要求具有可移植性,請使用 SoapFormatter。所要做的更改只是將以上程式碼中的格式化程式換成 SoapFormatter,而 Serialize 和 Deserialize 呼叫不變。對於上面使用的示例,該格式化程式將生成以下結果。
<SOAP-ENV:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd="" xmlns:SOAP- ENC=http://schemas.xmlsoap.org/soap/encoding/ xmlns:SOAP- ENV=http://schemas.xmlsoap.org/soap/envelope/ SOAP-ENV:encodingStyle= "" xmlns:a1=""> <SOAP-ENV:Body> <a1:MyObject id="ref-1"> <n1>1</n1> <n2>24</n2> <str id="ref-3">一些字串</str> </a1:MyObject> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
需要注意的是,無法繼承 Serializable 屬性。如果從 MyObject 派生出一個新的類,則這個新的類也必須使用該屬性進行標記,否則將無法序列化。例如,如果試圖序列化以下類例項,將會顯示一個 SerializationException,說明 MyStuff 型別未標記為可序列化。
public class MyStuff : MyObject
{
public int n3;
}
使用序列化屬性非常方便,但是它存在上述的一些限制。有關何時標記類以進行序列化(因為類編譯後就無法再序列化),請參考有關說明(請參閱下面的序列化規則)。
選擇性序列化
類通常包含不應被序列化的欄位。例如,假設某個類用一個成員變數來儲存執行緒 ID。當此類被反序列化時,序列化此類時所儲存的 ID 對應的執行緒可能不再執行,所以對這個值進行序列化沒有意義。可以通過使用 NonSerialized 屬性標記成員變數來防止它們被序列化,如下所示:
[Serializable] public class MyObject { public int n1; [NonSerialized] public int n2; public String str; }
自定義序列化
可以通過在物件上實現 ISerializable 介面來自定義序列化過程。這一功能在反序列化後成員變數的值失效時尤其有用,但是需要為變數提供值以重建物件的完整狀態。要實現 ISerializable,需要實現 GetObjectData 方法以及一個特殊的建構函式,在反序列化物件時要用到此建構函式。以下程式碼示例說明了如何在前一部分中提到的 MyObject 類上實現 ISerializable。
[Serializable] public class MyObject : ISerializable { public int n1; public int n2; public String str; public MyObject() { } protected MyObject(SerializationInfo info, StreamingContext context) { n1 = info.GetInt32("i"); n2 = info.GetInt32("j"); str = info.GetString("k"); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("i", n1); info.AddValue("j", n2); info.AddValue("k", str); } }
在序列化過程中呼叫 GetObjectData 時,需要填充方法呼叫中提供的 SerializationInfo 物件。只需按名稱/值對的形式新增將要序列化的變數。其名稱可以是任何文字。只要已序列化的資料足以在反序列化過程中還原物件,便可以自由選擇新增至 SerializationInfo 的成員變數。如果基物件實現了 ISerializable,則派生類應呼叫其基物件的 GetObjectData 方法。
需要強調的是,將 ISerializable 新增至某個類時,需要同時實現 GetObjectData 以及特殊的建構函式。如果缺少 GetObjectData,編譯器將發出警告。但是,由於無法強制實現建構函式,所以,缺少建構函式時不會發出警告。如果在沒有建構函式的情況下嘗試反序列化某個類,將會出現異常。在消除潛在安全性和版本控制問題等方面,當前設計優於 SetObjectData 方法。例如,如果將 SetObjectData 方法定義為某個介面的一部分,則此方法必須是公共方法,這使得使用者不得不編寫程式碼來防止多次呼叫 SetObjectData 方法。可以想象,如果某個物件正在執行某些操作,而某個惡意應用程式卻呼叫此物件的 SetObjectData 方法,將會引起一些潛在的麻煩。
在反序列化過程中,使用出於此目的而提供的建構函式將 SerializationInfo 傳遞給類。物件反序列化時,對建構函式的任何可見性約束都將被忽略,因此,可以將類標記為 public、protected、internal 或 private。一個不錯的辦法是,在類未封裝的情況下,將建構函式標記為 protect。如果類已封裝,則應標記為 private。要還原物件的狀態,只需使用序列化時採用的名稱,從 SerializationInfo 中檢索變數的值。如果基類實現了 ISerializable,則應呼叫基類的建構函式,以使基礎物件可以還原其變數。
如果從實現了 ISerializable 的類派生出一個新的類,則只要新的類中含有任何需要序列化的變數,就必須同時實現建構函式以及 GetObjectData 方法。以下程式碼片段顯示瞭如何使用上文所示的 MyObject 類來完成此操作。
[Serializable] public class ObjectTwo : MyObject { public int num; public ObjectTwo() : base() { } protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context) { num = si.GetInt32("num"); } public override void GetObjectData(SerializationInfo si, StreamingContext context) { base.GetObjectData(si,context); si.AddValue("num", num); } }
切記要在反序列化建構函式中呼叫基類,否則,將永遠不會呼叫基類上的建構函式,並且在反序列化後也無法構建完整的物件。
物件被徹底重新構建,但是在反系列化過程中呼叫方法可能會帶來不良的副作用,因為被呼叫的方法可能引用了在呼叫時尚未反序列化的物件引用。如果正在進行反序列化的類實現了 IDeserializationCallback,則反序列化整個物件圖表後,將自動呼叫 OnSerialization 方法。此時,引用的所有子物件均已完全還原。有些類不使用上述事件偵聽器,很難對它們進行反序列化,散列表便是一個典型的例子。在反序列化過程中檢索關鍵字/值對非常容易,但是,由於無法保證從散列表派生出的類已反序列化,所以把這些物件添加回散列表時會出現一些問題。因此,建議目前不要在散列表上呼叫方法。
序列化過程的步驟
在格式化程式上呼叫 Serialize 方法時,物件序列化按照以下規則進行:
檢查格式化程式是否有代理選取器。如果有,檢查代理選取器是否處理指定型別的物件。如果選取器處理此物件型別,將在代理選取器上呼叫 ISerializable.GetObjectData。
如果沒有代理選取器或有卻不處理此型別,將檢查是否使用 Serializable 屬性對物件進行標記。如果未標記,將會引發 SerializationException。
如果物件已被正確標記,將檢查物件是否實現了 ISerializable。如果已實現,將在物件上呼叫 GetObjectData。
如果物件未實現 Serializable,將使用預設的序列化策略,對所有未標記為 NonSerialized 的欄位都進行序列化。
版本控制
.NET 框架支援版本控制和並排執行,並且,如果類的介面保持一致,所有類均可跨版本工作。由於序列化涉及的是成員變數而非介面,所以,在向要跨版本序列化的類中新增成員變數,或從中刪除變數時,應謹慎行事。特別是對於未實現 ISerializable 的類更應如此。若當前版本的狀態發生了任何變化(例如新增成員變數、更改變數型別或更改變數名稱),都意味著如果同一型別的現有物件是使用早期版本進行序列化的,則無法成功對它們進行反序列化。
如果物件的狀態需要在不同版本間發生改變,類的作者可以有兩種選擇:
實現 ISerializable。這使您可以精確地控制序列化和反序列化過程,在反序列化過程中正確地新增和解釋未來狀態。
使用 NonSerialized 屬性標記不重要的成員變數。僅當預計類在不同版本間的變化較小時,才可使用這個選項。例如,把一個新變數新增至類的較高版本後,可以將該變數標記為 NonSerialized,以確保該類與早期版本保持相容。
序列化規則
由於類編譯後便無法序列化,所以在設計新類時應考慮序列化。需要考慮的問題有:是否必須跨應用程式域來發送此類?是否要遠端使用此類?使用者將如何使用此類?也許他們會從我的類中派生出一個需要序列化的新類。只要有這種可能性,就應將類標記為可序列化。除下列情況以外,最好將所有類都標記為可序列化:
所有的類都永遠也不會跨越應用程式域。如果某個類不要求序列化但需要跨越應用程式域,請從 MarshalByRefObject 派生此類。
類儲存僅適用於其當前例項的特殊指標。例如,如果某個類包含非受控的記憶體或檔案控制代碼,請確保將這些欄位標記為 NonSerialized 或根本不序列化此類。
某些資料成員包含敏感資訊。在這種情況下,建議實現 ISerializable 並僅序列化所要求的欄位
指令碼的欄位需要滿足什麼條件才能被序列化?(必須同時滿足)
1. public 型別,或者有 [SerializeField]
2. 不是 static
3. 不是 const
4. 不是 readonly
5. 型別必須是 Unity 可以序列化的型別
Unity 可以序列化哪型別?
1. 自定義的,非 abstract 的類,且有 [Serializable]
2. 有 [Serializable] 的結構體
3. UnityEngine.Object 的子類的引用
4. 原生型別(int,float,double,bool,string, etc)
5. 以上型別的陣列
6. 以上型別的 List<T>
以下幾種情況,Unity的序列化並不一定能按你的預期執行:
自定義類像結構: 待理解
[Serializable]
class Animal
{
public string name;
} class MyScript : MonoBehaviour
{
public Animal[] animals;
}
類成員裡面有Null的變數:
例如在序列化一個變數(類.結構等)的時候,如果這個變數裡面有Null,Unity將會的例項化一個新物件,然後序列化,然後進入死迴圈,因為Unity的序列化在非主執行緒執行,所以出現這種情況會導致程式效能下降
class Test : MonoBehaviour
{
public Trouble t;
} [Serializable]
class Trouble
{
public Trouble t1;
public Trouble t2;
public Trouble t3;
}
不支援多型:待理解
如果我們要正確的序列化以上問題,就要用ISerializationCallbackReceiver介面:
OnBeforeSerialize():
這個方法是執行在Unity序列化前, 用來通知你Unity將準備序列化
OnAfterDeserialize():
這個方法是執行在Unity序列化後,用來通知你Unity將已經序列化完
PS:當你在inspector面板編輯了資料(發生了序列化)後,Unity會在腳本里面先執行OnAfterDeserialize();然後再一直執行OnBeforeSerialize(),除非你又在inspector面板改資料(序列化),才會再觸發OnAfterDeserialize()
例如我想建立一個序列化樹型別 類裡面有為Null變數, 違反了第二條限制,直接讓Unity執行序列化,將會導致樹型別資料很大, 效能下降.
using UnityEngine;
using System.Collections.Generic;
using System; public class VerySlowBehaviourDoNotDoThis : MonoBehaviour
{
[Serializable]
public class Node
{
public string interestingValue = "value"; //The field below is what makes the serialization data become huge because
//it introduces a 'class cycle'.
public List<Node> children = new List<Node>();
} //this gets serialized
public Node root = new Node(); void OnGUI()
{
Display (root);
} void Display(Node node)
{
GUILayout.Label ("Value: ");
node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width()); GUILayout.BeginHorizontal ();
GUILayout.Space ();
GUILayout.BeginVertical (); foreach (var child in node.children)
Display (child);
if (GUILayout.Button ("Add child"))
node.children.Add (new Node ()); GUILayout.EndVertical ();
GUILayout.EndHorizontal ();
}
}
解決的方法使用ISerializationCallbackReceiver介面: 讓你生成一個快取變數來序列化,而不是直接序列化一個樹型別
using UnityEngine;
using System.Collections.Generic;
using System; public class BehaviourWithTree : MonoBehaviour, ISerializationCallbackReceiver
{
//node class that is used at runtime
public class Node
{
public string interestingValue = "value";
public List<Node> children = new List<Node>();
} //node class that we will use for serialization
[Serializable]
public struct SerializableNode
{
public string interestingValue;
public int childCount;
public int indexOfFirstChild;
} //the root of what we use at runtime. not serialized.
Node root = new Node(); //the field we give unity to serialize.
public List<SerializableNode> serializedNodes; public void OnBeforeSerialize()
{
//unity is about to read the serializedNodes field's contents. lets make sure
//we write out the correct data into that field "just in time".
serializedNodes.Clear();
AddNodeToSerializedNodes(root);
} void AddNodeToSerializedNodes(Node n)
{
var serializedNode = new SerializableNode () {
interestingValue = n.interestingValue,
childCount = n.children.Count,
indexOfFirstChild = serializedNodes.Count+
};
serializedNodes.Add (serializedNode);
foreach (var child in n.children)
AddNodeToSerializedNodes (child);
} public void OnAfterDeserialize()
{
//Unity has just written new data into the serializedNodes field.
//let's populate our actual runtime data with those new values. if (serializedNodes.Count > )
root = ReadNodeFromSerializedNodes ();
else
root = new Node ();
} Node ReadNodeFromSerializedNodes(int index)
{
var serializedNode = serializedNodes [index];
var children = new List<Node> ();
for(int i=; i!= serializedNode.childCount; i++)
children.Add(ReadNodeFromSerializedNodes(serializedNode.indexOfFirstChild + i)); return new Node() {
interestingValue = serializedNode.interestingValue,
children = children
};
} void OnGUI()
{
Display (root);
} void Display(Node node)
{
GUILayout.Label ("Value: ");
node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width()); GUILayout.BeginHorizontal ();
GUILayout.Space ();
GUILayout.BeginVertical (); foreach (var child in node.children)
Display (child);
if (GUILayout.Button ("Add child"))
node.children.Add (new Node ()); GUILayout.EndVertical ();
GUILayout.EndHorizontal ();
}
}
要小心使用序列化,因為序列化並不是執行在主執行緒!