Files
SectSimulator/doc/design/存档系统设计.md
T

10 KiB
Raw Blame History

存档系统设计(D8:存档接入)

对应开发计划表 D88/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_1slot_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

{
  "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 结构

{"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 读档箭头(点击读档)

交互规则

存档模式(游戏内) 读档模式(主菜单)
左侧组列表 当前组高亮(可切换查看,组行带"删"按钮,当前组禁用 可切换任意组(组行带"删"按钮)
槽位点击 回到该进度点(加载所选槽,未保存进度丢失) 点击读档进入游戏
删除按钮 可删(确认后) 可删(确认后)
暂停 打开时暂停,关闭恢复 不暂停

七、分步实现规划(小步可验证,每步独立跑通)

步骤 1SaveSystem 骨架(新 Autoload

内容
改动 新建 src/Core/SaveSystem.gdcreate_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()DiscipleManagerget/set_next_disciple_id()SectManagerset_spirit_stones/set_reputation/set_food()(带信号,读档后 HUD 自动刷新)
验证 存档文件内容完整(弟子/建筑/资源/日期全量)

步骤 3GameState + MainGame 分支 🟡GameState 已改,MainGame 分支待接)

内容
改动 GameStateprofile_id: intsave_slot: String(槽文件名);request_new_game(pid)request_load_game(pid, slot_name)MainGame._ready 分支 NEW_GAME(原流程)/ LOAD_GAMEload_game 还原,不弹窗不暂停,失败回退新游戏);sign_return_mainmenusave_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/24 依赖 1/3;5 依赖 4;6 为最终验证。

八、验收清单(D8 交付标准)

  • 主菜单"新游戏"自动创建新存档组(存档N)
  • 游戏内"存档"面板点"新建存档"→ 该组新增一条槽位(文件生成,内容完整)
  • 连续多次存档 → 同一组内多个槽位互不覆盖(序号命名,已验证 slot_1/2/3 内容独立)
  • 返回主菜单自动写自动槽(auto.json)
  • "读取存档"两级浏览:左侧组列表 → 右侧槽位列表(元数据显示)
  • 读档后:日期/资源/名录(含境界/修为/饥饿)/建筑全部还原
  • 读档进入不弹招募窗、不暂停
  • 删除槽位后列表刷新;损坏/缺失 JSON 不崩溃(回退提示)
  • 新游戏流程不受影响(仍弹开局招募);多组多槽共存