• 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特性


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

    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UI;
    4. using System.IO;
    5. using System;
    6. using System.Runtime.Serialization.Formatters.Binary;
    7. public class Test : MonoBehaviour
    8. {
    9. ///
    10. /// 序列化二进制
    11. ///
    12. public static bool BinarySerialize(string outputPath, object obj)
    13. {
    14. if (!IOUtils.IsFile(outputPath))
    15. {
    16. Debug.LogError($"序列化二进制失败,输出路径有误,filePath:{outputPath}");
    17. return false;
    18. }
    19. try
    20. {
    21. string dirPath = Path.GetDirectoryName(outputPath);
    22. if (!Directory.Exists(dirPath))
    23. {
    24. Directory.CreateDirectory(dirPath);
    25. }
    26. using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
    27. {
    28. BinaryFormatter binaryFormatter = new BinaryFormatter();
    29. binaryFormatter.Serialize(fs, obj);
    30. }
    31. }
    32. catch (Exception e)
    33. {
    34. Debug.LogError($"序列化二进制失败,{e}");
    35. return false;
    36. }
    37. return true;
    38. }
    39. ///
    40. /// 反序列化二进制
    41. ///
    42. public static T BinaryDeserialize<T>(string binaryFilePath)
    43. {
    44. if (!File.Exists(binaryFilePath))
    45. {
    46. Debug.LogError($"反序列化二进制失败,找不到二进制文件:{binaryFilePath}");
    47. return default;
    48. }
    49. T obj = default;
    50. try
    51. {
    52. using (FileStream fs = File.OpenRead(binaryFilePath))
    53. {
    54. BinaryFormatter binaryFormatter = new BinaryFormatter();
    55. obj = (T)binaryFormatter.Deserialize(fs);
    56. }
    57. }
    58. catch (Exception e)
    59. {
    60. Debug.LogError($"反序列化二进制失败,{e}");
    61. return obj;
    62. }
    63. return obj;
    64. }
    65. }

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


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

    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UI;
    4. using System.IO;
    5. using System;
    6. public class Test : MonoBehaviour
    7. {
    8. [MenuItem("Tools/JsonSerialize")]
    9. private static void JsonSerialize()
    10. {
    11. People p = new People();
    12. Name name = new Name();
    13. name.name1 = "l";
    14. name.name2 = "hw";
    15. p.name = name;
    16. p.age = 26;
    17. string jsonString = JsonUtility.ToJson(p);
    18. string path = Application.dataPath + "/Data/PeopleData.json";
    19. File.WriteAllText(path, jsonString);
    20. AssetDatabase.Refresh();
    21. }
    22. [MenuItem("Tools/JsonDeserialize")]
    23. private static void JsonDeserialize()
    24. {
    25. string path = Application.dataPath + "/Data/PeopleData.json";
    26. StreamReader sr = File.OpenText(path);
    27. string jsonString = sr.ReadToEnd();
    28. sr.Close();
    29. People p = JsonUtility.FromJson(jsonString);
    30. Debug.Log(p.name.name1);
    31. Debug.Log(p.name.name2);
    32. Debug.Log(p.age);
    33. }
    34. }
    35. public class People
    36. {
    37. public Name name;
    38. public int age;
    39. }
    40. [Serializable]
    41. public class Name
    42. {
    43. public string name1;
    44. public string name2;
    45. }

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


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

    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UI;
    4. using System.IO;
    5. using System.Xml.Serialization;
    6. public class Test : MonoBehaviour
    7. {
    8. ///
    9. /// 序列化XML
    10. ///
    11. public static bool XmlSerialize(string outputPath, object obj)
    12. {
    13. if (!IOUtils.IsFile(outputPath))
    14. {
    15. Debug.LogError($"序列化XML失败,输出路径有误,filePath:{outputPath}");
    16. return false;
    17. }
    18. try
    19. {
    20. string dirPath = Path.GetDirectoryName(outputPath);
    21. if (!Directory.Exists(dirPath))
    22. {
    23. Directory.CreateDirectory(dirPath);
    24. }
    25. using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
    26. {
    27. using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
    28. {
    29. XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
    30. xmlSerializer.Serialize(sw, obj);
    31. }
    32. }
    33. }
    34. catch (Exception e)
    35. {
    36. Debug.LogError($"序列化XML失败,{e}");
    37. return false;
    38. }
    39. return true;
    40. }
    41. ///
    42. /// 反序列化XML
    43. ///
    44. public static T XmlDeserialize<T>(string xmlFilePath)
    45. {
    46. if (!File.Exists(xmlFilePath))
    47. {
    48. Debug.LogError($"反序列化XML失败,找不到XML文件:{xmlFilePath}");
    49. return default;
    50. }
    51. T t = default;
    52. try
    53. {
    54. using (FileStream fs = File.OpenRead(xmlFilePath))
    55. {
    56. XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
    57. t = (T)xmlSerializer.Deserialize(fs);
    58. }
    59. }
    60. catch (Exception e)
    61. {
    62. Debug.LogError($"反序列化XML失败,{e}");
    63. return t;
    64. }
    65. return t;
    66. }
    67. }

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


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

    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.UI;
    4. using System;
    5. public class Test : MonoBehaviour
    6. {
    7. [MenuItem("Tools/ScriptableObjectSerialize")]
    8. private static void ScriptableObjectSerialize()
    9. {
    10. string path = "Assets/Data/PeopleData.asset";
    11. People p = ScriptableObject.CreateInstance();
    12. Name name = new Name();
    13. name.name1 = "l";
    14. name.name2 = "hw";
    15. p.name = name;
    16. p.age = 26;
    17. AssetDatabase.CreateAsset(p, path);
    18. AssetDatabase.Refresh();
    19. }
    20. [MenuItem("Tools/ScriptableObjectDeserialize")]
    21. private static void ScriptableObjectDeserialize()
    22. {
    23. People p = AssetDatabase.LoadAssetAtPath("Assets/Data/PeopleData.asset");
    24. Debug.Log(p.name.name1);
    25. Debug.Log(p.name.name2);
    26. Debug.Log(p.age);
    27. }
    28. }
    29. public class People : ScriptableObject
    30. {
    31. public Name name;
    32. public int age;
    33. }
    34. [Serializable]
    35. public class Name
    36. {
    37. public string name1;
    38. public string name2;
    39. }

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


    八:序列化接口—ISerializationCallbackReceiver

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

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


    九:序列化特性

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

    1. [Serializable]
    2. public class People
    3. {
    4. public Name name;
    5. public int age;
    6. [OnSerializing]
    7. virtual protected void OnSerializing(StreamingContext context)
    8. {
    9. Debug.Log("OnSerializing");
    10. }
    11. [OnSerialized]
    12. virtual protected void OnSerialized(StreamingContext context)
    13. {
    14. Debug.Log("OnSerialized");
    15. }
    16. [OnDeserializing]
    17. virtual protected void OnDeserializing(StreamingContext context)
    18. {
    19. Debug.Log("OnDeserializing");
    20. }
    21. [OnDeserialized]
    22. virtual protected void OnDeserialized(StreamingContext context)
    23. {
    24. Debug.Log("OnDeserialized");
    25. }
    26. }

    十: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)的标记

  • 相关阅读:
    2 C++中的引用
    普教建设数字化怎么设施?
    【linux】chmod命令
    路由基础:三层交换机、单臂路由的特点以及配置特点、DHCP报文类型、DHCP工作原理、在路由器上配置DHCP、在交换机上配置DHCP、配置DNS服务器
    linux中配置qt OpenCV的环境
    [附源码]Python计算机毕业设计Django的高校课程知识库
    python 迭代器
    CTFSHOW -SQL 注入
    ble 理论(15)ble 连接详解
    PyTorch的自动求导
  • 原文地址:https://blog.csdn.net/LLLLL__/article/details/126570498