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 | 参数不合法,比如成就标识为空、统计值不是整数 | 改正调用 |
failed | Steam 拒绝了请求或暂时失败,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",没连上时没有 |
overlayEnabled | Steam 浮层能否打开 |
overlayActive | 浮层此刻是否正打开着 |
SteamAchievement
| 字段 | 说明 |
|---|
apiName | 成就标识,也就是 Steamworks 后台的 API Name,解锁时用它 |
name / description | 标题和描述,按玩家的 Steam 语言返回 |
hidden | 后台设为隐藏的成就,未解锁前通常不要展示标题和描述 |
unlocked | 当前玩家是否已解锁 |
unlockTime | 解锁时刻的毫秒时间戳,未解锁时没有 |
SteamUnlockOutcome
| 值 | 含义 |
|---|
unlocked | 本次解锁成功 |
already-unlocked | 之前已经解锁过 |
queued | Steam 暂时没接受,已记下,之后自动重试 |
案例:显示成就列表
输入: 导出的 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() 这类可选链读取。