【Unity笔记04】数据持久化

🌟 方案核心思想

遵循以下设计原则:

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91430.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解 HTML5 Web Workers:提升网页性能的关键技术解析

深入理解 HTML5 Web Workers&#xff1a;提升网页性能的关键技术解析引言1. 什么是 Web Workers&#xff1f;Web Workers 的特点&#xff1a;2. Web Workers 的使用方式2.1 创建一个 Web Worker步骤 1&#xff1a;创建 Worker 文件步骤 2&#xff1a;在主线程中调用 Worker3. W…

会议室预定系统核心技术:如何用一行SQL解决时间冲突检测难题

文章目录 一、为什么时间冲突检测是预定系统的核心挑战? 二、黄金法则:两行线段重叠检测法 三、四大冲突场景实战解析(同一会议室) 四、生产环境完整解决方案 1. 基础冲突检测函数 2. 预定API处理流程 3. 高级边界处理技巧 五、性能优化关键策略 六、不同数据库的适配方案 …

13.正则表达式:文本处理的瑞士军刀

正则表达式&#xff1a;文本处理的瑞士军刀 &#x1f3af; 前言&#xff1a;当文本遇上神奇的密码 想象一下&#xff0c;你是一个图书管理员&#xff0c;面对着一堆乱七八糟的书籍信息&#xff1a; “联系电话&#xff1a;138-1234-5678”“邮箱地址&#xff1a;zhang.sangm…

linux下c语言访问mysql数据库

一、连接数据库基础1. 头文件与库文件连接 MySQL 需包含的头文件&#xff1a;#include <mysql/mysql.h> // 部分环境也可用 #include <mysql.h> 编译链接时&#xff0c;Linux 平台需指定库名&#xff1a;-lmysqlclient &#xff0c;用于链接 MySQL 客户端函数库。2…

6. 传输层协议 UDP

传输层负责数据能够从发送端传输接收端.1. 再谈端口号端口号(Port)标识了一个主机上进行通信的不同的应用程序在 TCP/IP 协议中, 用 "源 IP", "源端口号", "目的 IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信…

vue 开发总结:从安装到第一个交互页面-与数据库API

vue 总结 1、安装vue&#xff1a; WinR 输入&#xff1a;cnpm install -g vue/cli 验证是否安装成功&#xff1a;vue --version 2、新建Vue工程 在对应文件夹下右击打开集成终端 输入 vue create query_system&#xff08;新建项目名字&#xff09;名称不能存在大写&#x…

运维笔记:HTTP 性能优化

一、HTTP 协议特性与性能瓶颈1.1 HTTP 协议发展历程HTTP 协议的演进直接影响着 Web 性能&#xff0c;各版本关键特性对比&#xff1a;协议版本发布时间核心特性性能优势局限性HTTP/1.01996 年无状态、短连接简单易实现每次请求需建立 TCP 连接HTTP/1.11999 年长连接、管道化减少…

ubuntu:运行gcfsd-admin守护进程需要认证,解决方法

这里有个锁子&#xff0c;每次进入都要输入密码&#xff0c;怎么解决&#xff1f; 重新挂载 /data 磁盘 sudo umount /data sudo ntfsfix /dev/sda1 sudo mount -o rw /dev/sda1 /data

1.DRF 环境安装与配置

文章目录一. Django Rest_Framework二、环境安装与配置2.1 安装 DRF2.2 创建Django项目2.3 添加 rest_framework 应用三、启动项目一. Django Rest_Framework 核心思想&#xff1a;大量缩减编写 api 接口的代码 Django REST framework 是一个建立在 Django 基础之上的 Web 应…

设计模式(十九)行为型:备忘录模式详解

设计模式&#xff08;十九&#xff09;行为型&#xff1a;备忘录模式详解备忘录模式&#xff08;Memento Pattern&#xff09;是 GoF 23 种设计模式中的行为型模式之一&#xff0c;其核心价值在于在不破坏封装性的前提下&#xff0c;捕获并外部化一个对象的内部状态&#xff0c…

Qt/C++开发监控GB28181系统/录像回放/切换播放进度立即跳转/支持8倍速播放/倍速和跳转进度无缝切换

一、前言说明 在国标监控系统中&#xff0c;录像回放过程中&#xff0c;需要切换播放进度&#xff0c;对比过很过国标系统&#xff0c;绝大部分尤其是网页版的监控系统&#xff0c;在切换进度过程中都会黑屏&#xff0c;这个体验就很不友好了&#xff0c;明明gb28181协议中就有…

【11】大恒相机SDK C++开发 ——原图像数据IFrameData内存中上下颠倒,怎么裁剪ROI 实时显示在pictureBox中

文章目录3 当内存中的 图像数据是垂直翻转的时候怎么截取ROI 并显示3.1 对ROI在原图中的位置做转换3.2 将ROI的最后一行当做开始位置&#xff0c;从底部向上复制数据3.3 完整代码3.4 图像数据在内存中上下颠倒的情况3.5 调用验证4 unsafe代码 解释及注意事项 看我另一篇文章5 C…

小架构step系列29:校验注解的组合

1 概述如果遇到某些属性需要多种校验&#xff0c;比如需要非空、符合某正则表达式、长度不能超过某值等&#xff0c;如果这种属性只有有限几个&#xff0c;那么手工把对应的校验注解都加上即可。但如果这种属性比较多&#xff0c;那么重复加这些校验注解&#xff0c;也是一种代…

网络基础19:OSPF多区域实验

一、拓扑结构1. 网络拓扑&#xff1a;骨干区域&#xff08;Area 0&#xff09;&#xff1a;连接核心设备&#xff08;AR1、AR2、AR3、AR4、AR5、AR6&#xff09;。非骨干区域&#xff1a;Area 1&#xff1a;AR5 ↔ AR9Area 2&#xff1a;AR5 ↔ AR10Area 3&#xff1a;AR6 ↔ A…

goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案

问题 写了个自定义的包 calc.go&#xff0c;在路径 $GOPATH/go_project/src/demo_51_package/com/目录下&#xff0c;其中main.go 是main方法的入口代码 main.go 代码如下 package main import "demo_51_package/com" func main() {add : calc.Add(1, 2)println(add)…

HLS视频切片音频中断问题分析与解决方案

HLS视频切片音频中断问题分析与解决方案 问题背景 在使用FFmpeg进行HLS视频切片并通过hls.js前端播放时&#xff0c;开发者经常遇到一个典型问题&#xff1a;第一个视频切片播放正常且有声音&#xff0c;但后续切片却突然失去音频。这种现象在直播和点播场景中均有出现&#xf…

【Linux网络编程】网络层协议 - IP

目录 背景补充 协议头格式 IP报文的分片与组装 网段划分 网段划分是什么&#xff1f;为什么要进行网段划分&#xff1f; 怎么进行网段划分&#xff1f; 路由 路由表生成算法 背景补充 假设现在主机B要给主机C发送消息。在我们前面的学习中&#xff0c;一直都是将数据拷…

从“救火”到“先知”:润建曲尺运维大模型如何重构网络运维价值链

“7月18号&#xff0c;北京&#xff0c;晴&#xff0c;最高温度38摄氏度。”天气预报缓缓播报&#xff0c;商场、地铁、办公楼无不歌颂着威利斯开利的贡献&#xff0c;但这份凉爽的背后&#xff0c;离不开 “电” 的无声托举。5G毫秒级下载、丝滑的移动支付、智能电表、智能家居…

Element表格单元格类名动态设置

在 Element UI 的 el-table 组件中&#xff0c;cell-class-name 属性用于动态自定义表格单元格的 CSS 类名&#xff0c;通常用于根据数据条件设置样式。1. 基本用法在 el-table 上绑定 :cell-class-name 属性&#xff0c;值为一个函数。该函数接收一个对象参数&#xff0c;返回…

利用容器适配器实现stack和queue外加deque的介绍(STL)

文章目录前言什么是容器适配器&#xff1f;观察库中的源码那么该如何使用容器适配器呢&#xff1f;deque的简单介绍(了解)deque的原理介绍deque的优缺为什么选择deque作为stack和queue的底层默认容器&#xff1f;&#xff08;重点&#xff09;利用容器适配器实现我们自己的栈和…