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

文章目录

  • 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.mzph.cn/web/47623.shtml

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

相关文章

为什么我不建议用Excel做进销存系统?

进销存管理系统是一个企业中非常关键的部分&#xff0c;它涉及商品的采购、销售和库存管理等复杂流程。虽然EXCEL作为一个办公软件&#xff0c;它的通用性和灵活性使其能够处理这类数据&#xff0c;但实际上&#xff0c;使用它来构建专业的进销存管理系统存在一些明显的局限性。…

haproxy服务介绍

haproxy 搭建使用开启HAProxy的界面UI配置负载均衡配置web代理 HAProxy&#xff08;High Availability Proxy&#xff09;是一个高性能的TCP/HTTP负载均衡器和代理服务器&#xff0c;广泛用于提升Web应用的可用性和性能。[官网说明](https://docs.haproxy.org/2.8/intro.html#3…

NLP: 词袋模型和TFIDF模型

文章目录 词袋模型TF-IDF模型词汇表模型 词袋模型 文本特征提取有两个非常重要的模型&#xff1a; 词集模型&#xff1a;单词构成的集合&#xff0c;集合自然每个元素都只有一个&#xff0c;也即词集中的每个单词都只有一个。 词袋模型&#xff1a;在词集的基础上如果一个单词…

autoxjs的安装与配置

AutoxJs 是一个基于 JavaScript 的自动化工具&#xff0c;用于在 Android 平台上创建自动化脚本。它是在原 Auto.js 项目的基础上继续维护和升级而来的。 AutoxJs 的优势主要包括以下几点&#xff1a; 无需 root 权限&#xff1a;可以在没有 root 权限的设备上运行大部分功能&…

JavaWeb系列二十三: web 应用常用功能(文件上传下载)

文件上传下载 基本介绍文件上传基本原理文件上传应用实例文件上传注意事项和细节 文件下载基本原理文件下载应用实例文件下载注意事项 ⬅️ 上一篇: JavaWeb系列二十二: 线程数据共享和安全(ThreadLocal) &#x1f389; 欢迎来到 JavaWeb系列二十三: web 应用常用功能(文件上传…

创建最佳实践创建 XML 站点地图--SEO

您是否正在努力让您的网站被搜索引擎索引&#xff1f;您想提高您网站的搜索引擎知名度吗&#xff1f;如果是&#xff0c;您可能会错过 XML 站点地图的重要性。XML 站点地图在改善您网站的 SEO 方面发挥着至关重要的作用。‍ XML 站点地图是您网站结构的蓝图&#xff0c;可帮助…

YOLOv5项目梳理

1 项目介绍 参考项目&#xff1a;YOLO项目 1.1训练模型 YOLOv5模型 train.py 训练预训练模型 ... ... def parse_opt(knownFalse):# 命令行参数解析器初始化parser argparse.ArgumentParser()# 初始权重路径&#xff0c;默认为 ROOT / yolov5s.pt&#xff0c;用于指定模…

Navicat 17 for Mac 数据库管理软件

Mac分享吧 文章目录 效果一、准备工作二、开始安装1. 双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕。2. 应用程序/启动台显示Navicat图标&#xff0c;表示安装成功。 二、运行测试运行后提示&#xff1a;“Navicat Premium.pp”已损坏&#x…

在qt的c++程序嵌入一个qml窗口

//拖拽一个QQuickWidget c端和qml通信的桥梁 找到qml的main.qml的路径 ui->quickWidget->setSource(QUrl::fromLocalFile("../../../code/main.qml"));// QML 与 Qt Widgets 通信//窗口就成了一个类实例对象pRoot (QObject*)ui->quickWidget->rootObje…

redis面试基础知识

redis的数据类型 Redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;不过value的类型多种多样&#xff1a; 五种基本数据类型 Redis 通用命令 通用指令是部分数据类型的&#xff0c;都可以使用的指令&#xff0c;常见的有&#xff1a; KEYS&#xff…

思维(交互题),CF 1990E2 - Catch the Mole(Hard Version)

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 E2 - Catch the Mole(Hard Version) 二、解题报告 1、思路分析 考虑每次误判都会让鼹鼠上升一层&#xff0c;相应的&#xff0c;最外层的一层结点都没用了 由于数据范围为5000&#xff0c;我们随便找个叶子…

Electron案例解析-编写一个简单的electron程序

index.html <!DOCTYPE html> <html> <head><meta charset"UTF-8" /><!-- 内容安全策略--><metahttp-equiv"Content-Security-Policy"content"default-src self; script-src self"/><metahttp-equiv&quo…

C语言-栈和队列

文章目录 &#x1f3af;引言&#x1f453;栈和队列1.栈1.1栈的概念与结构1.2栈的实现 2.队列2.1队列的概念与结构2.2队列的实现 &#x1f947;结语 &#x1f3af;引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在之前的文章中&#xff0c;我们详细介绍了链表及其操作方法。…

8年前端总结和感想(转)~

我是牛奶&#xff0c;本文是我前端工作 8 年的一些总结和感想 主要记录下个人点滴、前端知识点、场景应用、未来的憧憬以及个人规划&#xff0c;供自己以后查漏补缺&#xff0c;也欢迎同道朋友交流学习。 自我介绍 我是一名工作在非知名公司的 8 年前端&#xff0c;双非普通本…

JMeter使用手册

安装 下载地址 https://jmeter.apache.org/download_jmeter.cgi 下载后解压到win的文件夹中 打开JMeter的bin文件夹&#xff0c;双击这个jar就启动了JMeter 启动 出现这样的界面 基本使用 添加变量 这个变量在使用的时候可以被引用 创建线程组 所有的请求都得基于…

Linux:Linux进程概念

目录 前言 1. 冯诺依曼体系结构 2. 操作系统 2.1 什么是操作系统 3. 进程 3.1 基本概念 3.2 描述进程——PCB 3.3 进程和程序的区别 3.4 task_struct-PCB的一种 3.5 task_struct的内容分类 4. 查看进程 4.1 通过系统文件查看进程 4.2 通过ps指令查看进程 4.3 …

lse:一款专为渗透测试和CTF设计的Linux枚举工具

关于linux-smart-enumeration linux-smart-enumeration是一款专为渗透测试和CTF设计的Linux枚举工具&#xff0c;该工具可以帮助广大研究人员收集与本地Linux系统安全相关的信息。 工具特性 该工具从2.0版本开始符合POSIX标准&#xff0c;并且经过了shellcheck和posh测试。它…

前端三大主流框架Vue React Angular有何不同?

前端主流框架&#xff0c;Vue React Angular&#xff0c;大家可能都经常在使用&#xff0c;Vue React&#xff0c;国内用的较多&#xff0c;Angualr相对用的少一点。但是大家有思考过这三大框架的不同吗&#xff1f; 一、项目的选型上 中小型项目&#xff1a;Vue2、React居多…

【数据结构-前缀和】力扣2550.统计范围内的元音字符串数

给你一个下标从 0 开始的字符串数组 words 以及一个二维整数数组 queries 。 每个查询 queries[i] [li, ri] 会要求我们统计在 words 中下标在 li 到 ri 范围内&#xff08;包含 这两个值&#xff09;并且以元音开头和结尾的字符串的数目。 返回一个整数数组&#xff0c;其中…

中文诗歌生成

用transformer在诗歌集上训练出的模型 import os os.environ["KERAS_BACKEND"] "tensorflow" # param ["tensorflow", "jax", "torch"] os.environ[TF_CPP_MIN_LOG_LEVEL] 2 os.environ[HF_ENDPOINT] https://hf-mirro…