Skip to content

存档

This content is not available in your language yet.

基于 EasySave3 的 JSON 文件存档:保存玩家船的金币、全部建筑、武器弹药、容器物品和世界探索状态;当前仅在专用测试场景中可手动触发,主游戏场景尚未接入,也没有任何自动存档。

存档系统(SaveSystemManager.cs,位于 Assets/Scripts/Service/SaveSystem/)负责把一艘船的“建造成果 + 物资 + 世界进度”写入磁盘并在读档时原地重建。底层使用第三方插件 EasySave3(ES3),格式为明文 JSON,不加密、不压缩。

当前接入状态(策划必须知道):存档系统的管理器 SaveSystemManager 和触发器 SaveSystemExample 只被放置在专用测试场景 SaveSystemTest.unity 中;主游戏场景 mvp_002.unity 里没有这两个对象,也没有任何代码在运行时创建它们。换句话说——正常游玩主场景时,玩家既不能存档也不能读档。游戏循环(见 游戏循环与胜负)目前是“单次会话”体验,死亡/胜利后不依赖存档。

存档按“船只编号(shipId)“组织。一次保存会在存档目录下写出 5 个 JSON 文件:

文件名 ES3 内部键 内容
{shipId}_info.json ShipData 船只基础信息(金币、阵营等)
{shipId}_buildings.json BuildingsData 全部建筑(位置、血量、建造进度等)
{shipId}_weapons.json WeaponsData 全部武器的当前弹药数
{shipId}_containers.json ContainersData 全部建筑容器及其中物品
{shipId}_world.json 多个键(见下文) 世界探索/迷雾/物价/新闻

存档目录:持久化数据目录/ShipSaves/。Windows 上即 C:\Users\<用户名>\AppData\LocalLow\<公司名>\<产品名>\ShipSaves\。目录名 ShipSaves 和扩展名 .jsonSaveSystemManager 的 Unity Inspector 序列化字段,可在场景里改。

存档时机:只有手动,没有自动

Section titled “存档时机:只有手动,没有自动”

代码中不存在任何自动存档:没有定时存档、没有过天存档、没有停靠港口存档、没有战斗结束存档、退出游戏时也不保存(全项目无任何“应用退出时写档”逻辑)。SaveSystemExample.cs 里出现的“自动存档完成”字样只是一条日志文案,并无自动触发源。

实际触发方式(全部在 SaveSystemTest.unity 测试场景内):

操作 触发方式 来源
保存 按键,代码默认 Insert SaveSystemExample.cs Inspector 字段 saveKey
读取 按键,代码默认 Home 同上,字段 loadKey
清空当前所有建筑 按键,默认 Delete 同上,字段 clearKey
保存/读取/清空 Unity 编辑器组件右键菜单(ContextMenu) SaveSystemExample.cs
显示存档列表 / 删除全部存档文件 Unity 编辑器组件右键菜单 SaveSystemManager.cs

注意按键的两套值:代码默认值是 Insert/Home(注释说明 F5/F9 已被场景测试工具 ScenarioTestRunner 占用,见 开发者工具),但 SaveSystemTest.unity 场景里序列化保存的仍是旧值 F5 保存 / F9 读取 / Delete 清空(场景文件中 saveKey=286、loadKey=290、clearKey=127,对应 KeyCode 的 F5/F9/Delete)。在该测试场景里实际生效的是场景序列化值。

测试场景中配置的船只编号为 ExampleShip_001SaveSystemExample Inspector 字段 shipId)。

保存过程是异步的(后台线程写文件),不会暂停游戏;保存期间游戏状态若继续变化,各文件之间可能出现微小不一致(先收集建筑、再收集武器、再收集容器,逐步进行)。

保存时从全局注册表现场收集数据(不是持续记录),五类内容如下。

1. 船只基础信息(ShipData.cs——只保存玩家船:

字段 含义 备注
version 数据版本号 恒为字符串 “1.0.0”,写入后从不校验(见“存档版本与兼容”)
shipId 船只编号 保存时被覆盖为本次存档的 shipId
isPlayerShip 是否玩家船
factionId 阵营编号 默认 “Player”
nationality 国籍 默认空
gold 当前金币 玩家经济唯一入档的数值
maxGold 金币上限 代码常量 9999999999(约 100 亿,ShipData.cs

2. 建筑(AllBuildingsInfo.csBuildingData.cs——逐栋保存:

字段组 内容
标识 实例编号(GUID)、建筑名(对应 BuildingConfig.csv 的 Name 列)
位置 网格坐标(格)、建筑层级、相对船体的旋转
状态 当前血量、是否建成、建筑状态(框架/建造中/完成等枚举)
建造进度 材料进度(0-1)、施工进度(0-1)、总工作量、所需材料清单、已送达材料清单
占格 占用的全部网格坐标、外部紧邻坐标

注意:收集范围是全局建筑注册表的所有建筑,并不按船过滤——shipId 只是档案上的标签。在只有一艘船的测试场景中没有问题;若在多船环境(主场景)启用,会把敌船建筑也收进同一份档。

3. 武器(AllWeaponInfo.csWeaponData.cs——每座武器只存两个字段:所属建筑的实例编号 + 当前弹药数(CurrentAmmo)。判定“哪些建筑算武器”的依据是建筑名出现在 WeaponConsumptionConfig.csv 的 WeaponId 列(该表 8 行数据,即 MachineGun、Cannon、Flame、AA_Gun 各自的 Dev/Prod 变体,定义弹匣容量、装填耗时等,详见武器与弹药)。除弹药数以外的武器运行时状态(攻击方式选择、装弹档位、弹药档案、冷却、目标)一律不存。

4. 容器与物品(AllContainerInfo.csSerializableContainerData.csSerializableItemStack.cs——逐建筑保存最多 3 个容器(输入/输出/存储),每个容器保存:

字段 内容 备注
capacity 容器容量(格) 读档时按存档值恢复,不读当前配置
filterItems 过滤器允许的物品编号列表
items 物品堆列表:物品编号、数量、属性列表 属性以“属性名 + 类型名 + 字符串值 + 可合并标记”保存
reservations 预留数据 字段存在但写入恒为空列表——预留序列化未实现(代码注释“暂时跳过”)

只有挂在建筑上的容器会被保存;不在任何建筑容器里的物品(例如搬运途中、地面散落物)不会进入存档。物品与容器机制详见物品与库存

5. 世界状态(WorldStateInfo.cs,聚合 4 个服务写入同一个 _world.json

服务 ES3 键 内容 数值
发现注册表(DiscoveryRegistry.cs DiscoveryCountries / DiscoveryCities / DiscoveryZones 已发现的国家/城市/海域编号集合 发现城市自动连带发现所属国家,连带关系也随存档保留
战争迷雾(FogMaskService.cs FogMaskData 战略图“已探索”位图 80×80 格 = 6400 位,打包为 800 字节(格子尺寸 = 世界最长边 ÷ 80,代码常量)
全局物价倍率(GlobalPriceModifier.cs GlobalPriceModifierData 物品编号 → 价格倍率字典 由“通货变动”新闻累乘产生(±5%/条),默认 1.0
新闻调度(EventScheduler.cs EventSchedulerNews / LastBriefingShownDay / LastInvestigatedDay 全部历史新闻条目、最后一次看简报的天数、最后一次投放新闻的天数 新闻每 7 天投放 1 条(代码常量)

世界状态部分有完整的 PlayMode 往返测试佐证(SaveLoadRoundTripTests.cs:保存 → 销毁全部服务 → 读取 → 四个服务状态完全恢复)。相关玩法见探索与情报交易城市与国家

以下内容完全不进存档,读档后回到场景初始状态:

不保存的内容 后果
船员的一切(属性、需求、心情、关系、职级、位置、任务) 船员;读档不影响船员
游戏时间(当前天数、时刻) 时间与调度;天数从场景初始重新计
船只位置、朝向、速度 ShipData 不含任何位置信息
敌船、怪物、遭遇战进度 遭遇与刷怪
阵营关系、声望 阵营与外交;战争/和平新闻虽存了新闻条目,但读档时不会重放其阵营关系效果
任务/工作队列 任务与工作
生产配方的进行中进度 容器里的原料/成品会存,正在加工的那一炉不存,见生产与制作
容器/物品的预留记录 序列化结构有字段但恒为空
武器的攻击方式/装弹档位/弹药档案选择 只存当前弹药数
玩家手动拆除自动船壳的记录 记录只在内存(AutoHullGenerator.cs 的清除集合不入档):同一次游戏会话内读档仍然生效(重扫时跳过这些格),但重启游戏后再读档记录丢失,拆掉的外壳会被自动补回
流体管道、胜利进度、开发者工具状态 各自系统无存档对接

读档(测试场景按 Home,或场景序列化的 F9)按以下顺序执行(SaveSystemManager.csSaveSystemExample.cs):

  1. 读文件:依次读取 5 个文件。船只信息文件缺失 → 使用默认值(金币 0),不算失败;世界状态文件缺失 → 视为首次启动,正常;建筑/武器/容器三个文件任一读取失败 → 整次读档判定失败,提示“没有找到存档”,什么都不改变。
  2. 应用船只数据:金币、阵营等覆盖到玩家船。
  3. 清场:销毁当前场上全部建筑(GameObject 一并删除)。
  4. 批量重建守卫开启:重建期间挂起自动船壳生成器(防止它在拆墙/建墙事件中插入与存档墙位置冲突的自动墙)。
  5. 逐栋重建建筑
    • 旧建筑名迁移(见下文“存档版本与兼容”);
    • 按建筑名查 BuildingConfig.csv 配置 → 实例化对应预制体;
    • 世界位置按“船当前位置 + 存档网格坐标”重算,世界旋转 = 船体旋转 × 建筑相对旋转(因此船移动过也能正确读档,建筑跟着船走);
    • 恢复血量、建造进度等数据并注册;
    • 配置缺失或预制体缺少建筑组件 → 静默跳过该栋(计数不含它)。
  6. 守卫关闭 + 全量补扫:刷新建筑可见性,广播“重建完成”,自动船壳生成器据此重新扫描一遍外圈补墙(见修建)。
  7. 恢复武器弹药:按建筑实例编号匹配,写回当前弹药数。
  8. 恢复容器:按建筑实例编号匹配,重建容器对象并把物品逐堆放回——放回时按当前配置的物品定义和堆叠规则执行,放不下的部分丢弃(仅日志警告)。
  9. 世界状态在第 1 步读取文件时已由 4 个服务各自恢复完毕(迷雾支持“先读档后初始化”的延迟回放)。

建筑血量的恢复规则:当前血量取存档值,最大血量取预制体上的序列化字段(建筑预制体 Inspector 的 _maxHP,默认 100)。

每个存档文件都写入了版本号字段 version = "1.0.0"AllBuildingsInfo.csAllWeaponInfo.csAllContainerInfo.csShipData.cs 各一份),但全项目没有任何代码读取或比较这个版本号——不存在版本化迁移框架。

唯一的兼容性处理是一条硬编码的建筑名迁移(SaveSystemManager.cs):

旧存档中的 Wall(旧版统一墙,150 血)在读档时自动改名为 WallIron(铁墙)。

选择 WallIron 的原因写在代码注释里:旧 Wall 的最大血量 150 与现行 BuildingConfig.csv 中 WallIron 的 MaxHealth=150 一致(其余墙变体:WallPine 30 血 / WallOak 80 血 / WallBrass 100 血),这样老玩家的船体防御力不会因为墙系统拆分而静默下降。该迁移仅当配置表里已不存在名为 “Wall” 的建筑时才执行。

除此之外,任何配置表与存档的不匹配都靠“跳过 + 日志”兜底,没有报错弹窗,详见下一节。

项目 数值 单位 来源
存档目录名 ShipSaves Inspector 字段(SaveSystemManager,SaveSystemTest 场景)
存档文件扩展名 .json 同上
每艘船的存档文件数 5 代码(SaveSystemManager.cs
存档格式 ES3 File + JSON,明文 代码(各 Info 类的 ES3 设置)
版本号字段 “1.0.0”,写入但从不校验 代码(4 个数据类)
自动存档 代码(全项目无自动触发)
保存按键(代码默认) Insert KeyCode Inspector 字段(SaveSystemExample.cs
读取按键(代码默认) Home KeyCode 同上
清空建筑按键(默认) Delete KeyCode 同上
保存/读取/清空按键(测试场景实际序列化值) F5 / F9 / Delete KeyCode SaveSystemTest.unity 场景序列化
测试场景船只编号 ExampleShip_001 Inspector 字段(SaveSystemExample
金币上限 maxGold 9999999999 金币 代码常量(ShipData.cs
迷雾位图分辨率 80×80 = 6400 格,打包 800 字节 格/字节 代码常量(FogMaskService.cs Grid=80)
迷雾格子尺寸 世界最长边 ÷ 80 代码公式(FogMaskService.cs
迷雾擦除半径 世界最长边 ÷ 10 代码公式(FogMaskService.cs
新闻投放间隔 7 代码常量(EventScheduler.cs IntervalDays)
通货变动新闻价格倍率 0.95 或 1.05(各 50% 概率,累乘) 代码常量(NewsTemplates.cs
旧 Wall 迁移目标 WallIron(MaxHealth 150) 代码(SaveSystemManager.cs)+ CSV(BuildingConfig.csv)
墙变体血量(迁移参照) WallPine 30 / WallOak 80 / WallBrass 100 / WallIron 150 CSV(BuildingConfig.csv,策划可改)
建筑默认最大血量 100 Inspector 字段(建筑预制体 _maxHP
容器默认容量(仅异常兜底路径) 50 代码常量(SerializableContainerData.cs 默认构造)

配置表改动对旧存档的影响(策划必读)

Section titled “配置表改动对旧存档的影响(策划必读)”

改配置表之前,先对照这张表评估对已有存档的破坏。所有不匹配都是静默跳过 + 日志,玩家看不到任何提示。

改动 对旧存档的影响
BuildingConfig.csv 删除某行或改 Name 列 旧存档中该名字的建筑读档时直接消失(查不到配置 → 跳过该栋)。唯一例外是 Wall→WallIron 自动迁移。改建筑名前必须评估存档迁移,或仿照 Wall 在代码里加迁移分支(程序工作,SaveSystemManager.cs)。
BuildingConfig.csv 改 MaxHealth 不影响旧档建筑的当前血量(按存档原值恢复);最大血量实际取预制体 Inspector 字段而非该列,是否会出现“当前血量超过新上限”未在存档链路中处理。
StaticItemConfig.csv 删除某个 ItemId 旧档容器里该物品整堆丢失(恢复时查不到物品配置 → 跳过,错误日志)。见物品数据表
StaticItemConfig.csv 调小堆叠上限等入库规则 物品按当前规则重新放回容器,放不下的部分丢弃(警告日志)。
容器容量类配置(如 BuildingContainerConfig.csv)改容量 对旧存档完全无效:容量从存档原值恢复,老建筑的容器维持旧容量,新建的才用新容量。
WeaponConsumptionConfig.csv 删除某个 WeaponId 该建筑此后不再被当作武器保存(保存时按 WeaponId 匹配建筑名);已有旧档中的弹药数据因按实例编号匹配仍可恢复一次。
WeaponConsumptionConfig.csv 调小 MagazineCapacity 旧档的 CurrentAmmo 按原值写回,可能暂时超过新弹匣容量(存档链路不做钳制)。

通用原则:存档里记录的是“编号/名字 + 数值”,定义本体永远查当前配置表。所以“改数值”大多安全(读档后按新配置表现),“改编号/删行”一定危险(旧档引用悬空 → 内容静默丢失)。

  • 修建:读档即“按图重建”,建造中的框架连同材料/施工进度一起恢复;自动船壳生成器在重建期间被守卫挂起,结束后全量补扫。
  • 建筑:建筑血量、状态随档恢复;血量上限来自预制体。
  • 武器与弹药:仅弹药数入档;武器身份由 WeaponConsumptionConfig.csv 定义。
  • 物品与库存:建筑容器及内容物入档;预留记录不入档。
  • 探索与情报 / 岛屿与陆地:发现集合与战争迷雾位图入档。
  • 交易:全局物价倍率入档,读档后城市报价延续涨跌。
  • 阵营与外交:阵营关系入档;战争新闻条目虽在档内,读档时不重放其效果。
  • 时间与调度:当前天数入档,但新闻系统的“最后投放日/最后简报日”入档——见下节的天数错位问题。
  • 船员 / 任务与工作:完全不入档。
  • 开发者工具:F5/F9 被场景测试工具占用,是存档默认按键改为 Insert/Home 的原因。
想调什么 改哪里
存档目录名 / 文件扩展名 SaveSystemTest.unity 场景中 SaveSystemManager 对象的 Inspector(字段 saveDirectory / saveFileExtension)
存/读/清按键 同场景 SaveSystemExample 对象的 Inspector(saveKey / loadKey / clearKey);注意场景里目前序列化的是 F5/F9/Delete 旧值
测试用船只编号 同上,字段 shipId(当前 ExampleShip_001)
旧建筑名迁移规则 代码(SaveSystemManager.cs 中 Wall→WallIron 分支),需程序添加新迁移
迷雾分辨率 / 新闻间隔 / 金币上限 代码常量(分别在 FogMaskService.csEventScheduler.csShipData.cs),需程序改
让主场景支持存档 需要程序把 SaveSystemManager(及触发入口)接入 mvp_002.unity,并解决“全局建筑注册表不分船”的收集问题——纯程序工作
手工查看/删除存档 直接打开 AppData\LocalLow\<公司>\<产品>\ShipSaves\ 下的 JSON 明文文件;或在编辑器中右键 SaveSystemManager 组件选“清空所有存档”
  • 主场景未接入,无自动存档:存档功能目前是测试场景专属能力。死亡/胜利/退出都不会落盘。
  • 天数错位:当前天数不入档,但新闻系统的“最后投放日”入档。读档后游戏天数从头计,而档内记录的投放日可能远大于当前天数,导致每 7 天一条的新闻在天数追上存档值之前不再投放;新闻列表里的历史日期也会显得“在未来”。
  • 新闻效果不重放:读档恢复的是新闻条目列表本身;战争/和平新闻曾经造成的阵营关系变化不会随读档恢复(阵营关系本身也不入档)。物价倍率因为单独入档,能正确恢复。
  • 预留数据未实现:容器与物品的预留(reservation)序列化结构存在但写入恒为空,相关代码注释明确标注“暂时跳过,后续完善”(SerializableContainerData.csSerializableItemStack.cs)。
  • 保存非原子:5 个文件依次写出,中途异常可能留下“半新半旧”的文件组合;没有备份/回滚机制。
  • 全局收集不分船:保存收集的是全局建筑注册表的所有建筑,shipId 只是标签。多船同场(主场景)下直接启用会把敌船建筑混入玩家档。
  • 静默跳过哲学:配置缺失、预制体缺组件、物品放不下,全部静默跳过并打日志,玩家与策划在游戏内均无感知;排查存档内容丢失问题要看日志(前缀 Save / Container / Registry)。
  • 物品属性以字符串存取:物品的动态属性按“属性名 + 类型名 + 字符串值”保存,读档时按属性名塞回字典;复杂属性(如自定义对象)会退化为字符串。
  • 文档与现状的差异Assets/Scripts/Service/SaveSystem/README.md 仍写“F5 保存 / F9 加载”,与代码默认值(Insert/Home)不符;README 中“压缩和加密设置”的描述也与实际(无压缩无加密)不符。以本页(即代码现状)为准。
  • 版本号是装饰:所有存档文件的 version 字段写死 “1.0.0” 且无任何读取方,未来做格式变更时没有现成的版本判断钩子可用。