IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 理解Unity中的序列化 -> 正文阅读

[游戏开发]理解Unity中的序列化

【什么是序列化】

序列化是将对象转换为二进制流的过程,反序列化是将二进制流转换为对象的过程。序列化主要解决对象的传输问题。Unity对Unity有自己的序列化机制(方法),但没有开放成API。Unity 的一些内置功能(保存和加载、Inspector 窗口、实例化和预制件)的实现需要使用序列化。

【Unity中可序列化的对象】

  • 该对象必须是非static、非const、非readonly,且为public 或者具有SerializeField属性
  • 可序列化的字段类型
    • 原始数据类型(intfloatdoubleboolstring?等)
    • 枚举类型
    • 具有?Serializable?属性的自定义结构体
    • 某些 Unity 内置类型:Vector2Vector3Vector4RectQuaternionMatrix4x4ColorColor32LayerMaskAnimationCurveGradientRectOffsetGUIStyle
  • 可自定义序列化类,但该类必须具有Serializable属性,且非静态、非抽象、非泛型(可继承自泛型类)
  • 可序列化的容器类型:上述类型的数组、线性表List<T>

【Unity中序列化的例子】

  • Inspector窗口

我们可以在Inspector窗口查看或修改脚本中字段的值;在游戏运行时,我们常常发现在Inspector窗口中字段的值会代替脚本中字段的值;修改这个值我们会立马在Play窗口看到效果;停止游戏时,这个值会变回原来的值,而不是最后修改的值。

能够查看是因为这个字段被序列化了,例如常见的public string name就会自动序列化了,所以我们能在Inspector窗口查看或修改name的值,具体可以看这个例子

常用的属性有SerializeField,HideInInspector,NonSerialized,Serializable

  • SerializeField : 表示字段可被序列化。公有字段可以在Inspector窗口中看到并编辑,而私有和保护字段不行。SerializeField与private,protected结合使用可让脚本的字段在Inspector窗口中可视化编辑,同时保持它的私有性。
  • HideInInspector : 将原本显示在Inspector窗口上的序列化值隐藏起来。
  • NonSerialized:不被序列化且不显示
  • Serializable:用于类前,表示该类可序列化

在Inspector窗口修改字段的值时,Unity 会序列化此数据,然后反序列化在 Inspector 窗口中显示数据。这个数据存储在native层。

unity其实是两层,C++层与unity控制层,因为unity是用c++编写的,但是我们自己编写的脚本是c#,就会有一个交互。当我们点击运行按钮时,先是把所有的序列化数据在内部创建,然后把他们存在c++这一层;然后清除unity控制层这边所有的内存和消息;然后加载我们编写的脚本;最后再把c++层中存储的序列化数据反序列化到unity控制层中去。

在运行时修改字段的值只是更改unity控制层上的数据,游戏运行过程中会读取这个数据,但不会保存在C++层(Native层)。

游戏停止后,会再次反序列化Native层中的数据,显示运行前没更改的那个数值,显示时不会与Unity Scripting API 通信。

  • Prefab

Prefab是一个或多个游戏对象和组件的序列化数据,包含对所有源物体的引用和相应的修改列表。任何派生自UnityEngine.Object的对象都可被实例化,当使用Instantiate实例化一个Prefab时,会先创建一个GameObject,随后根据引用找到源物体,反序列化得到源物体,在根据修改列表修改相应的值,最后得到实例化的物体。

【Unity不支持的对象及序列化的实现】

Unity不能序列化属性、树结构、泛型、字典、高维数组、委托等,最直观的表现是在Inspector窗口中看不到。

Unity不支持空引用。遇到空引用时,Unity会自动构造一个对象来填补这个引用,如果这个引用的类型就是自身,例如在树结构中每个节点为Node类型,还有两个Node类型的子节点,那么会无限循环的构造对象。尽管Unity对这个循环的次数做了限制(7次),但会影响运行时的性能。解决方法是自己创建一个不用的对象。

Unity不支持多态。如果具有一个Animal基类,在某个脚本中有字段public Animal[] animals,随后放入的是继承Animal类的Dog、Cat、Fish类的实例,那么在序列化后得到Animal实例,不能识别出派生类。解决方法是基类要继承UnityEngine.Object或Monobehaviour。

对于高维数组,将其低维化。,即底层采用一维数组来替代。

对于字典,key和value各自存储成List,运行时用字典,序列化时用数组。

对于泛型类,用一个新类将其封装并用?[Serializable]?修饰新类。

对于不带返回值的委托,可以用?UnityEvent?来序列化。

对于更复杂的情况,例如树结构,需实现ISerializationCallbackReceiver。

using UnityEngine;
using System.Collections.Generic;
using System;

//直接序列化将导致性能问题
public class VerySlowBehaviourDoNotDoThis : MonoBehaviour {
    [Serializable]
    public class Node {
        public string interestingValue = "value";
        //下面的字段使序列化数据变得巨大,
        //因为它引入了"类周期"。
        public List<Node> children = new List<Node>();
    }
    //这将经过序列化
    public Node root = new Node();
    void OnGUI() {
        Display (root);
    }
    void Display(Node node) {
        GUILayout.Label ("Value: ");
        node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
        GUILayout.BeginHorizontal ();
        GUILayout.Space (20);
        GUILayout.BeginVertical ();
        foreach (var child in node.children)
        Display (child);
        if (GUILayout.Button ("Add child"))
        node.children.Add (new Node ());
        GUILayout.EndVertical ();
        GUILayout.EndHorizontal ();
    }
}

using System.Collections.Generic;
using System;
//避免直接序列化,先处理成Unity支持的形式
public class BehaviourWithTree : MonoBehaviour, ISerializationCallbackReceiver {
    // 在运行时使用的 Node 类。
    //此类位于 BehaviourWithTree 类的内部,不会被序列化。
    public class Node {
        public string interestingValue ="value";
        public List<Node> children = new List<Node>();
    }
    // 我们将用于序列化的 Node 类。
    [Serializable]
    public struct SerializableNode {
        public string interestingValue;
        public int childCount;
        public int indexOfFirstChild;
    }
    //用于运行时树表示的根节点。不序列化。
    Node root = new Node();
    //这是我们提供给 Unity 进行序列化的字段。
    public List<SerializableNode> serializedNodes;
    public void OnBeforeSerialize() {
        //Unity 即将读取 serializedNodes 字段的内容。
        // 现在必须"及时"将正确的数据写入该字段。
        if (serializedNodes == null) serializedNodes = new List<SerializableNode>();
        if (root == null) root = new Node ();
        serializedNodes.Clear();
        AddNodeToSerializedNodes(root);
        // 现在 Unity 可自由地序列化这个字段,我们应该在稍后反序列化时
        // 找回预期的数据。
    }
    void AddNodeToSerializedNodes(Node n) {
        var serializedNode = new SerializableNode () {
            interestingValue = n.interestingValue,
            childCount = n.children.Count,
            indexOfFirstChild = serializedNodes.Count+1
        }
        ;
        serializedNodes.Add (serializedNode);
        foreach (var child in n.children)
        AddNodeToSerializedNodes (child);
    }
    public void OnAfterDeserialize() {
        //Unity 刚刚将新数据写入 serializedNodes 字段。
        //让我们用这些新值填充我们的实际运行时数据。
        if (serializedNodes.Count > 0) {
            ReadNodeFromSerializedNodes (0, out root);
        } else
        root = new Node ();
    }
    int ReadNodeFromSerializedNodes(int index, out Node node) {
        var serializedNode = serializedNodes [index];
        //将反序列化的数据传输到内部 Node 类
        Node newNode = new Node() {
            interestingValue = serializedNode.interestingValue,
            children = new List<Node> ()
        }
        ;
        // 以深度优先的方式读取树,因为这正是我们写入树的方式。
        for (int i = 0; i != serializedNode.childCount; i++) {
            Node childNode;
            index = ReadNodeFromSerializedNodes (++index, out childNode);
            newNode.children.Add (childNode);
        }
        node = newNode;
        return index;
    }
    // 此 OnGUI 在 Game 视图中绘制出节点树,其中包含用于添加新节点作为子项的按钮。
    void OnGUI() {
        if (root != null)
        Display (root);
    }
    void Display(Node node) {
        GUILayout.Label ("Value: ");
        // 允许修改节点的"有用值"。
        node.interestingValue = GUILayout.TextField(node.interestingValue, GUILayout.Width(200));
        GUILayout.BeginHorizontal ();
        GUILayout.Space (20);
        GUILayout.BeginVertical ();
        foreach (var child in node.children)
        Display (child);
        if (GUILayout.Button ("Add child"))
        node.children.Add (new Node ());
        GUILayout.EndVertical ();
        GUILayout.EndHorizontal ();
    }
}

【Json序列化】

还可以采用Json格式对类进行序列化,使用JsonUtility?类可在 Unity 对象与 JSON格式之间来回转换。基准测试表明,JsonUtility比流行的 .NET JSON 解决方案要快得多。使用方式参考下面的代码。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class test : MonoBehaviour
{
   

    // Start is called before the first frame update
    void Start()
    {
        Data date = new Data();
        date.num = 1;
        date.home = "sdfew";
        date.name = "enternalstar";
        string json = JsonUtility.ToJson(date);//将对象Json序列化
        Data getData = JsonUtility.FromJson<Data>(json);//反序列化再得到对象
        Data newData = new Data();
        newData.name = "saff";
        newData.num = 3;
        newData.home = "safjoi";
        JsonUtility.FromJsonOverwrite(json, newData);//将其他Data对象的数据覆盖到newData上
        
    }

    [Serializable]//自定义一个需要序列化的类
    public class Data
    {
        public int num;
        public string home;
        public string name;
    }
}

【参考】

Unity - Manual: Unity architecture (unity3d.com)

深入Unity序列化 - 知乎 (zhihu.com)

关于序列化Serializable - 简书 (jianshu.com)

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 16:34:29  更:2021-07-15 16:35:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:25:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码