2024-07-20 Unity插件 Odin Serializer2 —— 序列化教程

1 由根对象决定序列化

​ 在使用 Odin 时,Odin 会尽量避免序列化 Unity 本身应该已经序列化的任何字段。这是在根序列化对象的级别确定的:

public class ExampleScript : SerializedScriptableObject
{
    // Unity 不会序列化, Odin 会序列化。
    public Dictionary<int, string> FirstDictionary;

    // Unity 会序列化,因此 Odin 将不会序列化。
    public MyClass MyReference;
}

[Serializable]
public class MyClass
{
    // 尽管有 OdinSerialize 属性,但此字段未序列化。
    [OdinSerialize]
    public Dictionary<int, string> SecondDictionary;
}
  1. FirstDictionary 字段将由 Odin 序列化,因为 Unity 不会对其进行序列化,并且 ExampleScript 是序列化的根对象(所有 SerializedMonoBehaviour 或 SerializedScriptableObject 始终是根序列化对象)。

  2. ExampleScript 类中的 MyReference 字段可以由 Unity 序列化,因此 Odin 将跳过它。由于 Odin 跳过了 MyReference 字段,因此 Odin 甚至不会考虑 MyClass 类型的任何成员。因此,尽管有 OdinSerialize 属性,但 SecondDictionary 字段不会被序列化。

    在这种情况下,解决方案很简单。可以强制 Odin 序列化它:

    public class ExampleScript : SerializedScriptableObject
    {
        public Dictionary<int, string> FirstDictionary;
    
        [NonSerialized, OdinSerialize]
        public MyClass MyReference;
    }
    

    NonSerialized 属性阻止 Unity 序列化字段,而 OdinSerialize 属性强制 Odin 序列化该字段。这样,MyClass 类中的 SecondDictionary 字段现在将仅由 Odin 序列化。如果只添加了 [OdinSerialize] 而没有添加 [NonSerialized],则 Unity 和 Odin 将序列化同一字段,这可能会导致细微的错误和不必要的数据重复出现。

2 实现 Odin 序列化器

2.1 继承已有序列化类

​ 虽然 Odin 默认会处理所有没有自定义编辑器的类型,但它的序列化系统更为谨慎。你需要明确选择使用 Odin 的序列化系统。

​ 使用时,只需从特殊序列化类之一继承,例如 SerializedMonoBehaviour 或 SerializedScriptableObject。这种方式支持所有常用继承的 Unity 类型。

public class YourMonoBehaviour : SerializedMonoBehaviour
{
    [SerializeField]
    private object SerializedPrivateField;

    public object SerializedField;

    [OdinSerialize]
    public object SerializedProperty { get; set; }
}

2.2 自定义序列化类

​ 如果已有需要继承的特定基类型,则通过实现 Unity 的 ISerializationCallbackReceiver 接口并使用 Odin 的 UnitySerializationUtility 类,可以在需要的地方自行实现序列化。

  1. 自定义 Odin-serialized ScriptableObject
[ShowOdinSerializedPropertiesInInspector]
public class CustomSerializedScriptableObject : ScriptableObject, ISerializationCallbackReceiver
{
    [SerializeField, HideInInspector]
    private SerializationData serializationData;

    void ISerializationCallbackReceiver.OnAfterDeserialize()
    {
		UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData);
    }

    void ISerializationCallbackReceiver.OnBeforeSerialize()
    {
		UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData);
    }
}
  1. 自定义 Odin-serialized MonoBehaviour
[ShowOdinSerializedPropertiesInInspector]
public class CustomSerializedMonoBehaviour : MonoBehaviour, ISerializationCallbackReceiver, ISupportsPrefabSerialization
{
    [SerializeField, HideInInspector]
    private SerializationData serializationData;

    SerializationData ISupportsPrefabSerialization.SerializationData { get { return this.serializationData; } set { this.serializationData = value; } }

    void ISerializationCallbackReceiver.OnAfterDeserialize()
    {
		UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData);
    }

    void ISerializationCallbackReceiver.OnBeforeSerialize()
    {
		UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData);
    }
}

3 避免 Unity 无限深度警告

​ 默认情况下,Unity 不支持序列化引用、多态性或空值,目的是保护自己免受无限深度序列化循环的影响。

​ 当某个类型 NodeA 的成员 NodeB 包含其自身 NodeA 时,序列化将会无限递归,然后在控制台中向您发出警告。

img

​ 相比之下,Odin 支持序列化引用、多态性和空值。但 Unity 仍会发出警告,因此一种解决方法是:将成员同时标记为 NonSerialized 和 OdinSerialize,则该成员将由 Odin 序列化。这意味着 Unity 会忽略该成员的序列化,但 Odin 仍然会找到它并序列化它。

img

4 指定序列化秘钥

​ 使用 Odin 序列化可以自定义处理和恢复序列化数据的方式。

​ Odin 提供 3 种类型的外部参考解析器,允许使用 3 种不同类型的密钥。通过从接口继承来实现,并且必须在序列化和反序列化时在 SerializationContext 中指定。

  1. IExternalStringReferenceResolver
  2. IExternalGuidReferenceResolver
  3. IExternalIndexReferenceResolver

4.1 External String Reference Resolver

​ 允许您指定字符串 ID 以序列化和反序列化引用。例如,在 Unity 编辑器中,您可以使用它按照 Asset 的 GUID 序列化资产引用:

public class ScriptableObjectStringReferenceResolver : IExternalStringReferenceResolver
{
	// 可以将多个字符串引用解析器串联在一起。
    public IExternalStringReferenceResolver NextResolver { get; set; } 

    public bool CanReference(object value, out string id)
    {
        if (value is ScriptableObject)
        {
            id = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(value as ScriptableObject));
            return true;
        }

        id = null;
        return false;
    }

    public bool TryResolveReference(string id, out object value)
    {
        value = AssetDatabase.LoadAssetAtPath<ScriptableObject>(AssetDatabase.GUIDToAssetPath(id));
        return value != null;
    }
}

​ 序列化和反序列化时,需要在序列化上下文中指定解析程序:

byte[] Serialize(object obj)
{
    var context = new SerializationContext()
    {
        StringReferenceResolver = new ScriptableObjectStringReferenceResolver(),
    };
	return SerializationUtility.SerializeValue(obj, DataFormat.Binary, context);
}

object Deserialize(byte[] bytes)
{
    var context = new DeserializationContext()
    {
        StringReferenceResolver = new ScriptableObjectStringReferenceResolver(),
    };
	return SerializationUtility.DeserializeValue<object>(bytes, DataFormat.Binary, context);
}

4.2 External GUID Reference Resolver

​ 外部 GUID 引用解析程序的工作方式与字符串解析程序大致相同,但它严格使用 GUID 键。

​ 该实现也与字符串解析器几乎相同:

public class ScriptableObjectGuidReferenceResolver : IExternalGuidReferenceResolver
{
	// 可以将多个字符串引用解析器串联在一起。
    public IExternalGuidReferenceResolver NextResolver { get; set; } 

    public bool CanReference(Guid value, out string id)
    {
        if (value is ScriptableObject)
        {
            id = new Guid(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(value as ScriptableObject)));
            return true;
        }

        id = default(Guid);
        return false;
    }

    public bool TryResolveReference(Guid id, out object value)
    {
        value = AssetDatabase.LoadAssetAtPath<ScriptableObject>(AssetDatabase.GUIDToAssetPath(id.ToString()));
        return value != null;
    }
}

​ 当然,在序列化和反序列化时需要指定它。

byte[] Serialize(object obj)
{
    var context = new SerializationContext()
    {
        GuidReferenceResolver = new ScriptableObjectGuidReferenceResolver(),
    };
	return SerializationUtility.SerializeValue(obj, DataFormat.Binary, context);
}

object Deserialize(byte[] bytes)
{
    var context = new DeserializationContext()
    {
        GuidReferenceResolver = new ScriptableObjectGuidReferenceResolver(),
    };
	return SerializationUtility.DeserializeValue<object>(bytes, DataFormat.Binary, context);
}

4.3 External Index Reference Resolver

​ 最后,索引解析器允许使用索引 ID 进行引用。这在 Serialized- 类中使用,例如 SerializedScriptableObject,用于重建由 Odin 序列化的 Unity 引用。

​ 如果希望以相同的方式重建引用,在序列化和反序列化之间保持列表相同非常重要。与字符串和 guid 解析器的一个显著区别是,不能将多个索引解析器链接在一起。

public class IndexResolver : IExternalIndexReferenceResolver
{
	public List<UnityEngine.Object> ReferenceList;

	public IndexResolver()
	{
		this.referenceList = new List<UnityEngine.Object>();
	}

	public IndexResolver(List<UnityEngine.Object> references)
	{
		this.referenceList = references;
	}

	public bool CanReference(object value, out int index)
	{
		if (value is UnityEngine.Object)
		{
			index = this.referenceList.Count;
			this.referenceList.Add(value);
		}

		index = 0;
		return false;
	}

	public bool TryResolveReference(int index, out object value)
	{
	    value = this.referencedUnityObjects[index];
	    return true;
	}
}

​ 另一个显着的区别是,在序列化和反序列化过程中,为引用添加了列表本身。

byte[] Serialize(object obj, out List<UnityEngine.Object> references)
{
	var resolver = new IndexResolver();
    var context = new SerializationContext()
    {
		IndexReferenceResolver = resolver,
    };
	var bytes = SerializationUtility.SerializeValue(obj, DataFormat.Binary, context);
	references = resolver.ReferenceList;

	return bytes;
}

object Deserialize(byte[] bytes, List<UnityEngine.Object> references)
{
    var context = new DeserializationContext()
    {
        IndexReferenceResolver = new IndexResolver(references),
    };
	return SerializationUtility.DeserializeValue<object>(bytes, DataFormat.Binary, context);
}

4 功能与限制

4.1 平台支持

​ Odin 序列化目前在所有平台上都受支持:

  • 在非 IL2CPP 版本中,保存通用 Windows 平台;
  • 在 IL2CPP 或 Mono AOT 平台上使用 Odin 的序列化时,必须遵循特殊的 AOT 程序,才能使 Odin 的序列化在构建中正常工作。

4.2 启用序列化

​ 因此,使用 Odin 序列化通常不需要考虑太多,它将在必要时填补空白。对于如下情况, Odin 都会自动采取序列化:

  1. 任何未被 Unity 序列化的公共字段;
  2. 任何未被 Unity 序列化并标有 SerializeField 的私有字段。

​ 但如果确实需要确保特定成员始终由 Odin 序列化,则可以使用特定于 Odin 的 OdinSerialize 特性标记任何字段或自动属性,来确保这一点。

4.3 序列化内容

​ 默认情况下,Odin 将针对 Unity 不会序列化的对象,进行拓展序列化,这包括:

  1. 字典;
  2. 委托;
  3. 属性。
  4. 等。

​ 默认情况下,Odin 会尝试序列化你交给它的几乎所有东西。在大多数情况下,这将正常工作,在某些情况下则不会。对于不会的情况,可以手动扩展序列化。

4.4 支持的数据格式

  1. Binary

    性能最佳的格式,针对速度进行优化,并将垃圾分配保持在最低限度。默认情况下,它将在播放模式和构建中使用。

  2. Json

    默认情况下从不使用 json 格式,但支持对某些内容进行手动序列化并且希望序列化数据可读的情况。需要注意的是,json 格式没有最佳性能,并且在当前版本的 Odin 中分配了很多不必要的垃圾。目前,仅在必要时使用 json 格式。

  3. Nodes

    编辑器中的默认格式,但在播放模式下不是。它不会序列化为字节流,而是转成节点列表,由 Unity 再进行序列化,主要用于 Unity 编辑器的文本资产序列化。当与 Unity 的文本格式一起使用时,节点数据会被格式化,以便在版本控制中更容易合并,因此适合多人的协作项目。节点格式性能不错,但不如二进制格式快,默认只能在编辑器中使用。

5 序列化重构

5.1 重命名字段和属性

​ 重命名字段或属性时,可以使用 Unity 的 FormerlySerializedAs 或 Odin 的 BeforeSerializedAs 特性,让 Odin 知道如何将旧序列化数据映射到新字段。

[FormerlySerializedAs("oldName")]
public int NewName;

​ 建议使用 Unity 的 FormerlySerializedAs 来支持 Odin 的 BeforeSerializedAs,因为 Unity 只支持其中之一,但 Odin 同时支持两者。Odin 的 BeforeSerializedAs 变体主要用于支持罕见的(不推荐的)序列化情况。

注意:不能在 Odin 和 Unity 之间传输序列化数据。如果某个字段已被 Unity 序列化,则不能使用它将其反序列化为 Odin 字段。

5.2 重命名类型

​ 使用 BindTypeNameToType 属性使用 Odin 完成重构类型。允许指定旧类型名称,以及要将类型反序列化的类型。

// 也可以使用该特性对命名空间进行重命名
[assembly: BindTypeNameToType("MyOldNamespace.MyNewTypeName", typeof(MyNewNamesSpace.MyNewTypeName))]

namespace MyNewNamesSpace
{
	public class MyNewTypeName { ... }
}

​ 此特性仅适用于 Odin 序列化数据。

5.3 从 Unity 转移到 Odin

​ 有时可能需要在 Unity 和 Odin 序列化器之间传输数据,但没有现成的属性可以用来实现该功能,需要按几个顺序步骤进行,以确保不会丢失数据。

  • 不推荐的方法:

    仅仅重命名或从 Unity 可序列化的格式更改为 Odin 可序列化的格式,可能会导致数据丢失(建议使用版本控制项目,以便随时回退版本恢复错误)。

  • 推荐方法:

    更好的方法是简单地复制字段,把新字段改为 Odin 序列化,并在对象反序列化时手动传输和复制数据。

public class MyMono : SerializedMonoBehaviour
{
	// 隐藏旧数据的显示
	[HideInInspector] 
	public List<MyClass> OldList;

	[NonSerialized, OdinSerialize]
	public List<MyClass> NewList = null;

    // OnAfterDeserialize 方法定义在 SerializedMonoBehaviour 类中,
    // 但也可以在 Unity 中使用 ISerializationCallbackReceiver,
    // Odin 还支持 System.Runtime.Serialization 命名空间中定义的序列化事件属性,
    // 如 [OnDeserialized]。
	protected override OnAfterDeserialize()
	{
		if (this.NewList != null)
		{
			// 这种情况下,我们只复制旧列表,但你可以根据具体情况做适当的处理。
			this.NewList = this.OldList.ToList();
			
			// 此时,最好将对象标记为 dirty,以便 Unity 重新序列化它。
		}
	}
}

[Serializable]
public class MyClass
{
	...
}

​ 这样,数据会从旧格式复制到新格式。

注意:Unity 并不会每次都重新序列化和保存对象,所以不能立即删除旧列表。要么保留旧列表一段时间,要么强制 Unity 重新序列化项目中的所有对象。从 Unity 2018.1 开始,可以使用 AssetDatabase.ForceReserializeAssets API 来实现,调用此 API 会在整个项目中迁移数据。

6 序列化 Dictionary

6.1 使用 Odin 序列化字典

​ 只需继承序列化类 SerializedScriptableObject,就可以序列化字典了!

public MyScriptableObject : SerializedScriptableObject
{
	// 该字典将被 Odin 序列化
	public Dictionary<int, string> IntStringMap;
}

6.2 使用 Unity 序列化字典

​ 通过创建继承 Dictionary 和 Unity 的 ISerializationCallbackReceiver 接口的新类,可以将 Dictionary 数据转换为 Unity 可以序列化的格式。

public abstract class UnitySerializedDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
	[SerializeField, HideInInspector]
	private List<TKey> keyData = new List<TKey>();
	
	[SerializeField, HideInInspector]
	private List<TValue> valueData = new List<TValue>();

    void ISerializationCallbackReceiver.OnAfterDeserialize()
    {
		this.Clear();
		for (int i = 0; i < this.keyData.Count && i < this.valueData.Count; i++)
		{
			this[this.keyData[i]] = this.valueData[i];
		}
    }

    void ISerializationCallbackReceiver.OnBeforeSerialize()
    {
		this.keyData.Clear();
		this.valueData.Clear();

		foreach (var item in this)
		{
			this.keyData.Add(item.Key);
			this.valueData.Add(item.Value);
		}
    }
}

​ 由于 Unity 不序列化泛型类型,因此必须通过继承 UnitySerializedDictionary 来创建具体的 Dictionary 类型:

[Serializable]
public class KeyCodeGameObjectListDictionary : UnitySerializedDictionary<KeyCode, List<GameObject>> { }

[Serializable]
public class StringScriptableObjectDictionary : UnitySerializedDictionary<string, ScriptableObject> { }

​ 这样一来,就有了可以在 Unity 中使用的字典。

public class MyScriptableObject : ScriptableObject
{
	public KeyCodeGameObjectListDictionary KeyCodeMap;

	public StringScriptableObjectDictionary StringMap;
}

​ 这些 UnitySerializedDictionaries 可以像任何普通的字典一样在 Inspector 窗口中显示,甚至可以在 Odin Inspector 中显示为普通的字典。

注意:Unity 的预制件修改系统可能存在一些奇怪的交互,且预制件修改后的值可能无法在 Inspector 窗口中正确显示。

7 序列化协议

7.1 Odin 序列化协议

​ 在 MonoBehaviours、ScriptableObjects 等中使用 Odin 进行序列化时,具有如下特点:

  1. Odin 不会替换 Unity 序列化器,而是扩展它。

    Odin 通过将非 Unity 序列化的字段和属性转换为 Unity 可以序列化的数据格式来实现这一点。

  2. Odin 将序列化数据存储在 SerializationData 字段中。

    Unity 的序列化器将启动并使用自己的序列化器序列化 Odin 序列化数据,并将其写入某个位置的文件。

​ 这种实现具有很多优势:

  • 利用了 Unity 的所有现有系统,因此不会改变通常的 Unity 工作流程。
  • 允许 Unity 处理其自己对象引用的序列化。这允许在几乎任何地方引用 Unity 对象,并且这些引用在播放器中仍然有效。

​ 但此种实现也带来了一个缺点:对于同一字段,可能序列化两次:一次是 Unity,一次是 Odin。对此,Odin 将尝试智能地过滤掉应该已经由 Unity 序列化的字段:

public class MyScriptableObject : SerializedScriptableObject
{
    // Unity 可以序列化基本类型,因此 Odin 会跳过这个字段。
    public float PublicFloat;

    // 这个字段会被 Unity 和 Odin 都跳过。
    private float privateFloat;
    
    // OdinSerialize 强制 Odin 序列化这个字段。然而,这个字段也会同时被 Unity 序列化,导致数据重复。
    [OdinSerialize]
    public float OdinAndUnityFloat;

    // 在 Odin 中,NonSerialized 属性会被 OdinSerialize 属性覆盖。Unity 不会序列化这个字段,但 Odin 会。
    [NonSerialized, OdinSerialize]
    public float OdinOnlyFloat;
}

​ 序列化程序仅在根级别选择:类或结构的任何类成员都将由序列化程序序列化,该序列化最终包含该值的“根”字段:

public class MyScriptableObject : SerializedScriptableObject
{
    // 由于 MySerializedObject 类定义了 Serializable 属性,所以 Unity 会序列化这个字段。
    // 但是,Unity 不会序列化 Dictionary 字段,因此 Dictionary 字段不会被序列化。
    public MySerializedObject UnitySerialized;

    // 和上面的例子一样,Odin 被强制序列化这个字段。
    // Dictionary 将会被保存。
    [NonSerialized, OdinSerialize]
    public MySerializedObject OdinSerialized;
}

// 另一种强制 Odin 序列化这个类的方法是移除 MySerializedObject 定义中的 Serializable 属性。
// 这样做有效,因为 Unity 只会序列化带有 Serializable 属性的类型。
[Serializable]
public class MySerializedObject
{
    // 在这里使用类似 [NonSerialized, OdinSerialize] 的属性不会有区别,
    // 因为这种类型的序列化取决于选择的顶层序列化器。
    public Dictionary<int, string> MyDictionary;
}

​ 一个好的经验法则:如果某个属性或字段未显示在 Inspector 窗口中(不使用 ShowInInspector 属性之类的内容),则不会序列化该成员。

注意:

  1. Odin Serializer 速度非常快,但由于它扩展了 Unity 序列化器,因此它只能增加序列化和反序列化时间。

  2. 最重要的是,Unity 序列化器具有刻意的简单设计和原生实现,非常非常快——比 Odin 快得多。

    因此,如果使用 Unity 序列化就能满足要求,那么这绝对是推荐的方法。

7.2 Unity 序列化协议

  1. Unity 仅序列化定义 Serializable 属性的任何类或结构。

  2. Unity 不会序列化任何泛型类型变体,例如 Dictionary。

    但是,可以通过继承泛型类型来对泛型类进行具体定义,并且该类和所有泛型字段(当然其他规则仍然适用于这些)将由 Unity 序列化。List 是此规则的一个例外。

  3. Unity 不会序列化任何多态引用,因此不会序列化任何抽象、接口或对象字段。

  4. Unity 不会序列化 DateTime 字段或 mscorlib 中定义的任何其他结构和类。

  5. 根据版本的不同,Unity 不支持所有基础枚举类型。

    Unity 5.6 版引入了对除 long 和 ulong 之外的所有基元类型的支持。在此版本之前,仅支持 byte 和 int 枚举类型。

8 使用 Odin 保存数据

  1. 创建一个数据类,其包含多种数据类型,使数据与 Unity 组件分开。因为在运行时,Odin 无法保存对实际 Unity 对象的引用或内容,即 Unity 对象的引用或依赖关系。因此,建议将其分离为一个干净的 C# 数据类。
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;

[Serializable]
public class Data
{
    public int     i;
    public float   f;
    public string  s;
    public Vector3 v;

    public List<GameObject> lg = new List<GameObject>();

    [ShowInInspector]
    public Dictionary<string, Vector2> dsv = new Dictionary<string, Vector2>();
}
  • 需要添加 Serializable 特性,以实现该类的序列化。
  • 使用 ShowInInspector 特性,可以在 Inspector 窗口中查看该 Dictionary。
  1. 创建 Test 脚本,其包含一个 Data 成员作为数据,并添加如下特性:

    • SerializeField:使该数据被序列化(public 成员可默认不指定该特性)。
    • InlineProperty:将 data 内容显示在标签旁,而不是以折叠方式呈现。
    • LabelWidth:指定 data 标签长度。

    同时编写存储(Svae)和读取(Load)数据的方法,使用 Button 特性将其作为按钮使用。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;

public class Test : MonoBehaviour
{
    [SerializeField, InlineProperty, LabelWidth(45)]
    public Data data;

    [Button]
    public void Save() {
        byte[] bytes = SerializationUtility.SerializeValue(data, DataFormat.Binary);
        File.WriteAllBytes(Application.streamingAssetsPath + "/data", bytes);
    }

    [Button]
    public void Load() {
        byte[] bytes = File.ReadAllBytes(Application.streamingAssetsPath + "/data");
        data = SerializationUtility.DeserializeValue<Data>(bytes, DataFormat.Binary);
    }
}
  1. 将 Test 脚本拖拽到任意一个场景物体上,并在 Inspector 窗口上随意更改 Test 中的 data 数据。点击 Save 按钮,则 data 数据被存储到 StreamingAssets 文件夹下的二进制文件 data 中(可右键点击 Fresh 刷新 Project 窗口查看)。
image-20240720000337519
  1. 清除数据(remove 脚本再重新拖拽进来),点击 Load 按钮,可以看到数据被加载。这里 Lg 中的成员为 None,是因为 Odin 序列化不保存 Unity 对象的引用,这点需要注意。
image-20240720000510668

9 序列化调试器

​ Odin 包含一个序列化调试实用程序,如果遇到值消失或未显示在检查器中的问题,它可以为提供帮助。

​ 调试器将显示:

  • 任何给定类型的哪些成员正在序列化;
  • 是否由 Unity、Odin 还是两者共同序列化。
  • 从序列化的角度,提供任何给定成员所发生情况的详细描述。

打开方式:

  1. Tools > Odin > Serializer > Serialization Debugger 访问窗口,然后从下拉列表中选择脚本类型以开始调试。
image-20240720023053561
  1. 单击组件齿轮下拉菜单中的“调试序列化”按钮直接开始调试组件。
image-20240720020948625

10 自定义序列化类

​ 使用 SerializationUtility 类,该类包装序列化系统并处理所涉及的所有样板代码。示例代码如下所示:

// 定义一个包含一些随机垃圾字符串数据、随机数字、Unity 对象和循环引用的类
public class MyData
{
    public string str = new string(Enumerable.Range(0, 20).Select(i => (char)UnityEngine.Random.Range(50, 150)).ToArray());
    public List<float> numbers = new List<float>(Enumerable.Range(0, 10).Select(i => UnityEngine.Random.Range(0f, 100f)));
    public GameObject unityObjectReference = UnityEngine.Object.FindObjectOfType<UnityEngine.GameObject>();
    public MyData reference;
}

// 某处的方法可能如下所示,用于将数据序列化为 JSON
private void SerializeData()
{
    // 保存到 Assets 文件夹
    string path = Application.dataPath + "/data.json";

    // 初始化一些数据
    var originalData = new MyData();
    originalData.reference = new MyData();
    originalData.reference.reference = originalData;

    // Unity 应该能够处理其自己奇怪对象的序列化和反序列化。
    // 如果你的数据图包含 UnityEngine.Object 类型,你需要为 Odin 提供一个 UnityEngine.Object 列表,
    // Odin 将使用它作为外部引用解析器。
    List<UnityEngine.Object> unityObjectReferences = new List<UnityEngine.Object>();

    // 数据格式
    DataFormat dataFormat = DataFormat.JSON;
    // DataFormat dataFormat = DataFormat.Binary;
    // DataFormat dataFormat = DataFormat.Nodes;

    // 序列化
    {
        var bytes = SerializationUtility.SerializeValue(originalData, dataFormat, out unityObjectReferences);
        File.WriteAllBytes(path, bytes);

        // 如果你需要 JSON 字符串,使用 UTF8 编码
        // var jsonString = System.Text.Encoding.UTF8.GetString(bytes);
    }

    // 反序列化
    {
        var bytes = File.ReadAllBytes(path);

        // 如果你有要反序列化的字符串,使用 UTF8 编码获取字节
        // var bytes = System.Text.Encoding.UTF8.GetBytes(jsonString);

        var data = SerializationUtility.DeserializeValue<MyData>(bytes, dataFormat, unityObjectReferences);
    }
}

​ 如果查看生成的 json 文件,会看到 Odin 的 json 格式在特殊的 $ 前缀条目中保留了一堆元数据。因此,其他 Json 序列化器可能无法正常解析 Odin 序列化的 json 文件。Odin 序列化的 json 文件规定了额外元数据的特殊格式,例如类型信息,因此生成的 json 文件可能并不是标准的序列化 json。

注意:

  1. 是否应该使用它取决于您的确切用例。如果只是想将一些非常简单的数据保存到 json 中,建议使用 Unity 自己的 JsonUtility 类 - 它比 Odin 的 json 序列化快得多,后者相对较慢。(

  2. Odin 的二进制格式非常快且高度优化。相比之下,json 优化不是那么多。因为它优先级不高,且默认情况下,Json 从未在 Odin 中使用。

11 AOT 序列化

​ 详情见 https://odininspector.com/tutorials/serialize-anything/aot-serialization#odin-inspector

12 建议与总结

  • 尽可能使用 Unity 自己的序列化器!

  • 对于 MonoBehaviours/Components 和 ScriptableObjects,Odin 序列化程序不会替换,而是扩展现有的 Unity 序列化程序。

    1. Odin 首先序列化 Unity 未接触的所有数据,并将其转换为 Unity 可以理解的数据存储格式和 Unity 对象列表。
    2. 然后,Unity 将此数据与对象的其余部分一起序列化。

    因此,总会有两个序列化传递。当然这也意味着使用 Odin 序列化器总是比仅使用 Unity 的序列化器慢。

​ 因此,建议仅在 Unity 序列化器无法满足要求时才使用 Odin。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值