当前位置: 首页 > news >正文

【Unity游戏存档系统】

《游戏存档系统的实现与设计》

在游戏开发中,存档系统是至关重要的功能模块之一,它允许玩家保存游戏进度,随时退出游戏并在后续继续游戏。一个良好的存档系统不仅能提升玩家体验,还能为游戏增添更多乐趣与沉浸感。本文将深入剖析一个基于 C# 的游戏存档系统的设计与实现,从设计思路到代码实现,再到实际使用,全方位进行讲解,即使是初学者也能轻松理解和上手。

一、设计思路

(一)系统架构

构建一个通用且灵活的游戏存档系统,采用分层架构,将系统划分为以下几个关键模块:

  1. 存档数据管理模块 :由 GameData 类负责,它作为存档数据的载体,定义了存档中包含的各种游戏状态信息,如存档名称、存档时间、当前场景名称、游戏内货币以及物品库存等。
  2. 存档操作接口模块 :定义 ISaveManager 接口和 ISaveSystem 接口。ISaveManager 用于规范游戏内各个需要存档功能的组件的行为,规定它们必须实现 LoadDataSaveData 方法,以便将自身数据存入或从 GameData 中读取数据。ISaveSystem 则抽象出存档系统的共性操作,包括保存数据到指定路径和从指定路径加载数据,为后续具体存档格式的实现提供统一规范。
  3. 数据处理器模块DataHandler 类作为数据处理的核心,它负责管理存档目录、与具体的存档系统交互,完成游戏数据的保存、加载以及存档的删除操作。
  4. 存档系统实现模块 :提供两种具体存档实现方式,BinarySaveSystem 类实现二进制格式存档,JsonSaveSystem 类实现 JSON 格式存档(且支持加密),它们都遵循 ISaveSystem 接口的规范。
  5. 存档管理协调模块SaveManager 类作为整个存档系统的中央管理器,以单例模式存在,负责协调存档的创建、保存、加载和删除操作,它整合上述各个模块,使整个存档系统高效、有序地运行。

(二)设计原则

  1. 单一职责原则 :每个类或接口都有明确且单一的职责,例如 GameData 仅负责存储存档数据,ISaveSystem 仅定义存档操作规范,DataHandler 专注数据的读写处理等,这样使得系统结构清晰,便于维护和扩展。
  2. 开闭原则 :通过定义 ISaveSystem 接口,在不修改现有代码的基础上,可以轻松添加新的存档格式实现,如后续若要引入 XML 格式存档,只需新增一个实现该接口的 XML 存档类即可,增强了系统的可扩展性。
  3. 依赖倒置原则 :高层模块(如 SaveManager )不依赖于低层模块(如具体的 BinarySaveSystemJsonSaveSystem )的具体实现,而是依赖于 ISaveSystem 接口,这样降低了模块之间的耦合度,提高了系统的灵活性。

二、类图展示

以下是该游戏存档系统的类图:

三、代码实现详解

(一)存档数据管理模块(GameData)

using System;
using System.Collections.Generic;/// <summary>
/// 存档数据结构,包含游戏状态信息
/// </summary>
[Serializable]
public class GameData
{// 存档名称public string saveName;// 存档时间public string saveTime;// 当前场景名称public string levelName;// 游戏内货币public int currency;// 物品库存public List<string> inventory;public GameData(){// 初始化存档时间saveTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");// 初始化物品库存inventory = new List<string>();}
}

GameData 类通过 [Serializable] 特性标记,使其具备序列化能力,以便能够将其中的数据转换为特定格式(如 JSON 或二进制)进行存储。其构造函数初始化了存档时间和物品库存列表,为每个新创建的存档提供基础数据结构。

(二)存档操作接口模块(ISaveManager 和 ISaveSystem)

1. ISaveManager
public interface ISaveManager
{void LoadData(GameData data);void SaveData(GameData data);
}

ISaveManager 接口规定了实现该接口的类必须具备加载数据(LoadData)和保存数据(SaveData)的方法,游戏内需要存档功能的各个组件(如玩家属性组件、场景管理组件等)可以通过实现此接口,在存档过程中将自身数据整合到 GameData 中或从 GameData 中恢复自身数据。

2. ISaveSystem
using System;
using System.IO;/// <summary>
/// 保存系统的接口
/// </summary>
public interface ISaveSystem
{// 保存数据到指定路径void Save<T>(T data, string savePath) where T : class;// 从指定路径加载数据T Load<T>(string savePath) where T : class;
}

ISaveSystem 接口定义了存档系统必须实现的保存(Save)和加载(Load)操作,其中泛型的使用使得存档系统能够灵活处理不同类型的数据对象,只要是类类型(where T : class)即可。

(三)数据处理器模块(DataHandler)

using System;
using System.IO;
using UnityEngine;/// <summary>
/// 负责存档数据的读写操作,支持加密和文件管理
/// </summary>
public class DataHandler
{// 存档目录路径private string saveDirectory;// 存档系统实现private ISaveSystem saveSystem;public DataHandler(string saveDirectory, ISaveSystem saveSystem){this.saveDirectory = saveDirectory;this.saveSystem = saveSystem;// 确保存档目录存在if (!Directory.Exists(saveDirectory)){Directory.CreateDirectory(saveDirectory);}}/// <summary>/// 保存游戏数据/// </summary>/// <param name="data"></param>/// <param name="slotName"></param>public void Save<T>(T data, string slotName, string fileExtension) where T : class{string savePath = Path.Combine(saveDirectory, $"{slotName}{fileExtension}");saveSystem.Save(data, savePath);}/// <summary>/// 加载游戏数据/// </summary>/// <param name="slotName"></param>/// <returns></returns>public T Load<T>(string slotName, string fileExtension) where T : class{string savePath = Path.Combine(saveDirectory, $"{slotName}{fileExtension}");return saveSystem.Load<T>(savePath);}/// <summary>/// 删除存档/// </summary>/// <param name="slotName"></param>public void Delete(string slotName, string fileExtension){string savePath = Path.Combine(saveDirectory, $"{slotName}{fileExtension}");if (File.Exists(savePath)){File.Delete(savePath);Debug.Log($"存档 {slotName} 删除成功!");}else{Debug.Log($"没有找到存档 {slotName}!");}}
}

DataHandler 类接收存档目录路径和具体存档系统实现作为参数进行初始化,并确保存档目录存在。它的 Save 方法根据传入的数据、存档槽位名称和文件扩展名构建完整存档路径,调用存档系统的保存方法完成数据存储;Load 方法同样依据存档槽位名称和文件扩展名构建路径,利用存档系统的加载方法读取数据并返回;Delete 方法负责删除指定的存档文件,通过检查文件是否存在来决定是执行删除操作还是提示找不到存档。

(四)存档系统实现模块

1. BinarySaveSystem
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;public class BinarySaveSystem : ISaveSystem
{public void Save<T>(T data, string savePath) where T : class{try{using (FileStream fileStream = new FileStream(savePath, FileMode.Create)){BinaryFormatter binaryFormatter = new BinaryFormatter();binaryFormatter.Serialize(fileStream, data);Debug.Log("二进制存档成功!");}}catch (Exception e){Debug.LogError("二进制存档失败:" + e.Message);}}public T Load<T>(string savePath) where T : class{try{if (File.Exists(savePath)){using (FileStream fileStream = new FileStream(savePath, FileMode.Open)){BinaryFormatter binaryFormatter = new BinaryFormatter();return (T)binaryFormatter.Deserialize(fileStream);}}else{Debug.Log("没有找到二进制存档文件!");return null;}}catch (Exception e){Debug.LogError("二进制加载失败:" + e.Message);return null;}}
}

BinarySaveSystem 类实现了 ISaveSystem 接口,采用二进制格式进行存档。在 Save 方法中,利用 BinaryFormatter 将传入的数据对象序列化,并写入到指定的文件流中;Load 方法则是检查文件存在性后,通过 BinaryFormatter 从文件流中反序列化出数据对象并返回,过程中添加了异常捕获,以应对可能出现的文件操作错误等情况,并给出相应的调试信息提示。

2. JsonSaveSystem
using UnityEngine;
using System;
using System.IO;public class JsonSaveSystem : ISaveSystem
{private string encryptionKey = "your-encryption-key-1234567890123456";private string encryptionIV = "your-iv-12345678";public void Save<T>(T data, string savePath) where T : class{try{string json = JsonUtility.ToJson(data, true);string encryptedJson = AESUtility.Encrypt(json, encryptionKey, encryptionIV);File.WriteAllText(savePath, encryptedJson);Debug.Log("加密后的 JSON 存档成功!");}catch (Exception e){Debug.LogError("加密存档失败:" + e.Message);}}public T Load<T>(string savePath) where T : class{try{if (File.Exists(savePath)){string encryptedJson = File.ReadAllText(savePath);string json = AESUtility.Decrypt(encryptedJson, encryptionKey, encryptionIV);return JsonUtility.FromJson<T>(json);}else{Debug.Log("没有找到加密的 JSON 存档文件!");return null;}}catch (Exception e){Debug.LogError("加密存档加载失败:" + e.Message);return null;}}
}

JsonSaveSystem 类同样遵循 ISaveSystem 接口规范,采用 JSON 格式存档且支持加密。Save 方法先利用 Unity 提供的 JsonUtility.ToJson 方法将数据对象转换为 JSON 字符串,然后调用 AESUtility.Encrypt 方法(后文会提及该工具类)对 JSON 字符串进行加密,最后将加密后的字符串写入指定文件。Load 方法则是先读取文件内容,解密后使用 JsonUtility.FromJson 方法将 JSON 字符串还原为对应的数据对象,并且在整个操作流程中加入了异常处理和调试信息输出。

(五)存档管理协调模块(SaveManager)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;/// <summary>
/// 存档系统的中央管理器,协调存档的创建、保存、加载和删除操作
/// </summary>
public class SaveManager : MonoBehaviour
{// 单例模式,确保只有一个存档管理器实例public static SaveManager Instance;// 数据处理器public DataHandler dataHandler;public List<ISaveManager> saveTargets;public bool EncryptData = true;// 用于选择存档格式[Tooltip("选择存档格式")] // 添加悬停提示public enum SaveFormat { Json, Binary }public SaveFormat saveFormat = SaveFormat.Json;private void Awake(){// 单例模式初始化if (Instance != null){Destroy(gameObject);return;}Instance = this;DontDestroyOnLoad(gameObject);// 初始化存档系统string saveDirectory = Path.Combine(Application.persistentDataPath, "Saves");ISaveSystem saveSystem;// 根据 saveFormat 选择存档系统if (saveFormat == SaveFormat.Json){saveSystem = new JsonSaveSystem();}else{saveSystem = new BinarySaveSystem();}dataHandler = new DataHandler(saveDirectory, saveSystem);// 自动查找所有实现 ISaveManager 的组件saveTargets = FindObjectsOfType<MonoBehaviour>(true).OfType<ISaveManager>().ToList();}// 保存游戏public void SaveGame(string slotName){GameData gameData = new GameData();foreach (var saveTarget in saveTargets){saveTarget.SaveData(gameData);}// 根据 saveFormat 设置文件扩展名string fileExtension = saveFormat == SaveFormat.Json ? ".json" : ".bin";dataHandler.Save(gameData, slotName, fileExtension);}// 加载游戏public void LoadGame(string slotName){// 根据 saveFormat 设置文件扩展名string fileExtension = saveFormat == SaveFormat.Json ? ".json" : ".bin";GameData gameData = dataHandler.Load<GameData>(slotName, fileExtension);foreach (var saveTarget in saveTargets){saveTarget.LoadData(gameData);}}// 删除存档public void DeleteGame(string slotName){// 调用数据处理器删除存档// 根据 saveFormat 设置文件扩展名string fileExtension = saveFormat == SaveFormat.Json ? ".json" : ".bin";dataHandler.Delete(slotName, fileExtension);}
}

SaveManager 类以单例模式存在,确保整个游戏过程中只有一个存档管理器实例在运行,方便全局调用。在 Awake 方法中完成单例初始化、存档系统的初始化(根据选择的存档格式创建对应的存档系统对象,并初始化数据处理器),以及自动查找游戏内所有实现了 ISaveManager 接口的组件,将它们添加到 saveTargets 列表中,以便在存档和加载时统一协调这些组件的数据操作。

SaveGame 方法创建一个新的 GameData 对象,然后依次调用 saveTargets 中各个组件的 SaveData 方法,将它们的数据汇总到 GameData 中,接着根据选择的存档格式确定文件扩展名,借助数据处理器完成存档数据的保存。

LoadGame 方法依据存档格式构建文件扩展名,通过数据处理器加载对应的存档数据到 GameData 对象中,再循环调用 saveTargets 中各组件的 LoadData 方法,使它们从 GameData 中恢复自身数据,实现游戏状态的加载。

DeleteGame 方法同样是根据存档格式确定文件扩展名后,调用数据处理器的删除方法来移除指定的存档文件。

(六)加密工具模块(AESUtility)

using System;
using System.IO;
using System.Security.Cryptography; // 引入 AES 加密算法相关类
using System.Text;/// <summary>
/// AES 加密工具
/// </summary>
public class AESUtility
{/// <summary>/// AES 加密方法/// </summary>/// <param name="plainText">需要加密的原始字符串(明文)</param>/// <param name="key">加密使用的密钥(需与解密密钥相同)</param>/// <param name="iv">初始化向量(增加加密随机性,需与解密 IV 相同)</param>/// <returns>Base64 编码的加密字符串(密文)</returns>public static string Encrypt(string plainText, string key, string iv){using (Aes aes = Aes.Create()) // 创建 AES 加密对象{// 确保秘钥和 IV 的长度为 16 字节aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32)); // 密钥长度为 32 字节(256 位)aes.IV = Encoding.UTF8.GetBytes(iv.PadRight(16).Substring(0, 16));   // IV 长度为 16 字节(128 位)// 创建加密器ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);using (MemoryStream ms = new MemoryStream()) // 创建内存流用于存储加密数据{using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))// 创建加密流,将数据写入内存流时进行加密{using (StreamWriter sw = new StreamWriter(cs)) // 创建写入器,将字符串写入加密流{sw.Write(plainText); // 将明文写入加密流}// 将加密后的字节数组转换为 Base64 字符串(便于存储和传输)return Convert.ToBase64String(ms.ToArray());}}}}/// <summary>/// AES 解密方法/// </summary>/// <param name="cipherText">需要解密的 Base64 编码字符串(密文)</param>/// <param name="key">初始化向量(需与加密 IV 相同)</param>/// <param name="iv">解密后的原始字符串(明文)</param>/// <returns></returns>public static string Decrypt(string cipherText, string key, string iv){using (Aes aes = Aes.Create()) // 创建 AES 解密对象{// 确保密钥和 IV 的长度为 16 字节aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32)); // 密钥长度为 32 字节(256 位)aes.IV = Encoding.UTF8.GetBytes(iv.PadRight(16).Substring(0, 16));   // IV 长度为 16 字节(128 位)// 创建解密器ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(cipherText)))// 将 Base64 字符串转换为字节数组,并创建内存流{using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))// 创建解密流,从内存流读取时进行解密{using (StreamReader sr = new StreamReader(cs)) // 创建读取器,从解密流读取明文{return sr.ReadToEnd(); // 返回解密后的字符串}}}}}
}

AESUtility 类提供了 AES 加密和解密功能。Encrypt 方法中,先根据传入的密钥和初始化向量(IV)生成符合 AES 算法要求长度的密钥和 IV,然后创建加密器,利用内存流和加密流将明文数据进行加密,并最终将加密后的字节数组转换为 Base64 编码字符串,便于存储和传输。Decrypt 方法则是逆过程,将 Base64 编码的密文字符串转换回字节数组,通过解密流和内存流进行解密,还原出原始的明文字符串。该工具类为 JSON 格式存档的加密功能提供了底层支持,保障了存档数据的安全性。

四、实际使用示例

(一)设置存档格式与初始化

在 Unity 编辑器中,将 SaveManager 脚本挂载到一个空的游戏对象上。然后在Inspector 面板中,根据实际需求选择存档格式(JSON 或二进制),通常对于需要数据安全性的场景(如防止玩家篡改存档),选择 JSON 格式并开启加密功能较为合适;若追求存档效率和文件大小(如存档数据量较大且对安全性要求不高),二进制格式是不错的选择。

(二)为游戏组件添加存档功能

对于游戏内需要存档功能的组件,例如玩家属性组件(管理玩家生命值、经验值、等级等)、背包组件(管理玩家携带的物品)等,让它们实现 ISaveManager 接口。以下以一个简单的玩家属性组件为例:

using UnityEngine;public class PlayerAttributes : MonoBehaviour, ISaveManager
{public int health;public int experience;public int level;public void LoadData(GameData data){// 从 GameData 中恢复玩家属性数据if (data != null){health = data.currency; // 假设此处存档时将生命值存储在 currency 字段,需根据实际设计调整experience = data.inventory.Count; // 同样,仅为示例,实际应与存档时对应level = data.levelName.Length; // 示例,实际应合理映射}}public void SaveData(GameData data){// 将玩家属性数据保存到 GameData 中data.currency = health;data.inventory.Add(experience.ToString());data.levelName = level.ToString();}
}

在上述示例中,PlayerAttributes 类实现了 ISaveManager 接口,在 SaveData 方法中将自身的玩家属性数据(生命值、经验值、等级)存储到 GameData 的相应字段中;在 LoadData 方法中则从 GameData 中读取这些数据,恢复玩家属性。需要注意的是,此处仅为示例,实际项目中应根据具体的设计合理安排数据的存储和读取逻辑,确保数据的一致性和准确性。

(三)存档与加载操作

  1. 存档操作 :在游戏过程中,当玩家希望保存游戏进度时,可以通过调用 SaveManager.Instance.SaveGame("slot1"); 这样的代码来实现存档,其中 "slot1" 是存档槽位名称,可以根据需要自定义,如 "slot2""autosave" 等。SaveManager 会协调各个实现了 ISaveManager 接口的组件,将它们的数据汇总到 GameData 中,并根据设置的存档格式将数据保存到对应的文件中。

  2. 加载操作 :当玩家想要继续之前的游戏进度时,使用 SaveManager.Instance.LoadGame("slot1"); 来加载存档,SaveManager 会依据存档格式从对应文件中读取数据到 GameData,然后将这些数据分发给各个组件,使它们恢复到存档时的状态,继续游戏。

  3. 删除存档操作 :若玩家不再需要某个存档,可以执行 SaveManager.Instance.DeleteGame("slot1");,这样就能将对应的存档文件从存储设备中删除,释放存储空间。

五、总结与展望

本文详细介绍了基于 C# 的游戏存档系统的设计与实现,从系统架构的搭建、各个模块的划分以及核心类的代码实现,到实际的使用示例,全方位展示了如何构建一个通用、灵活且安全的游戏存档系统。通过合理的接口设计和模块划分,该系统不仅支持多种存档格式(JSON 和二进制),还能方便地进行扩展,以满足不同类型游戏的存档需求。

http://www.dtcms.com/a/274335.html

相关文章:

  • 爬虫练习1
  • 【环境配置】KAG - Windows 安装部署
  • 7.11文件和异常
  • kafka kraft模式升级metadata.version
  • JVM--监控和故障处理工具
  • Oracle 高可用性与安全性
  • SpringCloud【OpenFeign】
  • 数据治理(管理)能力评估——解读2024数据治理与数据管理能力成熟度评估模型【附全文阅读】
  • 10款主流报销管理平台对比及推荐
  • Linux操作系统之进程间通信:命名管道
  • Linux编程练习题1:打印图形
  • python学习DataFrame数据结构
  • 制作一款打飞机游戏79:道具拾取系统
  • c++设计模式:简单工厂模式
  • C++STL-list
  • 游戏的程序员会不会偷偷改自己账号的数据?
  • 线性回归的从零开始实现(详解部分疑问)
  • 【三】ObservableCollection 与 List 的区别
  • RK3566/RK3568 Android11 CAN开发(内核配置+测试验证+安卓app开发)
  • 2025 年第十五届 APMCM 亚太地区大学生数学建模竞赛C题 基于Quantum Boosting的二分类模型问题
  • 5G标准学习笔记15 --CSI-RS测量
  • 【龙泽科技】新能源汽车维护与动力蓄电池检测仿真教学软件【吉利几何G6】
  • 深入理解C语言内存空间、函数指针(三)(重点是函数指针)
  • Redis 主从复制及哨兵模式模拟部署
  • 3.检查函数 if (!CheckStart()) return 的妙用 C#例子
  • PBR渲染
  • 【网络安全】理解安全事件的“三分法”流程:应对警报的第一道防线
  • leaflet【十二】自定义图层——海量数据加载
  • 安全监测预警平台的应用场景
  • 机器学习数据集加载全攻略:从本地到网络