# จัดแพ็กเกจเกม PlayDrop ด้วย catalogue.json และ .play

จัดโฟลเดอร์เกมที่บิลด์แล้ว ZIP หรือ .play ให้มี catalogue.json ที่ราก ประกาศสื่อของหน้าเกมและเซิร์ฟเวอร์ แล้วอัปโหลดด้วย prepare_catalogue_upload

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

แพ็กเกจ (แพ็กเกจเกม ไม่ใช่แพ็กเกจครีเอเตอร์) คือผลลัพธ์ที่บิลด์แล้วของเกม พร้อม `catalogue.json` ที่อธิบายเกมนั้น เป็นเส้นทางสำหรับทุกฟีเจอร์ของ PlayDrop: หลายไฟล์ SDK ภาพและวิดีโอของหน้าเกม ลีดเดอร์บอร์ด ความสำเร็จ แอสเซ็ต และเซิร์ฟเวอร์ที่โฮสต์ให้ อัปโหลดจากแอป AI ของคุณเป็นโฟลเดอร์ ZIP หรือไฟล์ `.play`

## จัดโฟลเดอร์

วาง `catalogue.json` ไว้ที่ราก ข้างเกมที่บิลด์แล้ว พาธในแคตตาล็อกเป็นแบบสัมพัทธ์จากไฟล์นี้

```
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` เป็นเวอร์ชันแบบ Semantic Versioning ใหม่ทุกครั้งที่อัปโหลดเกมเดิม
- `surfaceTargets` ระบุหน้าจอที่คุณทดสอบแล้ว ส่วน `primarySurface` คือหน้าจอที่เกมออกแบบมา
- สื่อของหน้าเกม ไอคอนความสำเร็จ และไฟล์เริ่มต้นของเซิร์ฟเวอร์ถูกอ้างสิทธิ์โดยแคตตาล็อก: ไฟล์ใน `listing/` ที่ไม่มีฟิลด์ใดอ้างอิงไม่ถือเป็นสื่อของหน้าเกม และไฟล์ที่ไม่มีแอปใดอ้างสิทธิ์จะถูกปฏิเสธก่อนมีการเผยแพร่ใดๆ
- `server.entry` ชี้ไปที่บันเดิลโมดูล ES ที่บิลด์แล้วหนึ่งไฟล์ ดู[เซิร์ฟเวอร์เกมเรียลไทม์](https://www.playdrop.ai/th/create/learn/game-servers)

ทุกฟิลด์และขีดจำกัดอยู่ใน[เอกสารอ้างอิง catalogue.json](https://www.playdrop.ai/th/create/learn/catalogue-json) ไม่มีฟิลด์เวอร์ชันของแคตตาล็อกที่ต้องตั้ง

## อัปโหลดโฟลเดอร์

ส่ง `prepare_catalogue_upload` พร้อม `requestId` ใหม่ `expectedCurrentVersion` (`null` สำหรับเกมใหม่) ออบเจ็กต์แคตตาล็อกฉบับสมบูรณ์ และตัวอธิบายหนึ่งรายการต่อไฟล์ ไฟล์ข้อความขนาดเล็กใส่เป็น `text` แบบอินไลน์ได้ ไฟล์อื่นทั้งหมดเป็นการอัปโหลดแบบมีลายเซ็น: คำนวณขนาด SHA-256 (เลขฐานสิบหกตัวพิมพ์เล็ก 64 ตัว) และ MD5 แบบ base64 ในเครื่องจากไบต์ที่ตรงกันทุกประการ คำขอนี้สำหรับเกมสองไฟล์ครบถ้วนแล้ว ให้แทนที่ตัวยึดตำแหน่งของค่าแฮชสองตัวในไอคอนด้วยค่าจริงของไอคอน

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

คำตอบจะมี URL สำหรับอัปโหลดและเฮดเดอร์ของ `listing/icon.png` PUT ไบต์ของไอคอนที่ตรงกันทุกประการไปที่ URL นั้นพร้อมทุกเฮดเดอร์ที่ได้รับ แล้วเรียก `complete_catalogue_upload` ด้วย `requestId` เดิม `url` ในใบยืนยันจะเปิดเวอร์ชันส่วนตัว

## อัปโหลด ZIP หรือ .play

- **เอเจนต์ที่มีระบบไฟล์** แตกไฟล์บีบอัดในเครื่องแล้วอัปโหลดเป็นโฟลเดอร์ตามด้านบน
- **ChatGPT** ส่งไฟล์บีบอัดที่ระบุได้จากบทสนทนาเป็น `archive` พร้อม `download_url` และ `file_id` จริง โดยไม่มี `catalogue` หรือ `files` ไฟล์บีบอัดมีขนาดได้สูงสุด 20 MiB เมื่อบีบอัด

ไฟล์บีบอัดต้องเป็นไฟล์ ZIP ธรรมดา: ไม่มีการเข้ารหัส ไม่แบ่งหลายส่วน ไม่ใช่ ZIP64 ไม่มีลิงก์สัญลักษณ์ พาธซ้ำ พาธแบบสัมบูรณ์ หรือ `..` ไฟล์ว่างและ catalogue.json ที่ซ้อนอยู่ในโฟลเดอร์จะถูกปฏิเสธ

## ขีดจำกัด

ไฟล์หรือรายการในไฟล์บีบอัด 512 รายการ (รวม catalogue.json และไดเรกทอรี) 50 MiB ต่อไฟล์ 200 MiB เมื่อแตกไฟล์ และ JSON ของแคตตาล็อก 1 MiB บันเดิลเซิร์ฟเวอร์ที่โฮสต์ให้มีขนาดไม่เกิน 2 MiB

## อัปเดตแพ็กเกจ

อ่าน `list_games` คง `name` ของแต่ละแอปไว้ เพิ่ม `version` และตั้ง `expectedCurrentVersion` เป็นเวอร์ชันที่เผยแพร่ปัจจุบัน ส่งสแนปช็อตเต็ม: ทุกไฟล์และทุกฟิลด์ที่เวอร์ชันใหม่ควรมี ไฟล์ที่ไม่เปลี่ยนจากเวอร์ชันที่เก็บไว้ส่งด้วย `reuse: { gameId, version, path }` แทนไบต์ของไฟล์ได้ คีย์ลีดเดอร์บอร์ดและความสำเร็จลบไม่ได้และเปลี่ยนชนิดไม่ได้ ให้เลิกใช้ด้วย `"status": "RETIRED"`

## แอสเซ็ตที่คอนเนกเตอร์ยังอัปโหลดไม่ได้

PlayDrop รันโมเดล 3D ที่รันโค้ดได้ (แอสเซ็ต `MODEL_3D` ที่มีหมวดย่อย `procedural` หรือรูปแบบ `CUSTOM`) ในเครื่องมือของตัวเอง แต่คอนเนกเตอร์จะปฏิเสธ เพราะ API ตรวจสอบโค้ดของโมเดลเหล่านี้ไม่ได้ ให้แปลงเป็นรูปแบบที่รองรับ หรืออ้างอิงแอสเซ็ตที่เผยแพร่บน PlayDrop แล้ว ภาพและสไปรต์ชีตแบบโพรซีเจอรัลที่แปลงเป็นไฟล์สำเร็จรูปแล้วรับได้ ดู[แอสเซ็ตเกม](https://www.playdrop.ai/th/create/learn/game-assets)

## คู่มือที่เกี่ยวข้อง

- [catalogue.json](https://www.playdrop.ai/th/create/learn/catalogue-json): ฟิลด์ catalogue.json ที่เกม PlayDrop ใช้: ตัวตน หน้าเกม โหมดการเล่น หน้าจอ การเข้าสู่ระบบ Instant การโหลดล่วงหน้า สื่อ คำแปล คะแนน และเซิร์ฟเวอร์
- [เผยแพร่จากแอป AI ของคุณ](https://www.playdrop.ai/th/create/learn/mcp-publishing): วิธีที่เอเจนต์ AI เผยแพร่ไปยัง PlayDrop ผ่าน MCP: เส้นทางแบบง่ายและแบบแพ็กเกจ การทดสอบแบบส่วนตัว publish_game_version การอัปเดต ขีดจำกัด และการกู้คืน
- [ภาพและวิดีโอของหน้าเกม](https://www.playdrop.ai/th/create/learn/listing-assets): ภาพและวิดีโอที่หน้าเกม PlayDrop มีได้ พาธ รูปแบบ และแนวหน้าจอ และวิธีทำภาพที่ใช้ได้ดีในฟีด การค้นหา และภาพตัวอย่าง
- [เซิร์ฟเวอร์เกมเรียลไทม์](https://www.playdrop.ai/th/create/learn/game-servers): รันเซิร์ฟเวอร์ Colyseus 0.17 ที่โฮสต์ให้พร้อมฐานข้อมูล MongoDB สำหรับเกมของคุณ เชื่อมต่อจากเบราว์เซอร์ด้วย SDK ทดสอบด้วยผู้เล่นสองคน และรู้ขีดจำกัด
