# Микроформат «Вьюшки» Документ для авторов микроигр: что такое микроигра, как она общается с лентой, как её проверить и как добавить в каталог. Эталоны лежат в `games/stack`, `games/meteors`, `games/color`, `games/repeat`, `games/run`. ## Что такое микроигра Игра, которую человек понимает за 3 секунды, проходит за 30–90 секунд и хочет сыграть ещё раз. Она живёт в вертикальной карточке ленты, как видео в TikTok: свайп — и следующая. Первый тап — уже игра. У каждой игры есть **своя фишка** — один хук, ради которого её запомнят и перешлют другу. ## Жёсткие требования | Что | Требование | |---|---| | Файлы | Папка `games//` с `index.html`. Дополнительные `.js` можно класть туда же. | | Ресурсы | Всё процедурное и лежит в папке игры: никаких картинок, аудиофайлов, шрифтов из сети, CDN, `fetch`. Шрифты — системные или нарисованные кодом. | | Размер | До 5 МБ, но для процедурной игры ориентир — до 150 КБ. | | Старт | `MicroSDK.ready()` не позже 2 секунд после загрузки. | | Экран | Вертикальный, любой размер от 360×640. Игра заполняет весь `window.innerWidth × innerHeight` и переживает `resize`. | | Управление | Один палец: тап, удержание, ведение. Без мультитача, клавиатуры и наклона. | | Раунд | 30–90 секунд у среднего игрока. Раунд обязательно кончается: проигрыш, таймер или жизни. | | Первый тап | Первое касание — уже игровое действие и момент `MicroSDK.start()`. Никаких экранов «Нажми, чтобы начать». | | Обучение | Без текстовых туториалов. Максимум одна короткая строка-подсказка в режиме ожидания. | | Текст | Только русский, коротко. | | Внутри нет | Рекламы, покупок, регистрации, ссылок, своего финального экрана с кнопками. Финальный экран рисует лента. | | Хранилище | `localStorage`, cookies, IndexedDB недоступны: игра работает в `iframe sandbox="allow-scripts"`. Рекорды хранит лента. | | Звук | Только Web Audio, только после первого касания, замолкает на паузе, слушается кнопки звука ленты. Игра понятна без звука. | | Производительность | 60 кадров на среднем Android-телефоне. Плотность пикселей ограничивайте двойкой, частицы — сотнями, а не тысячами; тяжёлый `shadowBlur` на десятках объектов в кадре — нельзя. | | Совместимость | Android WebView и мобильный Chrome. Ничего новее Chrome 100 без проверки наличия. | ## Жизненный цикл ``` загрузка → ready() → ожидание (игра видна и живёт, ждёт касания) → первое касание → start() → раунд → end(score) → лента показывает финальный экран → событие restart → снова ожидание ``` - `MicroSDK.ready()` — игра загрузилась и нарисовала первый кадр. В режиме ожидания игра уже анимирована и показывает, что делать. - `MicroSDK.start()` — на первом игровом действии раунда. Повторные вызовы внутри раунда игнорируются. - `MicroSDK.end(score)` — один раз в конце раунда. `score` — целое число не меньше нуля, **больше — лучше**. Если естественная метрика «меньше — лучше» (время), переведите её в очки. Дайте игроку 0,5–1 секунды увидеть проигрыш, потом зовите `end`. - `MicroSDK.on('restart', fn)` — игрок нажал «Ещё раз»: полностью сбросить состояние в режим ожидания, включая таймеры и звуки. - `MicroSDK.on('pause', fn)` / `on('resume', fn)` — карточку пролистали или открыли окно поверх. На паузе замирает всё: логика, анимация, таймеры, звук. Логику ведите от `dt` игрового цикла, а не от `setTimeout`/`Date.now()`, иначе пауза сломается. - `MicroSDK.on('sound', fn)` — `fn(true | false)` от кнопки звука ленты. Текущее значение — `MicroSDK.soundOn`. - `MicroSDK.paused` — стоит ли сейчас пауза. Подключение (путь от `games//index.html`): ```html ``` Колесо мыши и стрелки внутри игры SDK пересылает ленте как листание — игре они не нужны. ## MicroKit — необязательный шаблон `kit/micro-kit.js` берёт на себя холст под экран с учётом плотности пикселей, игровой цикл с паузой, ввод и разблокировку звука. Игра описывает объект и отдаёт его в `MicroKit.setup(game)`: ```js var game = { resize: function (w, h, dpr) {}, // пересчитать раскладку; состояние не трогать; w и h не меньше 1, dpr — плотность пикселей до 2 reset: function () {}, // состояние режима ожидания; зовётся при старте и на restart update: function (dt) {}, // dt в секундах, от 0 до 0,05; на паузе не зовётся draw: function (ctx, w, h) {}, // кадр down: function (x, y) {}, // касание move: function (x, y, pointerType, pressed) {}, // pressed — палец прижат (на компьютере move идёт и без нажатия) up: function (x, y) {} // палец отпущен или система отменила касание (pointercancel) }; var view = MicroKit.setup(game); // сам зовёт MicroSDK.ready() после первого кадра // view: { w, h, dpr, canvas, ctx } — текущий размер и плотность пикселей (не больше 2) для своих вспомогательных холстов ``` Что Kit берёт на себя: - **Размер.** Пока лента грузит игру в фоне, iframe бывает нулевым или 300×150 по умолчанию. Если любая сторона меньше 200 пикселей, Kit раскладывает игру под 360×640, а настоящий размер приходит следующим `resize`. Кроме события окна Kit сверяет размер каждый кадр, так что потерянный `resize` не оставит игру в 300×150. Первый `resize` приходит **до** первого `reset`, когда состояние игры ещё не создано, и раньше, чем `setup` вернёт `view`, — поэтому плотность пикселей для своих вспомогательных холстов берите из третьего аргумента `dpr`. Холсты создавайте не меньше 1×1. - **Ошибки.** Исключение в `resize`, `update`, `draw` или `reset` не останавливает цикл навсегда: Kit пишет его в консоль один раз, и дымовой тест это увидит. Чинить всё равно надо. - **Звук.** На `restart` Kit обрывает хвосты звуков, запущенных через `tone` и `noise`. Свои синтезаторы и петли останавливайте сами. В режиме ожидания после рестарта игра молчит: звук разблокирован, но демо-анимация звучать не должна. - **Время.** При частоте ниже 20 кадров в секунду игровое время замедляется: так физика не проскакивает сквозь стены. Таймеры раунда ведите от `dt`, как и всё остальное. Помощники: `MicroKit.audio()` → `{ ctx, out }` (свой синтез подключайте к `out`, громкость и пауза уже учтены), `MicroKit.tone({ freq, to, type, dur, vol, attack, delay })`, `MicroKit.noise({ dur, vol, filter, freq, to, delay })`, `MicroKit.text(ctx, str, x, y, size, color, { weight, align, baseline, family })`, `MicroKit.roundRect`, `MicroKit.rand`, `MicroKit.clamp`. `tone` и `noise` — это только кирпичики. Звук каждой игры должен быть своим: собирайте из осцилляторов, фильтров, огибающих, FM, щипковых (Карплус — Стронг), шумовых ударных. Можно писать игру и целиком без Kit, на своём движке или WebGL, — контракт один: SDK. ## Разнообразие: чтобы 50 игр не были близнецами - **Визуальный стиль у каждой игры свой**: палитра, способ рисования, свет. Примеры приёмов: неоновые векторные линии со свечением (аддитивное смешивание), пиксель-арт с дизерингом, бумажная аппликация с мягкими тенями, изометрия, ложное 3D с плоской заливкой граней, глина с мягким затенением, чертёж на кальке, гравюра, витраж, ЭЛТ-развёртка, комиксный полутон, тилт-шифт-миниатюра, акварельные заливки. - **Свет и атмосфера**: динамические 2D-источники с тенями, фонарик в темноте, туман слоями, лучи света, смена дня и ночи, контровой свет, ambient-градиенты. Хотя бы у части игр свет — часть геймплея. - **Сеттинг** у каждой свой: не всё в космосе и не всё абстрактные кружки. - **Звук** у каждой свой: тембры, лад, ритм, характер ударов. Музыка необязательна; если она есть, это короткая тихая процедурная петля, которая останавливается на паузе. - **Хук**: одна понятная фишка, которую видно в первые секунды и которую хочется показать другу. Её и описывает `tagline` в каталоге: лента показывает его в визитке игры вместе с автором, визитка открывается по названию на карточке. - Не повторяйте эталоны (стопка блоков, уворот от метеоров, тап по цвету, «Саймон», раннер с прыжком) и игры других партий. ## Каталог Каждая партия пишет только в свой файл `catalog/batch-N.js` (его заводит `node tools/viewka.mjs batch new N`, запись добавляет `viewka game new`): ```js window.VIEWKA_CATALOG = window.VIEWKA_CATALOG || []; window.VIEWKA_CATALOG.push( { id: 'p3-coffee-rush', // = имя папки; латиница, цифры, дефис; префикс партии title: 'Кофе-аврал', // 1–3 слова author: 'Студия «Пенка»', // вымышленная студия, у каждой игры своя tagline: 'успей сварить, пока очередь не взбесилась', // строчными, до ~45 знаков: хук игры src: 'games/p3-coffee-rush/index.html', hue: 32, // главный оттенок игры, 0–360: им лента красит заставку и кнопку unit: ['чашка', 'чашки', 'чашек'], // формы для 1, 2, 5: «1 чашка», «3 чашки», «7 чашек» cta: null // у игр подборки всегда null, см. ниже }, // … ); ``` `cta` — кнопка на полную версию. Она бывает только у игр авторов: они указывают её в форме загрузки. У игр подборки Вьюшки полной версии нет, поэтому всегда `null`: на их финальном экране лента изредка показывает свою кнопку — «Вьюшка в приложении» или «Сделай свою игру». ## Проверка Всё делает одна команда — она сама поднимает сервер на свободном порту и гасит его: ```bash node tools/viewka.mjs game check # одна игра node tools/viewka.mjs game check pN- # вся партия ``` Внутри три шага. Их можно запускать и по отдельности, со своим сервером (`python tools/serve.py <порт>` в фоне): 1. **Статическая проверка:** `python tools/check_games.py pN-` — размер, SDK, запрещённые загрузки и хранилища, битая кодировка, запись в каталоге. 2. **Дымовой тест в фоновом Chrome:** `VIEWKA_BASE=http://127.0.0.1:<порт> node tools/smoke.mjs game `. Он открывает стенд `dev/harness.html`, ждёт `ready`, тапает и играет случайно до конца раунда. Потом проверяет рестарт и паузу, собирает ошибки консоли и сохраняет скриншоты — путь в конце вывода. **Скриншоты обязательно посмотрите**: это единственный способ увидеть игру глазами. Тесты запускайте по одному, не пачкой. 3. **Размеры:** `VIEWKA_BASE=… node tools/smoke.mjs sizes pN-` — каждая игра проходит через 300×150, 0×0 и телефонные размеры без ошибок. Ещё полезно: - **Встроенный браузер приложения не грузит вложенные iframe,** поэтому ленту и стенд в нём не открыть. Игру можно открыть напрямую: `http://127.0.0.1:<порт>/games//index.html`. Без ленты SDK пишет события в консоль (`[MicroSDK] start`). События ленты можно послать вручную: `MicroSDK.debug('pause')`, `MicroSDK.debug('restart')`. - **Предупреждение «раунд кончается быстрее 8 секунд при случайных тапах»** нормально для игр на мастерство. Но проверьте сами, что внимательный новичок продержится 30+ секунд. - **Новую игру начинайте с заготовки** `node tools/viewka.mjs game new …`. Она сразу проходит тест, а частые ошибки собраны в `docs/games.md`. ## Чек-лист готовой игры - [ ] В режиме ожидания игра живая и за 3 секунды ясно, что делать. - [ ] Первое касание — игра и `start()`. - [ ] Каждое действие даёт отклик: звук и картинку (частицы, вспышка, тряска, растяжение). - [ ] Сложность растёт, раунд у среднего игрока 30–90 секунд. - [ ] Счёт крупно и читаемо, единица в каталоге согласована с ним. - [ ] `restart` сбрасывает всё, пауза замораживает всё, включая звук. - [ ] Работает на 360×640 и на высоком экране; важное не ближе 12 пикселей к краям (у карточки скруглённые углы). - [ ] Дымовой тест без ошибок, скриншоты просмотрены, `check_games.py` — OK. - [ ] Своя фишка, свой стиль, свой звук, свой сеттинг.