# 存档系统设计(D8:存档接入) > 对应开发计划表 D8(8/10):① SaveSystem JSON 序列化 ② 存/读接口 ③ 替换 SavePanel 测试数据。 > 交付标准:读档后数据还原。本文档只描述设计,不包含代码。 > 状态:全部步骤已实现(SaveSystem/序列化/GameState+MainGame 分支/SavePanel 双模式/自动存档/两级浏览/读档还原/删除与容错/游戏内加载进度点/删除存档组),验收 1–9 全部通过 --- ## 一、两级存档结构(无限组 / 无限槽) **第一级:存档组 SaveGroup(一个角色/一次轮回,UI 显示"存档N")** → **第二级:存档槽 SaveSlot(该组下任意多个进度快照)** - **每个新游戏 → 创建一个 SaveGroup**(主菜单"新游戏"时自动生成,id 自增) - **每次点击存档 → 在所属 SaveGroup 下创建一个新 SaveSlot**(不覆盖、不固定槽位) - **组和槽的数量均无上限** ``` user://saves/ ├── profiles.json # 存档组列表 [{id, name, created, last_date}] ├── profile_1/ # 存档组 1 │ ├── auto.json # 自动存档(返回主菜单时写入,特殊槽) │ ├── slot_1.json # 手动槽(组内序号递增命名) │ └── slot_2.json └── profile_2/ ... ``` - 槽位命名:**组内序号递增**(`slot_1`、`slot_2`…),同一组内永不重名、互不覆盖;删除槽位后序号从当前最大 +1 继续(不重用) - 自动槽 `auto.json` 固定名(续档用) ## 二、可存档数据清单 | 系统 | 数据 | 说明 | |------|------|------| | GameState | `year/month/day` | 游戏日期(TimeSystem.set_date 恢复) | | SectManager | `spirit_stones` / `reputation` / `food` | 三资源 | | DiscipleManager | `disciples` + `_next_id` | 名录;候选池不存 | | BuildingManager | `buildings` | 建筑类型/等级 | > 修为/境界/饥饿天数都在 Disciple 字段内,随名录序列化。 ## 三、SaveSystem(新 Autoload) 存放位置:`src/Core/SaveSystem.gd`,注册于 project.godot(依赖各管理器,声明在最后)。 | 接口 | 说明 | |------|------| | `create_profile() -> int` | 新建存档组,返回 id(名"存档N") | | `get_profiles() -> Array[Dictionary]` | 存档组列表(id/name/created/last_date) | | `save_game(profile_id) -> String` | **新建一个槽位**并写入当前游戏数据,返回槽文件名;失败返回空串 | | `save_auto(profile_id) -> bool` | 写自动槽(返回主菜单时调用) | | `get_slots(profile_id) -> Array[Dictionary]` | 该组全部槽位(槽文件名 + 元数据),按时间升序 | | `load_game(profile_id, slot_name) -> bool` | 读取槽位 JSON → 还原各系统 | | `delete_profile(profile_id) -> bool` | 删除存档组(列表移除 + 递归删目录含全部槽位) | | `delete_slot(profile_id, slot_name) -> bool` | 删除槽位文件 | | `reset()` | 清空内存游戏数据(各管理器 reset + 资源清零) | ## 四、序列化设计 ### 1. 数据模型加 to_dict/from_dict - `Disciple.to_dict()` / `static Disciple.from_dict(data)`(14 字段全量,枚举转 int) - `Building.to_dict()` / `static from_dict(data)`(2 字段) ### 2. 槽位 JSON 结构(slot_1754734800.json) ```json { "version": 1, "profile_id": 1, "saved_date": {"year": 3, "month": 5, "day": 12}, "date": {"year": 3, "month": 5, "day": 12}, "resources": {"spirit_stones": 1230, "reputation": 45, "food": 210}, "next_disciple_id": 7, "disciples": [ {"id": 1, "name": "林清玄", "age": 16, "aptitude": 2, "roots": [0, 1], "is_heavenly": false, "join_year": 1, "join_month": 1, "join_day": 1, "realm_index": 0, "sub_realm_index": 4, "cultivation_exp": 512.5, "hunger_days": 0} ], "buildings": [ {"type": 0, "level": 2}, {"type": 1, "level": 1} ] } ``` - `saved_date`/`resources` 顶层冗余字段 = 槽位元数据(列表显示用,免解析全档) - `version` 为格式版本,预留兼容 ### 3. profiles.json 结构 ```json {"profiles": [{"id": 1, "name": "存档1", "created": "2026-08-10", "last_date": "3年5月12日"}]} ``` ## 五、存档/读档流程 ### 存档(游戏内,每次=新建槽) 1. TopBar 新增"存档"按钮 → SavePanel(存档模式,时间暂停) 2. 左侧显示存档组(当前组高亮),右侧显示该组全部槽位 3. 点"新建存档"按钮 → `save_game(profile_id)` 创建新槽 → 列表新增一条 4. 关闭面板恢复时间 ### 读档(主菜单,两级浏览) 1. 主菜单"读取存档" → SavePanel(读档模式) 2. **左侧存档组列表**(存档N + 最后日期)→ 点组 → **右侧显示该组槽位列表**(自动+手动,元数据齐全) 3. 点槽位 → `GameState.request_load_game(profile_id, slot_name)` → 进入 MainGame ### MainGame 分支(改造 _ready) ``` NEW_GAME → 现有流程:start_new_game + 开局资源 + 招募弹窗(暂停) LOAD_GAME → SaveSystem.load_game(profile_id, slot_name) → 还原 → 不弹窗、不暂停 ``` ### 自动存档 TopBar"返回"主菜单时(sign_return_mainmenu 处理前)→ `save_auto(profile_id)` 自动槽——保证"退→再进"能续档。 ## 六、UI:SavePanel(复用现有手绘界面) ``` SavePanel (PanelContainer 全屏) └── VBoxContainer ├── Header: TitleLabel("选择存档") + Spacer + Button_Esc("返回") ├── 存档模式:Button_NewSlot("新建存档") ← 每次点击创建新槽位 └── ScrollContainer └── HBoxContainer ├── SaveGroup 区(VBoxContainer) ← 左侧:存档组按钮列表(动态生成"存档N") └── SlotList 区(VBoxContainer) ← 右侧:该组槽位条目列表(动态生成 SaveSlot 实例) ``` **SaveSlot 条目**(复用现有场景,无空槽概念——每个条目都是真实存档): | 节点 | 填充内容 | |------|---------| | SlotIndexLabel | 序号(自动槽=0,手动槽按时间序 1..N) | | SaveNameLabel | "自动存档" / "存档 N" | | MetaLine1 | "X年X月X日 弟子N人" | | MetaLine2 | "灵石 M" | | DeleteButton | 删除该槽位(删除前确认) | | ArrowIcon | 读档箭头(点击读档) | **交互规则**: | 项 | 存档模式(游戏内) | 读档模式(主菜单) | |----|-------------------|-------------------| | 左侧组列表 | 当前组高亮(可切换查看,**组行带"删"按钮,当前组禁用**) | 可切换任意组(组行带"删"按钮) | | 槽位点击 | **回到该进度点**(加载所选槽,未保存进度丢失) | 点击读档进入游戏 | | 删除按钮 | 可删(确认后) | 可删(确认后) | | 暂停 | 打开时暂停,关闭恢复 | 不暂停 | ## 七、分步实现规划(小步可验证,每步独立跑通) ### 步骤 1:SaveSystem 骨架(新 Autoload)✅ | 项 | 内容 | |----|------| | 改动 | 新建 `src/Core/SaveSystem.gd`:`create_profile()`(建组,名"存档N")、`get_profiles()`、`save_game(profile_id)`(新建槽,文件名 `slot_{组内序号}.json`,返回槽名)、`save_auto(profile_id)`、`get_slots(profile_id)`(自动槽最前+手动按序号升序,含元数据)、`load_game(profile_id, slot_name)`、`delete_slot()`、`reset()`;`project.godot` 注册(声明在最后) | | 验证 | ✅ 建组(存档N 自增)→ 存档 → slot_1/2/3 生成 → `get_slots` 读回;删除后序号不重用 | ### 步骤 2:数据模型序列化 ✅ | 项 | 内容 | |----|------| | 改动 | `Disciple.to_dict()/static from_dict()`(14 字段,枚举转 int);`Building.to_dict()/static from_dict()`;`DiscipleManager` 加 `get/set_next_disciple_id()`;`SectManager` 加 `set_spirit_stones/set_reputation/set_food()`(带信号,读档后 HUD 自动刷新) | | 验证 | ✅ 存档文件内容完整(弟子/建筑/资源/日期全量) | ### 步骤 3:GameState + MainGame 分支 🟡(GameState 已改,MainGame 分支待接) | 项 | 内容 | |----|------| | 改动 | `GameState` 加 `profile_id: int`、`save_slot: String`(槽文件名);`request_new_game(pid)`、`request_load_game(pid, slot_name)`;`MainGame._ready` 分支 NEW_GAME(原流程)/ LOAD_GAME(`load_game` 还原,不弹窗不暂停,失败回退新游戏);`sign_return_mainmenu` 前 `save_auto(profile_id)` | | 验证 | 无头:存→清→读闭环,日期/资源/名录(境界修为饥饿)/建筑逐项一致 | ### 步骤 4:SavePanel 动态填充(复用现有手绘界面)✅ | 项 | 内容 | |----|------| | 改动 | `SavePanel.gd` 双模式:存档模式(游戏内,暂停,当前组高亮 + 槽位列表 + "新建存档"按钮 → `save_game` → 列表新增);读档模式(主菜单,左列组列表 → 点组 → 右列槽位列表,点槽读档)。`SaveSlot.gd` 填充:序号/名称/MetaLine1/MetaLine2/删除(确认后删)/箭头(读档)。TopBar 加"存档"按钮 + `sign_open_save` 信号 | | 验证 | ✅ 存档模式:打开暂停 → 新建存档 → 槽位生成(内容完整)→ 关闭恢复;读档模式待 F5 手测 | ### 步骤 5:接线闭环 🟡(MainMenu 建组已接,读档模式接线+自动存档待做) | 项 | 内容 | |----|------| | 改动 | `MainMenu`"新游戏"→ `create_profile()` 建组 → 进游戏;"读取存档"→ 面板读档模式;`MainGame.tscn` 挂 SavePanel 实例 | | 验证 | 完整闭环:新游戏(建组)→ 招弟子 → 挂机 → 存档(建槽)→ 返回(自动槽)→ 主菜单读档 → 数据还原 | ### 步骤 6:整体验证 + 提交 | 项 | 内容 | |----|------| | 改动 | 无(仅验证) | | 验证 | 对照八节验收清单逐项勾选;git 提交 | > 依赖关系:1→2 顺序执行(序列化被 SaveSystem 消费);3 依赖 1/2;4 依赖 1/3;5 依赖 4;6 为最终验证。 ## 八、验收清单(D8 交付标准) - [x] 主菜单"新游戏"自动创建新存档组(存档N) - [x] 游戏内"存档"面板点"新建存档"→ 该组新增一条槽位(文件生成,内容完整) - [x] 连续多次存档 → 同一组内多个槽位互不覆盖(序号命名,已验证 slot_1/2/3 内容独立) - [x] 返回主菜单自动写自动槽(auto.json) - [x] "读取存档"两级浏览:左侧组列表 → 右侧槽位列表(元数据显示) - [x] 读档后:日期/资源/名录(含境界/修为/饥饿)/建筑全部还原 - [x] 读档进入**不弹招募窗、不暂停** - [x] 删除槽位后列表刷新;损坏/缺失 JSON 不崩溃(回退提示) - [x] 新游戏流程不受影响(仍弹开局招募);多组多槽共存