Files
2026-08-30 22:25:23 +08:00

6.9 KiB
Raw Permalink Blame History

存档数据结构

定义游戏存档中需要持久化的数据。仅覆盖局外状态与已结束单局的结算记录;进行中的单局使用临时状态,不写入存档。 相关设计:Docs/GameDesign/06_PrototypeScope.md(存档相关验收项)、Docs/GameDesign/05_LootAndProgression.md(物品归属)。 代码位置:Assets/GameMain/Scripts/Base/Definition/DataStruct/(SaveData、ItemStack、CharacterSave、LoadoutSave、RunRecord),枚举复用 Luban 生成的 RunResultType、DifficultyTier。

设计原则

  • 存档只保存局外状态:主仓库内容、角色装备、当前战备配置、已结束单局的结算记录。
  • 背包、保险箱、单局进度均为进入单局时创建的临时状态,不落盘;异常关闭直接丢弃。
  • 数值上限(堆叠上限、背包格数等)来自 Luban 配表(ItemConfig.StackLimit、GlobalConfig.BackpackSlotCount 等),不写死在存档中。
  • 存档使用 JSON 序列化,便于调试与手动修改;SaveData.ToJson() 序列化,SaveData.FromJson(string) 反序列化并补齐缺失字段(空列表/空数组)。

序列化格式

枚举在 JSON 中以整数值存储:RunResultType(Extracted=1、Defeated=2、TimedOut=3、Quit=4)、DifficultyTier(Tier1=1、Tier2=2、Tier3=3)。

{
  "version": 1,
  "updatedAt": 1756540000000,
  "mainWarehouse": [
    { "itemId": 1001, "count": 3 },
    { "itemId": 2005, "count": 1 }
  ],
  "characters": [
    { "characterId": 1, "weaponItemId": 1001, "armorItemId": 0 },
    { "characterId": 2, "weaponItemId": 0,    "armorItemId": 0 }
  ],
  "loadout": {
    "partyCharacterIds": [1, 2],
    "carriedItems": [
      { "itemId": 3002, "count": 5 }
    ],
    "difficultyId": 1
  },
  "runHistory": [
    {
      "outcome": 1,
      "difficultyId": 1,
      "seed": 2026083001,
      "startedAt": 1756540000000,
      "endedAt": 1756541200000
    }
  ]
}

结构定义

SaveData(存档根)

字段 类型 说明
version int 存档结构版本,当前为 1;结构变更时递增并处理迁移
updatedAt long 最后写入时间,Unix 毫秒
mainWarehouse ItemStack[] 主仓库内容,格子数上限由 GlobalConfig.WarehouseSlotCount 配置,可空
characters CharacterSave[] 拥有的角色,数组顺序即角色入队顺序(速度并列时按此顺序行动)
loadout LoadoutSave 当前战备配置
runHistory RunRecord[] 已结束单局的结算记录,可空

ItemStack(物品堆叠)

字段 类型 说明
itemId int 物品 ID,对应 ItemConfig.Id
count int 数量,不超过配表 ItemConfig.StackLimit

所有物品容器统一使用堆叠列表存储,不记录格子在背包中的位置。

CharacterSave(角色)

字段 类型 说明
characterId int 角色 ID,对应 CharacterConfig.Id
weaponItemId int 武器栏物品 ID,0 表示空栏
armorItemId int 防具栏物品 ID,0 表示空栏

装备穿戴在角色装备栏,不占用共享背包格子;成功撤离与任何非全员阵亡的战斗后均保持穿戴,死亡结算时随角色丢失,结算逻辑负责处理,存档本身不记录"阵亡"等局内状态。

LoadoutSave(战备配置)

字段 类型 说明
partyCharacterIds int[] 出战角色 ID 及顺序,1~4 人,顺序决定同速时的行动次序
carriedItems ItemStack[] 携带进入地图的物品,开局时填充共享背包;首版无局内效果
difficultyId DifficultyTier 本局难度,对应 DifficultyConfig 主键

随机数(seed)不属于战备配置:每次进入单局时输入,只写入该局对应的 RunRecord,不持久化到 loadout。

RunRecord(结算记录)

字段 类型 说明
outcome RunResultType 结算结果
difficultyId DifficultyTier 本局难度
seed long 本局使用的随机数
startedAt long 进入单局时间,Unix 毫秒
endedAt long 结算时间,Unix 毫秒

结算结果枚举复用 Luban 生成的 RunResultType:

值 说明
Extracted 成功撤离
Defeated 全员阵亡,单局失败
TimedOut 25 分钟上限未撤离,撤离失败
Quit 玩家主动退出单局,按撤离失败结算

Defeated、TimedOut、Quit 均只保留保险箱内容,仅用于结算记录的区分。

生命周期

  • 新存档:characters 由 GlobalConfig.NewGameCharacterIds 生成,初始装备取 CharacterConfig.WeaponItemId / ArmorItemId;mainWarehouse、loadout.partyCharacterIds、loadout.carriedItems、runHistory 为空。
  • 进入单局:根据存档中的局外状态创建仅供本局使用的临时状态(背包、保险箱、单局进度),单局进行中只修改临时状态,不写入存档。
  • 正常结算(撤离成功 / 死亡 / 超时 / 主动退出):按结算规则把物品写入主仓库或丢弃,追加一条 RunRecord,一次性写盘。
  • 异常关闭:临时状态丢弃,存档保持进入该局前的状态,不产生结算记录。

读写组件

代码位置:Assets/GameMain/Scripts/Runtime/CustomComponent/Save/SaveComponent.cs,通过 GameEntry.Save 访问,挂在 Launcher 场景 Customs/Save 物体上。

API 说明
Data 当前内存中的存档数据,未加载或未创建时为 null
HasSave 磁盘上是否已存在存档文件
IsReady 存档数据是否可用
Load() 从磁盘读取存档;文件不存在返回 true 且 HasSave 为 false,解析失败返回 false
CreateNewGame() 依据配表创建新存档数据(不写盘)
Save() 将当前存档写入磁盘,自动更新 updatedAt;先写临时文件再替换,避免写入中断损坏存档
  • 存档文件路径为 Application.persistentDataPath 下的 save.json(SaveComponent 的 _fileName 可配置)。
  • 加载时若 version 与 SaveData.CurrentVersion 不一致,记录警告并继续加载,迁移逻辑后续需要时补充。

验收对应

对应 Docs/GameDesign/06_PrototypeScope.md 存档相关验收项:

  • 退出并重新打开游戏后,局外主仓库、角色装备和战备配置能够从本地存档恢复。
  • 已结束单局的结算数据能够写入本地存档并在重新打开游戏后读取。
  • 重新打开游戏时不会恢复尚未结束的单局。
  • 单局中主动退出时能够按撤离失败结算,保存保险箱内容和本局失败记录。
  • 异常关闭的未结算单局不会修改局外存档,也不会生成结算记录。

还没决定的问题

暂无。存档的加密、校验与多存档位支持不在原型范围内,后续需要时再补充。