Документация 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 (платформенный диалект).
<script src="sdk/gameslop.js"></script>
<script>
GameSlop.whenReady().then(function (info) {
// info.game - данные игры
// info.user - игрок или null
// info.room - комната или null
});
</script>Облачные сохранения
Сохраняйте любое состояние: позицию, уровень, инвентарь. Слот по умолчанию называется auto, но можно использовать свои: например, отдельный слот на каждого персонажа. Лимит одного сохранения 256 КБ, этого хватает почти любой браузерной игре.
// при старте: предложить продолжить
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 } — это место игрока в таблице и лучший результат игры, их можно показать на экране итогов. Лучший результат игрока хранится один: слабый повтор не двигает таблицу.
var res = await GameSlop.submitScore(score, { level: level });
console.log('место', res.rank, 'рекорд игры', res.best, 'сохранено', res.stored !== false);Комнаты для нескольких игроков
Комнату создаёт портал, ваша задача только обмениваться кадрами. Один игрок должен быть ведущим: он считает физику и рассылает состояние, остальные отправляют ввод. Проще всего назначить ведущим хозяина комнаты.
Кадры доставляются мгновенно и не хранятся: вошедший позже игрок увидит мир только со следующего полного состояния, поэтому шлите состояние целиком, а не дельту, — и не чаще 20–30 раз в секунду. На каждый mousemove отправлять нельзя: лимит 30 кадров в секунду на игрока, кадр длиннее 8 КБ или слишком частый поток отклоняются с понятной причиной, а не обрезаются.
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.