存档系统
MCE 使用基于 JSON 的存档系统,支持多栏位存档、版本控制和可扩展性。存档系统持久化玩家的完整游戏状态——队伍、物品栏、位置、徽章、任务进度和游戏变量。
架构
存档系统由多个组件组成:
| 组件 | 用途 |
|---|---|
SavegameManager | 协调存档/读档操作 |
SavableObject | 任何需要持久化数据的对象的基类 |
MCESavesSerializer | 处理 JSON 序列化和反序列化 |
GameVariables | 全局变量存储(布尔值、整数、字符串) |
GameVariable | 单个变量引用 |
GameVariableHolder | 游戏变量的 MonoBehaviour 包装器 |
SavegameInstaller | 存档系统的 Zenject 安装器 |
SavableGlobalGameData
核心存档数据容器是 SavableGlobalGameData(GlobalGameData 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.json、save_1.json、save_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,但手动编辑可能会导致问题,如引入无效数据(例如数据库中不存在的招式)。请改用游戏内工具或调试命令。
最佳实践
- 测试存档/读档往返。保存、退出、重新加载并验证一切完好。
- 使用 GameVariables 作为事件标志。它们自动持久化并与 CommandGraph 集成。
- 规划存档迁移。如果在更新中添加新数据,添加迁移步骤。
- 提供多个存档栏位。玩家期望至少有 3 个手动存档栏位。
- 优雅处理加载失败。如果存档文件损坏 ,显示清晰的错误而不是崩溃。