【Unity笔记04】数据持久化
🌟 方案核心思想
遵循以下设计原则:
- 数据安全第一:绝不使用明文存储,采用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
脚本。