在Unity中,存档系统大概分为2个部分.
文件存储部分——JSON,或其他文件格式.把已有的数据,序列化成为可存储的形式.或读取.
存档系统设计——整个存档系统的架构.包括统一的数据容器,和各模块的数据调度.
针对文件存储部分,有很多解决方案,比如Unity内置的,JsonUtility,EasyFileSave(插件)等,这里就不细说了.
下面主要针对存档系统设计来讲.
数据需要存储的部分,散落在各个模块内,比如背包,玩家数据,成就数据等.——不方便统一管理
各个模块,可能会重复调用相同的文件读取方法造成性能浪费——这是我们需要避免的.
可能会新加模块或者存储内容.——不同版本的存档支持.
统一存档数据类 + 模块化的加载与存储
如下.
[System.Serializable]
public class GameSaveData {
// 各模块的独立数据容器(避免直接引用游戏对象)
public PlayerData playerData;
public InventoryData inventoryData;
public OtherData otherData;
public AchievementData globalAchievements;
// 元数据
public int saveSlotIndex;
public DateTime saveTime;
} 单一入口管理:所有存档数据通过类一目了然,避免遗漏。
序列化友好:纯数据类(无方法/游戏对象引用)适合直接JSON/二进制序列化。
版本兼容:未来新增数据字段时,可通过[DefaultValue]等特性处理旧存档。
强制统一接口.
开发中,所有需要数据存储与加载的模块,都要继承这个接口.
public interface IDataPersister {
void SaveData(ref GameSaveData gameData); // 从模块提取数据到存档
void LoadData(GameSaveData gameData); // 从存档注入数据到模块
} public class Inventory : MonoBehaviour, IDataPersister {
public void SaveData(ref GameSaveData gameData) {
gameData.inventoryData = new InventoryData(items, gold);
}
public void LoadData(GameSaveData gameData) {
this.items = gameData.inventoryData.items;
}
} // 在存档加载场景或游戏初始化时
public class SaveSystem : MonoBehaviour {
public void LoadGame(int slotIndex) {
GameSaveData saveData = EasyFileSave.Load<GameSaveData>(slotIndex);
// 分发数据到各模块(模块需实现IDataPersister接口)
FindObjectOfType<Player>().LoadData(saveData.playerData);//演示模块加载,不推荐Find方法.
FindObjectOfType<Inventory>().LoadData(saveData.inventoryData);//演示模块加载,不推荐Find方法.
AchievementManager.Instance.Load(saveData.globalAchievements);
}
} 避免直接引用场景对象:存档类应只存储数据,不是MonoBehaviour。
按需加载:非即时需要的模块(如未打开的背包)可延迟加载。
比如,成就,或游戏画面设置,等,或存档槽位信息等,可以单独处理存储.
这种设计,由中央存档系统统一分发数据,各模块只负责处理与自己相关的数据部分。这种设计既保持了集中管理的可控性,又避免了模块间的耦合。
🎯 核心原则:"各司其职"
存档系统(SaveSystem)
唯一掌握所有数据的完整结构(GameSaveData)
负责:序列化、文件IO、版本迁移、槽位管理
不关心数据如何被使用
功能模块(Player/Inventory等)
只声明自己需要存储哪些数据(通过PlayerData等子结构)
实现LoadData()时直接读取传入的GameSaveData中属于自己的部分
不接触文件或其它模块的数据