【Unity】通用存档系统设计
有机紫菜
2025年04月18日 08:58

简介

在Unity中,存档系统大概分为2个部分.

  • 文件存储部分——JSON,或其他文件格式.把已有的数据,序列化成为可存储的形式.或读取.

  • 存档系统设计——整个存档系统的架构.包括统一的数据容器,和各模块的数据调度.

针对文件存储部分,有很多解决方案,比如Unity内置的,JsonUtility,EasyFileSave(插件)等,这里就不细说了.

下面主要针对存档系统设计来讲.

为啥需要存档系统设计.

  • 数据需要存储的部分,散落在各个模块内,比如背包,玩家数据,成就数据等.——不方便统一管理

  • 各个模块,可能会重复调用相同的文件读取方法造成性能浪费——这是我们需要避免的.

  • 可能会新加模块或者存储内容.——不同版本的存档支持.

应该设计怎样的存档系统?

  • 统一存档数据类 + 模块化的加载与存储

简单讲就是

读取数据时,只读取总表,并主动分配给每个模块数据

保存数据时,主动拉取每个模块的数据到总表保存.

如下.

代码块
C#
自动换行
复制代码
[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]等特性处理旧存档。

模块化数据接口

强制统一接口.

开发中,所有需要数据存储与加载的模块,都要继承这个接口.

代码块
C#
自动换行
复制代码
public interface IDataPersister {
    void SaveData(ref GameSaveData gameData); // 从模块提取数据到存档
    void LoadData(GameSaveData gameData); // 从存档注入数据到模块
}
复制成功

示例实现

代码块
C#
自动换行
复制代码
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;
    }
}
复制成功

模块化数据加载逻辑

代码块
C#
自动换行
复制代码
// 在存档加载场景或游戏初始化时
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。

按需加载:非即时需要的模块(如未打开的背包)可延迟加载。

全局数据特殊处理

比如,成就,或游戏画面设置,等,或存档槽位信息等,可以单独处理存储.

总结

这种设计,由中央存档系统统一分发数据,各模块只负责处理与自己相关的数据部分。这种设计既保持了集中管理的可控性,又避免了模块间的耦合。

🎯 核心原则:"各司其职"

  1. 存档系统(SaveSystem)

    • 唯一掌握所有数据的完整结构(GameSaveData

    • 负责:序列化、文件IO、版本迁移、槽位管理

    • 不关心数据如何被使用

  1. 功能模块(Player/Inventory等)

    • 只声明自己需要存储哪些数据(通过PlayerData等子结构)

    • 实现LoadData()直接读取传入的GameSaveData中属于自己的部分

    • 不接触文件或其它模块的数据