Unity存档系统丨如何正确的序列化,及构建一个较为完善的数据存储系统

哈喽大家好,你的橙哥突然出现~

本系列博客地址:传送门

在这里插入图片描述



上节我们讲了 PlayerPrefs,及如何构建一个基础的数据存储系统,

今天我们来加深一步,让学到的东西可以实际用在项目上。


一、JsonUtility 的用法

首先我们先学习一下后续会用到的API知识。

1、JsonUtility.ToJson("")

示例:

在这里插入图片描述




JsonUtility.ToJson(),接受一个object类的参数(可以是类、可以是string、int等)用来传递需要转换的对象,并且返回一个JSON格式的字符串。


2、JsonUtility.ToJson("" , true)

JsonUtility.ToJson的第二个重载,便是加上true或是false。
不加认为false。

加上true,转换后的Json字符串更符合我们的阅读习惯的。

下面是加和不加的对比:

1️⃣不加true,转换后的字符串

    [Serializable]
    class MyData
    {
        public string name;
        public int level;
    }

    private void Start()
    {
        MyData myData = new MyData();
        myData.name = "skode";
        myData.level = 1;

        print(JsonUtility.ToJson(myData));
    }

在这里插入图片描述



2️⃣加true,转换后的字符串

    [Serializable]
    class MyData
    {
        public string name;
        public int level;
    }

    private void Start()
    {
        MyData myData = new MyData();
        myData.name = "skode";
        myData.level = 1;

        print(JsonUtility.ToJson(myData,true));
    }

在这里插入图片描述



3、JsonUtility.FromJson()

它的作用和ToJson()相反,
是将JSON格式的字符串转换成Unity序列化程序支持的对象。

使用案例:

    [Serializable]
    class MyData
    {
        [NonSerialized] public string name;
        public int level;
    }

    private void Start()
    {
        MyData myData = new MyData();
        myData.name = "skode";
        myData.level = 1;

        string jsonValue = JsonUtility.ToJson(myData);
        MyData jsonClass = JsonUtility.FromJson<MyData>(jsonValue); //将json字符串转化为类。
    }



4、JsonUtility.FromJsonOverwrite( “” , object )

它接受一个JSON格式的字符串,以及一个object类型数据。
它的用法和FromJson()类似。

不同之处在于当数据转换完成之后,FromJsonOverwrite原有的数据会被转换后的数据所覆写,不像FromJson一样,需要自己取用里面的数值。

如果对象的某个字段没有 JSON 表示形式,则该字段保持不变。


使用场景:
比如一款游戏有多个存档,例如《动物之星》存档,可以存很多档。
那当我们还原某个档时,就需要这个函数了。

直接读取该档的json,解析后直接覆盖掉程序的值,不需要像JsonUtility.FromJson一样,得到数据后还需要挨个去赋值,费时费力。

在这里插入图片描述



二、什么样的类可以被转成JSON

如果之前就用过Json的同学,可能发现了一个事情。

就有的时候,想把一个类转成Json,可转换后的结果,只有一个空的大括号,里面什么也没有。

那这是为什么呢?

在这里插入图片描述



1、为什么有时候类无法转换成Json

简单一句话就是:

可以在Inspector面板显示属性,就能转换成Json。

如果类不能,就加上[Serializable],完成序列化。
只要能显示,就能转化。

同时值得注意的是:

ToJson()函数会尝试序列化传入数据中可序列化数据,而不是序列化整个传入的数据。

意思就是,传入的类,如果这个类中有数据标记了[NonSerialized],或者private、const等特性,这些特性是不能在层级面板显示出来的,就不能被序列化。


2、哪些类型的对象支持Unity序列化?

1️⃣、可被序列化的字段

在字段中,只有公有字段,以及添加了序列化字段特性的字段才支持Unity序列化。

在这里插入图片描述



2️⃣、可被序列化的类

继承自Mono的类也可被序列化。
这就意味着这些类,不需要加[Serializable]特性,使用时传入this、或者它的实例即可。

在这里插入图片描述



3️⃣、其他不能被序列化的类型

这里还要注意一下,一些类型的数据也是不支持Unity序列化的。比如:

  • 字典
  • 多维数组

即使加了[Serializable]也没用。在层级面板依旧显示不出来。


那么如何存储这些不支持序列化的数据呢?

  • 那我们字典可以拆开分成两个部分:键的列表以及值的列表。
  • 多维数组的话,则拆开成相应的多个一维数组。分别进行存储就行了。



三、构建一个基于JSON的存档系统

最后我们来构建一个基于JSON的存档系统。

1、编写存档工具脚本

using System;
using System.IO;
using UnityEngine;

public static class SaveSystem
{
    /// <summary>
    /// 存档
    /// </summary>
    /// <param name="saveFileName">文件名,后缀可以是任何字符。</param>
    /// <param name="data">保存了数据的类、int、string等数据</param>
    public static void SaveByJson(string saveFileName, object data)
    {
        var json = JsonUtility.ToJson(data);

        //Application.persistentDataPath,支持全平台,提供一个存储永久数据的路径。
        //路径随发布平台的不同而变更。
        var path = Path.Combine(Application.persistentDataPath, saveFileName);

        try
        {
            //將string写入到saveFileName文件
            //未存在,便创建。已存在,便覆写
            File.WriteallText(path, json);
            Debug.Log("Save Success" + path);
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }

    /// <summary>
    /// 读档
    /// </summary>
    /// <param name="saveFileName">保存的Json文件名全称</param>
    /// <returns></returns>
    public static T LoadFromJson<T>(string saveFileName)
    {
        var path = Path.Combine(Application.persistentDataPath, saveFileName);

        try
        {
            var json = File.ReadAllText(path);
            var data = JsonUtility.FromJson<T>(json);

            return data;
        }
        catch (Exception e)
        {
            Debug.LogError(e);
            return default;
        }
    }

    /// <summary>
    /// 删除存档
    /// </summary>
    /// <param name="saveFileName"></param>
    public static void DeleteSaveFile(string saveFileName)
    {
        var path = Path.Combine(Application.persistentDataPath, saveFileName);

        try
        {
            File.Delete(path);
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }
}



2、使用存档工具的示例

按下A键,进行存档。按下B键,进行读档。

using System;
using UnityEngine;

public class SaveTest : MonoBehavIoUr
{
    [Serializable]
    class MyData
    {
        public string name;
        public int level;
    }

    private void Update()
    {
        //存档示例
        if (Input.GetKeyDown(KeyCode.A))
        {
            MyData myData = new MyData();
            myData.name = "skode";
            myData.level = 1;

            SaveSystem.SaveByJson("我的存档.skode", myData);
        }

        //读档示例
        if (Input.GetKeyDown(KeyCode.B))
        {
            MyData value = SaveSystem.LoadFromJson<MyData>("我的存档.skode");
            print(value.name);
            print(value.level);
        }
    }
}

在这里插入图片描述


在这里插入图片描述



3、小提示

或许我们要用到删除存档的功能,但每次去掉方法挺麻烦的。

Unity也提供了删除所有PlayPrefs的功能
Edit - Clear All PlayPrefs,便可清除所有PlayPrefs。




好啦,这就是本章所有内容

后续我会出关于Unity存档系统商业化的教程,欢迎关注!

在这里插入图片描述





如果你有 技术的问题 项目开发

都可以加下方联系方式

和我聊一聊你的故事

相关文章

实现Unity AssetBundle资源加载管理器 AssetBundle是实现资源...
Unity3D 使用LineRenderer绘制尾迹与虚线 1.添加LineRendere...
Unity 添加新建Lua脚本选项 最近学习Unity的XLua热更新框架的...
挂载脚本时文件名和类名的关联方式 写过Unity脚本的人应该都...
Unity单例基类的实现方式 游戏开发的过程中我们经常会将各种...
这篇文章主要介绍了Unity游戏开发中外观模式是什么意思,具有...