关于 save-systems
帮助游戏开发者设计跨会话和版本更新的存档方案。内容涵盖状态序列化、崩溃安全写入、存档槽位及旧存档迁移。
主要功能
- 01序列化纯游戏状态数据
- 02版本化结构与迁移
- 03原子写入并保留备份
- 04存档槽位与独立自动存档
- 05防御式加载与验证
适用场景
- 01跨会话保存玩家进度
- 02实现防崩溃存档和快速存档
- 03更新后继续读取旧版本存档
- 04从损坏或无效存档中恢复
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill save-systems安装后在 Claude Code 中输入/save-systems确认已加载。
附带文件references/versioning-and-migration.md
详细介绍
存档系统
存档文件是序列化的游戏状态快照,即使重启后也能保留。 难点不在于写入字节,而在于选择要保存什么、确保写入过程中发生崩溃也不会损坏存档,以及发布补丁后仍能读取旧存档。把这三点做好,剩下的就只是常规工作。
适用场景
- 用于跨会话和游戏更新持久化进度:玩家属性、库存、世界标记、设置、位置等。
- 用于设计存档槽、快速存档/自动存档和防崩溃写入。
- 用于处理内容/代码变更后旧存档失效的问题(版本管理与迁移)。
**不适用场景:**如需了解 Roblox 云持久化的具体内容,请使用
roblox-datastores。如需了解存档序列化的数据模型(资源/SO),请使用
godot-resources / unity-scriptableobjects。对于 Godot 的 FileAccess/
ResourceSaver 和 user:// 路径,请参考 Godot 引擎技能,同时应用此处的模式。
核心工作流
- **确定哪些状态是权威数据。**保存数据(生命值、位置、种子、解锁标记),而不是引擎对象或场景节点。加载时根据数据重建对象——绝不要序列化实时节点引用。
- **定义带版本号的架构。**每个存档都要包含一个
version整数。对于计划持续更新的游戏,这是最重要的字段。 - **选择格式。**为便于阅读和调试,使用 JSON/文本;为减小体积、提升速度或略微防篡改,使用二进制格式。建议从 JSON 开始。
- **原子写入。**先序列化到临时文件,刷新数据,然后重命名并覆盖正式文件。发生崩溃时,留下的要么是旧存档,要么是新存档——绝不会是写了一半的存档。
- **防御性加载。**读取版本 → 迁移到当前版本 → 验证 → 实例化。保留最近一次正常存档的备份,并在解析出错时回退到备份。
- 在安全节点自动存档(关卡切换、检查点),对存档频率进行限制,并使用单独的存档槽,以免覆盖手动存档。
- **验证:**保存、完全退出、重新启动、加载——并通过检查确认状态一致。测试加载上一版本的存档。
模式
1. 将状态序列化为纯数据(而非引擎对象)
# Build a dictionary of pure data. Each savable object reports its own state.
func capture_state() -> Dictionary:
return {
"version": SAVE_VERSION, # ALWAYS stamp the schema version
"player": { "hp": player.hp, "pos": [player.position.x, player.position.y] },
"inventory": player.inventory.to_array(), # ids + counts, not Item nodes
"flags": world.flags, # e.g. {"met_guard": true}
"seed": world.seed, # regenerate procedural content
}
# On load, RECONSTRUCT objects from the data — do not expect live references back.
func apply_state(data: Dictionary) -> void:
player.hp = data["player"]["hp"]
player.position = Vector2(data["player"]["pos"][0], data["player"]["pos"][1])
player.inventory.from_array(data["inventory"])
world.flags = data["flags"]
2. 原子、安全防崩溃写入(临时文件 + 重命名)
# RIGHT: write to a temp file, then atomically rename over the target.
func save_atomic(path: String, data: Dictionary) -> void:
var tmp := path + ".tmp"
var f := FileAccess.open(tmp, FileAccess.WRITE)
f.store_string(JSON.stringify(data))
f.flush() # ensure bytes hit disk
f.close()
DirAccess.rename_absolute(tmp, path) # replaces the target; atomic on POSIX
# WRONG: opening `path` directly and writing in place — a crash mid-write leaves a
# truncated, unloadable save and destroys the player's progress.
在 POSIX 系统上,重命名覆盖目标文件是原子操作(同一卷);在 Windows 上,重命名替换不保证是原子操作,因此请在重命名前将旧文件保留为 path + ".bak"——这个备份才是真正能确保你从错误写入中恢复的保障。
3. 带迁移的版本化加载
SAVE_VERSION = 3
def load_save(raw_bytes):
data = parse(raw_bytes) # JSON/binary -> dict
v = data.get("version", 0)
if v > SAVE_VERSION:
raise NewerSaveError(v) # save is from a newer build; refuse
while v < SAVE_VERSION: # apply migrations in order, v -> v+1
data = MIGRATIONS[v](data)
v += 1
data["version"] = v
validate(data) # check required keys / ranges
return data
# Each migration is a pure function from one version's shape to the next.
def migrate_1_to_2(d):
d["flags"] = {k: True for k in d.pop("completed_quests", [])} # list -> set-map
return d
MIGRATIONS = {1: migrate_1_to_2, 2: migrate_2_to_3}
4. 存档槽 + 限频自动存档
const SLOT_PATH := "user://save_%d.json" # manual slots 0..N
const AUTOSAVE_PATH := "user://autosave.json" # separate file: never clobbers a slot
var _autosave_cooldown := 0.0
func autosave_if_due(dt: float) -> void:
_autosave_cooldown -= dt
if _autosave_cooldown <= 0.0:
save_atomic(AUTOSAVE_PATH, capture_state())
_autosave_cooldown = 60.0 # throttle: at most once a minute
# Trigger an immediate autosave on checkpoints/level transitions, not mid-combat.
常见问题
- 序列化引擎对象/节点路径会使存档依赖场景结构;重命名节点会导致所有旧存档失效。保存数据,并在加载时重建对象。
- **没有版本字段。**补丁发布当天,每个现有存档都会变成一道猜谜题。从版本 1 开始就写入
version。 - 原地写入会在崩溃/断电时损坏存档。务必先写入临时文件再重命名;保留一个
.bak。 - **盲目信任文件。**存档可能被截断、手动编辑,或被云同步覆盖为旧版本。加载时进行验证,失败时回退到备份。
- **浮点数和区域设置。**文本序列化器可能丢失精度,或在某些区域设置下使用逗号作为小数分隔符。请使用与区域设置无关的序列化器。
- 自动存档覆盖手动存档,或在操作过程中触发,导致保存了不一致的状态。使用专用自动存档槽,并在安全节点存档。
- **存储机密信息或信任多人游戏客户端存档。**本地存档由玩家控制;切勿将其视为在线状态的权威数据。对于云存档,请处理设备的数据限制和冲突(
roblox-datastores)。
参考资料
references/versioning-and-migration.md— 架构演进策略、迁移链、备份/回滚、格式权衡(JSON 与二进制),以及加载时验证清单。
相关技能
roblox-datastores— 云端持久化、请求限制和会话锁定。godot-resources、unity-scriptableobjects— 需要序列化的数据模型。procedural-gen— 保存种子以重新生成世界,而不是保存世界本身。rpg、survival-crafting、visual-novel— 与此技能结合使用的游戏类型。
评论
支持 Markdown