一个 脚本属性,指示 Unity 将字段序列化为引用而不是值。
有关序列化和完整序列化规则的信息,请参阅 序列化手册页面。
如果没有使用 [SerializeReference]
属性,Unity 会根据字段的类型以及这些序列化规则,按值或按引用序列化对象的每个字段。
[Serializable]
属性标记的自定义可序列化类或结构,则将其序列化为值。在自定义可序列化类的案例中,这意味着它只序列化分配给该字段的对象的数据,而不是对该对象的引用本身。要强制 Unity 将这些字段类型序列化为引用,请使用 [SerializeReference]
属性。
您可能希望按引用而不是按值序列化,当
[SerializeReference]
属性的情况下对自定义可序列化类使用 多态性,则派生类的附加字段将不会保存,您的对象将被序列化为该字段的声明类型。您可以在下面的 SerializeReferencePolymorphismExample
类中看到多态性。
优化
就存储、内存以及加载和保存时间而言,按值序列化比使用 SerializeReference 更有效,因此您应仅在需要的情况下使用 SerializeReference。
宿主对象和托管引用
在 SerializeReference 的上下文中,专门化 MonoBehaviour、ScriptableObject、ScriptedImporter 或其他 UnityEngine 类的对象称为**宿主对象**。您可以直接在宿主对象的字段上使用 SerializeReference,或者间接地在宿主对象内序列化的自定义结构或类的字段上使用 SerializeReference。
在宿主对象中使用 [SerializeReference]
属性的字段分配的对象是**托管引用**。每个托管引用都有一个唯一的 ID,该 ID 由包含与之关联的 [SerializeReference]
字段的 MonoBehaviour、ScriptableObject 或其他宿主对象提供。默认情况下,Unity 会自动生成此 ID;要指定 ID,请使用 ManagedReferenceUtility.SetManagedReferenceIdForObject。
当您使用 SerializeReference 时,托管引用对象仅作为定义它们的宿主对象内的共享引用可用。当宿主对象被序列化时,任何托管引用对象都会在序列化数据的“引用”部分被序列化,该部分是一个位于常规字段序列化之后的列表。每个托管引用对象在列表中都有一项条目,记录其 ID、其完全限定的类名以及其字段的值。
如果您使用的是默认的“强制文本” 资产序列化模式,您可以亲眼看到这些序列化数据。为此,将下面示例脚本之一分配给一个 GameObject,保存场景,然后在文本编辑器中打开 .unity
场景文件。每个托管引用的数据都存储在 .unity
文件中,位于序列化 MonoBehaviour 数据的“引用:”部分。
托管引用不会在其他 UnityEngine.Object 实例之间共享。如果您将同一个自定义可序列化类对象分配给两个不同宿主对象的字段,则序列化引用将变为单独的实例。此外,如果您使用托管引用克隆宿主对象,这也将创建所有托管引用对象的单独副本。
要共享多个宿主对象之间的引用值,请使用 ScriptableObject 而不是 SerializeReference。ScriptableObject 允许您将一组相关数据作为资产一起分组。由于 ScriptableObject 派生自 UnityEngine.Object,因此您可以在单个宿主对象之外共享对它们的引用。但是,序列化规则仍然适用于 ScriptableObject 上的字段,因此您可能需要在 ScriptableObject 派生类内的字段上使用 [SerializeReference]
属性。
SerializeReference 属性支持类型为以下任何内容的字段
分配给具有 SerializeReference 属性的字段的值(除非为 null)必须遵循以下规则
[SerializeReference]
属性的情况下序列化。有关在数组和列表中使用 SerializeReference 的说明
List<T>
字段,SerializeReference 属性适用于数组或列表的元素,而不是数组或列表对象本身。[SerializeReference] public System.Object a = new List<MyCustomClass>(); // 不支持
[SerializeReference] public List<MyCustomClass> a = new List<MyCustomClass>(); // 有效
其他说明
另请参阅: SerializedProperty.managedReferenceValue、MonoBehaviour、SerializationUtility、ManagedReferenceUtility。
using System; using UnityEngine;
public class SerializeReferencePolymorphismExample : MonoBehaviour { [Serializable] public class Base { public int m_Data = 1; }
[Serializable] public class Apple : Base { public string m_Description = "Ripe"; }
[Serializable] public class Orange : Base { public bool m_IsRound = true; }
// Use SerializeReference if this field needs to hold both // Apples and Oranges. Otherwise only m_Data from Base object would be serialized [SerializeReference] public Base m_Item = new Apple();
[SerializeReference] public Base m_Item2 = new Orange();
// Use by-value instead of SerializeReference, because // no polymorphism and no other field needs to share this object public Apple m_MyApple = new Apple(); }
using System; using System.Text; using UnityEngine;
public class SerializeReferenceLinkedListExample : MonoBehaviour { // This example shows a linked list structure with a single int per Node. // This would be much more efficiently represented using a List<int>, without any SerializeReference needed. // But it demonstrates an approach that can be extended for trees and other more advanced graphs
[Serializable] public class Node { // This field must use serialize reference so that serialization can store // a reference to another Node object, or null. By-value // can never properly represent this sort of self-referencing structure. [SerializeReference] public Node m_Next = null;
public int m_Data = 1; }
[SerializeReference] public Node m_Front = null;
// Points to the last node in the list. This is an // example of a having more than one field pointing to a single Node // object, which cannot be done with "by-value" serialization [SerializeReference] public Node m_End = null;
SerializeReferenceLinkedListExample() { AddEntry(1); AddEntry(3); AddEntry(9); AddEntry(81); PrintList(); }
private void AddEntry(int data) { if (m_Front == null) { m_Front = new Node() {m_Data = data}; m_End = m_Front; } else { m_End.m_Next = new Node() {m_Data = data}; m_End = m_End.m_Next; } }
private void PrintList() { var sb = new StringBuilder(); sb.Append("Link list contents: "); var position = m_Front; while (position != null) { sb.Append(" Node data " + position.m_Data).AppendLine(); position = position.m_Next; } Debug.Log(sb.ToString()); } }
using System; using System.Collections.Generic; using UnityEngine;
public interface IShape {}
[Serializable] public class Cube : IShape { public Vector3 size; }
[Serializable] public class Thing { public int weight; }
[ExecuteInEditMode] public class BuildingBlocks : MonoBehaviour { [SerializeReference] public List<IShape> inventory;
[SerializeReference] public System.Object bin;
[SerializeReference] public List<System.Object> bins;
void OnEnable() { if (inventory == null) { inventory = new List<IShape>() { new Cube() {size = new Vector3(1.0f, 1.0f, 1.0f)} }; Debug.Log("Created list"); } else Debug.Log("Read list");
if (bins == null) { // This is supported, the 'bins' serialized field is declared as a collection, with each entry as a reference. bins = new List<System.Object>() { new Cube(), new Thing() }; }
if (bin == null) { // !! DO NOT USE !! // Although this is syntactically correct, it is not supported as a valid serialization construct because the 'bin' serialized field is declared as holding a single reference type. bin = new List<System.Object>() { new Cube() }; } } }