Документация SDK

Портал открывает вашу игру в песочнице и общается с ней через window.postMessage. Подключите один скрипт, и у игры появятся облачные сохранения, таблица рекордов и комнаты для нескольких игроков. Ничего устанавливать не нужно.

Кадр игры живёт на отдельном origin: cookie, localStorage и запросы к API портала из игры недоступны. Всё, что должно пережить перезагрузку, кладите в облако через GameSlop.save().

Делаете игру нейросетью?

Дайте модели одну строку — она прочитает эталон правил упаковки, манифеста, SDK и проверки мультиплеера целиком:

curl -sL https://gameslop.ru/README-FOR-AI.mdСкачать .md

Если curl у модели закрыт — файл скачивается кнопкой и читается локально.

Подключение

Скопируйте файл SDK в архив игры и подключите относительным путём перед закрывающим тегом body: сборка раздаётся со своего домена, и абсолютный путь /sdk/… даст 404. Вне портала SDK можно не подключать: если объект GameSlop отсутствует, игра просто работает без облака. Файлы байт-в-байт: gameslop.js (расширенный: сейвы, рекорды, комнаты), gameslop-save.js и gameslop-mp.js (платформенный диалект).

index.html
<script src="sdk/gameslop.js"></script>
<script>
  GameSlop.whenReady().then(function (info) {
    // info.game  - данные игры
    // info.user  - игрок или null
    // info.room  - комната или null
  });
</script>

Облачные сохранения

Сохраняйте любое состояние: позицию, уровень, инвентарь. Слот по умолчанию называется auto, но можно использовать свои: например, отдельный слот на каждого персонажа. Лимит одного сохранения 256 КБ, этого хватает почти любой браузерной игре.

game.js
// при старте: предложить продолжить
var saved = await GameSlop.load('auto');
if (saved) restore(saved.level, saved.score);

// во время игры
await GameSlop.save({ level: level, score: score }, 'auto', 'Основная партия');

Рекорды

Отправляйте результат один раз в конце партии. Портал ведёт таблицу рекордов сам: ответ { rank, best, stored: true } — это место игрока в таблице и лучший результат игры, их можно показать на экране итогов. Лучший результат игрока хранится один: слабый повтор не двигает таблицу.

game.js
var res = await GameSlop.submitScore(score, { level: level });
console.log('место', res.rank, 'рекорд игры', res.best, 'сохранено', res.stored !== false);

Комнаты для нескольких игроков

Комнату создаёт портал, ваша задача только обмениваться кадрами. Один игрок должен быть ведущим: он считает физику и рассылает состояние, остальные отправляют ввод. Проще всего назначить ведущим хозяина комнаты.

Кадры доставляются мгновенно и не хранятся: вошедший позже игрок увидит мир только со следующего полного состояния, поэтому шлите состояние целиком, а не дельту, — и не чаще 20–30 раз в секунду. На каждый mousemove отправлять нельзя: лимит 30 кадров в секунду на игрока, кадр длиннее 8 КБ или слишком частый поток отклоняются с понятной причиной, а не обрезаются.

multiplayer.js
var room = GameSlop.getRoom();
var isHost = !room || room.isHost;
var lastSent = 0;

// ведущий: 20 полных состояний в секунду
setInterval(function () {
  if (isHost) GameSlop.send('state', { ball: ball, score: score });
}, 50);

// гость: ввод тоже с оглядкой на лимит, а не на каждое движение мыши
canvas.addEventListener('mousemove', function (e) {
  var now = Date.now();
  if (now - lastSent < 50) return;
  lastSent = now;
  GameSlop.send('input', { y: e.clientY });
});

GameSlop.onMessage(function (msg) {
  if (msg.type === 'state') applyState(msg.data);
});

Между кадрами чужое движение стоит дорисовать: сгладьте позиции, а мяч продлите по скорости между двумя последними кадрами. Так 20 кадров в секунду выглядят гладко.

Справочник методов

GameSlop.save(data, slot?, label?)

Сохраняет любое JSON-состояние в облако игрока. Возвращает промис. Часть платформ поле label не хранит — не полагайтесь на него как на данные.

await GameSlop.save({ level: 3, score: 120 })

GameSlop.load(slot?)

Читает сохранение. Если его нет, вернёт null.

const state = await GameSlop.load()

GameSlop.submitScore(value, meta?)

Отправляет результат в таблицу рекордов портала и возвращает { rank, best, stored }. Слабый повтор не меняет лучший результат игрока.

await GameSlop.submitScore(482, { level: 6 })

GameSlop.send(type, data)

Кадр остальным игрокам комнаты. Возвращает { ok, delivered } или { ok: false, reason }; не бросает исключений.

GameSlop.send("state", { x: ball.x })

GameSlop.onMessage(fn)

Подписка на кадры комнаты: fn({ from, type, data }).

GameSlop.onMessage(({ from, type, data }) => {})

GameSlop.getUser()

Профиль игрока или null для гостя.

const me = GameSlop.getUser()

GameSlop.getRoom()

Комната и роль в ней: host или guest.

const room = GameSlop.getRoom()

GameSlop.onControl(fn)

Команды портала: pause и resume (restart и mute пока не подключены).

GameSlop.onControl(({ action }) => {})

GameSlop.gameOver(payload)

Сообщает порталу, что партия закончена.

GameSlop.gameOver({ score: 12 })

GameSlop.MAX_FRAME_BYTES

Лимит одного кадра в байтах (8 КБ) — по нему удобно проверять своё состояние.

if (JSON.stringify(s).length > GameSlop.MAX_FRAME_BYTES) slim()

Требования к архиву

  • manifest.json и index.html (или файл из entry) в корне архива.
  • Все пути относительные, без «..», без ведущего слэша, без симлинков.
  • SDK лежит внутри архива в папке sdk/ и подключается относительным путём.
  • Никаких внешних URL, секретов и ключей: игра выполняется в браузере игрока.
  • Прогресс храните в облаке через GameSlop.save(): localStorage в песочнице общий для домена.
  • ZIP до 200 МБ, распакованного содержимого до 512 МБ, не больше 2000 файлов.
  • Полный чек-лист — в README-FOR-AI.md, раздел 12.

Готовы опубликовать игру?

Восемь игр портала собраны ровно по этой инструкции.

Загрузить игруПосмотреть примеры

Исходники восьми игр портала лежат в репозитории проекта: server/public/seed-games.