【unity框架开发12】从零手搓unity存档存储数据持久化系统,实现对存档的创建,获取,保存,加载,删除,缓存,加密,支持多存档

news2024/11/24 2:19:01

文章目录

  • 前言
  • 一、Unity对Json数据的操作
    • 方法一、JsonUtility
    • 方法二、Newtonsoft
  • 二、持久化的数据路径
  • 三、数据加密/解密
    • 加密方法
    • 解密方法
  • 四、条件编译指令限制仅在编辑器模式下进行加密/解密
  • 四、数据持久化管理器
    • 1、存档工具类
    • 2、一个存档数据
    • 3、存档系统数据类
    • 4、数据存档存储持久化管理器
    • 5、测试调用
  • 完结

前言

游戏存档不言而喻,是游戏设计中的重要元素,可以提高游戏的可玩性,为玩家提供更多的自由和控制权。

我们主要是实现对存档的创建,获取,保存,加载,删除,缓存,加密,支持多存档。存档有两类,一类是用户型存档,存储着某个游戏用户具体的信息,如血量,武器,游戏进度,一类是设置型存档,与任何用户存档都无关,是通用的存储信息,比如屏幕分辨率、音量设置等。

存档文件支持很多数据类似,这里我选择Json,可读性较强,易修改。

一、Unity对Json数据的操作

方法一、JsonUtility

它是Unty引擎中提供的一个用于序列化和反序列化JSON数据的工具类,该类提供了三个方法。

第一个函数ToJson可以将可序列化的对象转换为JSON字符串
第二个可选参数表示是否花费更多的性能将字符转换为更适合阅读的方式

//通过JS0N表示形式创建对象
string json = JsonUtility.ToJson(object obj, bool prettyPrint);

第二个函数FromJson可以将字符转换为对象
Playerlnfo是一个泛型参数,用于指定要将JSoN字符串转换为的对象类型

//生成对象的公共字段的JS0N表示形式
PlayerInfo playerInfo = JsonUtility.FromJson<PlayerInfo>(string json);

第三个函数则可以传入新的数据覆盖到已有对象中,而不是创建新的对象,在频繁读取数据时可以节省开销

//通过读取对象的JS0N表示形式覆盖其数据
JsonUtility.FromJsonOverwrite(string json, object objectTooverwrite);

方法二、Newtonsoft

前面使用JsonUtility进行json转化,遇到字典等无法序列化的数据是存不进去的,这里推荐使用Newtonsoft类库进行转化,可以很好解决这个问题。

安装插件支持
在这里插入图片描述

转换用户数据为JSON字符串

string jsonData = JsonConvert.SerializeObject(playerInfo);

将JSON字符串转换为unity对象数据

PlayerInfo playerInfo = JsonConvert.DeserializeObject<PlayerInfo>(jsonData);

二、持久化的数据路径

Application.persistentDataPath是一个持久化的数据路径,在不同的操作系统上,unity会为我们分配不同的持久化数据路径,这样可以确保应用程序在不同平台上都能正确保存和访问

//设置存档文件路径
savePath = Path.Combine(Application.persistentDataPath, "saveData.json");

不同平台存储的路径不一样,我们可以打印savePath查看自己的存储路径

比如我的就是
在这里插入图片描述

三、数据加密/解密

这里实现一个简单的加密和解密方法,使用了字符的异或(XOR)操作。

// 定义密钥字符
public static char[] keyChars = { 'a', 'b', 'c', 'd', 'e' };

// 加密方法
public static string Encrypt(string data)
{
    char[] dataChars = data.ToCharArray();
    for (int i = 0; i < dataChars.Length; i++)
    {
        char dataChar = dataChars[i];
        char keyChar = keyChars[i % keyChars.Length];
        // 重点: 通过亦或得到新的字符
        char newChar = (char)(dataChar ^ keyChar);
        dataChars[i] = newChar;
    }
    return new string(dataChars);
}

// 解密方法
public static string Decrypt(string data)
{
    return Encrypt(data);
}

加密方法

  • Encrypt 方法接收一个字符串 data 作为输入。
  • 将输入字符串转换为字符数组 dataChars。
  • 使用一个 for 循环遍历每个字符:
    • dataChar 是当前字符。
    • keyChar 是从密钥字符数组中获取的字符,使用 i % keyChars.Length 确保循环使用密钥字符,即如果数据长度超过密钥长度,会重复使用密钥。
    • newChar 是通过 dataChar 和 keyChar 的异或操作得到的新字符。异或操作的特点是相同的字符异或结果为0,而不同的字符异或结果为1,因此可以用这个特性进行简单的加密。
      最后将加密后的字符数组重新转换为字符串并返回。

解密方法

  • Decrypt 方法实际上调用了 Encrypt 方法。由于异或操作的特性,调用两次相同的异或操作可以恢复原始数据。因此,加密和解密过程是相同的。

四、条件编译指令限制仅在编辑器模式下进行加密/解密

为了保证数据的可读性,我们可能不希望在编辑器模式下,数据还被加密了,这样会影响我们测试

我们可以使用#if UNITY_EDITOR 条件编译指令,用于在 Unity 中编写代码时进行特定的编译和执行控制。这段代码只会在 Unity 编辑器中编译和执行,而不会在构建后的游戏中包含。这意味着你可以在编辑器环境中添加一些特定的功能,比如自定义工具、调试信息等,而这些功能在最终发布的版本中不会出现。

示例用法

#if !UNITY_EDITOR

// 放置加密/解密操作,这段代码不会在 Unity 编辑器中编译和执行

#endif

四、数据持久化管理器

1、存档工具类

/// <summary>
/// 存档工具类
/// </summary>
public static class IOTool
{
    // 定义密钥字符
    public static char[] keyChars = { 'x', 'y', 'f', 'r', 'a', 'm', 'e' };

    /// <summary>
    /// 保存Json数据
    /// </summary>
    /// <param name="saveObject">保存的对象</param>
    /// <param name="path">路径</param>
    public static void SaveJson(object saveObject, string path)
    {
        // string jsonData = JsonUtility.ToJson(saveObject);
        string jsonData = JsonConvert.SerializeObject(saveObject);
#if !UNITY_EDITOR
        jsonData = Encrypt(jsonData);//加密
#endif
        File.WriteAllText(path, jsonData);
    }

    /// <summary>
    /// 读取Json为指定的类型对象
    /// </summary>
    public static T LoadJson<T>(string path) where T : class
    {
        if (!File.Exists(path))
        {
            return null;
        }
        string jsonData = File.ReadAllText(path);
#if !UNITY_EDITOR
        jsonData = Decrypt(jsonData);//解密
#endif
        // return JsonUtility.FromJson<T>(jsonData);
        return JsonConvert.DeserializeObject<T>(jsonData);
    }

    /// <summary>
    /// 加密方法
    /// </summary>
    public static string Encrypt(string data)
    {
        char[] dataChars = data.ToCharArray();
        for (int i = 0; i < dataChars.Length; i++)
        {
            char dataChar = dataChars[i];
            char keyChar = keyChars[i % keyChars.Length];
            // 重点: 通过亦或得到新的字符
            char newChar = (char)(dataChar ^ keyChar);
            dataChars[i] = newChar;
        }
        return new string(dataChars);
    }

    /// <summary>
    /// 解密方法
    /// </summary>
    public static string Decrypt(string data)
    {
        return Encrypt(data);
    }
}

2、一个存档数据

/// <summary>
/// 一个存档数据
/// </summary>
[Serializable]
public class SaveItem
{
     // 存档ID
    public int saveID;

    // 私有字段,最后保存时间
    private DateTime lastSaveTime;

    // 公共属性,获取最后保存时间
    public DateTime LastSaveTime
    {
        get
        {
            // 如果 lastSaveTime 是默认值,则尝试将字符串解析为 DateTime
            if (lastSaveTime == default(DateTime))
            {
                DateTime.TryParse(lastSaveTimeString, out lastSaveTime);
            }
            return lastSaveTime; // 返回最后保存时间
        }
    }

    // 用于持久化的字符串,Json不支持 DateTime 类型
    [SerializeField] private string lastSaveTimeString;

    // 构造函数,初始化存档ID和最后保存时间
    public SaveItem(int saveID, DateTime lastSaveTime)
    {
        this.saveID = saveID; // 设置存档ID
        this.lastSaveTime = lastSaveTime; // 设置最后保存时间
        lastSaveTimeString = lastSaveTime.ToString(); // 将 DateTime 转换为字符串
    }

    // 更新最后保存时间
    public void UpdateTime(DateTime lastSaveTime)
    {
        this.lastSaveTime = lastSaveTime; // 更新最后保存时间
        lastSaveTimeString = lastSaveTime.ToString(); // 将新的 DateTime 转换为字符串
    }
}

3、存档系统数据类

/// <summary>
/// 存档系统数据类
/// </summary>
[Serializable]
public class SaveSystemData
{
    // 当前的存档ID
    public int currID = 0;
    // 所有存档的列表
    public List<SaveItem> saveItemList = new List<SaveItem>();
}

4、数据存档存储持久化管理器

/// <summary>
/// 数据存档存储持久化管理器
/// </summary>
public class SaveManager : Singleton<SaveManager>
{
    private SaveSystemData saveSystemData;//存档系统数据

    // 存档的保存
    private const string saveDirName = "saveData";
    // 设置的保存:1.全局数据的保存(分辨率、按键设置) 2.存档的设置保存。
    // 常规情况下,存档系统自行维护
    private const string settingDirName = "setting";

    // 存档文件夹路径
    private string saveDirPath = Application.persistentDataPath + "/" + saveDirName;
    private string settingDirPath = Application.persistentDataPath + "/" + settingDirName;

    // 存档中对象的缓存字典 
    // <存档ID,<文件名称,实际的对象>>
    private Dictionary<int, Dictionary<string, object>> cacheDic = new Dictionary<int, Dictionary<string, object>>();

    #region 初始化
    public SaveManager()
    {
        Debug.Log("存储管理器初始化成功");
        Init();
    }
    public void Init()
    {
        //检查路径并创建目录
        CheckAndCreateDir();
        // 初始化SaveSystemData
        InitSaveSystemData();
        // 清除存档缓存
        CleanCache();
    }
    #endregion

    #region 保存、获取全局设置存档
    /// <summary>
    /// 加载设置,全局生效,不关乎任何一个存档
    /// </summary>
    public T LoadSetting<T>(string fileName) where T : class
    {
        return LoadFile<T>(settingDirPath + "/" + fileName);
    }
    /// <summary>
    /// 加载设置,全局生效,不关乎任何一个存档
    /// </summary>
    public T LoadSetting<T>() where T : class
    {
        return LoadSetting<T>(typeof(T).Name);
    }
    /// <summary>
    /// 保存设置,全局生效,不关乎任何一个存档
    /// </summary>
    public void SaveSetting(object saveObject, string fileName)
    {
        SaveFile(saveObject, settingDirPath + "/" + fileName);
    }

    /// <summary>
    /// 保存设置,全局生效,不关乎任何一个存档
    /// </summary>
    public void SaveSetting(object saveObject)
    {
        SaveSetting(saveObject, saveObject.GetType().Name);
    }

    /// <summary>
    /// 删除所有设置存档
    /// </summary>
    public void DeleteAllSetting()
    {
        if (Directory.Exists(settingDirPath))
        {
            // 直接删除目录
            Directory.Delete(settingDirPath, true);
        }
        CheckAndCreateDir();
    }
    #endregion

     #region 创建、获取、删除某一项用户存档
    /// <summary>
    /// 获取SaveItem
    /// </summary>
    public SaveItem GetSaveItem(int id)
    {
        for (int i = 0; i < saveSystemData.saveItemList.Count; i++)
        {
            if (saveSystemData.saveItemList[i].saveID == id)
            {
                return saveSystemData.saveItemList[i];
            }
        }
        return null;
    }
    /// <summary>
    /// 获取SaveItem
    /// </summary>
    public SaveItem GetSaveItem(SaveItem saveItem)
    {
        GetSaveItem(saveItem.saveID);
        return null;
    }

    /// <summary>
    /// 添加一个存档
    /// </summary>
    /// <returns></returns>
    public SaveItem CreateSaveItem()
    {
        SaveItem saveItem = new SaveItem(saveSystemData.currID, DateTime.Now);
        saveSystemData.saveItemList.Add(saveItem);
        saveSystemData.currID += 1;
        // 更新SaveSystemData 写入磁盘
        UpdateSaveSystemData();
        return saveItem;
    }

    /// <summary>
    /// 删除存档
    /// </summary>
    /// <param name="saveID">存档的ID</param>
    public void DeleteSaveItem(int saveID)
    {
        string itemDir = GetSavePath(saveID, false);
        // 如果路径存在 且 有效
        if (itemDir != null)
        {
            // 把这个存档下的文件递归删除
            Directory.Delete(itemDir, true);
        }
        saveSystemData.saveItemList.Remove(GetSaveItem(saveID));
        // 移除缓存
        RemoveCache(saveID);
        // 更新SaveSystemData 写入磁盘
        UpdateSaveSystemData();
    }
    /// <summary>
    /// 删除存档
    /// </summary>
    public void DeleteSaveItem(SaveItem saveItem)
    {
        DeleteSaveItem(saveItem.saveID);
    }
    #endregion

    #region 保存、获取、删除用户存档中某一对象
    /// <summary>
    /// 保存对象至某个存档中
    /// </summary>
    /// <param name="saveObject">要保存的对象</param>
    /// <param name="saveFileName">保存的文件名称</param>
    /// <param name="saveID">存档的ID</param>
    public void SaveObject(object saveObject, string saveFileName, int saveID = 0)
    {
        // 存档所在的文件夹路径
        string dirPath = GetSavePath(saveID, true);
        // 具体的对象要保存的路径
        string savePath = dirPath + "/" + saveFileName;
        // 具体的保存
        SaveFile(saveObject, savePath);
        // 更新存档时间
        GetSaveItem(saveID).UpdateTime(DateTime.Now);
        // 更新SaveSystemData 写入磁盘
        UpdateSaveSystemData();

        // 更新缓存
        SetCache(saveID, saveFileName, saveObject);

    }

    /// <summary>
    /// 保存对象至某个存档中
    /// </summary>
    /// <param name="saveObject">要保存的对象</param>
    /// <param name="saveFileName">保存的文件名称</param>
    public void SaveObject(object saveObject, string saveFileName, SaveItem saveItem)
    {
        SaveObject(saveObject, saveFileName, saveItem.saveID);
    }
    /// <summary>
    /// 保存对象至某个存档中
    /// </summary>
    /// <param name="saveObject">要保存的对象</param>
    /// <param name="saveID">存档的ID</param>
    public void SaveObject(object saveObject, int saveID = 0)
    {
        SaveObject(saveObject, saveObject.GetType().Name, saveID);
    }
    /// <summary>
    /// 保存对象至某个存档中
    /// </summary>
    /// <param name="saveObject">要保存的对象</param>
    /// <param name="saveID">存档的ID</param>
    public void SaveObject(object saveObject, SaveItem saveItem)
    {
        SaveObject(saveObject, saveObject.GetType().Name, saveItem);
    }

    /// <summary>
    /// 从某个具体的存档中加载某个对象
    /// </summary>
    /// <typeparam name="T">要返回的实际类型</typeparam>
    /// <param name="saveFileName">文件名称</param>
    /// <param name="id">存档ID</param>
    public T LoadObject<T>(string saveFileName, int saveID = 0) where T : class
    {
        T obj = GetCache<T>(saveID, saveFileName);
        if (obj == null)
        {
            // 存档所在的文件夹路径
            string dirPath = GetSavePath(saveID);
            if (dirPath == null) return null;
            // 具体的对象要保存的路径
            string savePath = dirPath + "/" + saveFileName;
            obj = LoadFile<T>(savePath);
            SetCache(saveID, saveFileName, obj);
        }
        return obj;
    }

    /// <summary>
    /// 从某个具体的存档中加载某个对象
    /// </summary>
    /// <typeparam name="T">要返回的实际类型</typeparam>
    /// <param name="saveFileName">文件名称</param>
    public T LoadObject<T>(string saveFileName, SaveItem saveItem) where T : class
    {
        return LoadObject<T>(saveFileName, saveItem.saveID);
    }


    /// <summary>
    /// 从某个具体的存档中加载某个对象
    /// </summary>
    /// <typeparam name="T">要返回的实际类型</typeparam>
    /// <param name="id">存档ID</param>
    public T LoadObject<T>(int saveID = 0) where T : class
    {
        return LoadObject<T>(typeof(T).Name, saveID);
    }

    /// <summary>
    /// 从某个具体的存档中加载某个对象
    /// </summary>
    /// <typeparam name="T">要返回的实际类型</typeparam>
    /// <param name="saveItem">存档项</param>
    public T LoadObject<T>(SaveItem saveItem) where T : class
    {
        return LoadObject<T>(typeof(T).Name, saveItem.saveID);
    }

    /// <summary>
    /// 删除某个存档中的某个对象
    /// </summary>
    /// <param name="saveID">存档的ID</param>
    public void DeleteObject<T>(string saveFileName, int saveID) where T : class
    {
        //清空缓存中对象
        if (GetCache<T>(saveID, saveFileName) != null)
        {
            RemoveCache(saveID, saveFileName);
        }
        // 存档对象所在的文件路径
        string dirPath = GetSavePath(saveID);
        string savePath = dirPath + "/" + saveFileName;
        //删除对应的文件
        File.Delete(savePath);

    }

    /// <summary>
    /// 删除某个存档中的某个对象
    /// </summary>
    /// <param name="saveID">存档的ID</param>
    public void DeleteObject<T>(string saveFileName, SaveItem saveItem) where T : class
    {
        DeleteObject<T>(saveFileName, saveItem.saveID);
    }

    /// <summary>
    /// 删除某个存档中的某个对象
    /// </summary>
    /// <param name="saveID">存档的ID</param>
    public void DeleteObject<T>(int saveID) where T : class
    {
        DeleteObject<T>(typeof(T).Name, saveID);
    }

    /// <summary>
    /// 删除某个存档中的某个对象
    /// </summary>
    /// <param name="saveID">存档的ID</param>
    public void DeleteObject<T>(SaveItem saveItem) where T : class
    {
        DeleteObject<T>(typeof(T).Name, saveItem.saveID);
    }
    #endregion

    #region 获取、删除所有用户存档

    /// <summary>
    /// 获取所有存档
    /// 最新的在最后面
    /// </summary>
    /// <returns></returns>
    public List<SaveItem> GetAllSaveItem()
    {
        return saveSystemData.saveItemList;
    }

    /// <summary>
    /// 获取所有存档
    /// 创建最新的在最前面
    /// </summary>
    /// <returns></returns>
    public List<SaveItem> GetAllSaveItemByCreatTime()
    {
        List<SaveItem> saveItems = new List<SaveItem>(saveSystemData.saveItemList.Count);

        for (int i = 0; i < saveSystemData.saveItemList.Count; i++)
        {
            saveItems.Add(saveSystemData.saveItemList[saveSystemData.saveItemList.Count - (i + 1)]);
        }
        return saveItems;
    }

    /// <summary>
    /// 获取所有存档
    /// 最新更新的在最上面
    /// </summary>
    /// <returns></returns>
    public List<SaveItem> GetAllSaveItemByUpdateTime()
    {
        List<SaveItem> saveItems = new List<SaveItem>(saveSystemData.saveItemList.Count);
        for (int i = 0; i < saveSystemData.saveItemList.Count; i++)
        {
            saveItems.Add(saveSystemData.saveItemList[i]);
        }
        OrderByUpdateTimeComparer orderBy = new OrderByUpdateTimeComparer();
        saveItems.Sort(orderBy);
        return saveItems;
    }

    private class OrderByUpdateTimeComparer : IComparer<SaveItem>
    {
        public int Compare(SaveItem x, SaveItem y)
        {
            if (x.LastSaveTime > y.LastSaveTime)
            {
                return -1;
            }
            else
            {
                return 1;
            }
        }
    }

    /// <summary>
    /// 获取所有存档
    /// 万能解决方案
    /// </summary>
    public List<SaveItem> GetAllSaveItem<T>(Func<SaveItem, T> orderFunc, bool isDescending = false)
    {
        if (isDescending)
        {
            return saveSystemData.saveItemList.OrderByDescending(orderFunc).ToList();
        }
        else
        {
            return saveSystemData.saveItemList.OrderBy(orderFunc).ToList();
        }

    }

    /// <summary>
    /// 删除所有用户存档信息
    /// </summary>
    public void DeleteAllSaveItem()
    {
        if (Directory.Exists(saveDirPath))
        {            
            // 直接删除目录
            Directory.Delete(saveDirPath, true);
        }
        CheckAndCreateDir();
        InitSaveSystemData();
    }

    /// <summary>
    /// 删除所有存档信息
    /// </summary>
    public void DeleteAll()
    {
        CleanCache();
        DeleteAllSaveItem();
        DeleteAllSetting();
    }
    #endregion

    #region 更新、获取、删除用户存档缓存
    /// <summary>
    /// 设置缓存
    /// </summary>
    /// <param name="saveID">存档ID</param>
    /// <param name="fileName">文件名称</param>
    /// <param name="saveObject">要缓存的对象</param>
    private void SetCache(int saveID, string fileName, object saveObject)
    {
        // 缓存字典中是否有这个SaveID
        if (cacheDic.ContainsKey(saveID))
        {
            // 这个存档中有没有这个文件
            if (cacheDic[saveID].ContainsKey(fileName))
            {
                cacheDic[saveID][fileName] = saveObject;
            }
            else
            {
                cacheDic[saveID].Add(fileName, saveObject);
            }
        }
        else
        {
            cacheDic.Add(saveID, new Dictionary<string, object>() { { fileName, saveObject } });
        }
    }

    /// <summary>
    /// 获取缓存
    /// </summary>
    /// <param name="saveID">存档ID</param>
    /// <param name="saveObject">要缓存的对象</param>
    private T GetCache<T>(int saveID, string fileName) where T : class
    {
        // 缓存字典中是否有这个SaveID
        if (cacheDic.ContainsKey(saveID))
        {
            // 这个存档中有没有这个文件
            if (cacheDic[saveID].ContainsKey(fileName))
            {
                return cacheDic[saveID][fileName] as T;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    /// <summary>
    /// 移除缓存
    /// </summary>
    private void RemoveCache(int saveID)
    {
        cacheDic.Remove(saveID);
    }

    /// <summary>
    /// 移除缓存中的某一个对象
    /// </summary>
    private void RemoveCache(int saveID, string fileName)
    {
        cacheDic[saveID].Remove(fileName);
    }

    /// <summary>
    /// 清除存档缓存
    /// </summary>
    public void CleanCache()
    {
        cacheDic.Clear();
    }
    #endregion

    #region 内部工具函数
    /// <summary>
    /// 获取存档系统数据
    /// </summary>
    /// <returns></returns>
    private void InitSaveSystemData()
    {
        saveSystemData = LoadFile<SaveSystemData>(saveDirPath + "/SaveSystemData");
        if (saveSystemData == null)
        {
            saveSystemData = new SaveSystemData();
            UpdateSaveSystemData();
        }
    }

    /// <summary>
    /// 更新存档系统数据
    /// </summary>
    private void UpdateSaveSystemData()
    {
        SaveFile(saveSystemData, saveDirPath + "/SaveSystemData");
    }

    /// <summary>
    /// 检查路径并创建目录
    /// </summary>
    private void CheckAndCreateDir()
    {
        // 确保路径的存在
        if (Directory.Exists(saveDirPath) == false)
        {
            Directory.CreateDirectory(saveDirPath);
        }
        if (Directory.Exists(settingDirPath) == false)
        {
            Directory.CreateDirectory(settingDirPath);
        }
    }

    /// <summary>
    /// 获取某个存档的路径
    /// </summary>
    /// <param name="saveID">存档ID</param>
    /// <param name="createDir">如果不存在这个路径,是否需要创建</param>
    /// <returns></returns>
    private string GetSavePath(int saveID, bool createDir = true)
    {
        // 验证是否有某个存档
        if (GetSaveItem(saveID) == null) Debug.LogWarning("saveID 存档不存在!");

        string saveDir = saveDirPath + "/" + saveID;
        // 确定文件夹是否存在
        if (Directory.Exists(saveDir) == false)
        {
            if (createDir)
            {
                Directory.CreateDirectory(saveDir);
            }
            else
            {
                return null;
            }
        }

        return saveDir;
    }

    /// <summary>
    /// 保存文件
    /// </summary>
    /// <param name="saveObject">保存的对象</param>
    /// <param name="path">保存的路径</param>
    private void SaveFile(object saveObject, string path)
    {
        IOTool.SaveJson(saveObject, path);
    }

    /// <summary>
    /// 加载文件
    /// </summary>
    /// <typeparam name="T">加载后要转为的类型</typeparam>
    /// <param name="path">加载路径</param>
    private T LoadFile<T>(string path) where T : class
    {
        return IOTool.LoadJson<T>(path);
    }
    #endregion
}

5、测试调用

// GameSetting类中存储着游戏名称,作为全局数据
[Serializable]
public class GameSetting
{
    public string gameName;
}

[Serializable]
public class GameSetting2
{
    public string gameName;
}

public class SaveTest : MonoBehaviour {
    GameSetting gameSetting;
    GameSetting2 gameSetting2;
    SaveItem saveItem;
    Dictionary<string, string> info = new Dictionary<string,string>();
    private void Awake() {
        //添加测试数据
        gameSetting = new GameSetting();
        gameSetting.gameName = "测试";

        gameSetting2 = new GameSetting2();
        gameSetting2.gameName = "测试2"; 
    }

    private void OnGUI()
    {
        GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);   
        buttonStyle.fontSize = 25; // 设置字体大小
        int width = 400;
        int height = 150;

        //设置型存档 
        if (GUI.Button(new Rect(0, 0, width, height), "保存设置数据1", buttonStyle))
        {
            SaveManager.Instance.SaveSetting(gameSetting);
        }
        if (GUI.Button(new Rect(0, height, width, height), "追加设置数据2", buttonStyle))
        {
            SaveManager.Instance.SaveSetting(gameSetting2);
        }
        if (GUI.Button(new Rect(0, height*2, width, height), "加载设置数据1", buttonStyle))
        {
            string gameName = SaveManager.Instance.LoadSetting<GameSetting>().gameName;
            Debug.Log("gameName: " + gameName);
        }
        if (GUI.Button(new Rect(0, height*3, width, height), "删除设置存档", buttonStyle))
        {
            SaveManager.Instance.DeleteAllSetting();
        }

        //用户存档
        if (GUI.Button(new Rect(width, 0, width, height), "添加一个存档", buttonStyle))
        {
            saveItem = SaveManager.Instance.CreateSaveItem();
        }
        if (GUI.Button(new Rect(width, height, width, height), "保存用户存档数据1", buttonStyle))
        {
            info["name"] = "小明";
            info["age"] = "15";
            info["heright"] = "180";
            SaveManager.Instance.SaveObject(info, saveItem);
        }
        if (GUI.Button(new Rect(width, height*2, width, height), "追加数据2", buttonStyle))
        {
            SaveManager.Instance.SaveObject(gameSetting, saveItem);
        }
        if (GUI.Button(new Rect(width, height*3, width, height), "打印用户存档数据1", buttonStyle))
        {
            Dictionary<string, string> info = SaveManager.Instance.LoadObject<Dictionary<string, string>>(saveItem);
            Debug.Log("姓名:" + info["name"] + ",年龄:" + info["age"] + ",身高:" + info["heright"]);
        }
        if (GUI.Button(new Rect(width, height*4, width, height), "获取所有用户存档", buttonStyle))
        {
            SaveManager.Instance.GetAllSaveItem();//最新的在最后面
            SaveManager.Instance.GetAllSaveItemByCreatTime();//最近创建的在最前面
            SaveManager.Instance.GetAllSaveItem();//最近更新的在最前面
        }

        //删除用户存档
        if (GUI.Button(new Rect(width*2, 0, width, height), "删除用户存档数据1", buttonStyle))
        {
            SaveManager.Instance.DeleteObject<Dictionary<string, string>>(saveItem);
        }

        if (GUI.Button(new Rect(width*2, height, width, height), "删除所有用户存档", buttonStyle))
        {
            SaveManager.Instance.DeleteAllSaveItem();
        }
        if (GUI.Button(new Rect(width*2, height*2, width, height), "删除某一个用户存档", buttonStyle))
        {
            SaveManager.Instance.DeleteSaveItem(saveItem);
        }

        //删除所有存档 用户+设置
        if (GUI.Button(new Rect(width*3, 0, width, height), "删除所有存档", buttonStyle))
        {
            SaveManager.Instance.DeleteAll();
        }
    }
}

运行效果
在这里插入图片描述

SaveData和setting分别存储用户存档和设置型存档。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用户存档下根据saveID分成若干文件夹用于存储具体的对象。

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2212665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Photoshop——肤色变白——曲线】

1. 三通道曲线原理 在使用RGB曲线调整肤色时&#xff0c;你可以通过调整红、绿、蓝三个通道的曲线来实现黄皮肤到白皮肤的转变。 黄皮肤通常含有较多的红色和黄色。通过减少这些颜色的量&#xff0c;可以使肤色看起来更白。 具体步骤如下&#xff1a; 打开图像并创建曲线调…

几何完备的3D分子生成/优化扩散模型 GCDM-SBDD - 评测

GCDM 是一个新的 3D 分子生成扩散模型&#xff0c;与之前的 EDM 相比&#xff0c;GCDM 优化了其中的图神神经网络部分&#xff0c;使用手性敏感的 SE3 等变神经网络 GCPNET 代替了 EDM 中的 EGNN&#xff0c;让节点间消息传递、聚合根据手性不同而进行。本文对 GCDM-SBDD&#…

DMN决策引擎入门知识点

本文主要讲解Camunda是如何使用Dmn决策引擎&#xff0c;体验地址:www.jeecgflow.com Dmn决策表定义 Dmn在线设计 命中策略(Hit Policy) 策略名称策略描述Unique只有一个或者没有规则可以满足。决策表的结果包含满足规则的输出条目。如果超过一个规则满足&#xff0c;那么就违…

电脑知识:适用于 Windows 10 的 PDF 编辑器列表

PDF 是一种流行的、多功能且安全的文件格式&#xff0c;用于在线共享文档。但是&#xff0c;如果没有合适的应用程序&#xff0c;查看和编辑 PDF 文件可能会变得复杂。 幸运的是&#xff0c;有很多 PDF 编辑器可以帮助您更正重要文档上的错误、填写表格、为合同添加签名、更改…

【个人成长】编程小白如何成为大神?

1. 选择适合自己的编程语言 作为新手&#xff0c;选择一门适合自己的编程语言至关重要。不同的编程语言有不同的应用领域和特点。以下是几种适合初学者的编程语言&#xff1a; Python&#xff1a;广泛应用于数据科学、人工智能、自动化脚本和Web开发等领域。它语法简洁易懂&a…

Faker:自动化测试数据生成利器

Faker&#xff1a;自动化测试数据生成利器 前言1. 安装2. 多语言支持3. 常用方法3.1 生成姓名和地址3.2 生成电子邮件和电话号码3.3 生成日期和时间3.4 生成公司名称和职位3.5 生成文本和段落3.6 生成图片和颜色3.7 生成用户代理和浏览器信息3.8 生成文件和目录3.9 生成UUID和哈…

GPIO的原理

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 输入…

【Iceberg分析】Iceberg 1.6.1 源码使用IDEA本地编译

Iceberg 1.6.1 源码使用IDEA本地编译 文章目录 Iceberg 1.6.1 源码使用IDEA本地编译下载文件配置调整gradle相关修改bulid.gradlegradle.properties在IDEA上构建编译打包 可能出现的问题彩蛋与Spark部署Spark与Iceberg集成部署 下载 网络条件允许的情况下&#xff0c;使用git直…

引领智慧文旅新纪元,开启未来旅游新境界

融合创新科技&#xff0c;重塑旅游体验&#xff0c;智慧文旅项目定义旅游新未来 在全球化的浪潮中&#xff0c;旅游已成为连接世界的重要纽带。天津信之鸥科技有限公司&#xff08;以下简称“信鸥科技”&#xff09;今日宣布&#xff0c;公司倾力打造的智慧文旅项目正式投入运营…

10.继承与Data Member

目录 1、只要继承不要多态 2、加上多态 3、多重继承 4、虚拟继承 在C继承模型中&#xff0c;一个derived class object所表现出来的东西&#xff0c;是其自己的members加上其base class(es) members的总和。至于derived class members和base class(es) members的排列顺序&a…

基坑气膜:为清洁施工提供强力保障—轻空间

随着城市建设的不断推进&#xff0c;环保要求也日益提高。基坑气膜作为一种新型的施工技术&#xff0c;不仅在防尘降噪方面表现出色&#xff0c;还能支持复杂的施工设备运行&#xff0c;真正实现了从源头解决扬尘和噪音问题。 高效防尘&#xff0c;优化施工环境 传统施工中&…

Hyper-V管理器连接到服务器出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服务器。

尝试连接到服务器”XXXXXX"时出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服务器。 计算机"XXXXXX"上的操作失败: WinRM客户端无法处理该请求。如果身份验证方案与Kerberos不同&#xff0c;或者客户端计算机未加入到域中&#xff0c;则必须使…

工业物联网一直是风口,可视化大屏就是门面

工业物联网作为当下的热门领域&#xff0c;一直处于风口浪尖。而在这个领域中&#xff0c;可视化大屏确实充当着重要的门面角色。 可视化大屏以其震撼的视觉效果和直观的数据展示&#xff0c;为工业物联网赋予了强大的表现力。当人们走进工业物联网的应用场景&#xff0c;首先映…

Jina ColBERT v2: 一个多语言的晚期交互信息检索模型

Jina AI发布 Jina ColBERT v2版本&#xff0c; Jina ColBERT v2是一个多语言的晚期交互&#xff08;Late Interaction&#xff09;信息检索模型&#xff0c;基于BERT架构开发&#xff0c;旨在优化查询和文档之间的匹配和排序。用于在搜索引擎、推荐系统、问答系统等应用中实现高…

20 个精选 Midjourney SREFs 及额外代码,等你来用!

今天我整理了 Midjourney 的参考 ID 801 到 850&#xff0c;并选择了 20 种色彩和艺术风格多样的样式。灵感来源于 _旧金山的彩绘女士_&#xff0c;这是加利福尼亚州著名的排屋系列。 我使用文本提示“城市中的一组排屋”生成了图像&#xff0c;并应用了 20 个选定的 SREF。每张…

【Java】I/O 操作详解

&#x1f4c3;个人主页&#xff1a;island1314 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 目录 1. 引言 &#x1f680; 2. File 类 &#x1f4d5; 2.1 创建 File 对象 …

讲个故事(升级版)1.0

一、总述 离线强化学习&#xff08;Offline RL&#xff09;是交互推荐中的一种常用技术&#xff0c;它离线地学习离线数据以学习策略而不必与用户进行在线交互。Offline RL存在高估用户对离线数据中很少出现的物品的偏好的问题&#xff0c;当前采用了保守主义去解决这一问题&a…

基于SpringBoot的流浪动物救助系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

新160个crackme -081-fireworx.9

运行分析 输入key&#xff0c;点击OK无反应 PE分析 Delphi程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida搜索关键字符串&#xff0c;双击进入函数 通过静态分析&#xff0c;发现程序逻辑&#xff1a;1、获得固定值"678234"&#xff0c;转换为int2…