存储系统SaveManager

背景

  存储,几乎是所有游戏无法忽略的重要模块,使用什么样的方式保存数据,unity内置的PlayerPrefab,亦或是保存文件到本地,文件的形式是json还是xml,保存和读取数据的操作,序列化与反序列化数据。可见存储模块的多样性和复杂性。
  框架引入EasySave插件来协助完成存储模块,再通过SaveManager对一系列操作进行封装,开发者通过SaveManager的api接口即可完成数据读取和保存。考虑到存储模块的复杂性和需求的多样性,框架将存储模块尽可能地独立存在,开发者可以 自定义存储模块 以满足具体需求,实现必要接口,即可无缝切换储存模块,无需对业务逻辑进行任何修改。
  插件官方文档
  (文末有存储系统SaveManager 具体使用和拓展 的案例)

使用

数据文件路径

  • 通过unity界面上方“存档目录”摁钮,定位到游戏的数据目录,即 数据文件fileName 或 数据文件路径filePath 的 根目录。
  • fileName 即 文件名,根目录为存档目录,文件名fileName 等价于 文件路径filePath。

API接口

自定义实现存储模块时必须实现的SaveManager接口,即可实现 无缝切换 存储模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SaveManager : BaseSingleTon<SaveManager>
{
// 文件操作相关:
// 指定文件fileName 中是否有 指定键key
public static bool ExistKey(string key, string fileName = null)
// 是否存在指定文件fileName
public static bool ExistFile(string fileName)
// 创建文件
public static void CreateFile(string fileName)
// 删除 指定文件fileName 中的 指定键值对 key
public static void DeleteKey(string key, string fileName = null)
// 删除 指定文件fileName
public static void DeleteFile(string fileName)
// 删除 指定文件夹
public static void DeleteDic(string dicName)

// 数据操作相关:
// 保存数据项到指定文件
public static void Set<T>(string k, T v, string fileName)
// 指定文件中获取数据项
public static T Get<T>(string key, T defaultValue, string fileName)
}

API使用示例

以保存和读取当前存档的背包数据举例:

1
2
3
4
5
6
// 保存所有背包数据到当前存档的存档文件
SaveManager.Set("Inventory", inventoryDic, SaveType.SaveGame);

// 读取当前存档文件中的所有背包数据,没有则新建
var inventoryDic = SaveManager.Get(
"Inventory", new Dictionary<string, Inventory>(), SaveType.SaveGame);

背包数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// <背包名,背包数据>
var inventoryDic = new Dictionary<string, Inventory>();

// 背包数据
[Serializable] // 声明为序列化文件,保证EasySave保存时自动对数据进行序列化
public class Inventory
{
public InventoryData data; // 背包信息
public InventoryItem[] content; // 背包的物品容器
}

// 背包信息
[Serializable]
public class InventoryData
{
public string inventoryID; // 背包名
public RowCfgInventory RowCfgInventory; // RowCfgInventory 对应 inventory.xlsx 的数据项
}

// 背包物品信息
[Serializable]
public class InventoryItem
{
public InventoryItemData data; // InventoryItemData 对应 Item.xlsx 的数据项
public int quantity = 1; // 物品数量
}

注意事项

  • 虽然EasySave会自动进行序列化,但是开发者仍然需要保存的数据结构需要显式声明[Serializable],避免说如果更换了存储模块,自定义的存储模块不一定会自动进行序列化操作,导致数据保存失败。
  • 本文与资源管理系统的模块定位需要区分,资源管理系统保存的资源和数据是不会进行变更的,只能进行加载操作,而本文指的数据是在游戏运行中动态进行读取的,甚至是文件删除和创建的动态操作。
  • EasySave插件可以实现加密功能,开发中使用 明文以及json格式 保存,是为了方便查看和验证文件中的数据。
  • 自行实现加密格式,可以借助框架封装的,工具类Utils中的,常用加密解密 部分。
    1
    2
    3
    4
    5
    6
    7
    // 工具类路径:项目路径\Assets\Scripts\Misc\Util\UtilsForEncode.cs
    // 使用示例:
    // AES加密字节流,Utils.AesEncrypt(字节流,秘钥)
    var content = File.ReadAllBytes(fileName);
    var encryptContent = Utils.AesEncrypt(content, "yufulao@qq.com");
    // AES解密字节流,Utils.AesDecrypt(加密字节流,秘钥)
    var decryptContent = Utils.AesDecrypt(encryptContent, "yufulao@qq.com");

夹带私货(存档功能)

项目当前的数据存取流程:
  初步设置了基础的数据类别,包括全局数据Global、配置数据Cfg、存档数据。Cfg设置数据文件 和 Global全局数据文件 是主要文件MainFile,会在游戏启动时进行创建和初始化,SaveGame存档数据文件则需要手动创建和初始化。

  • 数据目录:
  • 存档数据目录SaveGame:
  • 存档数据的必要数据结构:
    1
    2
    3
    4
    5
    // 项目路径\Assets\Scripts\Core\Manager\SaveManager\SaveGameStruct.cs
    public struct SaveGameStruct
    {
    public string SaveGameTitle; // 存档标题
    }
  • 存储的方式
    1
    2
    3
    4
    5
    6
    7
    // 项目路径\Assets\Scripts\Core\Manager\SaveManager\SaveType.cs
    public enum SaveType
    {
    Cfg, // 保存到设置文件
    Global, // 保存到全局文件
    SaveGame, // 保存到当前存档文件
    }
  • 新增API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 设置当前存档index,可以在 进行存档选择时 进行调用,无存档功能的指定唯一存档文件名即可
    public static void SetCurrentSaveGame(int saveGameIndex)
    // 重置 所有文件,(删除Cfg、Global、所有存档文件。再新建Cfg和Global文件)
    public static void ResetAllFile()
    // 删除 所有存档文件
    public static void DeleteAllSaveGame()
    // 获取 当前存档index 对应的 存档文件名
    public static string GetSaveGameFileName(int saveGameIndex)

    // 以保存数据方式saveType,更新或创建 k,v 数据项
    public static void Set<T>(string k, T v, SaveType saveType)
    // 获取数据项,需要给出默认值,没有键时创建默认数据项,以saveType读取对应文件
    public static T Get<T>(string key, T defaultValue, SaveType saveType)