Arkanoid GL
Арканоид на чистом WebGL2 и JavaScript, без сборки и зависимостей.
Играть: https://dinoel.github.io/arkanoid-gl/
- Физика. Мяч, ракетка и кирпичи — настоящие тела со столкновениями многоугольников; кирпичи бывают
прямоугольные, круглые, ромбы, шестиугольники, треугольники, трапеции, параллелограммы, «таблетки»,
полукруги, кольцевые секторы, воздушные змеи. Многие движутся: кружат по орбитам, вращаются, ездят туда-сюда,
описывают фигуры Лиссажу, качаются на маятниках и бегут по ленте конвейера.
- Кирпичи. Обычные, прочные (2–3 удара), взрывные, стальные (не бьются) и особые: стекло мяч
разбивает с налёта и летит дальше; у бронированных часть граней в стальных пластинах — бить надо
в открытую: обычно броня снизу (бей сверху) или сверху (бей снизу), их много с первого уровня; живой с бьющимся сердцем зарастает трещинами, если его не добить за 3 с (но силы
у него кончаются); призрак периодически гаснет — мяч проходит сквозь; кристалл всегда роняет бонус.
- Осколки. Разбитый кирпич разлетается на осколки; их можно ловить ракеткой. За каждый — очки
(у прочных и взрывных больше), а поимки подряд, без пауз дольше секунды, умножают их до ×3.
- Гравитация. У кирпичей есть масса, и они искривляют траекторию мяча, почти не меняя его скорости
(клавиша G).
- Приманка. Если мяч долго не попадает по кирпичам (обычно когда остался последний), они начинают
притягивать его всё сильнее — вокруг появляется «воронка». Попадание снимает притяжение.
- Магнит. Пока зажата кнопка мыши или пробел, ракетка заворачивает мяч к себе: можно вернуть его
из-под потолка или подправить падение. Если подобран лазер, кнопка вместо этого стреляет.
- Вода (W). Внизу поля вода: каждый упавший в неё осколок поднимает её на свой объём. Ракетка
всегда плавает, как кораблик: качается на волнах, проседает от ударов мяча, гонит носом вал и оставляет за собой
след; поднимаясь вместе с водой, она оставляет всё меньше места до кирпичей, а мяч с неё уходит в брызгах. Осколки и бонусы тонут медленно — их можно догнать. Пойманный осколок убавляет воду
(выловленный из неё — ещё и на свой объём), взрывы её выпаривают, пройденный уровень вода покидает. Кромка плещется, от мяча, ракетки и осколков расходятся круги и летят брызги; мокрый мяч
роняет капли и стряхивает их о кирпичи. Шейдер пола рисует преломление, бледную каустику и пену; вода спокойная,
без бликов: рябь быстро гаснет, а кромка плещется медленно.
- Подводный мир (
js/seabed.js). Каждый уровень сажает на дне свой сад: ленты валлиснерии, которые
скручиваются, показываясь то плашмя, то ребром, широкие волнистые листья на черешках, стебли с парами листочков
(зелёные и красные), камешки у корней. Водоросли качает медленное течение, рыбки, мячи, осколки и бонусы их
расталкивают; вода прибывает — они тянутся за ней, а самые длинные ложатся под поверхностью. От листьев
поднимаются пузырьки, в толще висит взвесь.
- Рыбки. В воде всегда живут две рыбки. Обычно они серебристые и заняты своим: плавают, щиплют водоросли,
отдыхают у дна, следят за мячом, глотают воздух у поверхности. Пугливы: от мяча или осколка, плюхнувшегося
в воду рядом, шарахаются прочь, растопырив плавники, а тонущий осколок обплывают.
Бирюзовый бонус превращает одну из них, наугад, в золотую на 25 секунд (ещё бонус — ещё 25 той же рыбке, но
не больше 50; время идёт, только пока мяч в игре). Тогда она заранее плывёт туда, где упадёт мяч, который
ракетка упустила, и отправляет его обратно вверх, к кирпичам; спасает сколько угодно раз. Рыбка, видная сбоку,
втягивает мяч ртом, отплывает в сторону, задирает нос и выплёвывает его, а сама от отдачи сдаёт назад.
Рыбка, видная сверху, отбивает мяч хвостом, как дельфин: подставляет хвост туда, где мяч пересечёт её
дорожку, на подлёте замахивается и хлёстко бьёт навстречу, а саму её от удара бросает вперёд. В последние
секунды золотая рыбка вспыхивает, потом снова становится серебристой. Рыбки живые, по позвоночнику из 13 позвонков от головы к хвосту бежит плавательная
волна — тело извивается, хвост машет с запаздыванием, плавники трепещут, грудные на ходу прижаты, а на месте
гребут попеременно; рот всё время дышит, жаберные крышки ходят. Чешуя переливается, как перламутр: каждая
чешуйка наклонена по-своему и, когда рыбка изгибается и поворачивается, вспыхивает радугой, а по бокам бегут
блики ряби с поверхности; серебристая переливается ярче, золотая — мягче.
Одна (
js/fish.js) видна сбоку, как золотая рыбка: высокое тело с выгнутой спиной, тупое рыло, большой глаз
с золотой радужкой, жаберная крышка, крупная чешуя сеточкой, высокий спинной плавник и раздвоенный хвост.
Тело объёмное, освещено сверху; разворачиваясь, рыбка поворачивается мордой к нам или от нас, глаз следит
за мячом. Другая (js/fish-top.js) видна сверху и изгибается в плоскости экрана: хвост-вуаль, глаза
навыкате, чешуя внахлёст с отливом, спинной плавник кренится на поворотах; иногда крутит петли.
- Процедурные уровни. Каждый уровень собирается из сида; сид можно задать в адресе:
?seed=12345,
а начальный уровень — ?level=4. Раскладок 14: сетка, соты, треугольники, кольца, спираль, мозаика,
крепость, механизм (роторы с лопастями и шестерни), маятники, волна, конвейер, мишень, пиксель-арт
(спрайты: космический захватчик, череп, гриб, ракета…), паркет (ёлочка и плетёнка). Генератор проверяет,
что подвижные кирпичи никогда не сталкиваются, а сталь не запирает бьющиеся кирпичи там, куда мяч не долетит.
- Боссы. Каждый четвёртый уровень — босс, собранный из кирпичей: огненный змей-дракон, лунная медуза
на щупальцах-цепях, ночной скат с машущими крыльями из панелей и ИИ-Горгона. Он слетается по кусочку, плавает по полю,
следит глазами за мячом и время от времени, зарядившись, стреляет в ракетку — попадание её ненадолго оглушает.
Раз в 10–15 секунд босс пикирует к ракетке и несколько секунд кружит низко над ней, подбираясь к ней вбок;
выпущенные оттуда снаряды летят медленнее. Снаряды сбивает мяч и лазер, огонь гаснет в воде. Отбивать можно любые кирпичи босса (у медузы обрываются
щупальца, у ската — хвост), но змея и медузу побеждает только удар в глаз-сердце. У ската жизнь общая на всё тело:
шкалу убавляет любой выбитый кирпич, а удар в глаз — вчетверо сильнее; теряя крылья, он проседает к ракетке,
кренится на побитый бок и стреляет только с уцелевших концов. Когда сердце гаснет, тело рассыпается цепочкой.
ИИ-Горгона — голова-маска с третьим глазом во лбу и бегущим кодом вместо рта; вместо волос у неё змеи из
ячеек с мерцающими зелёными нулями и единицами, с которых сыплется двоичный дождь. Она не стреляет, а смотрит:
глаза-фонари разгораются, пунктир прицела ложится на ракетку, и через миг оттуда бьют два зелёных луча, которые
ползут за ней. Задержавшаяся в луче ракетка на 2 секунды каменеет и не двигается (рывок сквозь луч проходит
безнаказанно). Разбитый фонарь гаснет, без обоих смотрит третий глаз; жизнь у неё, как у ската, общая на всё тело.
- Звук. Адаптивная процедурная музыка в шести стилях: synthwave, techno, drum'n'bass, lo-fi, trap и chiptune.
У песни есть узнаваемая тема с ответом и разрешением; дроп возвращает её и исходную тональность,
а в остальных секциях фразы открываются тактом темы и продолжают её вариациями.
Бас подхватывает акценты бочки, арпеджио заполняет паузы мелодии, голоса аккордов движутся плавно.
Ударные используют локально синтезированный банк с двумя уровнями динамики и чередующимися вариантами ударов.
Песни lo-fi всегда, а drum'n'bass и synthwave иногда играют живым комплектом: малый, хэты, клэп, шейкер,
томы, тарелка и конги записаны (бочка остаётся синтезом). Пианино тоже записанное, а в арпеджио и мелодиях
встречаются маримба, вибрафон, калимба и тенор-саксофон.
Клавишные реагируют на силу нажатия; стереолиды, ping-pong-эхо и реверберация создают пространство.
Мультимяч, комбо и бонусы меняют плотность и яркость партий по тактам. Потеря мяча ведёт в брейк
(нарастание сначала доигрывает до короткого дропа), на новом уровне прошлая песня плавно гаснет под
начало новой; длительный высокий накал приближает дроп.
Параметры музыки можно крутить в Music Lab.
Там же можно повторить песню по сиду, проверить игровые события и записать результат.
Записи взяты из Versilian Community Sample Library (CC0) и лежат в
audio/
(около 1,8 МБ, список файлов — в audio/README.md). Грузятся они в фоне и только для тех стилей, которым нужны;
пока записи не готовы (или страница открыта без сервера), играет синтез. Библиотек и внешних загрузок нет:
всё работает на Web Audio.
- Глобальное освещение. Radiance cascades: мягкие тени, цветные прожекторы на стенах, свет от огня
и вспышек. Прожекторы висят выше кирпичей: лучи ложатся на их верх, а пол за кирпичом остаётся в тени.
Если видеокарта не успевает (игра замеряет, сколько она рисует кадр), сначала грубее считается свет, потом
снижается разрешение; выключается клавишей L.
- Цветомузыка. Прожекторы играют музыку светом. Цвет берётся из нот по квинтовому кругу, как у Скрябина:
аккорд перекрашивает весь зал. Нижние прожекторы горят цветом баса и бьют под бочку, верхние вспыхивают
от малого барабана, по арпеджио бежит огонь, верхние лучи ведут мелодию. Лучи танцуют фигурами,
которые меняются по тактам, а перед дропом свет мигает и гаснет.
- Настройки (O). Ползунки для свечения мяча: свет на поле, ореол, шлейф, яркость. По умолчанию
мяч не светит, а только отбрасывает тень. Качество картинки: свет (высокое, среднее, низкое) и разрешение
(×2, ×1,5, ×1 пикселя на точку на экранах с масштабом); с галочкой «авто» игра сама снижает их, если
видеокарта не успевает, выбор вручную её снимает. Выбор запоминается в браузере.
Управление
| |
|---|
| мышь / ← → | ракетка |
| клик / пробел | запуск мяча, лазер |
| зажать кнопку мыши / пробел | магнит: ракетка заворачивает мяч к себе (с лазером — стрельба) |
| P / Esc | пауза |
| M | музыка |
| G | гравитация |
| L | глобальное освещение |
| W | вода |
| O | настройки |
| F | счётчик кадров: FPS, худший кадр, время логики и рисования, качество света |
| N | пропустить уровень |
| R | новая игра с новым сидом |
Запуск локально
Нужен любой статический сервер, например:
python -m http.server 8000
и открыть http://localhost:8000/.
Проверка музыки
Композитор и контракт светомузыки проверяются без зависимостей (Node.js 22+):
node --test --test-isolation=none tests/music.test.cjs
Проверка игры
Игра без браузера (без звука и интерфейса, tests/headless.cjs). Боссы — на демо-игроке: каждый собирается
из кирпичей, кирпичи держатся на своих местах скелета и в полёте, и в пике, мяч наносит урон, а с разбитым
сердцем босс рассыпается и уровень пройден. Рыбки — без бонуса обе живут своей жизнью под водой и мяч не
ловят, а от осколка, упавшего в воду рядом, удирают; бонус золотит одну из них наугад, и, пока ракетка убегает
от мяча, она спасает его снова и снова (боковая ловит ртом и выплёвывает, верхняя отбивает хвостом), пока не выйдет её время (осколок её только заставит вздрогнуть). Водоросли держатся корнями, не вылезают из воды и растут вместе с ней:
node --test tests/boss.test.cjs tests/fish.test.cjs
Проверка в браузере
Для проверки настоящего Web Audio, Music Lab и WebGL нужен установленный Chromium/Chrome/Edge:
node tests/browser-smoke.cjs
Если браузер не найден автоматически, задайте путь к нему в переменной BROWSER.
Тест рендерит все шесть стилей через OfflineAudioContext, проверяет пики, стерео, освобождение голосов,
выключение реверберации вместе с мастер-громкостью, управление и прожекторы. Отчёт, скриншоты и
пример music-showcase.wav сохраняются в .artifacts/ (не входит в Git).
Музыка и свет используют одни и те же события: все слышимые партии проходят через обёртки в music.js,
которые отправляют фактическую громкость, MIDI-ноту и длительность в Music.lights().
Music.clock() остаётся на сетке аудиочасов; свинг смещает отдельные ноты, сохраняя тактовую сетку.
Новые инструменты следует подключать через эти обёртки, чтобы прожекторы оставались синхронными.