🌟 方案核心思想
遵循以下设计原则:
- 数据安全第一:绝不使用明文存储,采用AES加密算法保护数据。
- 性能优化:使用异步I/O操作,避免阻塞主线程导致游戏卡顿。
- 结构清晰:模块化设计,职责分离,便于维护和扩展。
- 易于集成:提供单例入口,全局访问方便。
🧱 模块架构设计
整个数据持久化系统由四个核心模块组成:
|
数据模型,定义可序列化的玩家数据结构 |
|
加密工具类,负责数据的加密与解密 |
|
异步文件操作工具,封装读写逻辑 |
|
核心管理器,统一调度数据加载与保存 |
1️⃣ 玩家数据模型:PlayerData.cs
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class PlayerData
{
public string playerName;
public int level;
public long gold;
public List<string> inventoryItems; // 背包物品列表
public Dictionary<string, int> equippedGear; // 装备信息(部位 -> 物品ID)
public int saveVersion; // 数据版本号,用于后续升级兼容
// 构造函数,初始化默认值
public PlayerData(string name, int lvl)
{
playerName = name;
level = lvl;
gold = 0;
inventoryItems = new List<string>();
equippedGear = new Dictionary<string, int>();
saveVersion = 1; // 初始版本
}
// 示例:添加物品
public void AddItem(string item)
{
inventoryItems.Add(item);
}
// 示例:升级
public void LevelUp()
{
level++;
Debug.Log($"玩家 {playerName} 升级到等级 {level}");
}
}
2️⃣ 安全加密:EncryptionUtility.cs
切记:永远不要用明文存储数据!
我们采用 AES(Advanced Encryption Standard) 对称加密算法,结合CBC模式和PKCS7填充,确保数据安全。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
public static class EncryptionUtility
{
// 🔐 演示用密钥(256位 = 32字节)
private static readonly byte[] Key = Encoding.UTF8.GetBytes("YourVeryStrongAndSecretKey123456");
// 🔐 演示用IV(128位 = 16字节)
private static readonly byte[] IV = Encoding.UTF8.GetBytes("AnotherSecretIV1");
/// <summary>
/// 加密字符串
/// </summary>
public static string Encrypt(string plainText)
{
if (string.IsNullOrEmpty(plainText)) return string.Empty;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
}
byte[] encryptedBytes = msEncrypt.ToArray();
return Convert.ToBase64String(encryptedBytes); // 转为Base64便于存储
}
}
}
/// <summary>
/// 解密字符串
/// </summary>
public static string Decrypt(string cipherText)
{
if (string.IsNullOrEmpty(cipherText)) return string.Empty;
byte[] cipherBytes;
try
{
cipherBytes = Convert.FromBase64String(cipherText);
}
catch (FormatException)
{
Debug.LogError("解密失败:输入字符串不是有效的Base64格式。");
return string.Empty;
}
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherBytes))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}
}
}
}
3️⃣ 异步文件操作:AsyncFileUtility.cs
using UnityEngine;
using System.IO;
using System.Threading.Tasks;
public static class AsyncFileUtility
{
private static readonly string _saveDirectory = Application.persistentDataPath;
/// <summary>
/// 异步写入加密数据
/// </summary>
public static async Task WriteAllTextAsync(string encryptedData, string fileName)
{
string filePath = Path.Combine(_saveDirectory, fileName);
try
{
await File.WriteAllTextAsync(filePath, encryptedData);
Debug.Log($"数据已成功异步写入到: {filePath}");
}
catch (System.Exception e)
{
Debug.LogError($"异步写入文件失败({filePath}): {e.Message}");
}
}
/// <summary>
/// 异步读取加密数据
/// </summary>
public static async Task<string> ReadAllTextAsync(string fileName)
{
string filePath = Path.Combine(_saveDirectory, fileName);
if (!File.Exists(filePath))
{
Debug.LogWarning($"文件不存在: {filePath}");
return null;
}
try
{
string encryptedData = await File.ReadAllTextAsync(filePath);
Debug.Log($"数据已成功异步从: {filePath} 读取。");
return encryptedData;
}
catch (System.Exception e)
{
Debug.LogError($"异步读取文件失败({filePath}): {e.Message}");
return null;
}
}
}
4️⃣ 核心管理器:DataManager.cs
using UnityEngine;
using System;
using System.Threading.Tasks;
public class DataManager : MonoBehaviour
{
public static DataManager Instance { get; private set; }
private PlayerData _currentPlayerData;
private const string PLAYER_SAVE_FILE = "playerSave.json";
// 数据加载完成事件,用于通知其他模块
public static event Action<PlayerData> OnDataLoaded;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // 场景切换不销毁
}
else
{
Destroy(gameObject);
}
}
void Start()
{
LoadGameAsync(); // 启动时自动加载
}
/// <summary>
/// 异步加载游戏数据
/// </summary>
public async void LoadGameAsync()
{
Debug.Log("开始异步加载游戏数据...");
string encryptedJson = await AsyncFileUtility.ReadAllTextAsync(PLAYER_SAVE_FILE);
PlayerData loadedData = null;
if (!string.IsNullOrEmpty(encryptedJson))
{
string jsonString = EncryptionUtility.Decrypt(encryptedJson);
if (!string.IsNullOrEmpty(jsonString))
{
try
{
loadedData = JsonUtility.FromJson<PlayerData>(jsonString);
// TODO: 可在此处添加版本兼容处理
}
catch (System.Exception e)
{
Debug.LogError($"反序列化失败: {e.Message}");
}
}
}
_currentPlayerData = loadedData ?? new PlayerData("新玩家", 1);
Debug.Log(loadedData != null ? "加载存档成功" : "创建新玩家");
OnDataLoaded?.Invoke(_currentPlayerData);
}
/// <summary>
/// 异步保存游戏数据
/// </summary>
public async void SaveGameAsync()
{
if (_currentPlayerData == null) return;
Debug.Log("开始异步保存...");
string jsonString = JsonUtility.ToJson(_currentPlayerData);
string encryptedJson = EncryptionUtility.Encrypt(jsonString);
await AsyncFileUtility.WriteAllTextAsync(encryptedJson, PLAYER_SAVE_FILE);
Debug.Log("保存完成");
}
/// <summary>
/// 获取当前玩家数据
/// </summary>
public PlayerData GetCurrentPlayerData() => _currentPlayerData;
// 建议在暂停或后台时保存
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus) SaveGameAsync();
}
void OnApplicationQuit()
{
SaveGameAsync(); // 注意:异步可能无法保证完成
}
}
在场景中创建一个空 GameObject(如命名为 DataManager
),并挂载 DataManager.cs
脚本。