# Đóng gói game PlayDrop với catalogue.json và .play

Sắp xếp thư mục game đã dựng, tệp ZIP hoặc .play có catalogue.json ở thư mục gốc, khai báo media giới thiệu, máy chủ, rồi tải lên bằng prepare_catalogue_upload.

Source: https://www.playdrop.ai/vi/create/learn/packaging

Một gói là bản dựng của game cộng với một `catalogue.json` mô tả nó. Đây là cách dùng được cho mọi tính năng PlayDrop: nhiều tệp, SDK, hình ảnh và video giới thiệu, bảng xếp hạng, thành tích, tài nguyên và máy chủ được lưu trữ. Hãy tải gói lên từ ứng dụng AI của bạn dưới dạng một thư mục, một tệp ZIP hoặc một tệp `.play`.

## Sắp xếp thư mục

Đặt `catalogue.json` ở thư mục gốc, cạnh game đã dựng. Các đường dẫn trong catalogue là tương đối so với tệp này.

```
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
```

Một tệp `.play` là thư mục này được nén ZIP với `catalogue.json` ở gốc tệp nén, không nằm trong một thư mục bao ngoài, rồi đổi đuôi thành `.play`. Nó chỉ chứa tệp đã dựng: không có `node_modules`, source map bạn không muốn công khai hay tệp `.env`. Hãy để người dùng của bạn giữ dự án mã nguồn; PlayDrop không thể trả lại nó.

## Viết 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` là đường dẫn chính xác của trang khởi động game. Nếu không có trường này, trang vào mặc định là `<name>.html`, không phải `index.html`.
- `version` là một phiên bản ngữ nghĩa mới cho mỗi lần tải lên cùng một game.
- `surfaceTargets` liệt kê các loại màn hình bạn đã thử; `primarySurface` là loại màn hình chính mà game được thiết kế để chơi.
- Media giới thiệu, biểu tượng thành tích và tệp vào của máy chủ phải được khai báo trong catalogue: một tệp trong `listing/` mà không trường nào tham chiếu thì không phải media giới thiệu, và các tệp không thuộc ứng dụng nào sẽ bị từ chối trước khi bất cứ thứ gì được xuất bản.
- `server.entry` trỏ tới một bundle module ES đã dựng; xem [máy chủ game thời gian thực](https://www.playdrop.ai/vi/create/learn/game-servers).

Mọi trường và giới hạn của chúng có trong [tài liệu tham khảo catalogue.json](https://www.playdrop.ai/vi/create/learn/catalogue-json). Không có trường phiên bản catalogue nào cần đặt.

## Tải lên một thư mục

Gửi `prepare_catalogue_upload` với một `requestId` mới, `expectedCurrentVersion` (`null` cho game mới), toàn bộ đối tượng catalogue và mỗi tệp một bộ mô tả. Các tệp văn bản nhỏ có thể dùng `text` nội tuyến. Mọi tệp khác là tải lên có chữ ký: hãy tính kích thước, SHA-256 (64 ký tự hex viết thường) và MD5 dạng base64 trên máy từ chính xác các byte của tệp. Yêu cầu dưới đây cho một game hai tệp là hoàn chỉnh; hãy thay hai giá trị giữ chỗ của mã băm trên biểu tượng bằng giá trị thật của biểu tượng.

```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" }
  ]
}
```

Phản hồi liệt kê một URL tải lên và các header cho `listing/icon.png`. PUT chính xác các byte của biểu tượng lên đó kèm mọi header được trả về, rồi gọi `complete_catalogue_upload` với cùng `requestId`. `url` của biên nhận mở phiên bản riêng tư.

## Tải lên tệp ZIP hoặc .play

- **Agent có hệ thống tệp** giải nén tệp nén trên máy và tải lên dưới dạng thư mục, như trên.
- **ChatGPT** truyền tệp nén mà nó đã phân giải từ cuộc trò chuyện dưới dạng `archive`, với `download_url` và `file_id` thật, và không có `catalogue` hay `files`. Tệp nén có thể lên đến 20 MiB khi nén.

Tệp nén phải là tệp ZIP thông thường: không mã hóa, không chia nhiều phần, không ZIP64, không liên kết tượng trưng, không đường dẫn trùng lặp, không đường dẫn tuyệt đối hay `..`. Tệp rỗng và các tệp catalogue.json lồng bên trong đều bị từ chối.

## Giới hạn

512 tệp hoặc mục trong tệp nén (kể cả catalogue.json và các thư mục), 50 MiB mỗi tệp, 200 MiB khi giải nén và 1 MiB JSON catalogue. Bundle của máy chủ được lưu trữ tối đa 2 MiB.

## Cập nhật một gói

Đọc `list_games`, giữ nguyên `name` của từng ứng dụng, tăng `version` của nó và đặt `expectedCurrentVersion` bằng phiên bản đang xuất bản. Gửi bản chụp đầy đủ: mọi tệp và trường mà phiên bản mới cần có. Các tệp không đổi của một phiên bản được lưu giữ có thể được gửi bằng `reuse: { gameId, version, path }` thay vì gửi các byte của chúng. Không thể xóa hay đổi loại của khóa bảng xếp hạng và thành tích; hãy ngừng dùng một khóa bằng `"status": "RETIRED"`.

## Tài nguyên connector chưa tải lên được

PlayDrop chạy mô hình 3D thực thi được (tài nguyên `MODEL_3D` có danh mục con `procedural` hoặc định dạng `CUSTOM`) trong các công cụ riêng của nó, nhưng connector từ chối chúng vì API không thể xác thực mã của chúng. Hãy bake chúng sang một định dạng được hỗ trợ, hoặc tham chiếu một tài nguyên đã được xuất bản trên PlayDrop. Hình ảnh và spritesheet procedural đã bake vẫn được chấp nhận. Xem [tài nguyên game](https://www.playdrop.ai/vi/create/learn/game-assets).

## Hướng dẫn liên quan

- [catalogue.json](https://www.playdrop.ai/vi/create/learn/catalogue-json): Các trường catalogue.json game PlayDrop dùng: định danh, trang giới thiệu, chế độ chơi, màn hình, đăng nhập, Instant, tải trước, media, bản dịch, điểm và máy chủ.
- [Xuất bản từ ứng dụng AI](https://www.playdrop.ai/vi/create/learn/mcp-publishing): Cách agent AI xuất bản lên PlayDrop qua MCP: cách đơn giản và cách dùng gói, thử riêng tư, publish_game_version, cập nhật, giới hạn và khôi phục.
- [Hình ảnh và video giới thiệu](https://www.playdrop.ai/vi/create/learn/listing-assets): Hình ảnh và video giới thiệu mà game PlayDrop có thể có, đường dẫn, định dạng, hướng màn hình, và cách làm hình ảnh hiệu quả trong bảng tin, tìm kiếm, bản xem trước.
- [Máy chủ game thời gian thực](https://www.playdrop.ai/vi/create/learn/game-servers): Chạy máy chủ Colyseus 0.17 được lưu trữ kèm cơ sở dữ liệu MongoDB cho game, kết nối từ trình duyệt bằng SDK, thử với hai người chơi và nắm rõ giới hạn.
