前言
如何在 Unity 中正确制作一个保存和加载系统,该系统使用JSON 文件来处理保存配置文件,可以保存和加载任何类型对象!标题为什么叫小型游戏存储功能呢?因为该存储功能可能只适合存储数据比较单一的情况,它非常的方便快捷,且易于使用和理解。但是如果你的游戏要存储的内容有很多,比如很多怪物的状态,很多宝箱的状态,很多物品的状态,那么它可能就不适用了。但是不用担心,后续我还会针对大型游戏,出更加复杂和全面的存储系统,解决存储数据比较多的情况。
存储
新增SaveProfile保存配置文件的泛型类和SaveProfileData抽象基类
[System.Serializable]
// 保存配置文件的泛型类
public sealed class SaveProfile<T> where T : SaveProfileData
{
// 保存配置文件的名称
public string name;
// 实际保存的数据
public T saveData;
// 私有的默认构造函数,防止无参数实例化
private SaveProfile() { }
// 公共构造函数,用于初始化名称和保存数据
public SaveProfile(string name, T saveData)
{
this.name = name;
this.saveData = saveData;
}
}
// 抽象基类,用于保存数据
public abstract record SaveProfileData {}
新增SaveManager,定义读取和存储 删除存档文件方法
using System;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;
public static class SaveManager
{
// 文件保存的根目录路径
private static readonly string saveFolder = Application.persistentDataPath + "/GameData";
// 删除指定存档文件
public static void Delete(string profileName)
{
if (!File.Exists($"{saveFolder}/{profileName}"))
throw new Exception($"保存配置文件 {profileName} 未找到!");
Debug.Log($"已成功删除 {saveFolder}/{profileName}");
File.Delete($"{saveFolder}/{profileName}");
}
// 加载指定类型的存档文件
public static SaveProfile<T> Load<T>(string profileName) where T : SaveProfileData
{
if (!File.Exists($"{saveFolder}/{profileName}"))
throw new Exception($"保存配置文件 {profileName} 未找到!");
// 读取文件内容为字符串
var fileContents = File.ReadAllText($"{saveFolder}/{profileName}");
// TODO:解密
Debug.Log($"已成功加载 {saveFolder}/{profileName}");
// 反序列化为指定类型的SaveProfile<T>对象并返回
return JsonConvert.DeserializeObject<SaveProfile<T>>(fileContents);
}
// 保存指定类型的存档数据
public static void Save<T>(SaveProfile<T> save) where T : SaveProfileData
{
if (File.Exists($"{saveFolder}/{save.name}")){
// throw new Exception($"保存配置文件 {save.name} 未找到!");
Delete(save.name);
}
// 将SaveProfile<T>对象序列化为JSON格式的字符串
var jsonString = JsonConvert.SerializeObject(save, Formatting.Indented,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
// TODO:加密
if (!Directory.Exists(saveFolder))
Directory.CreateDirectory(saveFolder);
// 将加密后的jsonString写入文件
File.WriteAllText($"{saveFolder}/{save.name}", jsonString);
}
}
使用
新增SaveData.cs,定义需要存储的数据,如果需要其他数据需要再添加
using UnityEngine;
// 玩家保存数据
public record PlayerSaveData : SaveProfileData
{
// 玩家位置
public Vector2 position;
// 成就数组
public int[] achievements;
}
// 世界保存数据
public record WorldSaveData : SaveProfileData
{
// 方块二维数组
public int[,] blocks;
}
新增Player ,保存测试数据和读取
public class Player : MonoBehaviour
{
void Start()
{
Debug.Log(Application.persistentDataPath);
// 保存玩家数据
var playerSave = new PlayerSaveData
{
position = new Vector2(1f, 1.5f),
achievements = new[] { 1, 2, 3, 4, 5 }
};
var saveProfile = new SaveProfile<PlayerSaveData>("playerSaveData", playerSave);
SaveManager.Save(saveProfile);
//保存世界数据
var worldSave = new WorldSaveData
{
blocks =new[,] { {1,1}, {1,2}, {1,3}}
};
var saveProfile2 = new SaveProfile<WorldSaveData>("WorldSaveData", worldSave);
SaveManager.Save(saveProfile2);
}
void Update()
{
//读取数据
if (Input.GetKeyDown(KeyCode.E))
{
Vector2 position = SaveManager.Load<PlayerSaveData>("playerSaveData").saveData.position;
Debug.Log(position);
transform.position = position;
}
}
}
运行之后,可以去查看保存的文件数据
运行按E成功加载数据
看到这里你应该就明白为什么叫小型游戏存储功能了吧!
数据加密
修改SaveManager,添加数据加密内容
using System;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;
public static class SaveManager
{
// 文件保存的根目录路径
private static readonly string saveFolder = Application.persistentDataPath + "/GameData";
// 加密:选择一些用于亦或操作的字符(注意保密)
public static char[] keyChars = { 'a', 'b', 'c', 'd', 'e' };
// 删除指定存档文件
public static void Delete(string profileName)
{
if (!File.Exists($"{saveFolder}/{profileName}"))
throw new Exception($"保存配置文件 {profileName} 未找到!");
Debug.Log($"已成功删除 {saveFolder}/{profileName}");
File.Delete($"{saveFolder}/{profileName}");
}
// 加载指定类型的存档文件
public static SaveProfile<T> Load<T>(string profileName) where T : SaveProfileData
{
if (!File.Exists($"{saveFolder}/{profileName}"))
throw new Exception($"保存配置文件 {profileName} 未找到!");
// 读取文件内容为字符串
var fileContents = File.ReadAllText($"{saveFolder}/{profileName}");
// TODO:解密
fileContents = Decrypt(fileContents);
Debug.Log($"已成功加载 {saveFolder}/{profileName}");
// 反序列化为指定类型的SaveProfile<T>对象并返回
return JsonConvert.DeserializeObject<SaveProfile<T>>(fileContents);
}
// 保存指定类型的存档数据
public static void Save<T>(SaveProfile<T> save) where T : SaveProfileData
{
if (File.Exists($"{saveFolder}/{save.name}")){
// throw new Exception($"保存配置文件 {save.name} 未找到!");
Delete(save.name);
}
// 将SaveProfile<T>对象序列化为JSON格式的字符串
var jsonString = JsonConvert.SerializeObject(save, Formatting.Indented,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
// TODO:加密
jsonString = Encrypt(jsonString);
if (!Directory.Exists(saveFolder))
Directory.CreateDirectory(saveFolder);
// 将加密后的jsonString写入文件
File.WriteAllText($"{saveFolder}/{save.name}", jsonString);
}
// 加密方法
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);
}
}
存储加密内容
按E读取数据正常
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~