Построй игру для маркетплейса
К концу этого руководства у тебя будет полная, играбельная игра Caputchin в одном JavaScript-бандле, готовая к тому, чтобы сделать её воспроизводимой и опубликовать. Это скучная версия «сделай-каждый-шаг»; сначала прочитай Выпусти игру в маркетплейс о том, куда это вписывается.
Тебе нужен Node и бандлер (esbuild, rollup, vite или webpack, любой из них). Тебе не нужен аккаунт Caputchin, чтобы строить, только чтобы публиковать.
1. Установи SDK
npm install @caputchin/game-sdkSDK крошечный: хелпер register плюс типы TypeScript. Он не встраивает рантайм виджета, так что твоя игра остаётся маленькой.
2. Зарегистрируй игровую фабрику
Игра это единственный вызов register с фабричной функцией. Виджет вызывает твою фабрику внутри iframe в песочнице; этот вызов сам и есть сигнал старта (нет отдельного события старта, которого надо ждать).
import { register } from "@caputchin/game-sdk";
register((container, bridge, ctx) => {
// container - a DOM element inside the sandboxed iframe; render into it.
// bridge - push-only channel to the host (pass / error / setSize).
// ctx - the per-round context (seed + resolved locale/skin/config).
const cleanup = startGame(container, bridge, ctx);
// Return an optional cleanup function; the widget calls it on unmount.
return cleanup;
});Ты не передаёшь манифест в register; сервер читает caputchin.json во время индексации и шлёт разрешённые пресеты вниз твоей фабрике как ctx. Смотри справочник SDK для полной поверхности.
3. Отрисуй из пер-раундового контекста
Третий аргумент фабрики, ctx, несёт всё, что меняется на каждого посетителя:
ctx.seed- пер-раундовый сид. Выводи всю случайность из него (см. шаг 5).nullвне проверенной сессии.ctx.locale- разрешённые языковые строки (ctx.locale._langплюс твои ключи) илиnull.ctx.skin- разрешённые цвета и URL ассетов (ctx.skin._theme, всегдаlightилиdark, плюс твои ключи) илиnull.ctx.config- разрешённая игровая конфигурация илиnull.
Читай свои собственные ключи с них; ты никогда сам не разрешаешь пресеты. Каждый null, когда твой манифест не объявляет соответствующего блока, так что всегда откатывайся на встроенное умолчание:
function startGame(container, bridge, ctx) {
const title = ctx.locale?.title ?? "Tap the targets";
const accent = ctx.skin?.accent_color ?? "#2da44e";
const targetCount = ctx.config?.target_count ?? 5;
// ...render with these...
}Если твоей раскладке нужен явный размер, вызови bridge.setSize(width, height) один раз после первой отрисовки.
4. Победа, поражение и ошибки
Твоя игра решает, когда игрок побеждает, и говорит хосту через мост:
- При победе вызови
bridge.pass({ trace })с трассой раунда (шаг 5). - При поражении или брошенности не вызывай ничего; молчание это сигнал сбоя.
- Если сама игра ломается (ассет не загрузился, исключение), вызови
bridge.error({ code, message }). Это для внутреннего сбоя игры, а не для проигрыша игрока.
function onWin(traceString) {
bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
bridge.error({ code: "asset-load-failed", message: String(err) });
}5. Запиши трассу
Это шаг, который делает игру воспроизводимой. По мере того как игрок действует, записывай вводы, которые ведут к исходу (по какой цели он попал, в порядке, с таймингом, если он важен), и сериализуй эту запись в строку или массив байтов: это твоя трасса. В сочетании с сидом трасса должна давать твоей логике воспроизвести точный результат, потому что сервер перезапускает её.
Два правила делают это возможным:
- Выводи каждый случайный выбор из
ctx.seed. Никогда не вызывайMath.random()и не читай настенные часы для чего-либо, что влияет на исход. - Записывай достаточно для реплея. Трасса плюс сид это полный ввод для твоей игровой логики.
function makeRng(seed) {
// seed is ctx.seed; feed it so the server reproduces the same sequence.
let s = hashSeed(seed);
return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}Форма seed, трассы и вердикта определены контрактом реплея; та страница покрывает превращение этой записанной игры в headless-артефакт run, который реплеит сервер. Если ручное написание детерминированной логики звучит муторно, движковый кит делает это за тебя, опционально.
6. Собери для песочницы
Виджет загружает ровно один URL скрипта, так что всё должно быть в этом одном файле. Настрой свой бандлер на один самодостаточный вывод:
- встрой ассеты как data-URL (спрайты, звуки, шрифты);
- отключи разбиение кода;
- если используешь WASM, встрой его как base64 и инстанцируй из байтов.
Шаблоны, которые не работают внутри iframe с непрозрачным источником и строгим CSP:
- путь-относительный
fetch('./sprite.png')- нет пути, откуда запрашивать; - динамический
import('./chunk.js')- второй URL заблокирован; new Worker('./worker.js')- порождай воркеры из инлайновыхBlob-URL вместо этого;- внешние запросы к CDN в рантайме -
connect-srcих блокирует.
7. Протестируй локально
Нет специальной обвязки. Встрой виджет в статическую HTML-страницу, наведи game-src на твой локальный вывод бандла и поиграй в браузере:
<caputchin-game sitekey="cpt_pub_..." game-src="http://localhost:8080/game.js"></caputchin-game>Это тот же путь game-src, что использует пользовательская игра; для маркетплейса ты вместо этого опубликуешь репозиторий, чтобы платформа закрепила бандл за тебя.
Следующие шаги
- Контракт реплея: преврати свою записанную игру в артефакт
run, который делает игру воспроизводимой. - Манифест caputchin.json: опиши игру, её пресеты и её бандл.
- Опубликуй в маркетплейс: пометь репозиторий тегом и выйди в эфир.
См. также
- Справочник игрового SDK: каждый экспорт, в полном виде.
- engine-kit: опциональный способ получить детерминизм даром.