当前位置: 首页 > news >正文

Unity中的序列化和反序列化

一:前言

序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程。序列化最主要的用途就是传递对象和保存对象
在Unity中保存和加载、prefab、scene、Inspector窗口、实例化预制体等都使用了序列化与反序列化


二:可序列化类型

——自定义的具有Serializable特性的非抽象、非泛型类(所有继承UnityEngine.Object的类都具有Serializable特性,如MonoBehaviour)
——自定义的具有Serializable特性的结构体(Unity内置结构体类型都都具有Serializable特性)
——所有基本数据类型,如int、string等(必须为public或具有SerializeField特性且不能为static、const、readonly)
——可序列化类型的数组和列表(如int、string列表),栈、队列、字典等都不能被序列化
——枚举类型


三:Unity中的序列化和反序列化

最直观的就是在Unity中的检视面板可以看到字段就是被成功序列化了的参数,与序列化相关的常用关键字有SerializeField,HideInInspector,NonSerialized,Serializable
——SerializeField : 表示变量可被序列化,SerializeField与private,protected结合使用可以达到让脚本的变量在检视面板里可视化编辑,同时保持它的私有性的目的
——HideInInspector : 将原本显示在检视面板上的序列化值隐藏起来
——NonSerialized :将一个公有变量不序列化并且不显示在检视面板中
——Serializable:用在类的前面,表示该类可被序列化,Serializable不会被派生类所继承,每个类想要被序列化需要单独加Serializable特性


四:通过二进制序列化与反序列化

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System.IO;
using System;
using System.Runtime.Serialization.Formatters.Binary;

public class Test : MonoBehaviour
{
    [MenuItem("Tools/BinarySerialize")]
    private static void BinarySerialize()
    {
        People p = new People();
        Name name = new Name();
        name.name1 = "l";
        name.name2 = "hw";
        p.name = name;
        p.age = 26;

        string path = Application.dataPath + "/Data/PeopleData.bytes";
        FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(fs, p);
        fs.Close();

        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/BinaryDeserialize")]
    private static void BinaryDeserialize()
    {
        TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>("Assets/Data/PeopleData.bytes");

        MemoryStream ms = new MemoryStream(textAsset.bytes);
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        People p = (People)binaryFormatter.Deserialize(ms);
        ms.Close();

        Debug.Log(p.name.name1);
        Debug.Log(p.name.name2);
        Debug.Log(p.age);
    }
}

[Serializable]
public class People
{
    public Name name;
    public int age;
}

[Serializable]
public class Name
{
    public string name1;
    public string name2;
}

使用二进制序列化时,每个类都需要Serializable标识
只有通过二进制进行序列化和反序列化时才能调用到OnSerializing、OnSerialized、OnDeserializing、OnDeserialized这四个特性


五:通过Json序列化与反序列化

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System.IO;
using System;

public class Test : MonoBehaviour
{
    [MenuItem("Tools/JsonSerialize")]
    private static void JsonSerialize()
    {
        People p = new People();
        Name name = new Name();
        name.name1 = "l";
        name.name2 = "hw";
        p.name = name;
        p.age = 26;

        string jsonString = JsonUtility.ToJson(p);
        string path = Application.dataPath + "/Data/PeopleData.json";
        File.WriteAllText(path, jsonString);

        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/JsonDeserialize")]
    private static void JsonDeserialize()
    {
        string path = Application.dataPath + "/Data/PeopleData.json";
        StreamReader sr = File.OpenText(path);
        string jsonString = sr.ReadToEnd();
        sr.Close();

        People p = JsonUtility.FromJson<People>(jsonString);

        Debug.Log(p.name.name1);
        Debug.Log(p.name.name2);
        Debug.Log(p.age);
    }
}

public class People
{
    public Name name;
    public int age;
}

[Serializable]
public class Name
{
    public string name1;
    public string name2;
}

使用Json序列化时,第一层类不需要Serializable标识,其他类都需要Serializable标识


六:通过XML序列化与反序列化

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System.IO;
using System.Xml.Serialization;

public class Test : MonoBehaviour
{
    [MenuItem("Tools/XmlSerialize")]
    private static void XmlSerialize()
    {
        People p = new People();
        Name name = new Name();
        name.name1 = "l";
        name.name2 = "hw";
        p.name = name;
        p.age = 26;

        string path = Application.dataPath + "/Data/PeopleData.xml";
        FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        StreamWriter streamWriter = new StreamWriter(fileStream, System.Text.Encoding.UTF8);
        XmlSerializer xmlSerializer = new XmlSerializer(p.GetType());
        xmlSerializer.Serialize(streamWriter, p);
        streamWriter.Close();
        fileStream.Close();

        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/XmlDeserialize")]
    private static void XmlDeserialize()
    {
        string path = Application.dataPath + "/Data/PeopleData.xml";
        FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(People));
        People p = (People)xmlSerializer.Deserialize(fileStream);
        fileStream.Close();

        Debug.Log(p.name.name1);
        Debug.Log(p.name.name2);
        Debug.Log(p.age);
    }
}

public class People
{
    public Name name;
    public int age;
}

public class Name
{
    public string name1;
    public string name2;
}

使用XML序列化时,类不需要Serializable标识,不能序列化私有变量,就算加上NonSerialized标识也无效


七:通过ScriptableObject序列化与反序列化

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System;

public class Test : MonoBehaviour
{
    [MenuItem("Tools/ScriptableObjectSerialize")]
    private static void ScriptableObjectSerialize()
    {
        string path = "Assets/Data/PeopleData.asset";
        People p = ScriptableObject.CreateInstance<People>();
        Name name = new Name();
        name.name1 = "l";
        name.name2 = "hw";
        p.name = name;
        p.age = 26;

        AssetDatabase.CreateAsset(p, path);
        AssetDatabase.Refresh();
    }

    [MenuItem("Tools/ScriptableObjectDeserialize")]
    private static void ScriptableObjectDeserialize()
    {
        People p = AssetDatabase.LoadAssetAtPath<People>("Assets/Data/PeopleData.asset");

        Debug.Log(p.name.name1);
        Debug.Log(p.name.name2);
        Debug.Log(p.age);
    }
}

public class People : ScriptableObject
{
    public Name name;
    public int age;
}

[Serializable]
public class Name
{
    public string name1;
    public string name2;
}

使用ScriptableObject序列化时,第一层类不需要Serializable标识,其他类都需要Serializable标识


八:序列化接口—ISerializationCallbackReceiver

——OnBeforeSerialize:序列化前
——OnAfterDeserialize:反序列化后

Unity中Inspector序列化显示Dictionary的方法


九:序列化特性

——OnDeserialized:序列化后
——OnDeserializing:序列化前
——OnSerialized:反序列化后
——OnSerializing:反序列化前
只有通过二进制进行序列化和反序列化时才能调用到OnSerializing、OnSerialized、OnDeserializing、OnDeserialized这四个特性

[Serializable]
public class People
{
    public Name name;
    public int age;

    [OnSerializing]
    virtual protected void OnSerializing(StreamingContext context)
    {
        Debug.Log("OnSerializing");
    }

    [OnSerialized]
    virtual protected void OnSerialized(StreamingContext context)
    {
        Debug.Log("OnSerialized");
    }

    [OnDeserializing]
    virtual protected void OnDeserializing(StreamingContext context)
    {
        Debug.Log("OnDeserializing");
    }

    [OnDeserialized]
    virtual protected void OnDeserialized(StreamingContext context)
    {

        Debug.Log("OnDeserialized");
    }
}

十:Unity中的Prefab

Unity中的Prefab就是游戏对象和组件经过序列化后得到的文件,当你创建一个空物体并制作成预制体后,他会序列化成一个xxx.prefab的文件,这个文件的格式可以是二进制的也可以是文本文件,通过下面的选项可以设置

为什么在运行时修改预制体上的值不会保存?
Unity其实是两层,C++层与Unity控制层,因为Unity是用C++编写的,但是我们自己编写的脚本是C#,就会有一个交互。当我们点击运行按钮时,先是把所有的序列化数据在内部创建,然后把他们存在C++这一层,然后清除Unity控制层这边所有的内存和消息,然后加载我们编写的脚本,最后再把C++层中存储的序列化数据反序列化到Unity控制层中去
在运行时修改字段的值只是更改Unity控制层上的数据,游戏运行过程中会读取这个数据,但不会保存在C++层(Native层),游戏停止后,会再次反序列化Native层中的数据,显示运行前没更改的那个数值

为什么在脚本中定义了变量值,在属性面板上改了之后,使用的是面板上的值而不是脚本中定义的值?
属性面板上的值并不是Unity调用脚本中的C#接口获取的,而是通过对象的反序列化得到的,当修改了属性面板上值后就进行了序列化操作,将值保存到了文件中,面板显示时通过反序列化将文件中的值赋给变量

实例化预制体Instantiate方法的内部过程是首先将参数original所引用的游戏对象序列化,得到序列化流后,再使用反序列化机制将这个序列化流生成一个新的游戏对象,可以说是对象的克隆操作,因此在运行中生成Prefab实例的话可以看到这些实例会带有(Clone)的标记

相关文章:

  • RocketMQ的架构设计
  • C++ 小游戏 视频及资料集(5)
  • k8s主节点与子节点的错误解决
  • spring boot 自定redis缓存注解
  • K8s 高可用集群架构(二进制)部署及应用
  • Linux上 tomcat的虚拟主机IP映射配置
  • 使用 MAUI 进行数据可视化:与 图表控件LightningChart JS 的兼容性项目模板
  • Profile注解
  • DPDK以太网部分代码整理
  • 元年专利解析|元数据管理系统和使用其对模型对象进行建模的方法
  • 新唐NUC980使用记录:向内核添加USB无线网卡驱动(基于RTL8188EUS)
  • 最全语音特征总结
  • Centos 7 迁移到 Anolis OS 7.9 及问题处理
  • 我的世界Minecraft基岩版开服教程(Linux)开服器开服包下载开服网站服务器开服核心开服端开服软件mac版Java启动器
  • SpringBoot Web开发----请求参数处理
  • .pyc 想到的一些问题
  • canvas 五子棋游戏
  • gcc介绍及安装
  • Git 使用集
  • httpie使用详解
  • idea + plantuml 画流程图
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • PhantomJS 安装
  • PHP CLI应用的调试原理
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • uni-app项目数字滚动
  • v-if和v-for连用出现的问题
  • 从0到1:PostCSS 插件开发最佳实践
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前端技术周刊 2019-01-14:客户端存储
  • 前端性能优化——回流与重绘
  • 区块链共识机制优缺点对比都是什么
  • 消息队列系列二(IOT中消息队列的应用)
  • 带你开发类似Pokemon Go的AR游戏
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #includecmath
  • #Linux(Source Insight安装及工程建立)
  • (20050108)又读《平凡的世界》
  • (2022 CVPR) Unbiased Teacher v2
  • (pytorch进阶之路)扩散概率模型
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (二)windows配置JDK环境
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (剑指Offer)面试题34:丑数
  • (转)原始图像数据和PDF中的图像数据
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET6实现破解Modbus poll点表配置文件
  • .NetCore 如何动态路由
  • .NET值类型变量“活”在哪?
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • @RequestMapping处理请求异常