今日速览

  • 存档的本质:把场景里散落的「状态」收集成「数据」,下次启动再反向还原——Godot 官方推荐用**分组(Group)**标记需要存档的节点
  • 标准流程:每个节点实现 save() 返回字典 → JSON.stringify 序列化 → FileAccess 写入 user:// 路径;读档时逐行 JSON.parse → load() 场景重建节点 → set() 还原属性
  • JSON 存不了 Vector2 / Vector3 / Color / Quaternion,位置要拆成 pos_x / pos_y 分别存
  • 数据量大或类型复杂时换二进制序列化(store_var / get_var):文件更小、类型支持更全、自定义类不用手写编解码
  • 用户设置(音量、画质)别混进存档,用专门的 ConfigFile 类

核心学习内容

1. 存档第一步:圈出「要存的东西」

官方做法是把需要持久化的节点加入 “Persist” 分组(编辑器或代码里都行),存档时:

var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
    if !node.has_method("save"): continue   # 没有 save() 的节点跳过
    var node_data = node.call("save")
    save_file.store_line(JSON.stringify(node_data))

每个节点自己写 save() 返回字典,load() 时从字典还原。谁的数据谁负责,存档管理器只做「收集 + 写入」,游戏加新系统时不用改管理器——这就是官方强调的”可扩展存档”。

2. 序列化与写入:JSON + user:// 组合拳

  • 路径用 user://savegame.save:Godot 自动映射到系统的用户数据目录(Windows 是 %APPDATA%,Steam 云存档也认这个目录),打包发布后依然可写;
  • 字典里所有值必须是 JSON 支持的类型:Vector2 等要拆字段存(pos_x/pos_y),自定义类要自己写转换逻辑;
  • 每行一条 JSON 记录,天然支持多个存档对象,也方便调试(记事本直接打开看)。

3. 读档:反着来一遍

读档三步:① 检查文件存在 → ② 清场(queue_free() 掉现有 Persist 节点,避免克隆重复)→ ③ 逐行解析,load(filename).instantiate() 重建节点、add_child 到原 parent、set() 还原其余属性。

两个易踩的坑:Persist 节点不要互相嵌套(parent 路径会失效,嵌套就分阶段存、先存父);读档时游戏状态重置逻辑因项目而异,官方明确说”这部分要自己根据项目定制”。

4. 什么时候换二进制?

JSON 优势是人类可读、好调试;但文本体积大、类型有限。当存档包含复杂状态或自定义资源时,用 FileAccess 的 store_var(node_data) / get_var() 走二进制序列化:文件小、几乎支持所有类型、自定义类免手写编解码(注意只有带 PROPERTY_USAGE_STORAGE 标志的属性才会被序列化)。小游戏起步用 JSON 足够,别过度设计。

🎯 DeepSeek 提示词

提示词 1:完整存档管理器(autoload + 分组模式)

你是一位 Godot 4 游戏开发专家。请为我实现一个通用存档系统,包含两部分:

1. SaveManager.gd(autoload 单例):
   - save_game(slot_index: int):遍历 "Persist" 分组的节点,调用每个节点的 save() 方法,把返回的字典用 JSON.stringify 序列化后写入 user://save_{slot_index}.save(一行一个对象,参考 Godot 官方 Saving games 教程)
   - load_game(slot_index: int):文件不存在则返回 false;先释放场景中现有的 Persist 分组节点,再逐行读取、JSON.parse,用 node_data["filename"] 实例化场景、add_child 到 node_data["parent"]、恢复 pos_x/pos_y 并 set() 其余属性;解析失败打印错误并跳过该行
   - 提供 has_save(slot_index) 和 delete_save(slot_index) 辅助方法
2. 附一份「节点接入示例」:一个 CharacterBody2D 玩家脚本如何实现 save() / load_from_dict(data) 两个方法接入系统

输出格式:完整可运行的 .gd 代码 + 每段关键代码一行中文注释 + 一个 100 字的使用说明。
约束:只用 Godot 4.x 内置 API;save() 返回的字典只能包含 JSON 兼容类型(Vector2 要拆成 pos_x/pos_y);处理"节点缺少 save() 方法"的情况。

适用场景:第一次给游戏加存档功能。生成后把玩家、背包、关卡等节点加入 Persist 分组并实现 save()/load 方法即可接入。

提示词 2:多存档槽 + 自动存档 + 存档信息预览

你是 Godot 4 游戏开发者。请扩展一个存档系统,增加以下能力:

1. 多存档槽(最多 3 个):每个槽独立文件 user://save_{slot}.save,互不影响
2. 自动存档:提供 autosave() 方法,调用时写入 user://autosave.save,并记录时间戳;游戏主循环每 60 秒自动触发一次
3. 存档预览信息:每次保存时额外写入一个 save_info_{slot}.json,包含 {slot, play_time_seconds, level_name, player_level, timestamp},供存档选择界面显示"第 3 关 · 5小时 · Lv.8"
4. 损坏容错:load 时若某一行 JSON 解析失败,跳过该行并继续加载其余数据,同时返回"存档有损坏行"的警告

要求:给出每个新增功能的函数签名和实现思路,关键函数给出完整代码;说明自动存档与手动存档在 UI 上如何区分(角标/文字)。

适用场景:存档从”能存能读”升级为”玩家可用”的完整功能——选存档、自动存档、坏档不崩溃,都是 Steam 玩家很在意的体验。

提示词 3:存档格式升级与迁移(防”更新毁档”)

你是 Godot 4 游戏开发专家。我的游戏存档是 JSON 格式(user://save_0.save,一行一个对象),现在要新增字段并重构数据结构,需要写一个迁移工具:

1. 在存档文件头部加入格式版本号(如第一行存 {"format_version": 2})
2. 写 migrate_save(old_path, new_path) 函数:读取旧文件,检测版本号;若版本低于当前版本,按迁移规则逐行转换(例如:旧字段 "hp" 改名为 "current_health" 并乘以 2,新增默认值字段 "mana": 100),写入新文件
3. 版本升级失败时保留旧文件备份(save_0.save.bak),不覆盖原档
4. 打印迁移日志:每行说明"哪条记录做了什么转换"

输出格式:完整迁移脚本代码 + 迁移规则表(旧字段 / 新字段 / 转换逻辑 / 默认值)+ 使用步骤。
约束:迁移必须幂等(重复执行结果一致);不允许静默丢弃数据,无法转换的字段要打印警告。

适用场景:游戏迭代加了新系统(技能树、背包)后,老玩家存档还能用。Steam 正式发布后”更新毁档”是差评高发区,迁移工具越早备越好。

学习心得

存档是”玩家时间”的保险柜——对独立游戏来说,存档坏了 = 玩家流失。好消息是存档系统是高度模式化的代码:分组标记、字典收集、序列化、重建节点,四步套路固定,正是 DeepSeek 最擅长生成的类型。今天学到的核心是”先定结构再写代码”:让 DeepSeek 先按官方 Persist 模式生成管理器,再加多槽位和自动存档,最后补迁移工具,三步就能得到一个发布级的存档系统,全程不用手写一行。

明日关注

  • 用提示词 1 生成 SaveManager,接入玩家节点实测「存档→重启→读档」全流程
  • 测试边界情况:存档文件被手动改坏一行,验证损坏容错是否生效
  • 查一下 Steamworks 的云存档(Cloud Storage)接口,为后续接入 Steam 版做准备

本文由自动化学习流水线每日生成,坚持即学即记。