6.1 KiB
6.1 KiB
13 · 存档系统规范
所属文档集 ← 返回索引
摘要:存档数据结构(Schema)、读写契约、版本迁移与存档槽管理。
目录
1. 存档设计原则
| 原则 | 说明 |
|---|---|
| 单一来源 | 存档数据是游戏状态的唯一持久化来源 |
| 原子性 | 存档要么完整写入,要么不写(不产生损坏的存档) |
| 版本兼容 | 旧版存档可被新版游戏读取(向前兼容) |
| 与引擎无关 | 存档格式为纯数据(JSON / 结构化二进制),不依赖引擎序列化 |
2. 存档数据 Schema
2.1 顶层结构
DataModel SaveData {
──── 元信息 ────────────────────────
schemaVersion : Integer // 用于版本迁移
saveSlot : Integer // 存档槽编号(1-3)
lastSaveTime : String // ISO 8601 时间戳
playTimeSecs : Integer // 游戏内游玩时长(秒)
──── 玩家状态 ──────────────────────
player : PlayerSaveData
──── 世界状态 ──────────────────────
world : WorldSaveData
──── 进程 ──────────────────────────
progression : ProgressionSaveData
──── 经济 ──────────────────────────
economy : EconomySaveData
}
2.2 玩家存档数据
DataModel PlayerSaveData {
currentHP : Integer
maxHP : Integer
maxSoulPower : Integer
maxSpiritPower : Integer
springCount : Integer
geoAmount : Integer
currentFormId : ID
unlockedFormIds : List<ID>
equippedCharmIds: List<ID> // 当前装备的护符
inventoryItems : List<{itemId: ID, count: Integer}>
lastBonfireId : ID // 死亡后重生位置
lastRoomId : ID // 关闭游戏前所在房间
lastPosition : Vector2 // 关闭游戏前的精确位置
}
2.3 世界存档数据
DataModel WorldSaveData {
visitedRoomIds : List<ID>
activatedBonfireIds: List<ID>
defeatedBossIds : List<ID>
permanentlyDeadEnemyIds: List<ID> // 不刷新的特殊敌人
openedGateIds : List<ID>
activatedSwitchIds: List<ID>
collectedCollectibleIds: List<ID>
geoShade : Optional<GeoShadeState> // 见 09_ProgressionSystem
}
2.4 进程存档数据
DataModel ProgressionSaveData {
unlockedAbilityIds: List<ID>
mainQuestStage : Integer
completedQuestIds: List<ID>
worldFlags : Map<String, Boolean> // 任意世界状态标记
notchCount : Integer
purchasedNotchCount: Integer
loreFoundIds : List<ID>
secretsFoundIds : List<ID>
lootPityCounters: Map<ID, Integer> // 保底计数(itemId → 未掉落次数)
}
2.5 经济存档数据
DataModel EconomySaveData {
ownedCharmIds : List<ID> // 已获得(非已装备)的护符
shopPurchaseHistory: Map<ID, List<ID>> // shopId → 已购买 itemId 列表
}
3. 存档读写契约
Interface ISaveSystem {
// 写入
save(data: SaveData, slot: Integer) → SaveResult
// 读取
load(slot: Integer) → Optional<SaveData>
// 检查
slotExists(slot: Integer) → Boolean
getSlotMetadata(slot: Integer) → Optional<SaveSlotMeta>
// 删除(需二次确认)
deleteSlot(slot: Integer) → Void
}
DataModel SaveSlotMeta {
slot : Integer
lastSaveTime : String
playTimeSecs : Integer
lastRoomName : String
playerHP : Integer
maxHP : Integer
}
DataModel SaveResult {
success : Boolean
error : Optional<String>
}
4. 存档触发时机
| 触发事件 | 存档类型 | 说明 |
|---|---|---|
OnBonfireRested |
完整存档 | 最主要的存档时机 |
OnCollectiblePickedUp(关键道具) |
完整存档 | 防止关键进程丢失 |
OnBossDefeated |
完整存档 | Boss 死亡立即存档 |
OnAbilityUnlocked |
完整存档 | 防止能力解锁丢失 |
| 游戏退出(发出退出信号) | 位置快照 | 保存位置,不算正式存档进度 |
设计决策:不提供手动存档按钮
原因:存档点机制是游戏节奏设计的一部分,强制存档会破坏张力
5. 版本迁移规范
5.1 版本号规则
schemaVersion每次修改 SaveData 结构时递增- 不向后兼容(新版存档无法在旧版游戏中读取)
- 向前兼容(旧版存档可被新版游戏迁移后读取)
5.2 迁移契约
Interface ISaveMigrator {
canMigrate(fromVersion: Integer, toVersion: Integer) → Boolean
migrate(data: RawSaveData, fromVersion: Integer) → SaveData
}
5.3 迁移策略
| 变更类型 | 处理方式 |
|---|---|
| 新增字段 | 旧存档中该字段赋默认值 |
| 删除字段 | 读取时忽略多余字段 |
| 字段重命名 | 迁移器执行映射 |
| 字段类型变更 | 迁移器执行转换逻辑 |
| 无法迁移 | 提示玩家,提供新开局选项(不自动删除) |
6. 存档槽管理
| 规格项 | 值 |
|---|---|
| 存档槽数量 | 3 个 |
| 每槽独立 | 完全独立,互不影响 |
| 新游戏 | 选择存档槽 → 若已有存档显示警告 → 确认后覆盖 |
| 存档文件位置 | 用户数据目录(不与游戏安装目录混存) |
| 备份 | 写入前备份旧文件为 .bak,写入成功后删除备份 |