> 技术文档 > 深入Unity序列化_unity serializable

深入Unity序列化_unity serializable

在Unity中,序列化是一个重要的概念,尤其是在编辑器中处理数据时。Unity的序列化机制有其独特之处,虽然它提供了许多便利,但也存在一些限制。以下是对Unity序列化机制的深入探讨,以及如何处理不能被Unity序列化的类型和数据结构的解决方案。

Unity的序列化机制

Unity使用自己的序列化机制来保存和加载游戏对象的状态。这种机制允许Unity在编辑器中显示对象的属性,并在运行时保持对象的状态。Unity的序列化机制支持的类型包括:

  • 基本数据类型(如intfloatstring等)
  • Unity的内置类型(如Vector3Color等)
  • 自定义的[Serializable]
  • 数组和List

不能被Unity序列化的类型

如你所提到的,Unity的序列化机制不支持某些类型,包括:

  • 泛型:如List中的T如果是一个泛型类型,Unity无法序列化。
  • 字典Dictionary类型无法直接序列化。
  • 高维数组:Unity不支持多维数组。
  • 委托:Unity无法序列化委托类型。
  • 接口:虽然Unity 2019.3引入了[SerializeReference]属性来支持接口类型的序列化,但在此之前,接口类型的成员无法被序列化。

解决方案

对于不能被Unity序列化的类型,以下是一些常见的解决方案:

  1. 高维数组

    • 将高维数组转换为一维数组。例如,使用一维数组来存储数据,并在访问时进行索引转换。
  2. 字典

    • 将字典的键和值分别存储为两个List,在序列化时使用这两个列表,在运行时再将其转换为字典。
    [System.Serializable]public class SerializableDictionary<TKey, TValue> : ISerializationCallbackReceiver{ [SerializeField] private List<TKey> keys = new List<TKey>(); [SerializeField] private List<TValue> values = new List<TValue>(); private Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(); public void OnBeforeSerialize() { keys.Clear(); values.Clear(); foreach (var kvp in dictionary) { keys.Add(kvp.Key); values.Add(kvp.Value); } } public void OnAfterDeserialize() { dictionary.Clear(); for (int i = 0; i < keys.Count; i++) { dictionary[keys[i]] = values[i]; } }}
  3. 泛型类

    • 创建一个非泛型的包装类,并使用[Serializable]属性修饰该类。
  4. 委托

    • 对于不带返回值的委托,可以使用UnityEvent。对于带返回值的委托,可能需要更复杂的处理,通常不建议直接序列化。
  5. 空引用

    • Unity在序列化时会自动构造空引用的对象,但要注意避免无限循环的情况。可以通过设计避免类之间的循环引用。
  6. 多态

    • 对于多态类型,可以使用ISerializationCallbackReceiver接口来手动处理序列化和反序列化。

使用ISerializationCallbackReceiver

通过实现ISerializationCallbackReceiver接口,可以在序列化和反序列化时自定义处理逻辑。例如,处理字典的序列化:

[System.Serializable]public class MyClass : ISerializationCallbackReceiver{ [SerializeField] private List<string> keys; [SerializeField] private List<int> values; private Dictionary<string, int> myDictionary = new Dictionary<string, int>(); public void OnBeforeSerialize() { keys = new List<string>(myDictionary.Keys); values = new List<int>(myDictionary.Values); } public void OnAfterDeserialize() { myDictionary.Clear(); for (int i = 0; i < keys.Count; i++) { myDictionary[keys[i]] = values[i]; } }}

Unity无法序列化泛型的几个原因

Unity的序列化机制在设计时主要考虑了性能和易用性,因此它对某些类型的支持是有限的。具体到泛型类型,尤其是List中的T如果是一个泛型类型,Unity无法序列化的原因主要有以下几点:

1. 类型信息的丢失

Unity的序列化机制在序列化时需要知道每个字段的具体类型,以便能够正确地保存和恢复数据。对于泛型类型,尤其是当T本身也是一个泛型类型时,Unity在序列化时无法确定具体的类型信息。例如,List<List>List<List>是两种不同的类型,但在序列化时,Unity可能无法正确识别和处理这些类型。

2. 复杂性和性能

泛型类型的序列化涉及到类型参数的解析和处理,这会增加序列化和反序列化的复杂性。Unity的序列化机制旨在保持高效,因此它选择不支持某些复杂的类型,以避免在序列化过程中引入额外的性能开销。

3. Unity的序列化设计

Unity的序列化机制是基于特定的规则和约定的,主要支持简单的、常见的数据结构。对于复杂的泛型类型,Unity的设计者可能认为这些类型在游戏开发中不够常见,或者开发者可以通过其他方式(如自定义序列化)来处理这些类型。

4. 避免潜在的错误

支持泛型类型的序列化可能会导致一些潜在的错误和不一致性。例如,开发者可能在不同的上下文中使用相同的泛型类型,但在序列化时可能会遇到类型不匹配的问题。Unity选择不支持这些类型,可以减少这种潜在的错误。

解决方案

虽然Unity不支持泛型类型的序列化,但开发者可以通过以下方式来处理这些情况:

  • 使用非泛型类型:可以创建一个非泛型的包装类来替代泛型类型,并使用[Serializable]属性修饰该类。

  • 自定义序列化:通过实现ISerializationCallbackReceiver接口,手动处理泛型类型的序列化和反序列化。

  • 使用其他序列化方案:如前面提到的JSON、XML或二进制序列化,这些方案通常能够处理更复杂的数据结构,包括泛型类型。

通过这些方法,开发者可以在Unity中有效地处理泛型类型的序列化问题。