v3.0.0 新增
ctx.steam 让扩展使用 Steam 能力。剧本里「解锁 Steam 成就」和「Steam 操作」能做的事,扩展都能做,另外还能读取成就列表和解锁情况。 只有项目开启了「启用 Steam 支持」、导出成桌面版的游戏才真正连到 Steam。Studio 预览、扩展沙箱和网页版里每个调用都返回 { ok: false, reason: "unavailable" },所以界面要能在没有 Steam 时正常显示。项目配置见 Steam 桌面集成。

调用结果

除 isAvailable() 外,所有方法都返回 Promise,并且不抛错。先判断 result.ok:
type SteamResult<T> =
  | { ok: true; data: T }
  | { ok: false; reason: SteamFailureReason; message?: string };
reason含义处理
unavailable连不到 Steam:编辑器预览、网页版、项目没开 Steam,或 Steam 客户端没运行、没登录显示扩展自己的离线内容
invalid参数不合法,比如成就标识为空、统计值不是整数改正调用
failedSteam 拒绝了请求或暂时失败,message 里是排查用的代号记录日志或稍后重试
unsupported游戏是用旧版 Studio 导出的用新版 Studio 重新导出

API

方法返回的 data说明
isAvailable()同步返回 boolean当前游戏是否接入了 Steam。true 不代表已连上,连接情况看 getStatus()
getStatus()SteamStatus连接状态、玩家昵称、语言和浮层状态
achievements.list()SteamAchievement[]Steamworks 后台配置的全部成就和当前玩家的解锁情况
achievements.unlock(apiName)SteamUnlockOutcome解锁一项成就
achievements.showProgress(apiName, current, maximum)无弹出成就进度提示,不解锁。current 必须小于 maximum,达到上限时直接调用 unlock
stats.get(name)number读取整数统计
stats.set(name, value)写入后的值写入整数统计
stats.add(name, amount)写入后的值在当前值上加减,amount 可以是负数
richPresence.set(key, value)无设置好友列表里的状态文字。value 传空字符串等于删除这一项,最多同时保留 20 项
richPresence.clear()无清空本游戏设置过的全部状态
dlc.get(appId)SteamDlcState玩家是否拥有、是否已安装这个 DLC
overlay.open(target, options?)无打开 Steam 浮层。target 是 "friends"、"community"、"achievements"、"store";"store" 可以用 options.appId 指定商店页面,默认是本游戏
takeScreenshot(caption?)无截取当前画面存进玩家的 Steam 截图库,caption 是截图说明

SteamStatus

字段说明
connected是否已连上 Steam 并登录了玩家账号
appId项目设置里填写的 Steam App ID
playerName玩家的 Steam 昵称,没连上时没有
language玩家 Steam 客户端的语言代号,如 "schinese"、"english",没连上时没有
overlayEnabledSteam 浮层能否打开
overlayActive浮层此刻是否正打开着

SteamAchievement

字段说明
apiName成就标识,也就是 Steamworks 后台的 API Name,解锁时用它
name / description标题和描述,按玩家的 Steam 语言返回
hidden后台设为隐藏的成就,未解锁前通常不要展示标题和描述
unlocked当前玩家是否已解锁
unlockTime解锁时刻的毫秒时间戳,未解锁时没有

SteamUnlockOutcome

值含义
unlocked本次解锁成功
already-unlocked之前已经解锁过
queuedSteam 暂时没接受,已记下,之后自动重试

案例:显示成就列表

输入: 导出的 Steam 桌面版游戏,后台配置了成就 first_ending(标题「第一个结局」,已解锁)和隐藏成就 true_ending(未解锁)。
function AchievementList() {
  const ctx = useExtensionContext();
  const [items, setItems] = useState<SteamAchievement[] | null>(null);

  useEffect(() => {
    let active = true;
    void ctx.steam.achievements.list().then((result) => {
      if (active && result.ok) setItems(result.data);
    });
    return () => { active = false; };
  }, [ctx]);

  if (!items) return <p>未连接 Steam</p>;
  return (
    <ul>
      {items.map((item) => (
        <li key={item.apiName}>
          {item.hidden && !item.unlocked ? "隐藏成就" : item.name}
          {item.unlocked ? "(已解锁)" : ""}
        </li>
      ))}
    </ul>
  );
}
输出: 列表显示「第一个结局(已解锁)」和「隐藏成就」。在 Studio 预览里 result.ok 为 false,组件显示「未连接 Steam」。
游戏刚启动时 Steam 可能还没把成就数据发过来,拿到空列表可以稍后再读一次。

案例:在剧本方法里解锁成就

static clearChapter = method({
  title: "通关章节并解锁成就",
  async run(ctx) {
    if (ctx.flow.isRestoring) return;
    const result = await ctx.steam.achievements.unlock("chapter_1_clear");
    if (!result.ok && result.reason !== "unavailable") {
      console.warn("解锁成就失败", result.reason, result.message);
    }
  },
  runImmediately() {
    // 历史重放不重复解锁。
  },
});
输出: 在 Steam 桌面版里成就被解锁,result.data 是 unlocked;重复调用时是 already-unlocked。在预览和网页版里 reason 是 unavailable,方法不会报错。

限制

  • 读档、回放重建时(ctx.flow.isRestoring 为 true,或方法的 runImmediately)不要解锁成就、不要改统计,否则会重复结算。
  • 统计只支持整数,统计名和成就标识都要先在 Steamworks 后台配置。
  • 打开浮层需要项目开启「Steam 浮层」。截图需要同时开启「Steam 浮层」和「Steam 截图库」,两次截图至少间隔 1 秒。
  • 扩展自己的成就系统(存档里的成就)和 Steam 成就互相独立。要两边同步,就在解锁本地成就时调用 ctx.steam.achievements.unlock()。
使用这组接口需要新版 SDK。老扩展先在程序开发界面「同步 SDK」,并把 extension.json 的 sdkVersion 声明为 >=3.0.0。需要兼容旧版 Studio 时,用 ctx.steam?.isAvailable() 这类可选链读取。