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

文章目录

  • 前言
  • 一、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_EDITORjsonData = Encrypt(jsonData);//加密
#endifFile.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_EDITORjsonData = 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
{// 存档IDpublic int saveID;// 私有字段,最后保存时间private DateTime lastSaveTime;// 公共属性,获取最后保存时间public DateTime LastSaveTime{get{// 如果 lastSaveTime 是默认值,则尝试将字符串解析为 DateTimeif (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; // 设置存档IDthis.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
{// 当前的存档IDpublic 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();// 初始化SaveSystemDataInitSaveSystemData();// 清除存档缓存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){// 缓存字典中是否有这个SaveIDif (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{// 缓存字典中是否有这个SaveIDif (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.rhkb.cn/news/446525.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

访问控制列表(课内实验)

实验2&#xff1a;访问控制列表 实验目的及要求&#xff1a; 通过实验&#xff0c;进一步的理解标准ACL与扩展ACL的工作原理及执行过程。理解通配符的概念&#xff0c;熟练掌握标准ACL与扩展ACL的配置指令&#xff0c;掌握将访问控制列表应用VTY线路上&#xff0c;并且能够判断…

【基于ARM深入分析C程序】1--ARM架构与汇编、分析C语句`a++`的执行过程

【基于ARM深入分析C程序】1–ARM架构与汇编、分析C语句a的执行过程 文章目录 【基于ARM深入分析C程序】1--ARM架构与汇编、分析C语句a的执行过程一、3个操作指令二、CPU是怎么知道执行这三条操作指令的&#xff1f;2.1 CPU的架构 2.2 寄存器 本文作为学习笔记&#xff0c;围绕的…

【Next.js 入门教程系列】09-优化技巧

原文链接 CSDN 的排版/样式可能有问题&#xff0c;去我的博客查看原文系列吧&#xff0c;觉得有用的话&#xff0c; 给我的库点个star&#xff0c;关注一下吧 上一篇【Next.js 入门教程系列】08-发送邮件 优化技巧 本篇包括以下内容: Optimizing imagesUsing third-party JS…

正点原子学习笔记之汇编LED驱动实验

1 汇编LED原理分析 为什么要写汇编     需要用汇编初始化一些SOC外设     使用汇编初始化DDR、I.MX6U不需要     设置sp指针&#xff0c;一般指向DDR&#xff0c;设置好C语言运行环境 1.1 LED硬件分析 可以看到LED灯一端接高电平&#xff0c;一端连接了GPIO_3上面…

C# WinForm实现画笔签名及解决MemoryBmp格式问题

目录 需求 实现效果 开发运行环境 设计实现 界面布局 初始化 画笔绘图 清空画布 导出位图数据 小结 需求 我的文章 《C# 结合JavaScript实现手写板签名并上传到服务器》主要介绍了 web 版的需求实现&#xff0c;本文应项目需求介绍如何通过 C# WinForm 通过画布画笔…

Appium环境搭建、Appium连接真机

文章目录 一、安装Android SDK二、安装Appium-desktop三、安装Appium Inspector 一、安装Android SDK 首先需要安装jdk&#xff0c;这里就不演示安装jdk的过程了 SDK下载地址&#xff1a;Android SDK 下载 1、点击 Android SDK 下载 -> SKD Tools 2、选择对应的版本进行下…

SpringBoot基础(五):集成JUnit5

SpringBoot基础系列文章 SpringBoot基础(一)&#xff1a;快速入门 SpringBoot基础(二)&#xff1a;配置文件详解 SpringBoot基础(三)&#xff1a;Logback日志 SpringBoot基础(四)&#xff1a;bean的多种加载方式 SpringBoot基础(五)&#xff1a;集成JUnit5 目录 一、JUnit…

前端开发设计模式——组合模式

目录 一、组合模式的定义和特点 1.定义 2.特点&#xff1a; 二、组合模式的实现方式 1.定义抽象组件类 2.创建叶节点类 3.创建组合类&#xff1a; 三、组合模式的应用场景 1.界面布局管理 2.菜单系统构建 3.组件库开发 四、组合模式的优点 1.简化客户端代码 2.增…

GO网络编程(七):海量用户通信系统5:分层架构

P323开始&#xff08;尚硅谷GO教程&#xff09;老韩又改目录结构了&#xff0c;没办法&#xff0c;和之前一样&#xff0c;先说下目录结构&#xff0c;再给代码&#xff0c;部分代码在之前讲过&#xff0c;还有知识的话由于本人近期很忙&#xff0c;所以这些就不多赘述了&#…

【C++】12.string类的使用

文章目录 1. 为什么学习string类&#xff1f;1.1 C语言中的字符串1.2 两个面试题(暂不做讲解) 2. 标准库中的string类2.1 string类(了解)2.2 auto和范围for 3. 查看技术文档4. string的访问5. 如何读取每个字符呢&#xff1f;6. auto语法糖&#xff08;C11&#xff09;7. 范围f…

浅析主流监控告警系统基本架构和原理

浅析主流监控告警系统基本架构和原理 一&#xff0c;监控系统的作用和目前主流监控系统 1&#xff0c;作用&#xff1a;监控系统一般有以下这几个作用 实时采集监控数据&#xff1a;包括硬件、操作系统、中间件、应用程序等各个维度的数据。实时反馈监控状态&#xff1a;通过…

【目标检测】集装箱缺陷检测数据集1476张5类缺陷VOC+YOLO格式

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1476 标注数量(xml文件个数)&#xff1a;1476 标注数量(txt文件个数)&#xff1a;1476 标注…

ubuntu下打开摄像头

ubuntu下打开摄像头 在Ubuntu下,你可以使用cheese,这是一个开源的摄像头应用程序。如果你还没有安装它,可以通过以下命令安装: sudo apt-get updatesudo apt-get install cheese 安装完成后,你可以通过命令行启动它: cheese 或者,你也可以使用ffmpeg来打开摄像头并进…

MATLAB - 机器人机械臂设计轨迹规划器

系列文章目录 前言 本示例介绍了一种设计抓取和轨迹规划器的方法,该规划器可用于垃圾箱拣选系统。 在机器人技术中,垃圾箱拣选包括使用机械手从垃圾箱中取出物品。智能垃圾箱拣选是这一过程的高级版本,具有更强的自主性。使用摄像系统感知部件,规划器生成与场景相适应的无碰…

Telegram——Bot 机器人/小程序入门指南

一、Bot 介绍 在 TG 中,机器人可以用于接收和发送消息、管理群组(在有权限的情况下可以封禁用户、删除消息、置顶消息等)、通过API进行编程操作、使用 Inline 查询功能在不同的聊天室中提供查询服务、创建自定义键盘按钮、发出账单并收款、接入小程序游戏等。 然而,Bot 默…

智汇云舟亮相WAFI世界农业科技创新大会,并参编数字农业产业图谱

10月10日&#xff0c;2024WAFI世界农业科技创新大会农食行业创新与投资峰会在北京金海湖国际会展中心举行。中国农业大学MBA教育中心主任、教授付文阁、平谷区委常委、统战部部长刘堃、华为公共事业军团数字政府首席专家刘丹、荷兰瓦赫宁根大学前校长Aalt Dijkhuizen、牧原食品…

免费送源码:Java+Springboot+MySQL 水环境检测系统的设计与实现 计算机毕业设计原创定制

摘 要 在我国,水源的污染是不可忽视的问题。对于水质监测进行数据的采集工作,目前主要通过人工实现。因此,部分地区的采集工作,实施起来难度很大,比如恶劣环境和偏僻山区等地。所以,目前对于水质监测的研究,主导方向是建立更加高效完善,智能化的水质监测系统。近几年,无线传感器…

RWKV-CHN模型部署教程

一、模型介绍 RWKV 语言模型&#xff08;用纯 100%RNN 达到 GPT 能力&#xff0c;甚至更强&#xff09;&#xff0c;该项目旨在通过为您自动化所有事情来消除使用大型语言模型的障碍。您需要的是一个只有几兆字节的轻量级可执行程序。此外&#xff0c;该项目还提供了一个接口兼…

计算机网络——p2p

流媒体是指在网络上以流式传输技术实时播放的多媒体内容&#xff0c;如音频、视频等。以下是关于流媒体的详细介绍&#xff1a; 一、工作原理 数据分割与传输&#xff1a; 流媒体技术将多媒体文件分割成较小的数据包。这些数据包按照特定的顺序进行编号&#xff0c;然后通过网络…

[单master节点k8s部署]40.安装harbor

harbor 是私有镜像仓库&#xff0c;用来存储和分发镜像的 。docker 还有一个官方的镜像仓库 docker hub&#xff0c;免费用户只能简单的使用&#xff0c;创建一个私有镜像仓库&#xff0c;存储镜像&#xff0c;付费用户才可以拥有更多权限&#xff0c;默认 docker pull 拉取镜像…