跳到主要内容

存档系统

MCE 使用基于 JSON 的存档系统,支持多栏位存档、版本控制和可扩展性。存档系统持久化玩家的完整游戏状态——队伍、物品栏、位置、徽章、任务进度和游戏变量。

架构

存档系统由多个组件组成:

组件用途
SavegameManager协调存档/读档操作
SavableObject任何需要持久化数据的对象的基类
MCESavesSerializer处理 JSON 序列化和反序列化
GameVariables全局变量存储(布尔值、整数、字符串)
GameVariable单个变量引用
GameVariableHolder游戏变量的 MonoBehaviour 包装器
SavegameInstaller存档系统的 Zenject 安装器

SavableGlobalGameData

核心存档数据容器是 SavableGlobalGameDataGlobalGameData GameObject 上的组件)。它存储:

数据描述
玩家位置当前场景、地块坐标、面朝方向
怪兽队伍队伍中所有怪兽的完整状态(物种、等级、个体值、努力值、性格、招式、PP、HP、状态、亲密度等)
PC 存储盒子中的怪兽
背包所有道具和数量
金钱玩家的货币
徽章已获得的道馆徽章
怪兽图鉴每个物种的已见/已捕获追踪
游戏变量所有 GameVariable 值(由 CommandGraph 使用)
任务进度活跃任务、已完成目标、已领取奖励
游戏时间总游戏时间

存档栏位

MCE 支持多个存档栏位。默认配置提供 3 个栏位,但可配置。

保存

// 通过 SDK(所有版本)
engine.SaveSystem.Save(slotIndex); // 0、1 或 2

// 或直接通过 SavegameManager(Source 版本)
savegameManager.Save(slotIndex);

读取

// 通过 SDK(所有版本)
engine.SaveSystem.Load(slotIndex);

// 先检查栏位是否有数据
if (engine.SaveSystem.HasSaveData(slotIndex))
{
engine.SaveSystem.Load(slotIndex);
}

删除

engine.SaveSystem.DeleteSave(slotIndex);

存档文件位置

存档文件使用 Unity 的 Application.persistentDataPath 存储:

平台路径
Windows%USERPROFILE%/AppData/LocalLow/<CompanyName>/<ProductName>/
macOS~/Library/Application Support/<CompanyName>/<ProductName>/
Linux~/.config/unity3d/<CompanyName>/<ProductName>/
Android内部存储(应用私有)
iOS应用沙箱

每个栏位保存为单独的 JSON 文件:save_0.jsonsave_1.jsonsave_2.json

存档版本控制

MCE 包含存档版本控制以处理游戏版本间的升级:

  • 每个存档文件包含一个版本号
  • 加载旧版存档时,MCESavesSerializer 应用迁移步骤更新数据结构。
  • 这防止了在更新中添加新字段或更改数据格式时损坏存档。
序列化字段名称

不要在不提供迁移的情况下重命名存档相关类中的序列化字段。如果重命名字段,现有存档将在加载时丢失该数据。字段重命名时使用 [FormerlySerializedAs("oldName")]

GameVariables

GameVariables 系统是 MCE 的全局状态存储。它持久化可从 CommandGraph 节点、脚本和存档文件中读写的任意键值数据。

变量类型

类型描述示例
Boolean真/假标志"defeatedBrock" = true
Integer数值计数器"fishCaught" = 42
String文本值"rivalName" = "Gary"

在 CommandGraph 中使用

  • SetVariable 节点:设置变量的值。
  • BranchOnVariable 节点:检查变量并分支。
  • AddToVariable 节点:递增数值变量。

在代码中使用

// 读取变量
bool hasBadge = GameVariables.GetBool("defeatedBrock");
int fishCount = GameVariables.GetInt("fishCaught");

// 写入变量
GameVariables.SetBool("defeatedBrock", true);
GameVariables.SetInt("fishCaught", fishCount + 1);

自定义存档数据

扩展 SavableObject(Source 版本)

拥有源代码后,创建新的 SavableObject

public class CustomSaveData : SavableObject
{
[SerializeField] private int customCounter;
[SerializeField] private List<string> unlockedFeatures;

public override void OnSave()
{
// 保存前收集当前状态
customCounter = MySystem.Counter;
unlockedFeatures = MySystem.GetUnlockedFeatures();
}

public override void OnLoad()
{
// 应用加载的状态
MySystem.Counter = customCounter;
MySystem.SetUnlockedFeatures(unlockedFeatures);
}
}

附加自定义数据(所有版本)

无需源代码访问,使用存档事件在 MCE 存档旁持久化你自己的数据:

public class MySaveExtension : MonoBehaviour
{
[Inject] private ISaveSystem saveSystem;

[Serializable]
private class MyData
{
public int highScore;
public List<string> achievements;
}

private MyData data = new MyData();

private void OnEnable()
{
saveSystem.OnSaved += OnSaved;
saveSystem.OnLoaded += OnLoaded;
}

private void OnSaved(int slot)
{
string json = JsonUtility.ToJson(data);
File.WriteAllText(GetPath(slot), json);
}

private void OnLoaded(int slot)
{
string path = GetPath(slot);
if (File.Exists(path))
{
data = JsonUtility.FromJson<MyData>(File.ReadAllText(path));
}
}

private string GetPath(int slot)
{
return Path.Combine(Application.persistentDataPath, $"custom_{slot}.json");
}
}

自动存档

MCE 支持在可配置触发条件下自动存档:

  • 地图切换时:进入新场景时保存。
  • 休息时:在治疗点休息时保存。
  • 定时间隔:每 N 分钟保存一次。

在游戏配置 ScriptableObject 中配置自动存档。

自动存档最佳实践
  • 始终使用专用的自动存档栏位(例如栏位 0),与手动存档栏位分开。
  • 保存时显示自动存档指示图标。
  • 不要在战斗或过场动画期间自动存档。

存档文件格式

MCE 存档为纯 JSON 格式,便于调试。简化示例:

{
"version": 3,
"playerName": "Red",
"playTime": 12345.6,
"currentScene": "PalletTown",
"position": { "x": 8, "y": 12 },
"money": 5000,
"badges": ["BoulderBadge", "CascadeBadge"],
"roster": [
{
"species": 6,
"level": 36,
"nickname": "Charizard",
"currentHP": 120,
"moves": ["Flamethrower", "AirSlash", "DragonClaw", "SwordsDance"],
"ivs": [28, 31, 24, 30, 26, 31],
"evs": [0, 252, 0, 4, 0, 252]
}
],
"gameVariables": {
"defeatedBrock": true,
"defeatedMisty": true,
"fishCaught": 12
}
}
不要手动编辑存档文件

虽然存档文件是人类可读的 JSON,但手动编辑可能会导致问题,如引入无效数据(例如数据库中不存在的招式)。请改用游戏内工具或调试命令。

最佳实践

  1. 测试存档/读档往返。保存、退出、重新加载并验证一切完好。
  2. 使用 GameVariables 作为事件标志。它们自动持久化并与 CommandGraph 集成。
  3. 规划存档迁移。如果在更新中添加新数据,添加迁移步骤。
  4. 提供多个存档栏位。玩家期望至少有 3 个手动存档栏位。
  5. 优雅处理加载失败。如果存档文件损坏,显示清晰的错误而不是崩溃。