昨天刚把本地存档(user:// + JSON)搭好,今天往前一步:让存档和分数跟着玩家的 Steam 账号走。这是 Steam 独立游戏里「性价比最高的一项加分体验」——玩家换一台电脑、一台 Steam Deck,进度还在;打完一局,分数直接进全球榜。这一篇把 Steam Cloud 和 Leaderboards 两个 Steamworks 功能一次讲清。

今日速览

  • Steam Cloud 有两条路:Auto-Cloud(后台配路径通配符,零代码)和 Cloud API / Remote Storage(代码里 fileWrite/fileRead,灵活可控)。新手先用 Auto-Cloud,需要跨平台统一或动态控制再上 API。
  • Godot 的存档天然写在 user://,正好能被 Auto-Cloud 覆盖——只需要在后台填对 Root Path。
  • 云存档三条铁律:文件要小、常变的和少变的分开存、绝对不要同步分辨率/显卡这类机器相关设置。
  • Steam Deck 的「挂起不退出」会引出同步盲区,需要 Dynamic Cloud Sync(beginFileWriteBatch / getLocalFileChange)兜底。
  • 排行榜(Leaderboards)每个 App 最多 10000 个,每个玩家每榜一条;分数是 int32,还能带最多 64 个 int32 的附加数据;把 Writes 设为 Trusted 可防客户端作弊。

核心学习内容

一、云存档:Auto-Cloud 还是 Cloud API?

Steam Cloud 的本质是「透明的远程文件存储」:游戏退出后,指定的文件自动上传;换机器时,在游戏启动前自动下载回本地。玩家可以在 Steam 设置里全局或按游戏关闭它,所以你的代码永远要假设可能没有云。

Auto-Cloud(推荐起步):在 Steamworks 后台 App Admin → Cloud → Auto-Cloud 里配置。关键是 Root Path 与通配符:

  • Windows:WinAppDataRoaming/Godot/app_userdata/<你的项目名>/,文件匹配 *.save
  • Linux:LinuxHome/.local/share/godot/app_userdata/<你的项目名>/
  • macOS:MacHome/Library/Application Support/Godot/app_userdata/<你的项目名>/

只要把 Godot 的存档写进 user://save_1.save,Auto-Cloud 就能自动抓到,一行代码都不用改。

Cloud API(Remote Storage):当你要做「只在特定时机同步」「跨平台路径统一」「云端/本地冲突选择」时用它。GodotSteam 里最常用的几个:

Steam.file_write("save_1.json", data)          # 写入云
Steam.file_read("save_1.json")                 # 读取(可能返回空)
Steam.file_exists("save_1.json")               # 判断云端是否存在
Steam.is_cloud_enabled_for_account()           # 账号是否开启云
Steam.is_cloud_enabled_for_app()               # 本游戏是否开启云

实践建议:本地仍是唯一真相源。启动时先读本地;本地不存在再尝试从云拉。写入时「先写本地 → 再推云」。这样即使云不可用,游戏也照常能玩。

二、Dynamic Cloud Sync:Steam Deck 时代的补丁

传统实现只在「游戏启动时」读云,于是有了经典事故:Deck 上挂起不退出 → 换台式机继续玩 → 回来唤醒 Deck,发现进度没同步。Valve 的解法是 Dynamic Cloud Sync:让正在运行的游戏也能感知云端变化。

GodotSteam 提供 get_local_file_change_count() 和 get_local_file_change(i) 枚举运行期间发生的本地文件变更,并建议用 begin_file_write_batch() / end_file_write_batch() 把「一次存档的多文件写入」包成原子批次,避免云同步读到半截状态。写云存档的代码,最好一开始就按「批量写」来设计。

三、排行榜:让分数变成社交货币

排行榜能让一个单机游戏长出「再打一把」的动力。规则要点:

  • 后台 Stats & Achievements → Leaderboards 创建;Sort Method 计时的用 Ascending、比分的用 Descending。
  • Writes = Trusted 时,客户端不能直接写分,只能用服务端 WebAPI 写——想防作弊就选它(但需要你搭后端)。
  • 分数是 int32,附带数据最多 64 个 int32(记录关卡、时长、build 版本等,Steam 不解析、每次覆盖)。

GodotSteam 的调用是异步 + 信号模式:

Steam.find_or_create_leaderboard("HIGH_SCORE", Steam.LEADERBOARD_SORT_METHOD_DESCENDING, Steam.LEADERBOARD_DISPLAY_TYPE_NUMERIC)
# → 信号 leaderboard_find_result(handle, found)

Steam.upload_leaderboard_score(handle, Steam.LEADERBOARD_UPLOAD_SCORE_METHOD_KEEP_BEST, score, [level_id])
# → 信号 leaderboard_score_uploaded(result)

Steam.download_leaderboard_entries(handle, Steam.LEADERBOARD_DATA_REQUEST_GLOBAL, 1, 100)
# → 信号 leaderboard_scores_downloaded(...)

全局榜用 GLOBAL,好友榜用 FRIENDS,还可以用 download_leaderboard_entries_for_users() 拉指定好友。注意:上传前必须先拿到 handle,所以流程永远是「find → 存 handle → upload/download」。

🎯 DeepSeek 提示词

提示词 1:生成一个带云存档的 SaveManager 单例

你是资深 Godot 4 + GodotSteam 开发者。请用 GDScript 写一个 Autoload 单例 SaveManager,
实现「本地优先、云端备份」的存档系统。

要求:
1. 本地存档写在 user://save_1.json,用 JSON 序列化 dict。
2. 提供 save_game(data: Dictionary) 与 load_game() -> Dictionary。
3. 写入顺序:先写本地文件,成功后再调用 Steam.file_write("save_1.json", json_string) 推云。
4. 读取顺序:先读本地;本地不存在时,若 Steam.is_cloud_enabled_for_app() 为真,
   用 Steam.file_exists / Steam.file_read 从云拉取并落回本地。
5. 所有 Steam 调用必须判空(非 Steam 环境/离线时不能崩)。
6. 写云时用 begin_file_write_batch() / end_file_write_batch() 包成批次。

输出格式:只给完整 GDScript 代码 + 顶部 5 行注释说明挂载方式(Autoload 名称),不要长篇解释。
约束:Godot 4.7,GodotSteam 4.x API,变量命名用 snake_case。

用法:新建项目时直接生成,10 分钟拿到一套能上架用的存档底座。

提示词 2:生成排行榜封装(上传 + 全球/好友榜下载)

你是 GodotSteam 专家。请写一个 Leaderboard.gd 节点脚本,封装排行榜的查找、上传与下载。

输入:排行榜 API 名称 "HIGH_SCORE",分数为 int,附带数据为 [level_id, time_seconds]。
要求:
1. 用 find_or_create_leaderboard 拿到 handle,存入变量,并处理 leaderboard_find_result 信号。
2. 提供 submit_score(score, extra_data) ,用 KEEP_BEST 策略上传,处理 leaderboard_score_uploaded。
3. 提供 fetch_top(range_start, range_end) 与 fetch_friends(),分别用
   LEADERBOARD_DATA_REQUEST_GLOBAL / FRIENDS,处理 leaderboard_scores_downloaded,
   把结果整理成 [{player_name, score, rank}] 数组并 emit 自定义信号。
4. 处理异步竞态:若 handle 尚未就绪就被调用,先缓存请求、拿到 handle 后补发。

输出格式:完整 GDScript + 信号定义列表。约束:Godot 4.7,不要阻塞主线程。

用法:把「找榜 → 传分 → 拉榜」的模板化工作交给 AI,你只专心做玩法。

提示词 3:让 DeepSeek 审查云存档的边界问题

你是一名严格的代码审查员。以下是一段 Godot + GodotSteam 的云存档代码,请只从
「边界与健壮性」角度审查,列出问题清单并给出修改后的代码。

请重点检查:1) 云配额超限/大文件导致的同步延迟;2) 多设备并发写入的冲突覆盖;
3) 离线或 Steam 未初始化时的崩溃风险;4) 机器相关设置被误同步;
5) 存档版本升级时的兼容性;6) 是否有半截文件被上传。

输出格式:表格列出 [问题 | 风险等级 | 修复建议],然后给出修改后的关键函数代码。
约束:不要重写全部逻辑,只改有问题的部分;每条建议要能直接落地。

【粘贴你的代码】

用法:每次加完云/榜功能后跑一遍,能提前拦掉一批上线后才暴露的同步事故。

学习心得

对「用 DeepSeek 做一款 Steam 游戏」来说,云存档和排行榜是典型的**「不搞不定生死、但极大影响口碑」**的功能:它们不难,难在边界情况多(离线、多设备、配额、作弊)。这恰好是 AI 最擅长的部分——把繁琐的脚手架和边界检查交给 DeepSeek 生成,你只负责定义「本地优先」这条架构原则并验收。今天的三个提示词合起来,其实就是一套「Steamworks 玩家数据层」的最小可用实现。

明日关注

  • 在 Steamworks 后台为测试 App 配好 Auto-Cloud 的 Root Path 与通配符,确认 user:// 存档能被抓到。
  • 把昨天的本地存档升级为「本地优先 + 云备份」,并写一个「离线不崩」的单元测试。
  • 建一个 HIGH_SCORE 测试榜,跑通 find → upload → download 全流程,验证好友榜与全球榜的差异。

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