chore: initial commit
This commit is contained in:
216
Docs/DesignSpec/13_SaveSystem.md
Normal file
216
Docs/DesignSpec/13_SaveSystem.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 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<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`,写入成功后删除备份 |
|
||||
Reference in New Issue
Block a user