ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Godot 多存档槽:3 层实现加 4 个高频坑的存档搭建指南

Godot 多存档槽:3 层实现加 4 个高频坑的存档搭建指南 Godot 多存档槽3 层实现加 4 个高频坑的存档搭建指南【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot测试时玩家总反馈进度被覆盖昨天打到第三关今天一开档回到第一关。单存档文件最经不起这种事一次异常写入就能毁掉全部进度。Godot 多存档槽游戏存档管理就是为了解决这个把进度拆进多个独立槽位坏一个不连坐。本文只讲本地存档的存盘、读档、备份和槽位管理云同步和加密不在范围内。心智模型存档槽像银行分账号多存档槽好比把存款分成几个子账户转错钱只影响一个账户每个账户还各有一张存折记录最近动向。类比到此为止往下看三个核心概念。游戏状态体位置、血量、背包这些真正要保存的数据由你的游戏逻辑定义结构。槽位元数据每个槽的占用状态、最后修改时间、缩略图路径。它让 UI 能列出槽位而不必挨个打开存档。数据流方向游戏逻辑把状态喂给存档管理器管理器序列化成 JSON写入user://下的槽位目录读档时反向流动反序列化后交还游戏逻辑。分层实现数据层定义存档结构这一层回答一个槽位里到底放什么。Godot 的存档文件默认落在user://指向的用户目录跨平台都可用。目录按槽位分子文件夹结构长这样user:// ├── saves/ │ ├── slot_0/ │ │ ├── data.json # 游戏状态 │ │ └── preview.png # 缩略图 │ └── slot_1/ └── backups/状态体设计成扁平字典别用深层嵌套# 扁平结构便于版本升级时按键取字段深层嵌套改起来伤筋动骨 func build_save_data() - Dictionary: return { version: 1, # 格式版本号迁移逻辑靠它判断 playtime: 1250, # 游戏时长 level: 3, player: { position: global_position, health: 87, }, inventory: [wood, iron], }数据层的底座是 JSON 类与 FileAccess 类前者负责字典变文本后者负责文本落盘引擎里分别见 JSON 和 FileAccess。数据定了谁来编排写文件、备份、元数据交给一个管理器。逻辑层存档管理器编排读写这一层回答存、读、删三个动作按什么顺序发生。管理器做成单例UI 和场景逻辑都能直接引用。class_name SaveManager extends Node const MAX_SLOTS : 3 const SAVE_DIR : user://saves/ const BACKUP_DIR : user://backups/ const META_FILE : slots_metadata.json static var instance: SaveManager func _ready() - void: # 单例防重场景里意外挂了第二份就自我销毁 if instance: queue_free() else: instance self # 启动就建目录避免存档时才发现写不进去 DirAccess.make_dir_recursive_absolute(SAVE_DIR) DirAccess.make_dir_recursive_absolute(BACKUP_DIR)func save_game(slot_id: int, data: Dictionary) - bool: if slot_id 0 or slot_id MAX_SLOTS: return false var slot_dir : %sslot_%d % [SAVE_DIR, slot_id] DirAccess.make_dir_recursive_absolute(slot_dir) create_backup(slot_id) # 先备份旧档再覆盖写坏也有退路 var file : FileAccess.open(slot_dir.path_join(data.json), FileAccess.WRITE) if file null: return false file.store_string(JSON.stringify(data)) file.close() update_metadata(slot_id) return true func load_game(slot_id: int) - Dictionary: var path : %sslot_%d/data.json % [SAVE_DIR, slot_id] if not FileAccess.file_exists(path): return {} # 文本先读出再反序列化中间任何一步失败都不会污染内存状态 var file : FileAccess.open(path, FileAccess.READ) var data JSON.parse_string(file.get_as_text()) file.close() return data if data is Dictionary else {}# 元数据只存槽位是否被占用具体信息从文件本身取避免两边不一致 func update_metadata(slot_id: int) - void: var meta: Variant {} if FileAccess.file_exists(SAVE_DIR.path_join(META_FILE)): var file : FileAccess.open(SAVE_DIR.path_join(META_FILE), FileAccess.READ) meta JSON.parse_string(file.get_as_text()) file.close() if not (meta is Dictionary): meta {} meta[String(slot_id)] {used: true, timestamp: Time.get_unix_time_from_system()} var out : FileAccess.open(SAVE_DIR.path_join(META_FILE), FileAccess.WRITE) out.store_string(JSON.stringify(meta)) out.close() func delete_slot(slot_id: int) - void: var dir : DirAccess.open(%sslot_%d % [SAVE_DIR, slot_id]) if dir: dir.delete_dir() update_metadata(slot_id)管理器挂到主场景下即可全局使用存档时机交给游戏逻辑自己挑。接下来让界面能点。交互层槽位选择界面这一层回答玩家怎么看见槽、怎么选槽。UI 不该关心文件格式和备份只管把槽位 ID 递给SaveManager。推荐这个节点结构CanvasLayer └── PanelContainer ├── Label # 标题选择存档槽 ├── HBoxContainer │ ├── Button # 删除选中槽 │ └── Button # 返回 └── ScrollContainer └── VBoxContainer # 运行时实例化槽位按钮# 循环生成槽位按钮UI 只传槽位 ID读档细节全在 SaveManager 里 for i in range(SaveManager.MAX_SLOTS): var slot_btn : SaveSlotButton.new() # 自定义控件带预览图和标签 slot_btn.slot_id i slot_btn.pressed.connect(_on_slot_pressed, [i]) $PanelContainer/ScrollContainer/VBoxContainer.add_child(slot_btn) func _on_slot_pressed(slot_id: int) - void: var data : SaveManager.instance.load_game(slot_id) if data.is_empty(): return get_tree().change_scene_to_file(res://game.tscn) # 状态在场景加载后统一灌入避免半初始化对象被写入 game_ref.apply_save_data(data)槽位按钮的缩略图可以复用在场景里弹 FileDialog 截图的思路把当前视口get_texture()存成 PNG 放进槽位目录。到此三层就绪剩下的坑提前说。容易翻车的 4 个点1. 手拼路径Windows 上找不到文件症状Linux 上正常Windows 读档永远返回空。根因%s/slot_%d % dir在 Windows 下拼出dir/slot_0正反斜杠混用。修法统一用slot_dir.path_join(data.json)或path_concat()。2. 旧存档读不了版本号漂移症状加新字段后老玩家的档直接报错或字段缺失。根因格式升级没有版本字段解析端无从判断新旧。修法数据里恒带version: 1读档时按版本走迁移分支老版本逐级升。3. 快速连存备份互相覆盖症状连点两次存盘备份只剩一份。根因时间戳秒级精度相同两次备份写进同一文件名。修法备份名加唯一后缀如slot_0_1719600000_3.json或写入前FileAccess.file_exists()检测重名。4. 备份目录只进不出症状玩几个月user://backups/占了几百 MB。根因每次存盘都新建备份从不清理。修法备份时按FileAccess.get_modified_time()排序只留最近 3 份。选型决策存档格式怎么选维度JSON 文本Variant 文本二进制 压缩可读性高可直接手改中语法生僻低需调试器看扩展成本低加键即可低高需兼容与迁移包体积中中小可再压缩适用场景文本型游戏状态图省事大状态、含二进制资源结论一般 2D/3D 游戏的状态就几百 KB选JSON——可手改、好调试、迁移直观。只有开放世界标记、完整建造数据这种大状态才值得上二进制加压缩。落地 Checklist在user://下规划saves/与backups/目录启动时自动创建。主场景挂载SaveManager单例确认SaveManager.instance可用。在退出、过关卡、进战斗前三个时机触发save_game()。存档界面按槽位循环生成按钮事件只传槽位 ID。数据字典加version字段写好旧版本到当前的迁移分支。备份保留策略设为最近 3 份超出自动清理。在 Windows 与 Android 各跑一轮存、读、删、覆盖的回归测试。跑通以上清单你的 Godot 存档系统就进入了联调阶段。【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表