Files
ImmortalSect/doc/SaveSystem.md
T

4.4 KiB
Raw Blame History

存档系统(SaveSystem)使用说明

Autoload 单例,路径 res://src/Core/SaveSystem.gd,已在 project.godot 注册。

架构

两级存档:

存档组 SaveGroup(一次轮回/一个角色)          profiles.json 中一条记录
  └── 存档槽 SaveSlot(进度快照)              user://saves/profile_{组id}/slot_{时间戳}.json
  • 每个"新游戏"创建一个组,组 id 由 SaveSystem 分配(1、2、3…)
  • 每次 save() 在组下新建一个槽,槽与组数量无上限
  • 存档文件为 JSON,含 versiondatedata 三段,data 为各系统数据合并后的字典

核心接口

接口 说明
create_profile() -> int 新建存档组,返回组 id。主菜单"新游戏"调用
get_profiles() -> Array[Dictionary] 组列表,每条含 id / name / created / last_date
save(profile_id, data = {}) -> String 保存游戏,返回槽文件名。data 缺省时自动收集各系统数据
load(profile_id, slot_name) -> Dictionary 读取槽位数据,槽不存在或版本高于当前返回 {}
list_slots(profile_id) -> Array[Dictionary] 组下槽列表(name / date),最新在前
delete_slot(profile_id, slot_name) 删除槽位
register_saver(callable) -> void 注册数据提供者(见下)
collect_data() -> Dictionary 手动收集所有注册系统的数据

各系统如何接入存档

新增需要存档的系统只需两步,无需改动 SaveSystem

# 例:SectSystem.gd
extends Node

func _ready() -> void:
	SaveSystem.register_saver(_to_save_dict)

## 提供存档数据,返回字典,key 用系统名避免冲突
func _to_save_dict() -> Dictionary:
	return {
		"sect": {
			"name": sect_name,
			"resources": resources,
			"members": members,
		},
	}

## 读档时恢复(由 MainGame 在 load 后按 key 分发调用)
func load_from_dict(d: Dictionary) -> void:
	if not d.has("sect"):
		return
	var sect: Dictionary = d["sect"]
	sect_name = sect.get("name", "")
	...

约定:

  • 每个系统实现 _to_save_dict()(提供数据)和 load_from_dict()(恢复状态)
  • key 用系统名(如 "sect"),避免系统间冲突
  • 读档用 d.get(key, {}) / .get(field, 默认值) 容错,保证旧档兼容
  • 只把游戏内数据放进去;会话参数(当前 mode、profile_id 等)由 GameState 管理,不落档

示例:GameState 保存游戏时间

GameState 是 Autoload(持久对象),在其 _ready 中注册:

# GameState.gd
extends Node

var year: int = 1
var month: int = 1

func _ready() -> void:
	SaveSystem.register_saver(_to_save_dict)

## 提供存档数据:key 为 "game_time",值是游戏时间字段
func _to_save_dict() -> Dictionary:
	return {
		"game_time": {
			"year": year,
			"month": month,
		},
	}

## 读档恢复(MainGame 在 load 后调用 GameState.load_from_dict(data)
func load_from_dict(d: Dictionary) -> void:
	var t: Dictionary = d.get("game_time", {})
	year = int(t.get("year", 1))
	month = int(t.get("month", 1))

存档后 JSON 中的形态:

"game_time": { "year": 12, "month": 7 }

要点:

  • register_saver 只接收无参 Callable,方法名直接传,不要写成 register_saver(_to_save_dict())(那是调用而不是注册)
  • year/month 读档用 get() 带默认值,旧存档没有该字段时也能启动

存档与读档流程

存档(回合结算 / 月度结算 / 关窗前)

# TimeSystem 月度结算处
var slot := SaveSystem.save(GameState.profile_id)
print("已保存:%s" % slot)

读档(主菜单进入游戏)

# MainGame 场景启动时
var data := SaveSystem.load(GameState.profile_id, GameState.save_slot)
if data.is_empty():
	push_error("存档读取失败")
	return
# 按 key 分发给各系统恢复
SectSystem.load_from_dict(data)
...

# 主菜单:列出组和槽供选择
var profiles := SaveSystem.get_profiles()
var slots := SaveSystem.list_slots(pid)   # 选中某组后
GameState.request_load_game(pid, slot["name"])

存档时机建议(回合制)

  • 每月结算时自动 save() 一次,覆盖当前组新建槽
  • 关窗前NOTIFICATION_WM_CLOSE_REQUEST)补存一次,避免丢失当月操作
  • 不需要手动存档 UI;如担心槽无限增长,可加"保留最近 N 槽"的清理逻辑