---
type: tutorial
title: 构建一个应用市场游戏
summary: "从一个空文件夹构建一个 Caputchin 游戏：安装 SDK、注册一个工厂、从逐回合上下文渲染、记录一条轨迹、并为沙箱化的 iframe 打包它。"
---

到这篇教程结束时，你将有一个完整的、可玩的 Caputchin 游戏，装在一个 JavaScript 包里，准备好被做成可回放并发布。这是那个枯燥的、每一步都做的版本；先读 [向应用市场交付一个游戏](/docs/marketplace-game-development/overview) 看它适配在哪里。

你需要 Node 和一个打包器（esbuild、rollup、vite 或 webpack，它们中任何一个）。构建不需要一个 Caputchin 账户，只有发布才需要。

## 1. 安装 SDK

```bash
npm install @caputchin/game-sdk
```

SDK 很小：一个 `register` 助手加上 TypeScript 类型。它不捆绑组件运行时，所以你的游戏保持小巧。

## 2. 注册一个游戏工厂

一个游戏就是带着一个 **工厂函数** 对 `register` 的单一一个调用。组件在沙箱化的 iframe 里调用你的工厂；那个调用本身就是那个开始信号（没有一个单独的开始事件要等）。

```js
import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container - a DOM element inside the sandboxed iframe; render into it.
  // bridge    - push-only channel to the host (pass / error / setSize).
  // ctx       - the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
```

你 **不** 把清单传给 `register`；服务器在索引时读 `caputchin.json`，并把解析出的预设作为 `ctx` 送到你的工厂里。完整的面见 [SDK 参考](/docs/marketplace-game-development/game-sdk-reference)。

## 3. 从逐回合上下文渲染

工厂的第三个参数，`ctx`，携带逐访客变化的一切：

- `ctx.seed`：逐回合的种子。**从这里派生所有随机性**（见第 5 步）。在一个已核验会话之外为 `null`。
- `ctx.locale`：解析出的语言字符串（`ctx.locale._lang` 加上你的键），或 `null`。
- `ctx.skin`：解析出的颜色和资源 URL（`ctx.skin._theme`，始终是 `light` 或 `dark`，加上你的键），或 `null`。
- `ctx.config`：解析出的玩法配置，或 `null`。

从这些上面读你自己的键；你自己从不解析预设。当你的清单声明没有相匹配的块时，每一个都是 `null`，所以总是回退到一个内置默认：

```js
function startGame(container, bridge, ctx) {
  const title = ctx.locale?.title ?? "Tap the targets";
  const accent = ctx.skin?.accent_color ?? "#2da44e";
  const targetCount = ctx.config?.target_count ?? 5;
  // ...render with these...
}
```

如果你的布局需要一个明确的尺寸，就在你第一次绘制之后调用 `bridge.setSize(width, height)` 一次。

## 4. 获胜、落败和错误

你的游戏决定玩家何时获胜，并通过 bridge 告诉宿主：

- 在一次获胜时，带着这个回合的 [轨迹](#5-record-a-trace)（第 5 步）调用 `bridge.pass({ trace })`。
- 在一次落败或放弃时，**什么都不调用**；沉默就是那个失败信号。
- 如果游戏自己坏了（一个资源失败、一个异常），就调用 `bridge.error({ code, message })`。那是给一个游戏内部失败的，不是给一个玩家落败的。

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
  bridge.error({ code: "asset-load-failed", message: String(err) });
}
```

## 5. 记录一条轨迹 [#5-record-a-trace]

这是那个让游戏 **可回放** 的步骤。当玩家行动时，把驱动结果的那些输入（他们按顺序击中哪个目标，如果时机要紧也带上时机）记录下来，并把那条记录序列化成一个字符串或字节数组：那就是你的 **轨迹**。和种子结合起来，这条轨迹必须能让你的逻辑重现那个确切的结果，因为服务器重跑它。

两条规则让那成为可能：

1. **从 `ctx.seed` 派生每一个随机选择。** 绝不为任何影响结果的东西调用 `Math.random()` 或读墙上时钟。
2. **记录足够多以便回放。** 轨迹加上种子就是你游戏逻辑的完整输入。

```js
function makeRng(seed) {
  // seed is ctx.seed; feed it so the server reproduces the same sequence.
  let s = hashSeed(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

`seed`、轨迹和裁定的形状由 [回放契约](/docs/marketplace-game-development/replay-contract) 定义；那一页讲把这次记录下来的游玩变成服务器回放的那个 headless `run` 工件。如果手写确定性逻辑听起来麻烦，那个 [引擎套件](/docs/marketplace-game-development/engine-kit) 可选地替你做这件事。

## 6. 为沙箱打包

组件恰好加载一个脚本 URL，所以一切都必须在那一个文件里。把你的打包器配置为一个自包含的产物：

- 把资源内联成 data URL（精灵图、声音、字体）；
- 禁用代码拆分；
- 如果你用 WASM，把它作为 base64 嵌入，并从字节实例化。

那些在不透明来源、严格 CSP 的 iframe 里面 **不** 起作用的模式：

- 路径相对的 `fetch('./sprite.png')`：没有一个可供拉取的路径；
- 动态的 `import('./chunk.js')`：那第二个 URL 被挡掉；
- `new Worker('./worker.js')`：改从内联的 `Blob` URL 生成 worker；
- 运行时的外部 CDN 拉取：`connect-src` 把它们挡掉。

## 7. 在本地测试它

没有特殊的测试床。把组件嵌入一个静态 HTML 页面，把 `game-src` 指向你本地的包产物，并在一个浏览器里玩它：

```html
<caputchin-game sitekey="cpt_pub_..." game-src="http://localhost:8080/game.js"></caputchin-game>
```

这和一个 [自定义游戏](/docs/custom-game-development/self-hosted-game) 所用的 `game-src` 路径相同；对于应用市场，你将改为发布这个仓库，好让平台替你固定这个包。

## 接下来的步骤

- [回放契约](/docs/marketplace-game-development/replay-contract)：把你记录下来的游玩变成那个让游戏可回放的 `run` 工件。
- [caputchin.json 清单](/docs/marketplace-game-development/the-caputchin-json-manifest)：描述这个游戏、它的预设和它的包。
- [发布到应用市场](/docs/marketplace-game-development/publish-to-the-marketplace)：给仓库打标签并上线。

## 另见

- [游戏 SDK 参考](/docs/marketplace-game-development/game-sdk-reference)：每一个导出，完整版。
- [engine-kit](/docs/marketplace-game-development/engine-kit)：免费获得确定性的那个可选方式。
