game background image

反序列化程序

作者:Lordfirespeed最后更新:2025-05-19 05:50:53463.7万555KB

R.E.P.O. 反序列化程序-1-奇游Mod大师

Mod简介

为Unity构建的快速、健壮、强大且可扩展的.NET序列化程序

Odin Serializer

为Unity构建的快速、健壮、强大且可扩展的.NET序列化程序

OdinSerializer是自定义序列化程序的开源版本,由以下人员构建和使用 Odin-检查器和序列化程序


Sirenix Twitter Discord server Inspect all data with Odin Serializer Download Donate


在数百款游戏中使用,并得到资产商店开发者的支持,例如

DevDog ParadoxNotion Ludiq


性能图表和比较

OdinSerializer在性能和垃圾分配方面与许多流行的序列化库相比非常好,同时提供了一个在Unity中使用的优越特性集。

本节中的性能图是用OdinSerializer的二进制格式描述的。

Odin序列化程序 统一JSON 全序列化程序 二进制格式化程序 JSON.NET Protobuf-net
开源 :heavy_check_mark: :x:
跨平台
开箱即用的Unity支持 :heavy_check_mark: :x:
支持Unity结构
二进制格式化程序 :x: :heavy_check_mark:
Json格式化程序
Unity对象中的合并友好数据 :x:
接口 :heavy_check_mark:
物业
多态性 :x: :heavy_check_mark:
仿制药
字典 :x: :heavy_check_mark:
循环参考文献
代表 :heavy_check_mark: :x:
多维数组
可扩展 :x:
重命名成员 :heavy_check_mark:
重命名类型
IL优化 :heavy_check_mark: - :x: -
支持.NET接口 ?
支持.NET回调属性 :x: :heavy_check_mark:

序列化一个没有多态性的简单对象

Benchmark

具有大量多态性的复杂对象的序列化

*Unity JsonUtility已被排除在此基准测试之外,因为它既不支持多态性也不支持字典

Benchmark

各种大型数组和列表的序列化

Benchmark

上述3个测试的垃圾分配

Benchmark

如何开始

OdinSerializer有许多不同的用例。如果您只是需要一个序列化库在您自己的私有项目中使用,我们可以建议您直接使用它。如果您想进行自己的调整和构建,或者如果您打算在您正在分发的包中包含OdinSerializer,那么分叉存储库会更好地为您服务。

使用OdinSerializer开箱即用

要按原样使用OdinSerializer,只需转到 下载 下载最新的提交作为.unitypackage文件,并将该包导入到您的Unity项目中。OdinSerializer将在您的项目中,随时可以使用。

分叉OdinSerializer

注意:目前,使用和构建OdinSerializer项目仅在Windows机器上使用Visual Studio进行了测试。

要开始,您可能需要阅读 GitHub的分叉指南,以获得分叉的基础知识。

一旦您分叉了OdinSerializer,您就可以开始对项目进行自己的更改。也许您想添加一个特性,或者调整它的一部分以更好地满足您自己的需求。

如果您打算在自己的产品发行版中包含OdinSerializer,您应该使用搜索和替换之类的工具修改所有源文件,将OdinSerializer命名空间移动到项目的适当命名空间中,并重命名。dll的。这是为了避免在同一项目中的多个不同资产都使用可能不同版本的OdinSerializer的情况下出现命名空间和程序集冲突。例如,您可以将“OdinSerializer”全局重命名为“MyProject.Internal.OdinSerializer”,并将。dll的重命名为“MyProject.Internal.OdinSerializer.dll”。

以下是在此过程中需要重命名的内容的转到列表:

  • OdinSerializer.csproj:AssemblyName和RootNamespace以及XML文档路径。
  • OdinBuildAutomation.cs:静态构造函数中的命名空间和程序集名称字符串。
  • 整个OdinSerializer项目中的命名空间(搜索和替换是您的朋友)。
  • AOT文件夹中包含的link.xml文件。

构建OdinSerializer

OdinSerializer项目被设置为一个独立的代码项目,它位于Unity之外,可以编译程序集以在Unity项目内部使用。它的构建设置被设置为使用特定的MSBuild发行版(Roslyn编译器)来构建与Unity兼容的程序集,并使用pdb2mdb工具来转换。pdb符号文件到。mdb符号文件,以支持Unity中正确的步进调试。在某些版本的Unity中,当代码进入不安全的上下文时,简单地使用Visual Studio的许多最新发行版附带的默认MSBuild版本进行构建似乎会导致即时运行时崩溃。

然而,构建和测试OdinSerializer最简单的方法是将构建文件夹作为Unity项目打开(任何5.3或以上的Unity版本都可以)。打开时,您将在项目场景视图中看到三个按钮:“使用调试编译”、“编译发布版本生成”和“打开解决方案”。

带调试的编译 将程序集编译到打开的项目中,以及用于逐步调试的适当符号文件。一旦Unity导入了这个程序集,您就可以通过将Visual Studio实例附加到它来逐步调试OdinSerializer。

编译发布版本 使用发布优化编译三种不同的OdinSerializer程序集变体。这三个程序集分别用于编辑器、支持JIT的构建(Windows/Mac/Linux)和AOT构建(IL2CPP)。OdinSerializer Unity项目还包括一个脚本文件OdinSerializerBuildAutomation.cs,它在构建过程中基于当前目标平台和脚本后端自动正确设置这三个程序集的导入设置,以便将正确的程序集用于该构建,如果构建是AOT编译的,还会自动扫描项目并为需要它的类型生成AOT支持。我们鼓励您修改此文件或以其他方式对其进行调整,以满足您自己特定的自动化需求。

开放式解决方案 只需使用默认应用程序打开OdinSerializer解决方案文件。

OdinSerializer的基本用法

本节不会详细介绍OdinSerializer的工作原理或如何以高级方式配置它——请参见下面的技术概述。相反,它旨在简单概述如何在基本功能中使用OdinSerializer。

从广义上讲,有两种不同的使用OdinSerializer的方法:

序列化常规C#对象

您可以将OdinSerializer用作独立的序列化库,只需序列化或反序列化您提供给它的任何数据,例如存储在文件中或通过网络发送。这是使用SerializationUtility类完成的,该类包含各种包装OdinSerializer的方法,以便直接、易于使用。

示例:序列化常规C#对象
使用OdinSerializerpublic static class Example{public static void Save(MyData data,string filePath){byte[]bytes=SerializationUtility.SerializeValue(data,DataFormat.Binary);File.WriteAllBytes(bytes,filePath);}public static MyData Load(string filePath){byte[]bytes=File.ReadAllBytes(filePath);return SerializationUtility.DeserializeValue<MyData>(字节,数据格式。二进制);}}

请注意,您不能以这种方式将对Unity对象或资产的引用保存到文件中。处理这个问题的唯一方法是在序列化时请求所有遇到的UnityEngine.Object引用的列表,然后在反序列化数据时将该引用列表传回系统。

示例:序列化包含Unity引用的常规C#对象
使用OdinSerializerpublic static class Example{public static void Save(MyData data,string filePath,ref List<UnityEngine.Object>unityReferences){byte[]bytes=SerializationUtility.SerializeValue(data,DataFormat.Binary,out unityReferences);File.WriteAllBytes(bytes,filePath);//unityReferences列表现在将填充所有遇到的UnityEngine.Object引用,并且保存的二进制数据包含指向此列表的索引指针。//您的工作是确保引用列表在序列化和反序列化之间保持不变。}公共静态MyData加载(string filePath,List<UnityEngine.Object>unityReferences){byte[]bytes=File.ReadAllBytes(filePath);返回SerializationUtility.DeserializeValue<MyData>(字节,数据格式。二进制,单位引用);}}

扩展UnityEngine.Object序列化

您还可以使用OdinSerializer来无缝扩展Unity对象类型的序列化,例如ScriptableObject和MonoBehaviour。有两种一般的方法可以做到这一点,一种是手动的,需要几行代码来实现,另一种非常容易实现,但只展示默认行为。

更简单的方法是简单地从许多预先创建的UnityEngine.Object派生的类型OdinSerializer提供的类型,这些类型已经实现了上述行为。请注意,这样做将具有不序列化Unity将序列化的字段的默认行为。

示例:轻松扩展UnityEngine.Object序列化
使用OdinSerializer公共类YourSpeciallySerializedScriptableObject:SerializedScriptableObject{public Dictionary<string, string>iAmSerializedByOdin公开名单<string>iAmSerializedByUnity;}

手动方法要求您在UnityEngine上实现Unity的ISerializationCallbackReceiver接口。您想要扩展其序列化的对象派生类型,然后使用OdinSerializer的UnitySerializationUtility类在Unity在适当时间调用的序列化回调期间应用Odin的序列化。

示例:手动扩展UnityEngine.Object序列化
使用UnityEngine使用OdinSerializer公共类YourSpeciallySerializedScriptableObject:ScriptableObject,ISerializationCallbackReceiver{[SerializeField,HideInInspector]private SerializationData;void ISerializationCallbackReceiver.OnAfterDeserialize(){//使Odin将SerializationData字段的内容反序列化到这个实例中。UnitySerializationUtility.DeserializeUnityObject(this,ref this.SerializationUtility.deserializeUnityObject(this,cachedContext.Value);}void ISerializeReceiver.onBeforeSerialize(this);是否总是序列化Unity也将序列化的字段。bool serializeUnityFields=false;//使Odin将此实例中的数据序列化到serializationData字段中。UnitySerializationUtility.SerializeUnityObject(this,ref this.serializationData,serializeUnityFields,cachedContext。值);}}

注意:如果您使用OdinSerializer来扩展Unity对象的序列化,而没有安装像Odin Inspector这样的检查器框架,Odin序列化的字段将无法在Unity的检查器中正确呈现。您要么必须获得这样一个框架,要么编写自己的自定义编辑器,以便能够在Unity的检查器窗口中检查和编辑这些数据。

此外,请始终记住,Unity并不严格知道这种额外的序列化数据的存在——每当您从自定义编辑器中更改它时,请记住手动将相关资产或场景标记为脏,以便Unity知道它需要重新序列化。

最后,请注意 默认情况下,预置修改不会简单地在专门序列化的组件/行为/单行为中起作用.如果您试图从父预置更改其自定义序列化数据,特殊序列化的预置实例可能会爆炸并死亡。OdinSerializer包含一个用于管理对象的特殊序列化预置修改并应用它们的系统,但这是OdinSerializer的高级使用,需要专门的自定义编辑器的大量支持,这在本自述文件中没有涉及。

如何投稿

我们接受Apache 2.0许可下的贡献,因此请随时提交拉取请求。投稿时请牢记以下规则:

  • 遵循OdinSerializer代码中预先存在的编码风格和标准。
  • 如果您在自己的fork中使用修改后的OdinSerializer名称空间,请确保pull请求使用该存储库的正确名称空间,否则会立即编译,因此我们不需要在接受pull请求时清理此类内容。

我们接受任何为OdinSerializer增加价值的贡献,而不会增加过度的膨胀或特性蠕变。然而,我们特别有兴趣看到以下领域的贡献:

错误修复

  • 如果您能提交您遇到并修复的任何错误的拉取请求,我们将不胜感激。

表现

  • 一般整体性能:代码越快总是越好,只要速度的提高不牺牲任何健壮性。
  • Json格式性能:Json格式(JsonDataWriter/JsonDataReader)的性能目前是难以形容的糟糕。该格式最初是作为OdinSerializer开发期间使用的测试平台编写的,因为它是人类可读的,因此对于调试目的非常有用,并且自那以后基本上没有被触及过。
  • EnumSerializer目前通过装箱序列化和反序列化的枚举值来分配垃圾。因此,序列化枚举总是导致分配不必要的垃圾。任何解决这个问题的方法都是最受欢迎的。可能需要一些不安全的代码,但是我们还没有时间真正正确地研究这个问题。

检测

  • 一套完整的独立单元测试。Odin Inspector有自己的OdinSerializer内部集成测试,但是目前我们没有像样的独立单元测试来单独与OdinSerializer一起工作。

技术概述

以下部分是对OdinSerializer的工作原理及其许多更大特性的简要技术概述。然而,首先,让我们有一个超级简短的概述,在我们开始之前给出一些背景。

这就是OdinSerializer在最高级别上的工作方式:

  • 要写入或读取的数据通常以流的形式传递给数据写入器/读取器。
  • 如果我们正在序列化,数据写入器/读取器连同要序列化的值一起传递给序列化器。
  • 如果该值可以被视为原子原语,序列化程序将使用传递的数据写入器/读取器直接写入或读取该值。如果值是“复杂的”,即它是一个由其他值组成的值,序列化程序将获取并包装使用格式化程序来读取或写入该值。

“仅堆栈”,仅转发

OdinSerializer是一个仅向前序列化程序,这意味着当它序列化时,它在检查对象图时立即写入数据,当它反序列化时,它在解析数据时立即重新创建对象图。序列化程序只向前移动——它不能“返回”并查看以前的数据,因为我们不保留任何状态,并且在向前移动时动态地做所有事情。与其他一些序列化程序不同,没有一个“元图”数据结构被分配来包含以后要保存的所有数据。

这意味着我们可以完全序列化和反序列化数据,而无需在堆上分配任何东西,这意味着在系统运行一次并且创建了所有的写入器、读取器、格式化器和序列化器实例之后,通常不会进行任何多余的GC分配,这取决于所使用的数据格式。

数据写入器和读取器

数据写入器和读取器是实现IDataReader和/或IDataWriter接口的类型。它们以原子原语的形式从数据写入和读取的实际原始数据格式中抽象出强类型C#数据的写入和读取。OdinSerializer目前附带了支持三种不同格式的数据读取器和写入器:Json、二进制和节点。

数据写入器和读取器还包含一个序列化或反序列化上下文,用于配置序列化和反序列化如何以各种方式操作。

原子原语

在OdinSerializer的上下文中,原子原语(或仅仅是原语)是可以在对数据写入器或读取器的单个调用中写入或读取的类型。所有其他类型都被认为是复杂类型,必须由格式化程序处理,格式化程序将该类型转换为一系列原子原语。您可以通过调用FormatterUtilities.IsPrimitiveType(类型类型)来检查某个东西是否是原子原语。

以下类型被视为原子原语:

  • 系统字符(字符)
  • 系统。字符串(字符串)
  • 系统布尔(布尔)
  • System.SByte(sbyte)
  • 系统字节(字节)
  • 系统。短(短)
  • System.UShort(ushort)
  • 系统。int(int)
  • 系统。uint(uint)
  • 系统。长(长)
  • System.ULong(ULong)
  • 系统。单(浮点)
  • 系统。双倍(双倍)
  • System.Decimal(十进制)
  • System.IntPtr
  • System.UIntPtr
  • 系统指南
  • 所有枚举

序列化程序和格式化程序

这是一个重要的区别——序列化程序是系统向外的“面孔”,并且都被硬编码到系统中。每个原子原语都有一个硬编码的序列化程序类型,还有一个包罗万象的ComplexTypeSerializer,它通过包装格式化程序的使用来处理所有其他类型。

格式化程序将一个实际的C#对象转换成它所组成的原始数据。它们是OdinSerializer中的主要扩展点——它们告诉系统如何处理各种特殊类型。例如,有一个处理数组的格式化程序、一个处理多维数组的格式化程序、一个处理字典的格式化程序等等。

OdinSerializer附带了大量用于常用序列化的自定义格式化程序。NET和Unity类型。自定义格式化程序的示例可能是Unity的Vector3类型的以下格式化程序:

使用OdinSerializer使用UnityEngine[程序集:RegisterFormatter(typeof(Vector3Formatter))]公共类Vector3Formatter:MinimalBaseFormatter<Vector3>{私有静态只读序列化程序<float>FloatSerializer=Serializer.Get<float>();受保护的覆盖无效读取(ref Vector3值,IDataReader读取器){value.x=FloatSerializer.ReadValue(读取器);value.y=FloatSerializer.ReadValue(读取器);value.z=FloatSerializer.ReadValue(读取器);}受保护的覆盖无效写入(ref Vector3值,IDataWriter写入器){FloatSerializer.ReadValue(value.x,写入器);FloatSerializer.ReadValue(value.x,写入器);FloatSerializer.ReadValue(value.y,写入器);

所有未声明自定义格式化程序的复杂类型都使用按需发出的格式化程序序列化,或者如果发出的格式化程序在当前平台上不可用,则使用基于回退反射的格式化程序。这些“默认”格式化程序使用当前上下文上设置的序列化策略来决定序列化哪些成员。

序列化策略

非自定义格式化程序(发出格式化程序和反射格式化程序)使用序列化策略来决定哪些成员应该序列化,哪些不应该序列化。SerializationPolicies类中提供了一组默认策略。

外部参考

外部引用是一个非常有用的概念。简而言之,这是序列化数据引用未存储在序列化数据本身中的对象的一种方式,而是应该在反序列化时从外部获取。

例如,如果对象图形引用存储在中央资产数据库中的非常大的资产(如纹理),则您可能不希望整个纹理与图形一起向下序列化,而是希望序列化对纹理的外部引用,然后在反序列化时再次解析该引用。在使用Unity时,这个特性特别有用,您将在下一节中看到。

OdinSerializer中的外部引用可以是索引(int)、guid(System.Guid)或字符串,并通过实现IExternalIndexReferenceResolver、IExternalGuidReferenceResolver或IExternalStringReferenceResolver接口以及在当前数据读取器或写入器上设置的上下文上设置解析器实例来使用。

所有引用类型(字符串除外,字符串被视为原子原语)都可能被外部解析,并且将为遇到的每个引用类型对象查询当前上下文中所有可用的外部引用解析器,以确定该对象是否应该被序列化为外部引用。

OdinSerializer如何在Unity中工作

OdinSerializer带有一个内置的Unity集成,用于与从Unity特殊的UnityEngine.Object类派生的类型一起使用。这种集成主要以UnitySerializationUtility类以及从中派生的各种便利类的形式出现,这些类使用UnitySerializationUtility实现OdinSerializer。每个此类类都源自给定的常用UnityEngine.Object类型:

  • 序列化UnityObject:UnityEngine.Object
  • 序列化ScriptableObject:UnityEngine.ScriptableObject
  • 序列化组件:UnityEngine.Component
  • 序列化行为:UnityEngine.Behaviour
  • SerializedMonoBehaviour:UnityEngine.MonoBehaviour
  • SerializedNetworkBehaviour:UnityEngine.NetworkBehaviour
  • SerializedStateMachineBehaviour:UnityEngine.StateMachineBehaviour

从这些类型中的任何一个派生意味着您的派生类型将使用UnitySerializationUtility序列化。请注意,默认情况下, 连载 全部 派生类型上的序列化成员。这些便利类型使用SerializationPolicies.Unity策略来选择要序列化的成员,并且还具有以下附加行为: 它们不会序列化Unity通常会序列化的派生类型上的任何成员。 请注意,这仅直接应用于序列化的UnityEngine.Object派生类型的根成员。举下面这个例子:

//这是你放在游戏对象上的组件。正是这个组件,也只有这个组件,决定了Odin公共类MyMonoBehaviour:SerializedMonoBehaviour//从SerializedMonoBehaviour继承组件意味着我们使用Odin的serialization{public Dictionary<string, string>一些字典;//将由Odin[SerializeField]私有SomeClass someClass1序列化;//将由Unity序列化,而不是Odin。“someClass1.someString”将被序列化,但“someClass1.someDict”不会被序列化。不支持多态性。不支持空值。[OdinSerialize]私有SomeClass someClass2;//将由Odin序列化,而不是Unity。“someClass2.someString”和“someClass2.someDict”都将被序列化。支持多态性和空值。}[Serializable]公共类SomeClass//无论你从这里继承什么,它对这个类的序列化没有任何区别——那是在组件本身“更高”决定的{public string someString;public Dictionary<string, string>someDict;}

如果您希望更改此行为,您必须使用Unity的ISerializationCallbackReceiver接口实现您自己的特殊序列化UnityEngine.Object类型,并手动更改策略或传递给UnitySerializationUtility的参数。例如:

使用UnityEngine使用OdinSerializer使用OdinSerializer.Utilities公共类YourMonoBehaviour:MonoBehaviour, ISerializationCallbackReceiver{[SerializeField, HideInInspector]private SerializationData serializationData;void ISerializationCallbackReceiver.OnAfterDeserialize(){using(var cachedContext=Cache<DeserializationContext>.Claim()){cachedContext.Value.Config.SerializationPolicy=SerializationPolicies.Everything;UnitySerializationUtility.DeserializeUnityObject(this,ref this.serializationData,cachedContext.Value);}}void ISerializationCallbackReceiver.OnBeforeSerialize(){using(var cachedContext=Cache<SerializationContext>.Claim()){cachedContext.Value.Config.SerializationPolicy=SerializationPolicies.Everything;UnitySerializationUtility.SerializeUnityObject(this,ref this.serializationData,serializeUnityFields:true,context:cachedContext.Value);}}}

最后,应该注意的是,UnitySerializationUtility类总是将UnityReferenceResolver设置为外部索引解析器。这个外部引用解析器确保在要序列化的数据中遇到的对Unity对象的所有引用都成为外部引用,这些引用被分流到SerializationData结构中的一个列表中,供Unity序列化,然后用于将外部引用链接回正确的Unity实例。

这样做是因为Odin没有办法实际序列化并在以后重建大多数UnityEngine.Object派生类型,因此,我们已经将其作为一个硬性规则,我们甚至永远不会尝试这样做。

AOT支持详细信息

OdinSerializer包含两个实用程序类,AOTSupportUtilities和AOTSupportScanner,用于为AOT(提前)编译的平台(如IL2CPP和Mono AOT)提供支持。这些实用程序可用于扫描整个项目(或仅是项目的一部分)并生成由OdinSerializer序列化的类型列表,它们可以获取类型列表并在项目中创建一个.dll文件,以确保AOT构建中所有给定类型的序列化支持。

为了自动化这个AOT支持过程,您可以使用Unity的IPreProcessBuild/IPreProcessBuildWithReport和IPostProcessBuild/IPostProcessBuildWithReport接口在构建时创建一个AOT支持dll,并在构建后再次删除它。(请注意,这仅在Unity 5.6中成为可能,其中引入了IPreProcessBuild。)

OdinSerializer已经在Unity项目中包含了一个脚本文件OdinSerializerBuildAutomation.cs,它在基于当前目标平台和脚本后端的构建过程中自动正确设置OdinSerializer的三个程序集变体的导入设置,以及自动扫描项目并为需要它的类型生成AOT支持(如果构建是AOT编译的)。我们鼓励您修改此文件以满足您自己特定的自动化需求。

本工具由三方[bufftool]提供Attention Nomal
下载

立即下载模组

下载客户端搜索R.E.P.O.后使用Mod大师工具。