存档系统
1.JSON讲解
关于JSON的具体描述可以百度
2.JsonUtility
JsonUtility可以将可序列对象转化为Json格式的文件,那么,使用JsonUtility搭建存档系统的核心思路就是
第一步,将需要保存的数据转化位Json格式
第二步,将转换后Json文件写在某个路径下的一个文件夹中
当然读取的时候反过来就可以了
我们知道Json有一个优点,那就是直观易读,但是在存档系统中,Json的这个有点将成为致命的缺点,那就是其他人也是易读的,这样就会造成一种结果,那就是存档后,将存档文件修改,就可以做到开G
所以,为了避免这种情况,我们将使用AES进行加密,其他的加密方式也可以,具体看自己需求
下面开始存档系统的搭建
3.第一步存档
写一个函数,需要传递两个参数,一个是保存的存档文件的文件名,另一个是需要存档的数据文件,为object类型
第一步便是json转换
第二步是AES加密(该函数待会儿写)
第三步确定保存的路径
var path = Path.Combine(Application.persistentDataPath, saveFileName);
这行代码Path.Combine()用于将传入的两个参数结合
Application.persistentDataPath 是Unity提供的API,自动获取路径,详见Unity手册
saveFileName 是保存的文件名字
接下来便使用try catch语句来进行异常的捕获
public static void SaveByJson(string saveFileName, object data)
{
var json = JsonUtility.ToJson(data);
var encryptedJson = EncryptString(key, json); // AES解密
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
File.WriteAllText(path, encryptedJson);
#if UNITY_EDITOR
Debug.Log($"成功保存到 {path}.");
#endif
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件保存到 {path}. \n{exception}");
#endif
}
}
4.读取存档
具体不多讲,看代码
public static T LoadFromJson<T>(string saveFileName)
{
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
//var json = File.ReadAllText(path);
var encryptedJson = File.ReadAllText(path);
var json = DecryptString(key, encryptedJson);// 使用解密函数解密数据
var data = JsonUtility.FromJson<T>(json);
Debug.Log($"成功读取到 {path}.");
return data;
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件从 {path} 加载. \n{exception}");
#endif
return default;
}
}
5.删除存档
public static void DeleteSaveFile(string saveFileName)
{
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
File.Delete(path);
Debug.Log($"成功删除到 {path}.");
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件从 {path} 删除. \n{exception}");
#endif
}
}
6.AES加解密(采用16位key进行加解密)
/// <summary>
/// AES加密函数
/// </summary>
/// <param name="key"></param>
/// <param name="plainText"></param>
/// <returns></returns>
public static string EncryptString(string key, string plainText)
{
byte[] iv = new byte[16];
byte[] array;
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(plainText);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
/// <summary>
/// AES解密函数
/// </summary>
/// <param name="key"></param>
/// <param name="cipherText"></param>
/// <returns></returns>
public static string DecryptString(string key, string cipherText)
{
byte[] iv = new byte[16];
byte[] buffer = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream(buffer))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader(cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
7.Python随机生成AES的key
from Crypto.Random import get_random_bytes
def generate_aes_key(key_length=16):
"""
生成指定长度的 AES 密钥
参数:
key_length: int,密钥长度,默认为 16 字节
返回值:
bytes,生成的 AES 密钥
"""
if key_length not in [16, 24, 32]:
raise ValueError("AES密钥长度必须是16、24或32字节")
return get_random_bytes(key_length)
# 生成一个16字节(128位)的 AES 密钥
aes_key = generate_aes_key()
print("生成的AES密钥:", aes_key.hex())
8.整体代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
namespace FilingSystem
{
/// <summary>
/// 存档系统
/// </summary>
public static class SaveSystem
{
private static string key = "b244a02444908deb248a0792ad540d25";
/// <summary>
/// 保存数据
/// </summary>
/// <param name="saveFileName">文件名,保存数据的文件名</param>
/// <param name="data">需要保存的数据</param>
public static void SaveByJson(string saveFileName, object data)
{
var json = JsonUtility.ToJson(data);
var encryptedJson = EncryptString(key, json); // AES解密
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
File.WriteAllText(path, encryptedJson);
#if UNITY_EDITOR
Debug.Log($"成功保存到 {path}.");
#endif
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件保存到 {path}. \n{exception}");
#endif
}
}
/// <summary>
/// 读取数据
/// </summary>
/// <param name="saveFileName">读取数据的文件名</param>
/// <typeparam name="T">泛型</typeparam>
/// <returns></returns>
public static T LoadFromJson<T>(string saveFileName)
{
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
//var json = File.ReadAllText(path);
var encryptedJson = File.ReadAllText(path);
var json = DecryptString(key, encryptedJson);// 使用解密函数解密数据
var data = JsonUtility.FromJson<T>(json);
Debug.Log($"成功读取到 {path}.");
return data;
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件从 {path} 加载. \n{exception}");
#endif
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);
Debug.Log($"成功删除到 {path}.");
}
catch(System.Exception exception)
{
#if UNITY_EDITOR
Debug.Log($"文件从 {path} 删除. \n{exception}");
#endif
}
}
/// <summary>
/// AES加密函数
/// </summary>
/// <param name="key"></param>
/// <param name="plainText"></param>
/// <returns></returns>
public static string EncryptString(string key, string plainText)
{
byte[] iv = new byte[16];
byte[] array;
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(plainText);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
/// <summary>
/// AES解密函数
/// </summary>
/// <param name="key"></param>
/// <param name="cipherText"></param>
/// <returns></returns>
public static string DecryptString(string key, string cipherText)
{
byte[] iv = new byte[16];
byte[] buffer = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream(buffer))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader(cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
}
}
9.测试代码
注意playerName的赋值,存档前和存档后playerName的值要一致
using System.Collections;
using System.Collections.Generic;
using FilingSystem;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class SaveSystemTest : MonoBehaviour
{
[SerializeField] private string playerName;
[SerializeField] private float score;
[SerializeField] private Vector3 vector;
[SerializeField] private Button saveButton;
[SerializeField] private Button loadButton;
[SerializeField] private Button deleteButton;
[SerializeField] private TMP_Text scoreText;
[SerializeField] private TMP_Text transformText;
const string PLAYER_SAVE_FILENAME = "JackieText";
[System.Serializable]class PlayerData
{
public string playerName; //玩家名字
public float playerScore; //玩家得分
public Vector3 playerTransfrom; //玩家位置
}
private void Start()
{
saveButton.onClick.AddListener(SaveButton);
loadButton.onClick.AddListener(LoadButton);
deleteButton.onClick.AddListener(DeleteButton);
}
//
private void SaveButton()
{
PlayerData playerData = new PlayerData();
playerData.playerName = playerName;
playerData.playerScore = score;
playerData.playerTransfrom = vector;
SaveSystem.SaveByJson(PLAYER_SAVE_FILENAME+playerName, playerData);
}
private void LoadButton()
{
var saveData = SaveSystem.LoadFromJson<PlayerData>(PLAYER_SAVE_FILENAME+playerName);
scoreText.text = saveData.playerScore.ToString();
transformText.text = saveData.playerTransfrom.ToString();
}
private void DeleteButton()
{
SaveSystem.DeleteSaveFile(PLAYER_SAVE_FILENAME);
}
}