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
{
    /// <summary>
    /// 序列化二进制
    /// </summary>
    public static bool BinarySerialize(string outputPath, object obj)
    {
        if (!IOUtils.IsFile(outputPath))
        {
            Debug.LogError($"序列化二进制失败,输出路径有误,filePath:{outputPath}");
            return false;
        }
        try
        {
            string dirPath = Path.GetDirectoryName(outputPath);
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(fs, obj);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"序列化二进制失败,{e}");
            return false;
        }
        return true;
    }

    /// <summary>
    /// 反序列化二进制
    /// </summary>
    public static T BinaryDeserialize<T>(string binaryFilePath)
    {
        if (!File.Exists(binaryFilePath))
        {
            Debug.LogError($"反序列化二进制失败,找不到二进制文件:{binaryFilePath}");
            return default;
        }
        T obj = default;
        try
        {
            using (FileStream fs = File.OpenRead(binaryFilePath))
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                obj = (T)binaryFormatter.Deserialize(fs);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"反序列化二进制失败,{e}");
            return obj;
        }
        return obj;
    }
}

使用二进制序列化时,每个类都需要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
{
    /// <summary>
    /// 序列化XML
    /// </summary>
    public static bool XmlSerialize(string outputPath, object obj)
    {
        if (!IOUtils.IsFile(outputPath))
        {
            Debug.LogError($"序列化XML失败,输出路径有误,filePath:{outputPath}");
            return false;
        }
        try
        {
            string dirPath = Path.GetDirectoryName(outputPath);
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }
            using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
                {
                    XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
                    xmlSerializer.Serialize(sw, obj);
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"序列化XML失败,{e}");
            return false;
        }
        return true;
    }

    /// <summary>
    /// 反序列化XML
    /// </summary>
    public static T XmlDeserialize<T>(string xmlFilePath)
    {
        if (!File.Exists(xmlFilePath))
        {
            Debug.LogError($"反序列化XML失败,找不到XML文件:{xmlFilePath}");
            return default;
        }
        T t = default;
        try
        {
            using (FileStream fs = File.OpenRead(xmlFilePath))
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
                t = (T)xmlSerializer.Deserialize(fs);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"反序列化XML失败,{e}");
            return t;
        }
        return t;
    }
}

使用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)的标记

猜你喜欢

转载自blog.csdn.net/LLLLL__/article/details/126570498