【Unity笔记04】数据持久化

发布于:2025-08-01 ⋅ 阅读:(10) ⋅ 点赞:(0)

🌟 方案核心思想

遵循以下设计原则:

  1. 数据安全第一:绝不使用明文存储,采用AES加密算法保护数据。
  2. 性能优化:使用异步I/O操作,避免阻塞主线程导致游戏卡顿。
  3. 结构清晰:模块化设计,职责分离,便于维护和扩展。
  4. 易于集成:提供单例入口,全局访问方便。

🧱 模块架构设计

整个数据持久化系统由四个核心模块组成:

PlayerData.cs

数据模型,定义可序列化的玩家数据结构

EncryptionUtility.cs

加密工具类,负责数据的加密与解密

AsyncFileUtility.cs

异步文件操作工具,封装读写逻辑

DataManager.cs

核心管理器,统一调度数据加载与保存

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 脚本。