# 用 catalogue.json 和 .play 打包 PlayDrop 游戏

整理一个根目录带有 catalogue.json 的已构建游戏文件夹、ZIP 或 .play，声明商店页面媒体和服务器，并用 prepare_catalogue_upload 上传。

Source: https://www.playdrop.ai/zh-hans/create/learn/packaging

包是游戏的构建产物，加上一个描述它的 `catalogue.json`。所有 PlayDrop 功能都通过包来实现：多个文件、SDK、商店页面图片和视频、排行榜、成就、素材以及托管服务器。你可以从 AI 应用以文件夹、ZIP 或 `.play` 文件的形式上传它。

## 整理文件夹

把 `catalogue.json` 放在根目录，与已构建的游戏放在一起。catalogue 中的路径都相对于它。

```
catalogue.json
index.html
assets/sprites.png
assets/music.mp3
listing/icon.png
listing/hero-landscape.png
listing/hero-portrait.png
listing/screenshots/landscape-1.png
listing/videos/portrait-1.mp4
achievements/first-win.png
server/index.mjs
```

`.play` 文件就是把这个文件夹压缩成 ZIP（`catalogue.json` 位于压缩包根目录，而不是在外层文件夹中），再重命名为 `.play`。它只包含构建产物：不要包含 `node_modules`、你不想公开的源映射文件或 `.env` 文件。源代码项目由你的用户保管；PlayDrop 无法返回它。

## 编写 catalogue.json

```json
{
  "apps": [{
    "name": "moon-jump",
    "displayName": "Moon Jump",
    "subtitle": "Lunar platform hopping against the clock",
    "description": "Jump between lunar platforms, collect oxygen and reach the flag.",
    "controls": "Tap or press Space to jump. Swipe or use arrows to steer.",
    "file": "index.html",
    "version": "1.0.0",
    "visibility": "PRIVATE",
    "authMode": "OPTIONAL",
    "tags": ["game-genre/casual", "game-genre/platformer"],
    "types": ["solo"],
    "controller": { "support": "optional", "type": "simpler" },
    "surfaceTargets": { "desktop": true, "mobileLandscape": true, "mobilePortrait": true },
    "primarySurface": "MOBILE_PORTRAIT",
    "sourceLocale": "en",
    "listing": {
      "icon": "listing/icon.png",
      "heroLandscape": "listing/hero-landscape.png",
      "heroPortrait": "listing/hero-portrait.png",
      "screenshotsLandscape": ["listing/screenshots/landscape-1.png"],
      "videosPortrait": ["listing/videos/portrait-1.mp4"]
    },
    "leaderboards": [{ "key": "best-time", "displayName": "Best time", "description": "Fastest run to the flag",
      "scoreType": "TIME_MS", "sort": "ASC" }],
    "achievements": [{ "key": "first-win", "displayName": "First flag", "description": "Reach the flag once",
      "kind": "STANDARD", "icon": "achievements/first-win.png" }],
    "server": { "entry": "server/index.mjs", "rooms": ["game"] }
  }]
}
```

- `file` 是启动游戏的页面的确切路径。没有它时，入口默认为 `<name>.html`，而不是 `index.html`。
- 同一款游戏每次上传，`version` 都要使用新的语义化版本号。
- `surfaceTargets` 列出你测试过的显示形态；`primarySurface` 是游戏设计时针对的显示形态。
- 商店页面媒体、成就图标和服务器入口都由 catalogue 认领：`listing/` 下没有被任何字段引用的文件不是商店页面媒体，而没有被任何应用认领的文件会在发布任何内容之前被拒绝。
- `server.entry` 指向一个已构建的 ES 模块包；请参阅[实时游戏服务器](https://www.playdrop.ai/zh-hans/create/learn/game-servers)。

所有字段及其限制都在 [catalogue.json 参考](https://www.playdrop.ai/zh-hans/create/learn/catalogue-json)中。不需要设置 catalogue 版本字段。

## 上传文件夹

用新的 `requestId`、`expectedCurrentVersion`（新游戏为 `null`）、完整的 catalogue 对象和每个文件一个描述符发送 `prepare_catalogue_upload`。小的文本文件可以是内联 `text`。其他所有文件都使用签名上传：根据确切的字节在本地计算它的大小、SHA-256（64 个小写十六进制字符）和 base64 编码的 MD5。下面这个两文件游戏的请求是完整的；把图标上的两个摘要占位符替换成图标的真实值即可。

```json
{
  "requestId": "moon-jump-1.0.0",
  "expectedCurrentVersion": { "moon-jump": null },
  "catalogue": {
    "apps": [{
      "name": "moon-jump",
      "displayName": "Moon Jump",
      "file": "index.html",
      "version": "1.0.0",
      "visibility": "PRIVATE",
      "authMode": "OPTIONAL",
      "tags": ["game-genre/casual"],
      "types": ["solo"],
      "controller": { "support": "off" },
      "surfaceTargets": { "desktop": true, "mobileLandscape": false, "mobilePortrait": true },
      "listing": { "icon": "listing/icon.png" }
    }]
  },
  "files": [
    { "path": "index.html",
      "text": "<!doctype html><html><head><meta charset=\"utf-8\"><title>Moon Jump</title></head><body><canvas id=\"game\"></canvas><script>/* built game */</script></body></html>" },
    { "path": "listing/icon.png", "contentType": "image/png", "sizeBytes": 50211,
      "sha256": "REPLACE_WITH_ICON_SHA256_HEX", "md5Base64": "REPLACE_WITH_ICON_MD5_BASE64" }
  ]
}
```

响应会列出 `listing/icon.png` 的上传 URL 和请求头。把图标的确切字节 PUT 到该 URL，并带上返回的所有请求头，然后用同一个 `requestId` 调用 `complete_catalogue_upload`。回执中的 `url` 会打开这个私密版本。

## 上传 ZIP 或 .play

- **有文件系统的 Agent** 先在本地解压压缩包，再像上面那样以文件夹形式上传。
- **ChatGPT** 把它从对话中解析出的压缩包作为 `archive` 传入，附上真实的 `download_url` 和 `file_id`，不传 `catalogue` 或 `files`。压缩包压缩后最大 20 MiB。

压缩包必须是普通的 ZIP 文件：不能加密、分卷、使用 ZIP64、包含符号链接、重复路径、绝对路径或 `..`。空文件和嵌套的 catalogue.json 文件会被拒绝。

## 限制

512 个文件或压缩包条目（包括 catalogue.json 和目录），每个文件 50 MiB，解压后 200 MiB，catalogue JSON 最大 1 MiB。托管服务器包最大 2 MiB。

## 更新包

读取 `list_games`，保留每个应用的 `name`，提高它的 `version`，并把 `expectedCurrentVersion` 设为当前已发布的版本。发送完整快照：新版本应包含的每个文件和每个字段。保留版本中未改动的文件可以用 `reuse: { gameId, version, path }` 发送，而不必发送它们的字节。排行榜和成就的键不能删除，也不能更改类型；请用 `"status": "RETIRED"` 停用。

## 连接器暂时无法上传的素材

PlayDrop 会在自己的工具中运行可执行的 3D 模型（带有 `procedural` 子类别或 `CUSTOM` 格式的 `MODEL_3D` 素材），但连接器会拒绝它们，因为 API 无法验证它们的代码。请将它们烘焙为受支持的格式，或引用 PlayDrop 上已发布的素材。已烘焙的程序化图片和精灵图可以上传。请参阅[游戏素材](https://www.playdrop.ai/zh-hans/create/learn/game-assets)。

## 相关指南

- [catalogue.json](https://www.playdrop.ai/zh-hans/create/learn/catalogue-json): PlayDrop 游戏使用的 catalogue.json 字段：标识、商店页面、游玩方式、显示形态、登录、即玩、预加载、媒体、翻译、分数和服务器。
- [从你的 AI 应用发布](https://www.playdrop.ai/zh-hans/create/learn/mcp-publishing): AI Agent 如何通过 MCP 发布到 PlayDrop：简单方式和包方式、私密测试、publish_game_version、更新、限制和恢复。
- [商店页面图片和视频](https://www.playdrop.ai/zh-hans/create/learn/listing-assets): PlayDrop 游戏可以有哪些商店页面图片和视频，它们的路径、格式和方向，以及如何制作在信息流、搜索和链接预览中都好看的图片。
- [实时游戏服务器](https://www.playdrop.ai/zh-hans/create/learn/game-servers): 为你的游戏运行托管的 Colyseus 0.17 服务器和 MongoDB 数据库，用 SDK 从浏览器连接，用两位玩家测试，并了解各项限制。
