Unity的数据存储,本地类 PlayerPrefs, Inspector,以及Prefab等都使用了序列化与反序列化的知识.

循序渐进,让我们一步步了解Unity中的序列化和反序列化的知识;

流与格式化器

序列化: 将对象转换为字节流.

反序列化: 将字节流转换为对象.

直接讲概念太抽象,我们先来看代码;

using UnityEngine;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

public class Test : MonoBehaviour

{

private void Start()

{

Hero hero_ins = new Hero();

hero_ins.id = 100;

hero_ins.attack = 99f;

hero_ins.defence = 99f;

hero_ins.name = "Calabash";

Stream st = FormatInstanceToMemory(hero_ins);

st.Position = 0;

hero_ins = null;

hero_ins = MemoryToInstance(st) as Hero;

Debug.Log(hero_ins.id.ToString());

Debug.Log(hero_ins.attack.ToString());

Debug.Log(hero_ins.defence.ToString());

Debug.Log(hero_ins.name);

}

//序列化 把实例对象写入流中

private static MemoryStream FormatInstanceToMemory(object instance)

{

//创建一个流

MemoryStream ms = new MemoryStream();

//创建格式化器

BinaryFormatter bf = new BinaryFormatter();

//序列化为二进制流

bf.Serialize(ms, instance);

return ms;

}

//反序列化, 从流中读出实例对象

private static object MemoryToInstance(Stream st)

{

//创建格式化器

BinaryFormatter bf = new BinaryFormatter();

//把二进制流反序列化为指定的对象

return bf.Deserialize(st);

}

}

关于Hero类的定义如下:

[Serializable] //注意这个关键字

public class Hero

{

public int id;

public float attack;

public float defence;

public string name;

}

代码中的注释已经写得很清楚了,通过代码我们要解释三个概念;

流(Stream): Unity中的二进制数据流,有 MemoryStream, FileStream 等子类来处理不同场景的数据流,但我们这里不讨论每种流的用法,只需要让大家理解 Stream提供了一个用来容纳经过序列化之后的字节块的容器

更多的Stream知识可以查阅这里: Unity的Stream流

格式化器: 使用序列化和反序列的工具,代码中只是使用到了 BinaryFormatter 这种格式化器,其实还有 SoapFormatter (需要导入对应的.dll文件),需要注意的是进行序列化和反序列的操作必须是相同的格式化器,否则可能会抛出System.Runtime.Serialization.SerializationException异常.

[Serializable]特性: 默认自定义的类型是无法被序列化的,需要使用 [Serializable] 特性来实现序列化与反序列化,关于此特性更多的内容见下节;

通过上面的示例,我们往流中写入了一个对象,那么可以写入两个,甚至多个不同的对象么?答案是肯定的,我们还是用代码测试一下;

using UnityEngine;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

public class Test : MonoBehaviour

{

private void Start()

{

Hero hero_ins = new Hero();

hero_ins.id = 100;

hero_ins.attack = 99f;

hero_ins.defence = 99f;

hero_ins.name = "Calabash";

Soldier soldier_ins = new Soldier();

soldier_ins.life = 50;

soldier_ins.weapon = "hammer";

//创建一个流

MemoryStream ms = new MemoryStream();

//创建格式化器

BinaryFormatter bf = new BinaryFormatter();

//序列化为二进制流

bf.Serialize(ms, hero_ins);

bf.Serialize(ms, soldier_ins);

ms.Position = 0;

hero_ins = null;

soldier_ins = null;

//从数据流中读出数据

//读出的顺序不能颠倒,因为是从ms的开端读取,因此要按写入的顺序读取

hero_ins = bf.Deserialize(ms) as Hero;

soldier_ins = bf.Deserialize(ms) as Soldier;

Debug.Log("hero: " + hero_ins.id.ToString());

Debug.Log("hero: " + hero_ins.attack.ToString());

Debug.Log("hero: " + hero_ins.defence.ToString());

Debug.Log("hero: " + hero_ins.name);

Debug.Log("soldier: " + soldier_ins.life.ToString());

Debug.Log("soldier: " + soldier_ins.weapon);

}

[Serializable]与[NonSerialized]的继承

1. [Serializable]

该特性只能用于以下类型:

引用类型(class)

值类型(struct)

枚举类型(enum)

委托类型(delegate)

该特性不会被派生的子类继承;

[Serializable] //注意这个关键字

public class Hero

{

public int id;

public float attack;

public float defence;

public string name;

}

public class GirlHero : Hero

{

public int girlAge;

}

public class Test : MonoBehaviour

{

private void Start()

{

Hero hero_ins = new Hero();

hero_ins.id = 100;

hero_ins.attack = 99f;

hero_ins.defence = 99f;

hero_ins.name = "Calabash";

GirlHero girl_ins = new GirlHero();

girl_ins.girlAge = 18;

//创建一个流

MemoryStream ms = new MemoryStream();

//创建格式化器

BinaryFormatter bf = new BinaryFormatter();

//序列化为二进制流

bf.Serialize(ms, hero_ins);

bf.Serialize(ms, girl_ins);

ms.Position = 0;

hero_ins = null;

girl_ins = null;

//从数据流中读出数据

//读出的顺序不能颠倒,因为是从ms的开端读取,因此要按写入的顺序读取

hero_ins = bf.Deserialize(ms) as Hero;

girl_ins = bf.Deserialize(ms) as GirlHero;

Debug.Log("hero: " + hero_ins.id.ToString());

Debug.Log("hero: " + hero_ins.attack.ToString());

Debug.Log("hero: " + hero_ins.defence.ToString());

Debug.Log("hero: " + hero_ins.name);

Debug.Log("girl: " + girl_ins.girlAge.ToString());

}

}

点击运行后,果不其然会报 SerializationException 的一个错误:

SerializationException: Type 'GirlHero' in Assembly 'Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

错误信息很明显,我们的 GirlHero 类没有标记 Serializable 特性,当我们给这个类也标记上该特性后,结果可以正常被打印;

我们在来看另外一种情况,只有派生类使用特性,基类不使用:

public class Hero

{

public int id;

public float attack;

public float defence;

public string name;

}

[Serializable]

public class GirlHero : Hero

{

public int girlAge;

}

运行后报错如下:

SerializationException: Type 'Hero' in Assembly 'Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

通过这个测试,我们可以知道:

Serializable特性不会被继承,每个派生类如果要想被序列化,需要单独添加此特性字段.

派生类添加了 Serializable 特性,而基类不使用,那么从基类派生的任何类都无法被序列化.

可以这么理解,基类如果无法被序列化,那么它的字段无法被序列化,派生类同样包含该基类的字段,那么自然也是无法被序列化的.C#中的所有类都是继承自 System.Object 类,这个类已经应用了 Serializable 特性.

2. [NonSerialized]

在默认情况下,序列化会读取对象的所有字段,无论这些字段生命的访问权限是 public 还是 private, 如果我们有些敏感字段或者计算属性不想被序列化,有没有办法呢?

在不想被序列化的字段上面使用 NonSerialized 属性即可;

[Serializable] //注意这个关键字

public class Hero

{

public int id;

[NonSerialized]

public float attack;

public float defence;

public string name;

}

使用上面Test的脚本,打印结果如下:

NonSerialized特性的使用

我们可以看到反序列化后,被标记为 NonSerialized 特性的字段值变为了0,这是由于

attack 字段不能被序列化,它的值99并不会写入到流中,因此被反序列化后,其余字段都能够被正常赋值,该字段由于从流中读取不到对应的值,只能设置为0;

那么能不能在反序列化的时候,把正确的值赋值回去呢?答案是肯定的,我们下节再来解决这个问题,我们继续查看 NonSerialized 的继承特点;

[Serializable] //注意这个关键字

public class Hero

{

public int id;

[NonSerialized]

public float attack;

public float defence;

public string name;

}

[Serializable]

public class GirlHero : Hero

{

public int girlAge;

}

public class Test : MonoBehaviour

{

private void Start()

{

GirlHero girl_ins = new GirlHero();

girl_ins.id = 100;

girl_ins.attack = 99f;

girl_ins.defence = 99f;

girl_ins.name = "Calabash";

girl_ins.girlAge = 18;

//创建一个流

MemoryStream ms = new MemoryStream();

//创建格式化器

BinaryFormatter bf = new BinaryFormatter();

//序列化为二进制流

bf.Serialize(ms, girl_ins);

ms.Position = 0;

girl_ins = null;

//从数据流中读出数据

girl_ins = bf.Deserialize(ms) as GirlHero;

Debug.Log("girl_id: " + girl_ins.id.ToString());

Debug.Log("girl_attack: " + girl_ins.attack.ToString());

Debug.Log("girl_defence: " + girl_ins.defence.ToString());

Debug.Log("girl_name: " + girl_ins.name);

Debug.Log("girl_age: " + girl_ins.girlAge.ToString());

}

}

打印结果如下:

NonSerialized特性可以被继承

通过上面的测试可以得知: [NonSerialized] 特性可以被派生类继承;

控制序列化和反序列化的流程

在上一节提出的问题,对于 NonSerialized 修饰的字段,在反序列化的时候应该如何赋值,以及如果我们想在序列化和反序列化之前和之后做些操作,应该怎么实现?

[Serializable]

public class GirlHero : Hero

{

public int girlAge;

[OnDeserialized]

private void CaculateAttack(StreamingContext context)

{

this.attack = 1000;

}

}

在上一节的代码基础上,我们对 GirlHero 做了上面的改动,增加了一个 CaculateAttack 方法,并且使用了 [OnDeserialized] 特性,我们再来看打印结果:

控制反序列化

通过这样的方法和特性我们对 attack 字段在反序列化的时候进行了赋值;

从特性的名字可以看出,是在反序列化过程完成后调用所修饰的方法,还有其他三个相关特性我们一起来看看;

1. 序列化与反序列化过程的方法特性

OnSerializing :格式化器在序列化对象字段之前,调用该特性修饰的方法.

OnSerialized :格式化器在序列化对象字段之后,调用该特性修饰的方法.

OnDeserializing :格式化器在反序列化对象字段之前,调用该特性修饰的方法.

OnDeserialized ::格式化器在反序列化对象字段之后,调用该特性修饰的方法.

这几个特性是在 System.Runtime.Serialization 命名空间下,共同点是用来修饰类型中定义的方法;注意他们的调用时机.

2. StreamingContext

在上面的实例代码中,可以看到方法参数是一个 StreamingContext 类,这个类是序列化与反序列化时流的上下文,我们通过程序集可以看到,这个类型是一个值类型.

public struct StreamingContext

{

//调用方定义的附加上下文引用,一般为空

public object Context {

get;

}

//用来标记序列化和反序列对象的来源和目的地

public StreamingContextStates State {

get;

}

//构造方法

public StreamingContext (StreamingContextStates state);

public StreamingContext (StreamingContextStates state, object additional);

//重载System.Object方法

public override bool Equals (object obj);

public override int GetHashCode ();

}

通过State的属性我们可以查看序列化和反序列化时对应的来源和目的地,更多的信息请查阅这里:StreamingContextStates枚举

我们在上面序列化时使用的格式化器的 Context 属性就是 StreamingContext, 它的 State 属性默认是All,我们也可以在创建格式化器的时候手动指定 State 的类型来满足不同的需求,比如:

//指定state类型,深度克隆一个对象

BinaryFormatter bf = new BinaryFormatter();

bf.Context = new StreamingContext(StreamingContextStates.Clone);

Unity的Inspector

在属性监视板中可以看到游戏脚本中某个对象的信息,这些字段和值并不是Unity调用游戏脚本中的C#接口获取的,而是通过显示对象的反序列化得到这些属性数值,然后在面板中展示出来;

Unity的Prefab

Prefab是Unity中很重要的一种资源类型,真正实现了游戏对象的克隆,预制体是游戏对象和组件经过序列化后得到的文件,它的格式可以是二进制的也可以是文本文件,可以通过下面的选项来设置:

资源格式设置

它的特点如下:

可以被放入多个场景中,也可以在一个场景中放入多个

在场景中增加一个Prefab,就实例化了一个该Prefab的实例

所有的Prefab实例都是Prefab的克隆,因此在运行中生成Prefab实例的话可以看到这些实例会带有(Clone)的标记

只要Prefab的原型发生了变化,场景中所有的prefab实例都会发生变化

脚本创建Prefab实例我们都是通过Instantiate方法:

public static Object Instantiate (Object original, Vector3 position, Quaternion rotation)

在该方法内部,会首先将参数original所引用的游戏对象序列化,得到序列化流后,再使用反序列化机制将这个序列化流生成一个新的游戏对象,可以说是对象的克隆操作;

Unity在System.Runtime.Serialization命名空间下定义了一个FormatterServices的类型,只包含一些静态方法,用来辅助序列化与反序列化的过程;

序列化过程

调用FormatterServices的 GetSerializableMembers ;

//两个重载版本

//type: 正在序列化或克隆的类型

//context: 发生序列化的上下文

//MemberInfo[]: 返回类型对象的数组,每一个元素都对应一个可以成员字段的名称

public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context)

public static MemberInfo[] GetSerializableMembers(Type type)

调用FormatterServices的 GetObjectData ;

//obj: 表示要写入序列化程序的对象实例

//members: 代表的是第一步提取的成员字段的名称

//Object[]: 返回的是对应members中每个元素表示的字段对应的值,理解为Value的集合

public static Object[] GetObjectData(Object obj, MemberInfo[] members)

经过前两个步骤获取了对象的成员和其对应的值,这一步先把程序集标识以及类型的完整名称写入流中.

格式化器遍历第一步与第二步得到的数组获取成员名称和其对应的值,将这些信息写入流中.

反序列化过程

格式化器从流中读取程序集标识和完整的类型名称,然后调用FormatterServices的 GetTypeFromAssembly ;

//assem: 读取到的程序集标识

//name: 完整的类型名称

//Type: 返回值便是反序列化对象的实际类型

public static Type GetTypeFromAssembly(Assembly assem, string name)

获取了对象的类型后,接下来就是要在为新的对象分配一块内存空间,调用FormatterServices的 GetUninitializedObject ;

//为指定类型分配内存空间

public static Object GetUninitializedObject(Type type)

需要注意的是,此时还没有调用构造函数,对象的所有字节都被初始化为 null 或者 0 ;

分配好内存空间后,还是调用FormatterServices的 GetSerializableMembers 构造并初始化一个新的 MemberInfo 数组;

这个方法的说明见序列化过程的第一步;

调用方法后获取该类型的所有成员字段名称的集合 MemberInfo[] members ;

获取到字段信息后,这一步就要获取字段对应数组的信息;格式化器会根据流中包含的数据创建一个 Object 数组,对其进行初始化;

到了这一步,你就有了一个未初始化的对象,一个成员变量集合和对应数值的集合;

这一步就要调用FormatterServices的 PopulateObjectMembers 方法对实例对象初始化;

//obj: 表示刚才创建要被初始化的对象实例

//members: 对象需要被填充的成员或者属性

//data: 对象需要被填充的成员或者属性对应的数值

//Object: 返回一个初始化好的实例对象

public static Object PopulateObjectMembers(Object obj, MemberInfo[] members, Object[] data)

参考文章: <> 陈嘉栋

unity引用类型序列化_Unity中的序列化与反序列化相关推荐

  1. java kryo 序列化_java中的序列化方式及dubbo使用kryo序列化

    java中的序列化方式: 1. 自带序列化  ObjectInputSteam. ObjectOutStream等 2. hession2 3. json ,xml等格式 4.kryo 5.FST - ...

  2. 对象序列化Java中的序列化

    首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 当两个进程在停止远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都市以二进制序列的情势在络网上传送.发送方需要把这个Java对 ...

  3. lua和unity如何交互_Unity中C#对象与Lua之间交互的原理

    lua与其他语言交互,都是通过操作虚拟栈交流的.而操作虚拟栈最原始的方法就是通过lua与c语言的api,由于直接操作lua与c比较繁琐而且容易出错.因此tolua作为中间层而存在. 那么unity要把 ...

  4. unity控制程序等待_Unity 中的协同程序

    今天咱就说说,协同程序coroutine.(这文章是在网吧敲的,没有unity,但是所有结论都被跑过,不管你信得过我还是信不过我,都要自己跑一下看看,同时欢迎纠错) 先说说啥是协程:协同程序是一个非常 ...

  5. unity 发光字体_Unity中字体的一些知识

    Unity中的字体常用的有:内置字体,外部导入的字体,自定义的字体. 1,内置字体. 内置字体一般是指unity自带的字体,Windows下unity自带字体为Arial,如果游戏中使用Arial字体 ...

  6. unity 画球面_unity中实现Mesh画球体、半球体、四分之一球体以及任意弧面

    感谢两篇文章: mesh绘制模型:https://blog.csdn.net/qq_29579137/article/details/77369734?depth_1-utm_source=distr ...

  7. java 序列化实例_Java中的序列化与反序列化实例

    创建的字节流与平台无关.因此,在一个平台上序列化的对象可以在另一个平台上反序列化. 为了使Java对象可序列化,我们实现java.io.Serializable可序列化接口. ObjectOutput ...

  8. Unity中的序列化和反序列化

    一:前言 序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程.序列化最主要的用途就是传递对象和保存对象 在Unity中保存和加载.prefab.scene.Inspecto ...

  9. K:java中的序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?以下内容将围绕这些问题进行展开讨论. Java序列化与反序列化 简单来说Java序列化是指把Java对象转 ...

  10. Pytorch中的序列化容器-度消失和梯度爆炸-nn.Sequential-nn.BatchNorm1d-nn.Dropout

    Pytorch中的序列化容器-度消失和梯度爆炸-nn.Sequential-nn.BatchNorm1d-nn.Dropout 1. 梯度消失和梯度爆炸 在使用pytorch中的序列化 容器之前,我们 ...

最新文章

  1. LeetCode 报错解决 heap-buffer-overflow Heap-use-after-free Stack-buffer-overflow Global-buffer-overflow
  2. Keil工程Lib库文件的制作和运用
  3. docker (centOS 7) 使用笔记4 - etcd服务
  4. xhprof的简单使用
  5. 图片做背景撑开div
  6. oracle基本笔记整理及案例分析1
  7. vue-cli3项目通过vue如何引入第三方js包完成登陆功能
  8. 包装类转换基本数据类型与基本数据类型转换为包装类
  9. stack示例_C.示例中的Stack.CopyTo()方法
  10. python可视化库matplotlib_Python数据可视化matplotlib库
  11. devc运行不出窗口_足不出户“云出庭”?沾化“智慧检务”让法律监督“不打烊...
  12. OpenCV初探 —— VS2019配置环境
  13. java靜態常量_Java靜態變量、靜態常量、靜態方法
  14. 计算机图形(Computer Graphics)经典书籍推荐(1)
  15. 1、分组选择器, 2、尺寸 (Dimension)属性,3、Display(显示) 与 Visibility(可见性),4、CSS Display - 块和内联元素,5、CSS Position(定位
  16. 24小时改变你的人生【转】
  17. Titanic(泰坦尼克号数据集)
  18. 单片机常用外设驱动电路
  19. 移动硬盘linux系统安装win7系统,超简单的移动硬盘安装系统win7教程
  20. 闲谈mac地址学习以及IVL/SVL

热门文章

  1. STM32/STM8选型手册
  2. 教程:GIMP中改变画布大小
  3. 1200,1500PLC通过将FB284封装成一个标准FB块控制V90PN伺服EPOS定位
  4. 多尺度地理加权回归(MGWR),地理加权回归(GWR),最小二乘法(OLS)回归模型的对比分析
  5. 干货 | 利用SPSS进行高级统计分析第二期
  6. 破解wifi并实施中间人攻击
  7. 什么是强人工智能程序,需要具备哪些基本能力?
  8. ps -ef | grep 命令详解
  9. 省市区三级联动 mysql_javaweb--json--ajax--mysql实现省市区三级联动(附三级联动数据库)...
  10. 绝地求生登录计算机需要授权,Steam第三方授权登录错误 《绝地求生大逃杀》国服绑定受影响!...