Bỏ qua tới nội dung
Các trang tài liệu

Nhà phát triển · Tham chiếu

game.json

Tham chiếu manifest của game — từng trường, kiểu, giá trị mặc định, ràng buộc, category và tag hợp lệ, cách chọn limits.

Trên trang này

game.json mô tả game của bạn cho portal: tên, mô tả, phân loại, hướng màn hình, ảnh đại diện và các giới hạn điểm mà server dùng để chống gian lận. File nằm ở gốc thư mục game, cạnh thư mục build. Đội vận hành kiểm tra nó bằng wsg validate trước khi phát hành; trường nào không có sẽ nhận giá trị mặc định.

Ví dụ đầy đủ

JSONgame.json
{
  "slug": "flappy-demo",
  "version": "1.0.0",
  "title": "Flappy Demo",
  "description": {
    "vi": "Chạm để bay qua ống. Ống càng lúc càng hẹp.",
    "en": "Tap to fly through the pipes. Gaps get tighter."
  },
  "category": "arcade",
  "tags": ["flappy", "one-tap", "pixel", "tap"],
  "orientation": "portrait",
  "controls": ["tap", "keyboard"],
  "thumbnail": "./thumbnail.png",
  "cover": null,
  "sdk": { "major": 1 },
  "limits": {
    "maxScore": 9999,
    "maxScorePerSecond": 3,
    "minDurationMs": 1000,
    "scoreOrder": "desc"
  },
  "build": { "command": "pnpm build", "outDir": "dist" },
  "features": { "leaderboard": true, "rewarded": true, "interstitial": true, "storage": true },
  "minPlayers": 1,
  "maxPlayers": 1,
  "author": { "name": "WSG", "url": null },
  "generated": { "by": null, "pipelineRunId": null }
}

game.json là JSON thuần: không có comment, không có dấu phẩy thừa.

Các trường

TrườngKiểuBắt buộcMặc địnhRàng buộc
slugstringCó—Khớp ^_?[a-z0-9][a-z0-9-]{1,48}$: 2–49 ký tự chữ thường, số, gạch ngang, không bắt đầu bằng gạch ngang. Tiền tố _ dành cho game nội bộ. Phải trùng tên thư mục game
versionstringCó—Semver MAJOR.MINOR.PATCH, cho phép hậu tố như 1.2.0-beta.1. Mỗi lần phát hành dùng một version mới
titlestringCó—1–80 ký tự. Một chuỗi duy nhất, không theo ngôn ngữ
description{ vi, en }Có—Cả vi và en đều bắt buộc, mỗi bản 1–500 ký tự
categorystringCó—Một slug trong bảng category
tagsstring[]Có—1–8 slug trong bảng tag; slug lặp lại bị cảnh báo
orientationstringCó—portrait, landscape hoặc any
controlsstring[]Có—Ít nhất một trong tap, swipe, keyboard, mouse, gamepad
thumbnailstringCó—Đường dẫn tương đối trong thư mục game tới ảnh .png, .jpg, .jpeg hoặc .webp; 512×512, tối đa 150 KB
coverstring | nullKhôngnullĐường dẫn tương đối tới ảnh bìa, nên là 1200×630
sdk.majorintegerKhông1≥ 1; major của SDK game dùng
limitsobjectCó—Xem Chọn limits
build.commandstringKhông"pnpm build"Lệnh build, chạy trong thư mục game
build.outDirstringKhông"dist"Thư mục chứa bundle, phải nằm trong thư mục game; "." nghĩa là không có bước build
featuresobjectKhôngcả bốn trueleaderboard, rewarded, interstitial, storage dạng boolean
minPlayers, maxPlayersintegerKhông1, 1≥ 1 và minPlayers ≤ maxPlayers
author.namestringKhông"unknown"1–80 ký tự
author.urlstring | nullKhôngnullURL hợp lệ hoặc null
generatedobjectKhông{ "by": null, "pipelineRunId": null }Dành cho pipeline sinh game tự động; để null

Ghi chú về một số trường:

  • slug là định danh vĩnh viễn của game: đường dẫn bundle /<slug>/<version>/, trang https://leonogames.com/games/<slug> và gameId truyền vào WSG.init(). Đổi slug là tạo một game khác.
  • orientation quyết định khung iframe trên portal: portrait giữ tỉ lệ 9:16 và cao tối đa bằng màn hình, landscape giữ 16:9, any lấp đầy vùng chơi. Trên điện thoại, portal gợi ý xoay máy khi người chơi cầm sai chiều.
  • thumbnail và cover chỉ dùng cho trang danh sách, không cần nằm trong bundle. Khi phát hành, ảnh được tải lên https://games.leonogames.com/<slug>/media/ và game.json gửi tới API mang URL thật. wsg validate chỉ đọc được kích thước của ảnh PNG; ảnh định dạng khác chỉ nhận cảnh báo.
  • features chỉ được phản ánh qua WSG.isAvailable(); portal hiện không chặn lời gọi theo các cờ này.
  • build: với gói nộp đã build sẵn, đặt command là lệnh không làm gì (ví dụ "echo prebuilt") và outDir trỏ tới thư mục build. Xem Nộp và phát hành.

Category

category nhận đúng một giá trị trong bảng sau:

SlugTên
arcadeArcade
puzzleGiải đố
actionHành động
casualGiải trí
sportsThể thao
racingĐua xe
strategyChiến thuật
card-boardBài và cờ
kidsTrẻ em
io.io

Tag

tags nhận 1–8 slug thuộc bất kỳ nhóm nào dưới đây. Trường controls chỉ nhận các slug của nhóm điều khiển. Slug swipe có ở cả nhóm cơ chế và nhóm điều khiển nhưng chỉ là một giá trị.

NhómSlug hợp lệ
Thể loại (genre)endless-runner, flappy, match-3, merge, stack, snake, breakout, 2048, shooter, platformer, idle, quiz, word
Cơ chế (mechanic)one-tap, timing, physics, drag-drop, swipe, aim, rhythm
Chủ đề (theme)space, animals, food, pixel, neon, fantasy, sports
Điều khiển (controls)tap, swipe, keyboard, mouse, gamepad

Taxonomy được mở rộng dần; cần category hay tag mới, hãy đề xuất với đội vận hành thay vì dùng giá trị gần đúng.

Chọn limits

Server dùng limits để từ chối điểm không thể đạt được bằng cách chơi thật. Đặt quá chặt thì người chơi giỏi bị từ chối oan; quá lỏng thì bảng xếp hạng dễ bị gian lận. Luật đầy đủ ở trang Điểm và bảng xếp hạng.

TrườngKiểuÝ nghĩaLý do từ chối
maxScoreinteger, 0–2^53Điểm cao nhất một lượt có thể đạtover_max
maxScorePerSecondnumber > 0Tốc độ ghi điểm tối đa: điểm chia cho số giây chơirate_too_high
minDurationMsinteger ≥ 0Thời gian chơi tối thiểu trước khi nộp điểmtoo_fast
scoreOrderdesc | ascdesc (mặc định): cao hơn là tốt hơn; asc: thấp hơn là tốt hơn, ví dụ thời gian—

Server đo thời gian chơi (elapsed) từ lần gameplayStart() gần nhất tới lúc nhận điểm, tối thiểu 1 giây. Vì vậy minDurationMs từ 1000 trở xuống không bao giờ gây too_fast (wsg validate cảnh báo khi giá trị dưới 1000).

Cách làm khuyến nghị:

  1. Tính tốc độ ghi điểm tối đa từ thiết kế, không từ tay người: nhịp sinh vật phẩm, thời gian khoá giữa hai lần ghi điểm, điểm thưởng cuối màn. Nếu game cho phép bấm nhanh không giới hạn, hãy thêm giới hạn trong game.
  2. Nhân 1,5 để có biên, ra maxScorePerSecond.
  3. maxScore là điểm cao nhất có thể đạt; game không có trần (chạy vô tận) thì ước lượng thật rộng, vì maxScorePerSecond đã giới hạn điểm theo thời gian. Nên kẹp điểm trong game ở maxScore để không bao giờ nộp vượt.
  4. minDurationMs là thời gian nhanh nhất một lượt hợp lệ có thể kết thúc, trừ đi vài giây cho độ trễ mạng.

Ví dụ tính

Chạy vô tận. +1 điểm mỗi 300 ms sống sót (3,33 điểm/giây) và +5 cho mỗi đồng xu; game sinh tối đa một đồng xu mỗi 2 giây (≤ 2,5 điểm/giây). Tốc độ tối đa là 5,83 điểm/giây, nhân 1,5 ra maxScorePerSecond: 9. Chết sớm nhất sau khoảng 4 giây, chọn minDurationMs: 3000. Điểm kẹp trong game ở 5000, nên maxScore: 5000.

Giải đố có thưởng cuối màn. Mỗi cặp thẻ khớp +10, mỗi lần lật phải chờ ít nhất 750 ms, qua màn được thưởng thêm. Bot biết hết đáp án qua màn 1 được 148 điểm trong 6,9 giây, tức 21,4 điểm/giây; đặt maxScorePerSecond: 25 (biên nhỏ hơn 1,5 vì con số này đã là giới hạn tuyệt đối của thiết kế). Lưu ý điểm thưởng dồn vào cuối màn vẫn được chia cho toàn bộ thời gian từ gameplayStart().

Đua thời gian (asc). Điểm là số mili giây hoàn thành màn. Vì điểm xấp xỉ bằng thời gian chơi, điểm chia cho số giây luôn gần 1000, nên maxScorePerSecond phải từ 1000 trở lên, ví dụ 1200. minDurationMs là thời gian hoàn thành nhanh nhất có thể (ví dụ 20000), maxScore là thời gian chậm nhất còn được ghi nhận (ví dụ 600000, tức 10 phút).

JSONgame.json (trích)
{
  "limits": { "maxScore": 600000, "maxScorePerSecond": 1200, "minDurationMs": 20000, "scoreOrder": "asc" }
}

Cảnh báo

Các luật hiện tại được thiết kế cho desc. Với asc, một thời gian khai báo thấp hơn thực tế (ví dụ chơi 30 giây nhưng nộp 1 ms) vẫn qua cả ba kiểm tra. Nếu bảng xếp hạng quan trọng, cân nhắc quy đổi sang điểm desc, ví dụ điểm = số giây còn lại × 10.

Kiểm tra game.json

Đội vận hành (hoặc bạn, nếu có repo) chạy wsg validate trên thư mục game. Lệnh kiểm tra: JSON hợp lệ và đúng schema; slug trùng tên thư mục; category và tags có trong taxonomy (tag trùng chỉ cảnh báo); ảnh thumbnail tồn tại trong thư mục game, đúng định dạng, tối đa 150 KB, đúng 512×512 nếu là PNG; cover nên là 1200×630 (chỉ cảnh báo); build.outDir nằm trong thư mục game; minPlayers ≤ maxPlayers; minDurationMs dưới 1000 thì cảnh báo.

Kết quả hợp lệ
$ pnpm wsg validate tap-rush
  OK    game.json hợp lệ: tap-rush@1.0.0 "Tap Rush"
  OK    thumbnail ./thumbnail.png 512×512, 3.6 KB

validate: OK (0 lỗi, 0 cảnh báo)
Kết quả có lỗi
$ pnpm wsg validate tap-rush
  ERR   game.json version: version phải là semver, ví dụ 1.0.0
  ERR   game.json category: "shooter" không có trong category trong taxonomy (arcade, puzzle, action, casual, sports, racing, strategy, card-board, kids, io)

validate: FAIL (2 lỗi, 0 cảnh báo)