Привет, Хабр! Как-то раз после работы мне захотелось взять и написать небольшую компьютерную игру. А почему бы и нет? Играть я люблю, программировать — тоже. Захотелось проверить, можно ли сделать что-то прикольное на уровне современных AAA-игр, не изучая дополнительных языков программирования, а также избежать банального повторения тех же «велосипедов», которые уже 100500 раз выложены на различных стримах и, конечно, не раз разбирались на Хабре. В этом посте я хотел бы поделиться с вами своим небольшим экспериментом в области GameDev на базе JS и обсудить возможности, которые есть у любознательного программиста с бэкграундом в сфере JavaScript. Меня зовут Семен Брятов, я занимаюсь разработкой фронтэнда в команде Леруа Мерлен. При необходимости иногда немножко работаю fullstack-программистом на базе Node JS, но случается это пока что редко (хотя кто знает, куда меня занесет судьба завтра 😃). Периодически занимаюсь всякими экспериментами и интересными пет-проектами. И хотя являюсь практически нубом в этой сфере, сегодня хочу поделиться своим опытом GameDev’а. Может быть, мой пост вдохновит ещё кого-нибудь заняться чем-то интересным в свободное от работы время.  Вообще, сама идея написать свою собственную игру давно витала где-то у меня в голове. Мало того, что я люблю видеоигры и как любой (не)нормальный программист отдыхаю тоже в коде, так еще мне нравится испытывать боль и удовольствие от удач и неудач. При этом программировать я умею лучше всего на JavaScript. Все это привело к мысли: пора уже сделать свою собственную игру на JS! 💪💪💪 У любого разработчика с опытом при подходе к новому проекту неизбежно возникает мысль: «Зачем городить костыли и велосипеды, ведь их уже нагородили?» Нет, конечно, написать свой собственный фреймворк на JS — задача интересная, но мне хотелось получить игровой результат в какие-то более-менее адекватные сроки.  Когда я впервые подступался к подобной задаче(а было это давно, я тогда только погружался в мир веба), у меня возникли опасения, что найти подходящий движок под JS будет не так просто. На тот момент я был уже знаком с движками Unreal и Unity и даже немного пробовал в них ковыряться. Но я прекрасно понимал, что при всей своей крутости Unreal Engine сильно использует С++. Да, в нем в последнее время добавились Blueprints, которые позволяют заниматься визуальным программированием (примерно как в нашей Platformeco). Но во всем этом нужно еще разбираться и разбираться… да и все равно есть подозрение, что без С++ далеко на Unreal не уедешь. Движок Unity плотно сидит на C#. В нем была (это ключевое слово) поддержка JS. Но, судя по всему, разработчики решили от нее отказаться. Думаю, они правильно сделали: лучше специализироваться на чем-то одном, но хорошо. Когда-то в универские годы у меня был опыт разработки как на С++, так и на С#. Но кривая дорожка судьбы привела меня в мир веба. По этой причине, отбросив два самых популярных игровых движка в геймдеве, я решил отправиться в свободное плавание…  Я знал, чувствовал в глубине души, что ситуация не может быть такой мрачной. Сейчас, будучи опытным юзером веб-технологий, я знаю что для JS написано не просто много либ, а СЛИШКОМ много либ. Среди них просто обязано быть подходящее мне решение! И выяснилось, что так оно и есть. Ассортимент действительно большой — и это поначалу даже пугает. Так сказать, рождает большую ответственность разработчика за выбор «правильного», подходящего инструмента. Тут-то и начался рисерч. В целях первого ознакомления он был относительно недолгим. Уверен, можно раздобыть гораздо больше инфы, однако, на мой нубский взгляд, вот самые популярные фреймворки на 2022 год, которые я успел раскопать. Движки для 2D Один из наиболее beginner-friendly вариантов —  GDevelop. В этом фреймворке есть своя IDE. В ней можно программировать не только с использованием JS, но также при помощи блоков — снова приведу в пример решение Platformeco от моей компании. Ну а если ближе к геймдеву, то здесь стоить вспомнить про упомянутую ранее Blueprints от Unreal Engine. Вообще, визуальное программирование активно развивается, и, думаю, работать с GDevelop будет удобно многим, кто уже имеет подобный опыт. Phaser, пожалуй, самый популярный фреймворк разработки для 2D на JS. Насколько я знаю, Phaser уже лет 10 существует и развивается. И такая история обеспечивает ему огромное количество дочерних библиотек и дополнительных примочек от огромного сообщества.  Melonjs — фреймворк-конструктор, в котором просто собирать проекты из различных модулей. Его тоже активно используют разработчики 2D-игр. Глубоко в него не копал, но довольно часто слышал его упоминание в различных топ-обзорах популярных фреймворков для JS. Движки для 3D Когда мы говорим про 3D, сразу же на ум приходит PlayCanvas. Как мне показалось во время исследования, это настоящий гигант в мире разработки игр на веб-технологиях. На базе PlayCanvas можно прямо в браузере запустить специализированную IDE и уже в ней работать с текстурами и модельками. Платформа широко поддерживает 3D Web-рендеринг и показывает, кстати, отличную производительность. Пока я проводил свой ресерч, запускал пару демок, и на PlayCanvas получился вполне нормальный уровень графики. Это ведь в браузере! В общем, штука хорошая. Babylon.js — крупный комбайн для работы с графикой, который позволяет подгружать разные текстуры и модельки. Под капотом есть множество опций для работы с физикой. Этот фреймворк также вполне можно использовать, если вы захотите сделать игру, которая будет сложной с графически-физической точки зрения. ThreeJS — еще одна популярная библиотека для работы с графикой в браузере (думаю, что самая популярная). Ее можно свободно использовать, она относительно простая и, самое главное, бесплатная! Но если говорить о геймдеве, с ней придется много что писать самому с нуля. Ведь это больше инструмент рендеринга 3D для веба, а не полноценный движок с физикой, готовыми контроллерами и прочими прелестями игровых фреймворков. Так что инструмент хорош для тех, кто любит  «заходить с кода и продолжать кодом», не боясь «получить Нобелевскую за изобретение очередного велосипеда». Зато какие красивые сайты-визитки можно с ее помощью делать! Я вдохновился! И вам советую посмотреть примеры с официальной страницы библиотеки https://threejs.org/. Что выбираем? Повторю то, что сказал в самом начале. Я — нуб. Поэтому решил выбрать что-то попроще, самое популярное и подробно разжеванное. После недолгих раздумий я решил начать с 2D, взял Phaser и погнал работать… Но куда работать-то? Стоит ли реально закапываться или лучше сделать что-то простенькое? В голову пришел бесконечный раннер… Но вот смотришь на YouTube 1001 туториал о том, как индусы кодят тот же самый раннер, и начинаешь думать: «Какая посредственность… зачем мне это нужно? Даже для учебы брать уже как-то скучно». Тут, к счастью, у меня состоялась беседа с коллегой, Вадимом Жуковым (руководитель разработки на одном из проектов, тоже интересуется гейм-девом), который предложил мне кое-что изменить и сделать управление в раннере… с помощью звука! И вот в этот момент у меня в голове, наконец, загорелась лампочка: «Отличная идея, нужно попробовать!» И я начал делать свой сверхзвуковой раннер. Итак, я взял базовый сборщик на базе Vite и TypeScript, а также использовал пакеты audiomotion — analyzer v3 (помогает распознавать звуки) и phaser v3. Деплой реализовал через gh-pages (быстро-модно-молодежно). Ссылочка на проект, ридми и демку тут — https://github.com/SimonBryatov/Phaser3-RunnerGame Итак, как все это выглядит в phaser? Есть сам проект. Я назвал его… Phaser3-RunnerGame. Мы создаем в нем instance: определяем ширину и высоту нашего экрана, настраиваем зум. Можно добавить физику (кстати, аркадная физика лучше всего подходит для 2D), назначаем гравитацию — по оси Y, чтобы динозаврика тянуло обратно на землю.  Объект игры ссылается на сцены — это вторая core-составляющая Phaser. Сцены — это отдельные классы, в которых мы описываем логику для каждого конкретного момента.  Сцены бывают активные игровые, а бывает preloader или, к примеру, gameover. Я использую preloader, чтобы загрузить все текстуры, картинки, музыку и прочее (так было рекомендовано в туториале, и я решил следовать лучшим практикам!). По факту, если в игре ресурсы будут жирными, то надо сделать экран загрузки, где потенциальный игрок будет весело проводить время в ожидании подготовки основной сцены игры. Для моего проекта речь идет про какие-то доли секунды, однако на будущее в голове надо держать этот концепт. Плюс данная сцена — некая абстракция для инкапсуляции неигровой «скучной» логики подгрузки ассетов, чтобы не засорять лишними букафффами основную игровую сцену.  Когда все подгрузится, мы переходим на этап сцены Game:  В этом классе описана вся логика геймплея. Кто работал в react, тот знает lifecycle-методы — тут что-то похожее. init — аналог on mount create — что-то среднее между mount и первым update  update — как render, только подвязанный на знакомый многим Window.requestAnimationFrame() — каждый очередной момент времени движок вызывает данный метод, и происходит вычисление следующего состояния сцены. В update можно как раз узнать силу входного звукового сигнала в каждый такт игры. Мы считываем ее, делаем примитивные калькуляции и даем объекту нужную скорость.  Тут же определяется вся логика взаимодействия динозаврика с миром. Если динозавр сталкивается с машиной, сцена переходит в сцену GameOver, которая кладется «поверх» сцены Game. Когда мы совершаем клик мышкой, происходит сброс основной сцены Game и таким образом производится перезапуск игры с начальными параметрами. Признаюсь, я сам до конца в phaser не разобрался и делал все методом тыка, поэтому не претендую на то, что мой код предельно оптимальный. Однако, он работает! 🚀 Так что можно сказать, сейчас делюсь с вами теми основами, которые нашел сам. Очевидно, что можно все завернуть глубже, круче и интереснее! Ключевой момент — управление звуком 🎙️🕹️  Но вернемся к тому, что делает мой раннер чем-то особенным. И это управление звуком. Для этого я обратился к крутой либе audioMotion. Это полноценный музыкальный плеер, который содержит множество функций. Только вот сам плеер мне не был нужен и тут мне несказанно повезло. Эти ребята также выпустили отдельно пакет audioMotion-analyzer, который содержит в себе только вычислительные функции для работы с звуковым входом в браузере. На его основе можно проводить как визуализации (визуализатор работает на технологии canvas прямо из коробки!), так и получать конкретные значения амплитуды звукового сигнала в выбранном частотном диапазоне.  Предоставляемый библиотекой метод getEnergy(...) как раз таки нам и нужен. В нашем случае он вызывается при каждом вызове update-метода сцены Phaser, то есть на каждый обрабатываемый игрой такт, и возвращает «силу сигнала» в указанном диапазоне частот. Я использовал эти значения, чтобы вычислить скорость, с которой наш динозаврик должен бежать и прыгать.  Фактически, под капотом программных абстракций происходит очень интересная штука — звук от входного аудиоустройства и аудиокарты компьютера передается браузеру и библиотеке, в которой и происходит вся магия по обработке сигнала. Благо эту «магию» делаем не мы — за нас уже постарались физики, низкоуровневые ЯП и разработчики библиотеки audiomotion :) Наше дело несложное — направить «нужные циферки» в «нужное русло» игрового контроллера под управлением движка Phaser и заставить героя выполнять примитивные действия, ради нашей забавы. Как я уже описал выше, метод GetEnergy выдает результаты от 0 до 1, оценивая «силу» входного звука (не знаю, какой радиотехнический термин тут корректнее употребить, так что прошу меня простить). При этом можно выделить только определенный диапазон частотного спектра и получить значение для конкретных типов входящих сигналов. Для движения динозавра я решил выбрать частоты от 0 до 60 Гц. Это характерный звук от, скажем, стука кулаком по столу или топанья по полу. Библиотека оценивает «среднюю энергию звука», а я уже перемножаю это число от 0 до 1 на определенный коэффициент, нормализуя его до подходящих значений для физики движка. В общем, немного уже нашей магии — и становится понятно, когда динозавр начинает бежать 😃  С прыжком то же самое, но на более высоких частотах. Я выбрал диапазон от 2 до 16 КГц — это уровень звука «хлопка в ладоши». Но тест-драйв показал, что туда же ложатся крики и даже шмыганье носом. Так что можно устроить себе очень интересную игру, например, выкрикивая «Ко-ко!», чтобы динозавр прыгнул.  Что еще потребовалось? Даже на таком простом проекте нужно еще кое-что, чтобы игра действительно заиграла. Я использовал программу Aseprite. Она пригодилась, чтоб нарисовать динозаврика, а также анимировать его. Кстати, создатели Aseprite реально старались. Прога получилась отличная, и я нарисовал отличного Дино, и даже трассу сам нарисовал (хотя и не очень старался). За этот удобный инструментарий для PixelArt я даже решил заплатить аж 350 рублей. Никаких вам «йо-хо-хо» и «бутылки рома»! 🏴‍☠️ 😂 К фону я подошел проще — взял фотографию города и пикселизировал ее для пущей аутентичности. Машину и кустик просто взял из открытого доступа. Они неплохо вписались в антураж.  В общем, получилась забавная игруха, в которой я даже сам пока не очень преуспел, потому что наверное... медленно шмыгаю носом и хлопаю в ладоши! Сразу скажу, что играть в нее лучше в наушниках, чтобы музыка (а там есть саундтрек просто супертематический!) не мешала библиотеке audioMotion-analyzer определять «энергию ваших звуков» правильно. Хотя если сделать музыку потише, а микрофон при этом у вас относительно нормальный, можно играть и без наушников. Как раз те самые «страшные коэффициенты» для умножения значений уровня входного звука я старался подобрать так, чтобы минимизировать ложные срабатывания. Но не переоценивайте мою экспертизу в этом вопросе — решал задачу методом тыка, конечно же! Судя по всему, игру уже можно экспортировать как Android- и iOS-приложение — кажется, я встречал такие возможности во всех (или почти всех) упомянутых при рисерче библиотеках игровых движков для JS. Что и говорить, сейчас Web пытается встраиваться куда угодно. Может быть, скоро мой раннер получится запустить даже на «вашем тостере». Ну, вы понимаете :) В любом случае, когда будете запускать, сделайте предварительно потише громкость в системе, чтобы нечаянно не оглохнуть) Конечно, по-хорошему нужно бы еще настраивать и настраивать эту игруху, но я свою задачу выполнил и действительно сделал свой первый раннер, причем полностью на JS. Если вам интересно или хотите сделать что-то свое по образу и подобию, дублирую ссылочку на GitHub тут. Там же в ридми есть ссылочка на демку, развернутую на gh-pages.  А вообще, спасибо за внимание, буду рад вашим комментариям!  Всем хорошего настроения и творческого подхода! 😜   JS Dev: Frontend(85%) + Backend(15%)