Set your project up once and every npm run build gives you a gamero.zip with everything the game page needs: the game, its details, cover, screenshots and texts. Upload it, check the preview, publish.
Want a scoreboard or achievements? Add one script tag and your game gets leaderboards and awards, shown right next to it on the game page.
Players
The prompt now also explains Gamero multiplayer: online rooms for 2–16 players, with no server code to write.
Using Claude Code, Cursor, Copilot or a chat assistant? Paste this prompt into it, inside your game project. It describes everything below: the build, screens/, game.json with every allowed value and the rules of “Best strategy game”, the ZIP script, and how to add leaderboards and awards if your game has a score or achievements, plus how to build online multiplayer with Gamero rooms.
You are helping me publish this web game on Gamero (a weekly game-jam site). Set up the project so that `npm run build` produces a ready-to-upload `gamero.zip` in the project root. When I upload that ZIP, Gamero unpacks the game AND reads a `screens/` folder that fills in the WHOLE game page: title, tagline, genre, controls, cover, screenshots, description, how-to-play. Naming must follow the rules below exactly; anything unexpected makes the upload fail.
I'm entering the challenge "Best strategy game". It requires "category": "Strategy". Its requirements:
- Must be a strategy game – decisions and planning should matter more than reflexes
- Playable in the browser on both mobile (portrait) and desktop
- One full session should take 2–10 minutes
- Include a short "How to play" screen or tutorial
- Original work created or substantially updated during the challenge week
- No external logins, ads or in-app purchases
- Submit by 14.11, 00:00 – late entries won't be accepted
Use "challengeNotes" in game.json to explain how the game fits.
## 1. The build
- Keep the existing build (Vite, webpack, Phaser template, plain HTML…). Its output folder (e.g. `dist/`) must contain `index.html` at its root.
- All asset paths must be RELATIVE (for Vite set `base: "./"`). The game is served from a sub-path inside a sandboxed iframe: no cookies, no popups, no access to the parent page, no server-side code.
- Total size: ZIP max 100 MB, unpacked max 200 MB, max 3000 files. No .exe/.dll/.sh/.php/.py/.jar or other executable/server files.
- It must work on phones: touch input, resize-aware canvas, no hover-only controls.
## 2. The screens/ folder
Create a `screens/` folder in the PROJECT ROOT (not in public/ or the build output) and commit it. No subfolders. Images: PNG, JPG, WebP or GIF, max 5 MB each. Allowed files:
| File | Used for | Rules |
|---|---|---|
| `game.json` | All game details (see section 3) | Required for the first upload. |
| `cover.png` | Card image + page header | 16:9, at least 630×354 (ideally 1280×720). Center-cropped to 16:9. No important content near the edges. |
| `screenshot-1.png` … `screenshot-8.png` | Screenshot gallery | Ordered by the number. Longest side 320–1920 px. Optional caption after the number in lowercase-kebab-case: `screenshot-2-boss-fight.png` → caption "Boss fight". |
| `preview.mp4` or `preview.webm` | Optional hover clip on the game card | MP4 (H.264) or WebM, 16:9, max 8 MB; 5–10 seconds of real gameplay, no sound needed (it plays muted and loops). Not a QuickTime .mov. |
| `description.md` | "About" section | Markdown, max 10 000 characters. What the game is, what makes it fun. Don't repeat the title as a heading. |
| `description-1.png`, `description-2.png` … | Pictures inside the description | Max 10. Reference them as ``; any picture you don't reference is appended at the end. |
| `how-to-play.md` | "How to play" section | Markdown, max 3 000 characters. Goal + controls for EVERY supported input (touch, keyboard, mouse, gamepad). |
| `how-to-play-1.png` … | Pictures inside the instructions | Max 10, e.g. a controls diagram. Reference as ``. |
Markdown: no raw HTML, no external images (they are not shown; only screens/ pictures are), links are fine. Keep sentences short; players skim.
Images: use REAL gameplay captures, not mock-ups. If you can run the game, use Playwright (or a similar headless browser) against the built game to capture the cover and 3–5 screenshots of different, interesting moments at 1280×720 (plus a portrait 720×1280 shot if the game supports portrait). If you can't capture them, tell me exactly which shots to take; don't invent placeholder art.
## 3. screens/game.json
A JSON object. Every property is optional except "title" and "tagline" on the first upload; a property you leave out keeps its current value on the site. Unknown properties are rejected (so don't put the description in here; it goes in description.md).
- "title": string, max 100.
- "tagline": string, max 120, one punchy line shown on the game card.
- "theme": one of "Abstract / Minimal", "Alien invasion", "Ancient Egypt", "Ancient Rome / Greece", "Animals", "Arabian Nights", "Arctic", "Autumn", "Aviation", "Aztec / Maya", "Biopunk", "Cars / Racing", "Castle", "Cave / Underground", "Christmas", "Circus / Carnival", "City / Urban", "Claymation", "Comic book", "Countryside", "Cute / Cozy", "Cyberpunk", "Desert", "Detective / Mystery", "Dieselpunk", "Dinosaurs", "Dragons", "Dreams / Surreal", "Dungeon", "Easter", "Factory / Automation", "Fairy tale", "Fantasy", "Farm", "Feudal Japan", "Food / Candy", "Forest", "Garden", "Ghosts / Haunted", "Halloween", "Hand-drawn", "Heist / Crime", "Horror", "Hospital", "Industrial", "Insects / Bugs", "Island / Tropical", "Jungle", "Kitchen / Cooking", "Low poly", "Magic / Wizards", "Medieval", "Microscopic / Cells", "Military / War", "Mining", "Monochrome", "Monsters", "Music / Concert", "Mythology", "Nature", "Neon / Synthwave", "Ninja / Samurai", "Noir", "Ocean / Sea life", "Office", "Paper / Cardboard", "Pirates", "Pixel art", "Post-apocalyptic", "Prehistoric", "Retro / 8-bit", "Robots", "School", "Sci-fi", "Sky / Clouds", "Solarpunk", "Space", "Sports", "Spring", "Spy / Espionage", "Steampunk", "Suburbia", "Summer", "Superheroes", "Time travel", "Toys", "Trains", "Underwater", "Valentine's", "Vampires", "Victorian", "Vikings", "Volcano", "Voxel", "Watercolor", "Weather / Storms", "Western", "Winter / Holidays", "Witches", "Zombies".
- "category" and "subCategory": pick the category, then optionally one of its sub-categories:
- "Action": "Platform", "Shooter", "First-person shooter", "Third-person shooter", "Shoot 'em up", "Light gun", "Hero shooter", "Arena shooter", "Fighting", "Beat 'em up", "Hack and slash", "Stealth", "Survival", "Rhythm", "Battle royale"
- "Action-adventure": "Survival horror", "Metroidvania", "Soulslike", "Immersive sim"
- "Adventure": "Text adventure", "Graphic adventure", "Point-and-click", "Visual novel", "Interactive movie", "Real-time 3D adventure", "Walking simulator"
- "Puzzle": "Logic", "Tile-matching", "Physics", "Programming", "Hidden object", "Escape room", "Breakout clone", "Trivia"
- "Role-playing": "Action RPG", "MMORPG", "Roguelike", "Roguelite", "Tactical RPG", "Sandbox RPG", "First-person party-based RPG", "Monster tamer", "Dungeon crawler"
- "Simulation": "Construction and management", "City-building", "Business / Tycoon", "Colony management", "Life simulation", "Social simulation", "Pet / Virtual pet", "Dating sim", "Vehicle simulation", "Flight simulator", "Space flight", "Farming"
- "Strategy": "4X", "Artillery", "Auto battler", "MOBA", "Real-time strategy", "Real-time tactics", "Tower defense", "Turn-based strategy", "Turn-based tactics", "Wargame", "Grand strategy", "Deck-building / Card"
- "Sports": "Racing", "Team sports", "Individual sports", "Competitive / Esports", "Sports-based fighting"
- "Massively multiplayer": "MMO", "MMORPG", "MMOFPS"
- "Other": "Casual", "Idle / Incremental", "Party", "Sandbox", "Survival", "Horror", "Educational", "Music", "Board game", "Card game", "Hyper-casual", "Endless runner", "Typing", "Exergame"
- "dimension": one of "2D", "2.5D", "3D".
- "tags": up to 10 short lowercase tags, e.g. ["pixel-art", "roguelike"].
- "inputMethods": the inputs the game ACTUALLY supports, any of "Touch", "Keyboard", "Mouse", "Gamepad".
- "orientation": "Any", "Portrait" or "Landscape".
- "contentWarnings": any that apply of "FlashingLights", "Violence", "Gore", "LoudAudio", "Horror"; [] if none.
- "engine": e.g. "Phaser 3", "Godot 4", "Three.js", "Plain JavaScript". Max 100.
- "videoUrl": optional YouTube link. "sourceCodeUrl": optional repo link. Both http(s) URLs.
- "challengeNotes": how the game fits the weekly challenge, markdown, max 3000.
- "credits": team, music, fonts, asset licenses, markdown, max 3000.
- "aiDisclosure": honestly state what AI was used for (code, art, audio, text), max 1000.
- "awards": optional achievements, the FULL list (awards left out are deleted). Each: "key" (lowercase letters, digits, - and _, max 32, unique), "title" (max 60), "description" (max 200), "icon" (one emoji), "secret" (true hides it until unlocked). Max 50.
Example:
{
"title": "Neon Drift",
"tagline": "Race through a city that rebuilds itself every lap",
"theme": "Neon / Synthwave",
"category": "Sports",
"subCategory": "Racing",
"dimension": "2D",
"tags": ["racing", "arcade", "neon"],
"inputMethods": ["Touch", "Keyboard", "Gamepad"],
"orientation": "Landscape",
"contentWarnings": ["FlashingLights"],
"engine": "Phaser 3",
"videoUrl": "https://youtu.be/xxxxxxxxxxx",
"sourceCodeUrl": "https://github.com/me/neon-drift",
"challengeNotes": "The track changes shape every lap, which fits this week's theme.",
"credits": "Music: Jane Doe (CC-BY 4.0)",
"aiDisclosure": "Code written with Claude Code; all art and music made by hand."
}
Awards example (inside the same object):
"awards": [
{ "key": "first-win", "title": "First win", "description": "Win a round", "icon": "🏆" },
{ "key": "no-hit", "title": "Untouchable", "description": "Clear a level without getting hit", "icon": "🛡️" },
{ "key": "hidden-room", "title": "Explorer", "description": "Find the hidden room", "icon": "🗝️", "secret": true }
]
## 3b. Leaderboards and awards (optional)
If the game has a score, a time or achievements, integrate the Gamero SDK:
- Add `<script src="https://gamero.gg/sdk/gamero.js"></script>` to index.html before the game code. It defines `window.Gamero`.
- Guard every call with `if (window.Gamero?.available)`: outside Gamero (local dev) it is false and calls reject.
- `Gamero.submitScore(value, { board = "main", order = "desc" })` when a run ends. Integers only; for times use milliseconds with order "asc". Only the player's best is kept. Resolves { best, improved, rank, players }.
- `Gamero.getLeaderboard(board, { limit })` resolves { entries: [{ rank, player, value, isMe }], me }.
- `Gamero.unlockAward(key)` for each award in game.json, whenever its condition is met (repeat calls are ignored).
- Calls reject with err.code "signin" for guests: keep the game fully playable, never block on these calls, and catch errors.
- Gamero shows its own leaderboard and award panels and an "Award unlocked" popup, so in-game UI is optional.
## 3c. Online multiplayer (Gamero rooms)
This is a multiplayer game. Gamero provides online rooms through the same SDK (`<script src="https://gamero.gg/sdk/gamero.js"></script>` in index.html). There is NO game server and you must not add one (no Node/socket.io/WebRTC/Firebase servers, the game can't open its own connections reliably from the sandbox): Gamero relays JSON messages between the players of a room, and the game decides who is authoritative.
API (`const mp = Gamero.multiplayer`):
- `await mp.join({ maxPlayers })` quick-matches into an open public room with the same maxPlayers (2–16, default 2), or creates one. `mp.join({ private: true, maxPlayers })` creates a room only reachable by its code. `mp.join({ code })` joins a friend's room. Resolves the room `{ code, me, host, maxPlayers, isPublic, locked, players: [{ id, name }] }`. Rejects with err.code "not-found", "full", "locked" (already started), "busy", "network" or "unavailable" (not on Gamero).
- `mp.room` is the current room (players and host kept up to date) or null. `mp.isHost()` is true for the host: the longest-connected player. When the host leaves, the next player becomes host.
- `mp.send(data)` sends JSON-serializable data to everyone else; `mp.send(data, playerId)` to one player. Fire-and-forget, ordered, reliable. Max 16 KB per message and 60 messages per second per player; the sender never receives its own message.
- `mp.lock()` (host only) stops new players from joining: call it when the match starts.
- `mp.leave()` leaves the room.
- Events, registered with `mp.on(event, fn)` BEFORE calling join: "message" (data, fromPlayer), "join" (player), "leave" (player), "host" (newHostPlayer), "close" (reason: the connection dropped, e.g. "disconnected", "slow", "rate"), "error" (err).
- Player names are Gamero display names; guests can play too and appear as "Guest 1234". Ids are small integers, unique within the room.
How to build it:
- Host-authoritative: the host runs the simulation (physics, collisions, scoring, spawning, randomness) and sends compact state snapshots 10–20 times per second. Other players send only their inputs to the host (`mp.send(input, mp.room.host)`) and render the snapshots with interpolation (render ~100 ms in the past) and local prediction for their own character so it feels responsive. Turn-based games: send each move, the host validates it and broadcasts the result.
- Tag every message with a type, e.g. `{ type: "input" | "state" | "start" | "end", ... }`. Keep snapshots small: round numbers, short keys, only what changed.
- Never send messages every animation frame; use a fixed send rate.
- Lobby screen with three touch-friendly buttons: "Quick match", "Create private room" (then show the room code big and a waiting list of players) and "Join with code" (a 5-character code input). Show who is in the room. The host has a Start button, enabled once enough players joined; Start calls `mp.lock()` and broadcasts a "start" message with any shared seed/settings.
- Handle players leaving mid-match (remove them, or end the round if too few remain) and host migration: on "host", if `mp.isHost()` is now true, continue the simulation from the last snapshot. On "close", show a message and go back to the lobby.
- A player who joins a room mid-lobby must receive the current lobby state: the host sends it to them on "join".
- Keep the game usable when `Gamero.available` is false (local dev): show the lobby with a "Multiplayer works on Gamero" note, and keep a local/solo or bot mode so the game can be tried alone. Structure the network code behind a small interface so a fake in-memory transport can drive two game instances locally for testing.
- Leaderboards/awards still work as in 3b; submit each player's own score from their own client.
## 4. npm run build → gamero.zip
- Add `archiver` as a devDependency and a script `scripts/gamero-zip.mjs` that zips the CONTENTS of the build folder at the ZIP root (index.html must be at the top level, not inside dist/) plus the `screens/` folder as `screens/`.
- Chain it in package.json: `"build": "<existing build command> && node scripts/gamero-zip.mjs"`.
- Add `gamero.zip` to .gitignore.
- The script must fail with a clear message if index.html is missing from the build output.
## 5. Check before you finish
- Run `npm run build` and list the ZIP contents. Confirm: `index.html` at the root, `screens/` next to it, only allowed file names in screens/, game.json is valid JSON with only the properties above, every `` reference in the .md files points to an existing `description-N` / `how-to-play-N` picture.
- Serve the build output from a sub-folder (e.g. `npx serve` one level up) and confirm the game loads with no 404s.
- If you added the SDK: the script tag is in the built index.html, every Gamero call is guarded by `Gamero.available` and catches errors, the game is fully playable without it, and every `unlockAward` key exists in game.json "awards".
- Multiplayer: event handlers are registered before `join`, nothing is sent every frame, every message is under 16 KB, the lobby works on a phone, and leaving / host migration / "close" are handled. Tell me to test it with **Test play** in two browser windows (or two devices) side by side.
- Tell me the ZIP size and anything left for me to do. On Gamero I only have to upload the ZIP in step 1 of the submission page, check the preview, and press Publish.gamero.zip
├── index.html ← your game (must be at the root; add the SDK script here for leaderboards/awards)
├── assets/ … ← anything your game loads, relative paths
└── screens/ ← game page data, NOT served with the game
├── game.json ← title, tagline, theme, genre, controls, credits, awards…
├── cover.png ← card + page header, 16:9, at least 630×354
├── screenshot-1.png ← gallery, in number order (max 8)
├── screenshot-2-boss-fight.png ← text after the number = caption "Boss fight"
├── preview.mp4 ← optional: few-second muted loop played when hovering the card
├── description.md ← "About" text (markdown, max 10 000 chars)
├── description-1.png ← pictures for the description
├── how-to-play.md ← "How to play" text (markdown, max 3 000 chars)
└── how-to-play-1.png ← pictures for the instructions (controls diagram…)index.html can also sit inside one top-level folder (e.g. dist/index.html). Then screens/ can be next to that folder or inside it.game.json with at least a title and tagline. Without it, fill in the game details by hand first.awards list in game.json replaces all awards.game.json property stops the upload with an error, and nothing changes.screens/ folder is never served with your game, so name your game's own folders differently.Every field from the submission form. Values must match the site's lists (the AI prompt includes them all), and “Best strategy game” fixes some of them.
{
"title": "Neon Drift",
"tagline": "Race through a city that rebuilds itself every lap",
"theme": "Neon / Synthwave",
"category": "Sports",
"subCategory": "Racing",
"dimension": "2D",
"tags": ["racing", "arcade", "neon"],
"inputMethods": ["Touch", "Keyboard", "Gamepad"],
"orientation": "Landscape",
"contentWarnings": ["FlashingLights"],
"engine": "Phaser 3",
"videoUrl": "https://youtu.be/xxxxxxxxxxx",
"sourceCodeUrl": "https://github.com/me/neon-drift",
"challengeNotes": "The track changes shape every lap, which fits this week's theme.",
"credits": "Music: Jane Doe (CC-BY 4.0)",
"aiDisclosure": "Code written with Claude Code; all art and music made by hand."
}inputMethods: any of Touch, Keyboard, Mouse, Gamepad. orientation: Any, Portrait or Landscape.contentWarnings: any of FlashingLights, Violence, Gore, LoudAudio, Horror.dimension: 2D, 2.5D or 3D. Up to 10 tags.awards: optional list of achievements your game unlocks, see Awards. Leave it out to manage awards on the submission page instead..md files so they can include pictures.| File | Becomes | Rules |
|---|---|---|
cover.png | Cover (card + page header) | 16:9, min 630×354, ideally 1280×720. Center-cropped. |
screenshot-1.png … -8.png | Screenshot gallery | Number order. Longest side 320–1920 px. screenshot-3-night-level.png → caption “Night level”. |
preview.mp4 / .webm | Hover clip on the card (optional) | MP4 (H.264) or WebM, 16:9, max 8 MB. 5–10 s of gameplay; plays muted and looping. |
description.md | About | Markdown, max 10 000 characters. |
description-N.png | Pictures in About | Up to 10. Use . Unreferenced ones go at the end. |
how-to-play.md | How to play | Markdown, max 3 000 characters. Goal + controls per input. |
how-to-play-N.png | Pictures in How to play | Up to 10, e.g. a controls diagram. |
Images can be PNG, JPG, WebP or GIF up to 5 MB each. We convert them to WebP, so they show as still images. Markdown can't contain HTML, and only pictures from screens/ are displayed.
Example description.md
Race through a city that rebuilds itself every lap.  - 12 hand-made tracks - Ghost replays of your best lap
Doing it by hand? Add this script, then chain it after your build in package.json:
// scripts/gamero-zip.mjs: zips the web build + screens/ into gamero.zip
// npm i -D archiver
import { createWriteStream, existsSync } from "node:fs";
import archiver from "archiver";
const BUILD_DIR = "dist"; // folder that contains index.html after the build
if (!existsSync(`${BUILD_DIR}/index.html`)) {
console.error(`${BUILD_DIR}/index.html not found, did the build run?`);
process.exit(1);
}
const output = createWriteStream("gamero.zip");
const zip = archiver("zip", { zlib: { level: 9 } });
zip.pipe(output);
zip.directory(BUILD_DIR, false); // game files at the ZIP root
if (existsSync("screens")) zip.directory("screens", "screens"); // game page data
await zip.finalize();
await new Promise((resolve) => output.on("close", resolve));
console.log(`gamero.zip ready (${(zip.pointer() / 1024 / 1024).toFixed(1)} MB). Upload it in step 1 of your submission.`);"scripts": {
"build": "vite build && node scripts/gamero-zip.mjs"
}With Vite, set base: "./" so assets load from relative paths. Add gamero.zip to .gitignore.
Optional. With the Gamero SDK your game can post scores and unlock awards for the signed-in player. Gamero shows the leaderboard and awards next to the game, plus an “Award unlocked” popup over it, so you don't have to build any UI for them.
Add this to your index.html, before your game's own scripts. It defines window.Gamero.
<script src="https://gamero.gg/sdk/gamero.js"></script>
// After the run ends (integers only: points, or a time in ms)
if (window.Gamero?.available) {
const { rank, improved } = await Gamero.submitScore(score);
await Gamero.submitScore(timeMs, { board: "speedrun", order: "asc" }); // lower wins
// Unlock awards whenever the condition is met; repeats are ignored
Gamero.unlockAward("first-win").catch(() => {});
// Show the top 10 in your own UI
const { entries, me } = await Gamero.getLeaderboard("main", { limit: 10 });
}Your game runs in a sandboxed iframe and never sees the player's login. The SDK passes each call to the Gamero page, which saves it for the signed-in player. Every method returns a Promise.
| Call | What it does | Resolves |
|---|---|---|
Gamero.available | true when running on Gamero | A boolean, not a Promise. False on your local dev server. |
submitScore(value, { board, order }) | Report a finished run | { best, improved, rank, players }. board defaults to "main". |
getLeaderboard(board, { limit }) | Top scores (default 10, max 100) | { key, order, players, entries: [{ rank, player, value, isMe }], me } |
unlockAward(key) | Unlock an award for the player | { unlocked, award }. unlocked is true only the first time. |
getAwards() | All awards and the player's progress | [{ key, title, description, icon, isSecret, unlockedBy, unlockedAt }] |
getItems() | Items your game sells for gamecoins | [{ key, title, description, icon, price, owned }] |
hasItem(key) | Whether the player owns an item | true or false (always false for guests). |
buyItem(key) | Ask the player to buy an item | { bought, item }. bought is false if they already owned it. |
getPlayer() | Who is playing | { displayName }, or null for guests. |
Failed calls reject with an Error whose code is "signin" (a guest tried to save progress), "unavailable" (not running on Gamero), "cancelled" (the player declined a purchase), "coins" (not enough gamecoins), "timeout" or "error" (the message says why, e.g. an unknown award key).
improved: false and changes nothing."main", "level-3"). A board is created by its first score, and that score's order is fixed from then on: "desc" (higher wins, the default) or "asc" (lower wins, for times).Define them in step 4 of your submission (Awards), or in screens/game.json. Each award has a key your game uses, a title, an optional description and emoji icon, and can be secret: hidden from players until they unlock it.
"awards": [
{ "key": "first-win", "title": "First win", "description": "Win a round", "icon": "🏆" },
{ "key": "no-hit", "title": "Untouchable", "description": "Clear a level without getting hit", "icon": "🛡️" },
{ "key": "hidden-room", "title": "Explorer", "description": "Find the hidden room", "icon": "🗝️", "secret": true }
]- and _, max 32 characters, unique within your game. Up to 50 awards.game.json list is the full list: every upload replaces the awards on the site. Awards are matched by key, so players keep the ones they unlocked. Removing an award also removes it from players.unlockAward every time the condition is met. Repeat calls are ignored, so you don't need to remember what the player already has.Your game can sell extras for gamecoins, Gamero's currency: a skin, a bonus level, a power-up. Define them in step 5 of your submission (Shop items) with a key, title, emoji and a price from 10 to 500 coins.
// On start: turn on what the player owns
if (window.Gamero?.available && await Gamero.hasItem("gold-skin")) useGoldSkin();
// From your shop button: Gamero shows the price and asks the player
try {
await Gamero.buyItem("gold-skin");
useGoldSkin();
} catch (e) {
// e.code: "cancelled" (player said no), "coins" (not enough), "signin" (guest)
}buyItem never spends coins on its own. Gamero shows the item, the price and the player's balance over your game and the player confirms or cancels. Don't pause the game while waiting for that answer.code: "signin", and Gamero asks them to sign in. Never block or pause the game waiting on these calls, and always catch their errors.Gamero.available is false, so check it before calling. Test the real thing with Test play on the submission page. Scores and awards you earn on your draft are saved as yours.JavaScriptBridge.eval("Gamero.submitScore(1234)"). Unity: call the same functions from a .jslib plugin.Optional. Gamero runs the online part for you: players join a room (2–16 players) and every message one of them sends is passed to the others. There is no server code to write or host. One player, the host, runs the game's rules and the others follow. Choose Online multiplayer at the top of this page and the AI prompt explains all of it.
It uses the same SDK script as leaderboards. Register your handlers, then join:
const mp = Gamero.multiplayer;
// Listen first, then join
mp.on("join", (player) => showLobby(mp.room.players));
mp.on("leave", (player) => removePlayer(player.id));
mp.on("host", (player) => { if (mp.isHost()) takeOverSimulation(); });
mp.on("message", (data, from) => {
if (data.type === "input") applyInput(from.id, data); // host: inputs from players
if (data.type === "state") applySnapshot(data); // everyone else: the host's world
});
mp.on("close", (reason) => backToMenu(reason));
// Lobby buttons
const room = await mp.join({ maxPlayers: 4 }); // Quick match
// await mp.join({ private: true, maxPlayers: 4 }); // Create a room, show room.code to friends
// await mp.join({ code: "K7QX2" }); // Join a friend's room
// Host presses Start
if (mp.isHost()) { mp.lock(); mp.send({ type: "start", seed: 1234 }); }
// In the game loop, 15–20 times a second (not every frame)
if (mp.isHost()) mp.send({ type: "state", players: snapshot() });
else mp.send({ type: "input", left, right, jump }, mp.room.host);| Call | What it does | Notes |
|---|---|---|
join({ maxPlayers }) | Quick match | Joins an open public room of the same size (2–16, default 2), or opens one. Resolves the room. |
join({ private: true }) | Create a private room | Only reachable with its 5-character room.code. Show it so friends can join. |
join({ code }) | Join a friend's room | Rejects with code "not-found", "full" or "locked". |
room | The current room, or null | { code, me, host, maxPlayers, isPublic, locked, players: [{ id, name }] }, kept up to date. |
send(data, to?) | Message the room | JSON data to everyone else, or only to player to. Max 16 KB and 60 per second. |
isHost() | Whether you're the host | The longest-connected player. If they leave, the next one takes over. |
lock() | Close the room (host) | Call it when the match starts, so nobody joins mid-game. |
leave() | Leave the room | Back to the lobby. |
on(event, fn) / off | Listen to the room | message (data, player), join, leave, host (player), close (reason), error. |
send(input, mp.room.host). Don't send every frame.host, the new host carries on from the last snapshot. On close, the connection is gone, so go back to the lobby.join rejects with "unavailable", so keep a solo or bot mode for local development. To test the real thing, open Test play in two browser windows.gamero.zip in step 1. It creates your draft and fills in every step below it. Not entering a challenge? Upload it as a standalone game instead: no deadline or theme, and you can publish as many as you like.npm run build again and upload the new ZIP.The game runs in a sandboxed iframe on a separate domain: no cookies, popups or parent-page access. Any HTML5 engine works (plain JS, Phaser, PixiJS, Godot single-threaded web export, Unity WebGL…). Test it on a phone too.