# 13 · 存档系统规范 > **所属文档集** [← 返回索引](./README.md) > **摘要**:存档数据结构(Schema)、读写契约、版本迁移与存档槽管理。 --- ## 目录 1. [存档设计原则](#1-存档设计原则) 2. [存档数据 Schema](#2-存档数据-schema) 3. [存档读写契约](#3-存档读写契约) 4. [存档触发时机](#4-存档触发时机) 5. [版本迁移规范](#5-版本迁移规范) 6. [存档槽管理](#6-存档槽管理) --- ## 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 equippedCharmIds: List // 当前装备的护符 inventoryItems : List<{itemId: ID, count: Integer}> lastBonfireId : ID // 死亡后重生位置 lastRoomId : ID // 关闭游戏前所在房间 lastPosition : Vector2 // 关闭游戏前的精确位置 } ``` ### 2.3 世界存档数据 ``` DataModel WorldSaveData { visitedRoomIds : List activatedBonfireIds: List defeatedBossIds : List permanentlyDeadEnemyIds: List // 不刷新的特殊敌人 openedGateIds : List activatedSwitchIds: List collectedCollectibleIds: List geoShade : Optional // 见 09_ProgressionSystem } ``` ### 2.4 进程存档数据 ``` DataModel ProgressionSaveData { unlockedAbilityIds: List mainQuestStage : Integer completedQuestIds: List worldFlags : Map // 任意世界状态标记 notchCount : Integer purchasedNotchCount: Integer loreFoundIds : List secretsFoundIds : List lootPityCounters: Map // 保底计数(itemId → 未掉落次数) } ``` ### 2.5 经济存档数据 ``` DataModel EconomySaveData { ownedCharmIds : List // 已获得(非已装备)的护符 shopPurchaseHistory: Map> // shopId → 已购买 itemId 列表 } ``` --- ## 3. 存档读写契约 ``` Interface ISaveSystem { // 写入 save(data: SaveData, slot: Integer) → SaveResult // 读取 load(slot: Integer) → Optional // 检查 slotExists(slot: Integer) → Boolean getSlotMetadata(slot: Integer) → Optional // 删除(需二次确认) deleteSlot(slot: Integer) → Void } DataModel SaveSlotMeta { slot : Integer lastSaveTime : String playTimeSecs : Integer lastRoomName : String playerHP : Integer maxHP : Integer } DataModel SaveResult { success : Boolean error : Optional } ``` --- ## 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`,写入成功后删除备份 |