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

news2024/9/24 13:23:38

文章目录

  • 1 由根对象决定序列化
  • 2 实现 Odin 序列化器
    • 2.1 继承已有序列化类
    • 2.2 自定义序列化类
  • 3 避免 Unity 无限深度警告
  • 4 指定序列化秘钥
    • 4.1 External String Reference Resolver
    • 4.2 External GUID Reference Resolver
    • 4.3 External Index Reference Resolver
  • 4 功能与限制
    • 4.1 平台支持
    • 4.2 启用序列化
    • 4.3 序列化内容
    • 4.4 支持的数据格式
  • 5 序列化重构
    • 5.1 重命名字段和属性
    • 5.2 重命名类型
    • 5.3 从 Unity 转移到 Odin
  • 6 序列化 Dictionary
    • 6.1 使用 Odin 序列化字典
    • 6.2 使用 Unity 序列化字典
  • 7 序列化协议
    • 7.1 Odin 序列化协议
    • 7.2 Unity 序列化协议
  • 8 使用 Odin 保存数据
  • 9 序列化调试器
  • 10 自定义序列化类
  • 11 AOT 序列化
  • 12 建议与总结

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1942558.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物联网在电力行业的应用

作者主页: 知孤云出岫 这里写目录标题 作者主页:物联网在电力行业的应用简介主要应用领域代码案例分析1. 智能电表数据采集和分析2. 设备监控和预测性维护3. 能耗管理和优化4. 电力负载预测5. 分布式能源管理6. 电动汽车充电管理7. 电网安全与故障检测 物联网在电力行业的应用…

vue3-video-play 导入 以及解决报错

npm install vue3-video-play --save # 或者 yarn add vue3-video-play import Vue3VideoPlay from vue3-video-play; import vue3-video-play/dist/style.css; app.use(Vue3VideoPlay) <template><div id"main-container-part"><div class"al…

基于Qt的上位机通用框架

0.前言 最近一年多的时间一直在开发设备控制相关的软件&#xff0c;加上之前在聚光的两年时间&#xff0c;前前后后开发这种设备控制类型的上位机软件也有三年的时间了。总结出了一套基于Qt的上位机编程框架&#xff0c;核心思想类似于C#的依赖注入&#xff0c;对象的初始化都…

【启明智显分享】甲醛检测仪HMI方案:ESP32-S3方案4.3寸触摸串口屏,RS485、WIFI/蓝牙可选

今年&#xff0c;“串串房”一词频繁引发广大网友关注。“串串房”&#xff0c;也被称为“陷阱房”“贩子房”——炒房客以低价收购旧房子或者毛坯房&#xff0c;用极度节省成本的方式对房子进行装修&#xff0c;之后作为精修房高价租售&#xff0c;因甲醛等有害物质含量极高&a…

[Python库](5) rich库

作者制作不易&#xff0c;关注、点赞、收藏一下吧&#xff01; 目录 1.rich库简介 2.下载并导入rich库 2.1.下载 2.2.导入 3.使用 3.1.输出表情包 3.2.文本的样式 3.3.表格 3.4.日志 1.rich库简介 Rich 是一个 Python 库&#xff0c;用于在终端中添加丰富的文本&…

月影护眼大路灯怎么样?书客|月影|霍尼韦尔超硬核实力性能测评pk!

月影护眼大路灯怎么样&#xff1f;选到专业优质的护眼大路灯是真的可以使我们在用眼时减少疲劳感&#xff0c;达到护眼效果&#xff0c;但如果不慎买到劣质的护眼灯产品&#xff0c;不仅达不到健康的环境光&#xff0c;还越用越觉得眼睛疲劳感加重&#xff0c;在水深的护眼灯市…

数学建模(7)——Logistic模型

一、马尔萨斯人口模型 import numpy as np import matplotlib.pyplot as plt# 初始人口 N0 100 # 人口增长率 r 0.02 # 时间段&#xff08;年&#xff09; t np.linspace(0, 200, 200)# 马尔萨斯人口模型 N N0 * np.exp(r * t)# 绘图 plt.plot(t, N, labelPopulation) plt.…

适用于 Mac 或 MacBook 的最佳数据恢复软件

Apple 设计的电脑可靠且用户友好&#xff0c;但即使是最好的最新款 MacBook硬件也会出现故障。当您的存储出现问题时&#xff0c;数据恢复软件可以帮助您恢复丢失和损坏的文件。 数据丢失的另一个原因是有时会发生令人尴尬的错误。如果您不小心丢弃了所需的文件&#xff0c;然…

《智能物联技术》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《智能物联技术》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《智能物联技术》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;中国电子科技集团公司 主办单位&#xff1a;…

微软的Edge浏览器如何设置兼容模式

微软的Edge浏览器如何设置兼容模式&#xff1f; Microsoft Edge 在浏览部分网站的时候&#xff0c;会被标记为不兼容&#xff0c;会有此网站需要Internet Explorer的提示&#xff0c;虽然可以手动点击在 Microsoft Edge 中继续浏览&#xff0c;但是操作起来相对复杂&#xff0c…

LeetCode 125.验证回文串 C++写法

LeetCode 125.验证回文串 C写法 思路&#x1f914;&#xff1a; 我们不对字符串进行删除&#xff0c;这样效率太低了&#xff0c;所以可以左右开工&#xff0c;下标begin和end遇到不是字母数字字符的就跳过&#xff0c;当两边都是字母就进行比对&#xff0c;一样就继续往后走&a…

ubuntu本地部署llama3大模型

安装大模型 首先需要安装curl&#xff1a; sudo apt install curl 然后安装 ollama&#xff0c;可以去ollama.com复制下载命令&#xff1a; curl -fsSL https://ollama.com/install.sh | sh 然后就是启动ollama服务&#xff1a; ollama serve 出现这个说明已经运行&#xff0c…

PyTorch手写体数字识别实例

MNIST数据集的准备 “HelloWorld”是所有编程语言入门的基础程序&#xff0c;在开始编程学习时&#xff0c;我们打印的第一句话通常就是这个“HelloWorld”。本书也不例外&#xff0c;在深度学习编程中也有其特有的“HelloWorld”&#xff0c;一般就是采用MNIST完成一项特定的…

Java二十三种设计模式-代理模式模式(8/23)

代理模式&#xff1a;为对象访问提供灵活的控制 引言 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它为其他对象提供一个代替或占位符&#xff0c;以控制对它的访问。 基础知识&#xff0c;java设计模式总体来说设计模式分为三大类&#…

Ant Design Vue中日期选择器快捷选择 presets 用法

ant写文档的纯懒狗 返回的是一个day.js对象 范围选择时可接受一个数组 具体参考 操作 Day.js 话不多说 直接上代码 <a-range-pickerv-model:value"formData.datePick"valueFormat"YYYY-MM-DD HH:mm:ss"showTime:presets"presets"change&quo…

一、C#概述

本文是网页版《C# 12.0 本质论》第一章解读。欲完整跟踪本系列文章&#xff0c;请关注并订阅我的Essential C# 12.0解读专栏。 前言 第一章的内容非常简单&#xff0c;毕竟仅仅是Introducing C#。不过正如《0.前言》所述&#xff0c;《C# 12.0本质论》本身就不是一本零基础的…

【Redis】主从复制分析-基础

1 主从节点运行数据的存储 在主从复制中, 对于主节点, 从节点就是自身的一个客户端, 所以和普通的客户端一样, 会被组织为一个 client 的结构体。 typedef struct client {// 省略 } client;同时无论是从节点, 还是主节点, 在运行中的数据都存放在一个 redisServer 的结构体中…

S71200 - 笔记

1 S71200 0 ProfiNet - 2 PLC编程 01.如何零基础快速上手S7-1200_哔哩哔哩_bilibili 西门子S7-1200PLC编程设计学习视频&#xff0c;从入门开始讲解_哔哩哔哩_bilibili

Facebook在内容创作中的新策略与机会

随着社交媒体的不断发展&#xff0c;内容创作已经成为了平台吸引和留住用户的核心竞争力。Facebook作为全球最大的社交平台之一&#xff0c;不断调整和优化其内容创作策略&#xff0c;以适应用户需求的变化和技术的进步。本文将深入探讨Facebook在内容创作中的新策略与机会&…

【深度学习】yolov8-det目标检测训练,拼接图的分割复原

项目背景 https://blog.csdn.net/x1131230123/article/details/140606459 似乎这个任务是简单的&#xff0c;利用目标检测是否可以完成得好呢? 生成数据集 利用这个代码产生数据集&#xff1a; 为了将标签转换为YOLOv5格式&#xff0c;需要将左上角和右下角的坐标转换为Y…