Getting started
From an empty folder to a playable preview in four steps. You need a Gems Arcadia account with a developer profile: open the developer portal and follow the sign-up prompts first.
1. Lay out your game folder#
A build is a folder of static files with an index.html at the top. No server
code runs on our side: the arcade serves your files from its games domain.
gem-catcher/
├── index.html ← the entry point (required)
├── game.js
├── style.css
└── assets/
├── sprites.png
└── blip.ogg2. Include the SDK#
Load the SDK before your game code. It defines a GemsArcadia global.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gem Catcher</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<canvas id="screen" width="320" height="240"></canvas>
<p>CREDITS <span id="credits">0</span></p>
<button id="insert-coin" type="button">INSERT COIN</button>
<p id="message" aria-live="polite"></p>
<!-- 1. The SDK first: it defines the GemsArcadia global. -->
<script src="https://gemsarcadia.com/sdk/v1.js"></script>
<!-- 2. Then your game. Paths are relative to index.html. -->
<script type="module" src="game.js"></script>
</body>
</html>Then wire your coin button. This is the whole coin flow: init(), listen for
the cabinet's own coin button, call insertCoin() from yours, and call
ready() once loaded.
const ui = {
coinButton: document.querySelector("#insert-coin"),
credits: document.querySelector("#credits"),
message: document.querySelector("#message"),
};
let credits = 0;
let paused = false;
/** One coin went in: give the player a credit. */
function grantCredit(insertionId) {
credits += 1;
ui.credits.textContent = String(credits);
// Show a receipt code players can quote to your support team.
ui.message.textContent = `RECEIPT ${insertionId}`;
}export const ready = GemsArcadia.init().then((session) => {
const { player, coinsPerCredit, balance, testMode } = session;
ui.message.textContent =
`Hi ${player.displayName}! ${balance} coins, ` +
`${coinsPerCredit} per credit${testMode ? " (test mode)" : ""}.`;
// Coins inserted with the cabinet's own INSERT COIN button.
GemsArcadia.on("coinInserted", ({ insertionId }) => grantCredit(insertionId));
// The player switched tabs: stop the clock.
GemsArcadia.on("pause", () => (paused = true));
GemsArcadia.on("resume", () => (paused = false));
GemsArcadia.ready(); // loaded and ready to take coins
return session;
});const REASONS = {
INSUFFICIENT_COINS: "Out of coins! Top up at the Token Booth.",
NOT_SIGNED_IN: "Log in to play.",
EMAIL_NOT_VERIFIED: "Verify your email to play.",
SESSION_EXPIRED: "Session timed out. Reload the game.",
RATE_LIMITED: "Easy there! One coin at a time.",
BUSY: "A coin is already dropping...",
};
/** Coins inserted from inside your game. insertCoin() never rejects. */
export async function insertCoin() {
ui.coinButton.disabled = true;
try {
const result = await GemsArcadia.insertCoin();
if (result.ok) grantCredit(result.insertionId);
else ui.message.textContent = REASONS[result.reason] ?? "The coin slot jammed. Try again.";
return result;
} finally {
ui.coinButton.disabled = false;
}
}
ui.coinButton.addEventListener("click", insertCoin);Prefer a bundler? import { init, insertCoin } from "@gems-arcadia/sdk" gives
you the same API with TypeScript types. Pass init({ host: "https://gemsarcadia.com" })
because a bundled copy can't tell where it was loaded from.
3. Test it locally#
Open index.html straight from your disk or any static server. Outside the
arcade the SDK runs in mock mode: you get 9999 free test coins and every
insertCoin() succeeds. See testing & preview.
4. Zip and upload#
Zip the contents of your build folder (or the folder itself: a single top-level folder is detected and stripped).
cd gem-catcher
zip -r ../gem-catcher.zip . -x "*.DS_Store"On Windows, select the files inside the folder, right-click and choose Send to → Compressed (zipped) folder.
Builds must follow these rules:
index.htmlat the root of the zip, or inside a single top-level folder (it is detected and stripped).- Zip up to 100 MB, at most 300 MB unpacked.
- At most 2,000 files.
- Relative paths with forward slashes only: no absolute paths, no
.., no symlinks. - Hidden files and OS junk (
__MACOSX/,.DS_Store,Thumbs.db), source maps (.map) and markdown files (.md) are skipped, not rejected.
Allowed file types:
.html.js.mjs.css.json.png.jpg.jpeg.gif.webp.svg.wasm.mp3.ogg.wav.ttf.woff.woff2.txt.glb.gltf.bin.data
Then, in the portal:
- Create a game at /developer/games/new: title, slug, genre, cover art and coins per credit.
- Upload the zip on the game's page. The arcade validates it, warns if
index.htmldoesn't reference the SDK, and unpacks it. - Preview it: you get a test-mode cabinet with free coins.
- Submit for review. Once approved, your cabinet goes live on the arcade floor.
Next: see exactly what happens when a coin drops, in the coin flow.