Wings GMS Online
Онлайновый воздушный бой на фейковом 3D, собранный в одиночку на GameMaker — витрина техники и крафта игры, которая так и не была доделана и лежит в паблике именно поэтому.
Обзор
Wings — онлайновый воздушный бой на фейковом 3D, который я собрал практически в одиночку на GameMaker: код, графика и ощущение игры. Это был заказ. Веб-платформа (с криптой) заплатила за него $2000, а потом свернулась и ушла. Игра так и не вышла, живых игроков за пределами внутренних тестов у неё не было, а исходник с тех пор лежит нетронутым. В паблике он по одной причине — из-за плотности геймдизайнерских и инженерных решений внутри. Это витрина, а не продукт.
Зачем это здесь
Читай это как витрину техники и крафта. Интересное в Wings — не запуск и не число игроков, а то, сколько ощущения, атмосферы и систем удалось упаковать в 2D-движок: убедительное 3D из плоских спрайтов, боевой тулкит, настроенный на импакт, полностью диегетический HUD, день и ночь по твоим реальным часам и рендер, который сам масштабируется, чтобы держать кадры на чём угодно.
Вдохновение
Форму задали четыре референса: - Cortex Command — эффекты и цвет; плотные, приятные взрывы. - Luftrausers — ощущение, механики и подсмотренная идея фейкового 3D. - HighFleet — слоёные горы, реализм и подсветка от вспышек выстрелов. - Ace Combat — интерфейс в духе кокпита.
От космического proof-of-concept к небу
Началось с крошечного: два корабля в космосе на вебсокетах, просто летают. Тот прототип должен был доказать всего несколько вещей — что поведение можно синхронизировать по сети, что других клиентов видно по их айдишникам и что стрельба работает через провод. Как только это подтвердилось, началась сама игра.
Фейковое 3D из трёх спрайтов
Никакого 3D-движка здесь нет. Каждый самолёт — это три нарисованных вручную спрайта: вид сверху, снизу и сбоку, которые подменяются по мере тангажа. Когда image_yscale интерполируется к нулю, код меняет спрайт ровно на переходе, так что один «вмывается» в другой, а вертикальное масштабирование убедительно продаёт крен. Смена знака держит разворот корректным.
Летающая тарелка выше — этот трюк в самом наглом виде: по-настоящему объёмный на вид 3D-объект, собранный из пары плоских спрайтов и кривой масштаба.
Полётная модель
Самолёт сложно описать, и в этом суть — он ощущается одновременно тяжёлым куском железа в небе и очень поддатливой, манёвренной штукой. У него есть гравитация, и он летит против неё. Повороты несут инерцию, усиленную подвижной камерой, поэтому мгновенно развернуться на 360 невозможно: максимальный угол поворота за секунду ограничен скоростью — чем быстрее летишь, тем труднее поворачивать. Продержи курс (в пределах десяти градусов) пару секунд — «включаются двигатели», скорость накапливается, а резкий выход из этого состояния даёт видимый выхлоп, так что учишься подстреливать в развороте, компенсируя разницу инерции. На практике получалось очень здорово.
Бой
Джус — чтобы каждый выстрел ощущался
Огромная часть работы ушла в импакт, чтобы выстрел никогда не был просто выстрелом. У пуль базовая баллистика: пролетев определённое время, они падают, а гильзы вылетают, почти сразу сыплются вниз и дают всплески на воде. Ракеты вылетают, метров через десять зажигают двигатель и тянут клубы дыма; самонаводящиеся какое-то время преследуют цель и просто падают, когда «кончается горючее». Каждый взрыв даёт небольшой стан, серия из пулемёта наносит больше, чем точечные выстрелы, а каждое спецоружие ощущается по-своему. Дым от чужих самолётов рассеивается, если пролететь сквозь него; самолёт можно «потерять» в облаке благодаря глубине фейкового 3D. Выстрелы подсвечивают окружение — заметнее всего ночью, где напавшего, которого не видно, можно засечь по одним только трассерам. Звука нет вообще — до него я не дошёл. Всё ощущение силы держится на движении, свете и камере.
Орбитальный ИИ
ИИ намеренно простой и при этом читается как настоящий противник в догфайте. Он берёт ближайшего игрока и летит в случайные точки на окружностях вокруг него — узкие круги (96–144px) для атаки, широкие (256–384–640px) для перегруппировки. Демпфированный поворот ведёт его к целевой точке, а огонь открывается только когда курс совпал в пределах порога и перезарядка готова. Убедительный бой почти без логики. В FFA опознавательных знаков нет ни у кого, поэтому с парой ботов разной степени агрессивности ты действительно не отличишь, где человек — в маленьком пространстве сессии они ощущаются живыми.
Динамическая камера
Влияние делят три объекта камеры. Камера ведёт по скорости (camDist = spd * 96), а в «режиме интереса» подтягивается к ключевым объектам с кинематографичным зумом: когда враг попадает в лок-он, она отъезжает, чтобы держать в кадре оба самолёта сразу, а потом плавно возвращается. Она следует за действием, а не за игроком.
День и ночь — по твоим реальным часам
Небо — это три градиента, отрисованных через draw_rectangle_color, чьи цвета интерполируются поканально от date_get_hour(). То есть время суток в игре — это твоё настоящее время суток. Ночью палитра сдвигается, а подсветка от вспышек выстрелов превращается в отдельный источник информации; облака подсвечиваются или затеняются в зависимости от часа, а самолёт, рассекающий облако, выбрасывает свои партиклы.
Мир — одна карта 8×8k
Глобально карта одна и огромная — 8×8k, бесшовная за счёт врапа по бокам. Внизу лежит 300 пикселей воды. В ранних тестах вода не ощущалась водой — ты просто получал урон и через секунду умирал под ней, — поэтому я дал ей два вида всплесков: маленькие на границе воздушного потока, когда идёшь низко, и огромные в пределах примерно пяти метров. Пролететь у самой воды и резко уйти бочкой наверх превратилось в настоящий момент из «Как приручить дракона». Наверху — край атмосферы. Гравитация там бьёт сильнее; это «безопасная территория», но у тебя есть десять секунд: упасть, быть сбитым — или тебя унесёт вверх в стартовое меню (а на входе обратно ты сразу вываливаешься в бой). Край стал зоной бесплатных killов: там удобно подлавливать тех, кто хочет уйти, особенно быстрыми прямыми ракетами, выпущенными трезубцем. Середина карты — основное мясо, где постоянно что-то происходит. За всем этим — четыре слоя бесшовных гор в духе HighFleet, двигающихся параллаксом относительно положения самолёта, дающих настоящую глубину и реагирующих на время суток.
HUD, который живёт в мире
Интерфейс почти целиком диегетический — зелёная рамка вокруг твоего самолёта. Слева стрелка показывает высоту относительно мира (падает у воды, поднимается к атмосфере), сверху такая же для горизонтали, заменяя миникарту. Снизу хэлсбар заполняется по мере перезарядки спецоружия, чтобы знать, когда можно стрелять. Вокруг самолёта — два курсора: тонкий следит за мышью, широкий за наклоном самолёта. Когда они сходятся, получается плотный прицел, который говорит, что разброс сейчас минимальный. Добавь рамку лок-она, которая появляется на враге в радиусе и нужном угле (показывая заряд и точность соответствия угла) — и это весь экранный интерфейс. Индикатор HP я тоже планировал, но не стал: в мире и так хватало диегетических сигналов, а HP читается по дыму и огню.
Оружие
Пулемёты с разбросом и вылетающими гильзами; ракеты с уроном по площади; самонаводящиеся ракеты; лазер на collision_line с динамической длиной, наносивший урон каждый тик — по-настоящему имбовое, зрелищное оружие, которое приходилось постоянно ребалансить относительно всего остального; и «панч» — скоростной таран-рывок, способный убить сразу. Спецоружие выпадало из бонусных ящиков-пикапов.
Интерфейс из draw-вызовов
Интерфейс почти на 100% построен на примитивах draw_ из GameMaker, а не на UI-ассетах, и стилизован под кокпиты Ace Combat.
Инженерия производительности
Самая эффектная вещь в проекте оказалась и худшим его багом: динамические облака на 2000 партиклей. Они меняли форму, оставляли позади себя куски на скорости и выглядели безумно реалистично — идеально на M1 Mac и ужас на вебе. Их вырезали. Прижилось структурное решение: игровой мир рендерится на собственный слой, отдельно от интерфейса, поэтому я мог зафиксировать соотношение сторон при запуске и на лету менять разрешение рендера мира в процентах. Медиана FPS за секунду ниже нормы — разрешение падает, от 100% до 60% шагом в 2%, и в насыщенной пиксельной игре это почти незаметно, зато держит стабильность даже на самом слабом «пентиум-ведре». Лучше всего то, что это дало единую точку оптимизации сразу для ПК и телефонов. (Под этим лежат пять именованных пресетов, для мобильного Safari по умолчанию 65%.)
Управление на обеих платформах
На телефонах это D-pad плюс две кнопки стрельбы — и этого хватило с головой. Рантайм-определитель выясняет, играешь ты с тача или с мыши (по тому, меняются ли координаты мыши при зажатом клике, и по мультитачу) и переключает управление в реальном времени, так что на экране всегда правильная схема. Ещё я сделал небольшую внутриигровую консоль, чтобы следить за состоянием после запуска без дебаг-инструментов.
Неткод — честная часть
Сеть была настоящей болью. Node.js + Socket.io тянут синхронизацию состояния и пуль по WebSockets, задумывалось под FFA на 32 игрока. Но работа так и не была закончена: клиент отдаёт свою позицию, сервер раздаёт её всем остальным, а лерпинг плюс небольшой предикт угла не дают самолётам телепортироваться — никакой серверной авторитетности, ничего глубже. Сложным это делали две вещи. Связка GameMaker + Socket.io + Node давала откровенно ужасную сетевую производительность, с которой приходилось постоянно бороться. И бэкенд пришёл от другого разработчика, поэтому написать свой я не мог — максимум дорабатывать под конкретные запросы. Тестировалось всё только внутри, с парой ботов в замесе для динамики.
В каком оно состоянии
Играбельное, но незаконченное демо. Есть динамический рендер, примерно половина интерфейса, куча отключённых вещей и сырой «летай и стреляй» в основе. Исходник as-is — ровно там, где всё остановилось.
Чем это могло стать
План был такой: небольшая весёлая онлайновая io-игра в вебе, а дальше — пятиминутная синглплеерная версия с прокачкой для мобилок и Steam Deck. Всё захлопнулось, когда клиент свернулся. Синглплеер мне до сих пор искренне хочется когда-нибудь доделать — уже не на GameMaker, но сохранив все до единого приколы.
Выводы
Wings — проект про плотность идей. Соло, в 2D-движке, здесь сложено убедительное фейковое 3D из плоских спрайтов, боевой тулкит, где ощущение важнее всего, полностью диегетический HUD, день и ночь по реальным часам и рендер, который сам масштабируется, чтобы выжить на любом железе. По отдельности ничего революционного — вместе получается игра, в которой было приятно летать те несколько минут, что она работала. Она так и не вышла, и держать её в паблике никогда не было ради этого. Смысл — во всём, что внутри.