版本: Unity 6 (6000.0)
语言英语
  • C#

SerializeReference

UnityEngine 中的类

/

实现于: UnityEngine.CoreModule

建议更改

成功!

感谢您帮助我们提高 Unity 文档的质量。尽管我们无法接受所有提交内容,但我们会阅读用户提出的每项更改建议,并在适当的情况下进行更新。

关闭

提交失败

由于某种原因,您的更改建议无法提交。请<a>稍后再试</a>。感谢您抽出时间帮助我们提高 Unity 文档的质量。

关闭

取消

描述

一个 脚本属性,指示 Unity 将字段序列化为引用而不是值。

有关序列化和完整序列化规则的信息,请参阅 序列化手册页面

如果没有使用 [SerializeReference] 属性,Unity 会根据字段的类型以及这些序列化规则,按值或按引用序列化对象的每个字段。

  • UnityEngine.Object 字段,按引用
    如果字段类型派生自 UnityEngine.Object,Unity 会将其序列化为对该对象的引用。例如,一个定义了 Transform 字段的 MonoBehaviour。引用 UnityEngine.Object 的字段(如本例所示)不需要 SerializeReference 属性,因为字段的序列化始终记录对独立序列化的对象的引用。

  • 其他字段类型,按值
    如果字段类型是 Unity 可以自动按值序列化(简单字段类型,如 int、string、Vector3 等),或者它是使用 [Serializable] 属性标记的自定义可序列化类或结构,则将其序列化为值。

在自定义可序列化类的案例中,这意味着它只序列化分配给该字段的对象的数据,而不是对该对象的引用本身。要强制 Unity 将这些字段类型序列化为引用,请使用 [SerializeReference] 属性。

您可能希望按引用而不是按值序列化,当

  • 您希望对自定义可序列化类的同一实例有多个引用。
    例如,基于自定义可序列化类的基于引用的拓扑结构(如链表、树结构或循环图)需要引用序列化。这是因为默认的值序列化将每个引用存储为对象的单独副本,而字段最初共享对同一对象的单个引用。特别是,如果您序列化循环图数据结构,则必须使用 SerializeReference 来正确地保存图,并避免默认序列化方法可能导致的潜在冻结或崩溃。

  • 您希望在类型为自定义可序列化类的字段上使用多态性。
    如果您尝试在没有 [SerializeReference] 属性的情况下对自定义可序列化类使用 多态性,则派生类的附加字段将不会保存,您的对象将被序列化为该字段的声明类型。您可以在下面的 SerializeReferencePolymorphismExample 类中看到多态性。

  • 您希望序列化空值。
    基于值的序列化无法表示空值。如果没有使用 SerializeReference,空值将被替换为具有未分配字段的内联对象,并在序列化数据中。使用 SerializeReference 允许您存储空引用。

优化

就存储、内存以及加载和保存时间而言,按值序列化比使用 SerializeReference 更有效,因此您应仅在需要的情况下使用 SerializeReference。

宿主对象和托管引用

在 SerializeReference 的上下文中,专门化 MonoBehaviourScriptableObjectScriptedImporter 或其他 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 属性支持类型为以下任何内容的字段

  • 常规类
  • 抽象类
  • 接口
  • System.Object

分配给具有 SerializeReference 属性的字段的值(除非为 null)必须遵循以下规则

  • 必须是自定义类的实例,并且具有 [Serializable] 属性。
  • 必须是该字段类型的实例,或派生自该类型的类型。
  • 不能派生自 UnityEngine.Object。例如,它不能是 GameObject、MonoBehaviour、ScriptableObject 或 Transform。
  • 不能是 C# 值类型。因此,像整数这样的简单类型以及结构不受支持,应该改为在没有 [SerializeReference] 属性的情况下序列化。
  • 不能是 C# Dictionary,或其他 Unity 序列化不支持的类型

有关在数组和列表中使用 SerializeReference 的说明

  • Unity 支持按引用序列化对象数组和列表。
  • 对于数组和 List<T> 字段,SerializeReference 属性适用于数组或列表的元素,而不是数组或列表对象本身。
  • 您不能将数组或列表分配给 System.Object 类型的字段。相反,字段类型需要显式声明为数组或列表。
    此示例中说明了这一点

    [SerializeReference] public System.Object a = new List<MyCustomClass>(); // 不支持
    [SerializeReference] public List<MyCustomClass> a = new List<MyCustomClass>(); // 有效

其他说明

  • 当宿主对象派生自 ScriptableObject 或 ScriptedImporter 时,不支持对引用对象的字段进行动画处理。
  • 当反序列化时 SerializeReference 引用的类型不再可用时,Unity 将用 null 替换实例,但序列化信息将被保留。有关更多信息,请参阅 SerializationUtility.HasManagedReferencesWithMissingTypes

另请参阅: SerializedProperty.managedReferenceValueMonoBehaviourSerializationUtilityManagedReferenceUtility

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() }; } } }