Обратно в блог
  • DevOps
  • frontend

Будьте в курсе всех событий

Геймдев в отделе рекламы Маркета, или Приключения красного фургончика

Привет! Меня зовут Миша Лардугин. Наша команда занимается развитием рекламы. И не только на Маркете, но и в других сервисах экосистемы: Go, Еде, Лавке, Кинопоиске и даже на планшетах в такси. Обычные наши задачи — это баннеры, спецпроекты на лендингах и развитие инструментов партнёрской сети для привлечения внешнего трафика. Но внутри команды давно зрела одна интересная идея: а что, если сделать рекламу по-настоящему живой?

Не просто статичный креатив, который пользователь скроллит за секунду, а настоящее взаимодействие — динамику, реакцию, полноценный интерактив. На мозговых штурмах мы накидывали разные варианты: от баннера, где можно виртуально погладить котика, до мини-симулятора управления электробритвой. Всё это, конечно, весело, но больше походило на ситуативные развлечения-однодневки. Нам же хотелось создать масштабируемое решение, способное вызывать эмоции и удерживать внимание.

Так родилась концепция мини-игры внутри приложения, в которой пользователь мог бы зарабатывать призы с помощью игровых механик. В этой статье я расскажу, как эта идея прошла путь от прототипа, собранного на коленке, до полноценного релиза.

Выбираем движок

Начать мы решили с главного — идеи. Цель была понятной: создать игру, которая вызывает эмоцию и чётко ассоциируется с брендом. Первый вариант, который у нас появился, выглядел примерно так: с неба падают монетки, пользователь кликает и собирает их за 30 секунд. Реализовали это на обычном Canvas. Быстро собрали, запустили и поняли — играть в это скучно. Механика попросту не цепляет.

Тогда мы стали раздумывать над популярными паттернами. Вспомнили классику — Subway Surfers, решили сделать его вариацию в 2D и с машинками. И тут пазл сложился: фургончик Маркета, который развозит заказы, уворачиваясь от трафика. Этот концепт оказался уже гораздо более интересным и на десктопе, и на тач-устройствах.

01.png

Но для реализации раннера обычного Canvas было уже недостаточно. Нам требовался движок, который возьмёт на себя физику, коллизии и управление сценами. В выборе стека мы отталкивались от жёсткого ограничения: хотелось уложить всю игру, включая код и ассеты, в 5–6 МБ. Потому что игра должна загружаться мгновенно — пользователь заходит в приложение, видит баннер, тапает и сразу играет. Никаких долгих загрузочных экранов.

Кандидатов было трое:

  • Unity. Мощный инструмент, хорошо нам знакомый, но для веба он подходил плохо. Unity запускается через свой WebGL-плеер, который сам по себе весит около 20 МБ. Это слишком тяжело для лёгкой встраиваемой игры. К тому же, интеграция такого плеера в существующий интерфейс Маркета вряд ли была бы бесшовной. Unreal Engine мы отмели сразу — он вообще не про веб.
  • PixiJS. Отличный, быстрый рендерер, но это конструктор из разряда «собери сам». В нём нет ни встроенной физики, ни системы коллизий, ни менеджера сцен. Всё это пришлось бы писать с нуля или подключать сторонние библиотеки.
  • Phaser. Этот вариант стал разумным компромиссом между мощностью и лёгкостью. У него из коробки есть система сцен, готовая физика, универсальный API для обработки input-а и отличная документация.

Phaser оказался самым оптимальным выбором для построения MVP и последующего релиза. На нём мы построили основную механику, используя привычное ООП, и, что самое главное, смогли уложиться в требования по весу сборки.

Пишем физику

В геймплее мы сразу столкнулись с классической проблемой раннеров: как сделать трассу интересной, нескучной, но при этом проходимой.

Изначально препятствия, в роли которых выступали другие машины, генерировались полностью случайно — просто спавнились на одной из линий абсолютно рандомно. Практика показала, что такой подход работает не очень хорошо. Иногда алгоритм выдавал скучные комбинации, где игроку даже не нужно маневрировать, а иногда — непроходимые стены из трёх машин подряд.

Тогда мы пришли к выводу, что нужны пресеты. Мы подготовили несколько наборов паттернов расстановки препятствий. Из этих готовых наборов игра как бы собирает длинную линию в 50 машин. Игрок едет, и как только добирается до 40-го препятствия, скрипт подгружает новую пачку. Это позволило нам контролировать сложность и темп. Например, мы смогли реализовать паттерн вроде шахматной доски, когда две полосы заняты, одна свободна, и свободная полоса постоянно меняется. Это заставляет игрока постоянно маневрировать, а не просто ехать прямо.

Помимо машин, поначалу пробовали добавлять открытые люки. Но с ними возникли проблемы: часто оставалось слишком мало пространства для манёвра, и мы решили заменить их на механику топлива. Теперь фургончик тратит бензин, и его нужно пополнять. Канистры спавнятся строго по триггерам: когда остаётся 75%, 50% и 25% бака. Если пропустил — новая появится только на следующей отсечке, а там можно уже и не доехать, если часто их пропускать.

02.png

С позиционированием канистр тоже экспериментировали. Сначала ставили их на безопасную, свободную от машин линию. Оказалось слишком просто — едешь себе прямо и автоматически собираешь бонусы. Поэтому сделали спавн случайным. Иногда канистра появляется в труднодоступном месте, заставляя игрока рисковать и выруливать в последний момент.

Визуальный стиль выбрали так, чтобы он был одновременно и не слишком сложным, но и чтобы выглядел стильно. Поэтому остановились на Pixel Art. В нём просто рисовать, и в нём нужно прям очень сильно постараться, чтобы получилось откровенно плохо. В качестве инструмента использовали Aseprite — это практически отраслевой стандарт. Работали итеративно: набросали спрайты фургона и окружения, добавили анимации и эффекты.

У пиксель-арта есть и неочевидный технический плюс. В Phaser есть опция округления пикселей. Благодаря этому картинка всегда рендерится на целочисленных координатах и выглядит максимально чёткой, без характерного мыла.

Оптимизируем

Поскольку игра всегда запускается в WebView, мы неизбежно столкнулись с классическими проблемами мобильного веба.

Во-первых: высокий DPI современных экранов. Изначально мы разрешили движку рендерить картинку как есть, подстраиваясь под devicePixelRatio устройства. На экранах с DPI 3 (например, новые iPhone) Canvas становился гигантским, и рендерер просто не справлялся — FPS падал в пол. Тогда пришлось принудительно ограничить максимальный DPI до 2. В сочетании с пиксельной графикой разница в качестве была незаметна глазу, зато производительность вернулась в норму.

Во-вторых, сюрприз преподнёс iOS со своим режимом энергосбережения. В этом режиме система жёстко режет частоту обновления экрана, отдавая браузеру около 24 кадров в секунду вместо 60. Phaser по умолчанию настроен рисовать анимации и физику, рассчитывая на стабильные 60 FPS. В итоге когда кадров становится меньше, движок пытается компенсировать это, но делает это специфически.

03.png

Ну и классика жанра — скролл и жесты. В Safari свайпы назад/вперёд и резиновый скролл страницы часто срабатывали прямо во время игры, ломая UX. Особенно эта проблема была заметна во второй нашей игре Match World, где взаимодействие завязано на свайпах. Там решение нам помогли реализовать коллеги из мобильной рекламы — сделали собственное кастомное WebView, заточенное под игры, где эти нативные жесты отключены на уровне приложения. Но об этом уже в следующих статьях.

Кстати, один из багов мы даже планируем превратить в фичу. Пользователи заметили, что в некоторых сценариях при многократном перезапуске игры экран постепенно темнел — видимо, что-то наслаивалось в рендере. Выглядело это так, как будто медленно наступает ночь. Баг мы, конечно, пофиксили, но благодаря ему решили сделать полноценный ночной режим: включать фары, когда на Маркете активируется тёмная тема, или как награду за 7000 км пробега. Обязательно добавим её в следующих релизах.

Интегрируем

Технически игра сейчас живёт в iframe и общается с виджетом Маркета через PostMessage. Протокол простой: игра отправляет события (например, обновление прогресса или фиксацию выполнения задания), а фронтенд их обрабатывает.

04.png

Сама игра ничего не знает о дневных испытаниях или наградах — она лишь транслирует статистику сессии через событие GAME_OVER, а уже виджет решает, выдавать ли пользователю монетки.

05.png

Мы не стали делать полноценный авторитарный сервер, который обсчитывает физику и движение машинки на своей стороне, — для маленькой промо-игры это избыточно. Вместо этого внедрили концепцию игровых сессий.

Работает это так:

  1. При запуске игры на бэкенде создаётся сессия и засекается время старта.
  2. В конце заезда клиент отправляет итоговый километраж.
  3. Сервер делит полученное расстояние на время, прошедшее с начала сессии.
  4. Если средняя скорость оказывается физически невозможной для нашей игровой механики, результат отклоняется.

Финалим

Спустя несколько недель разработки, споров о физике и итераций с пиксельными спрайтами, первая версия Market Rush увидела свет. Одометр нашего красного фургончика начал отсчитывать километры в начале мая 2025 года и не останавливается до сих пор. Ежедневно тысячи игроков заводят двигатель, чтобы доставить воображаемые заказы.

06.png

Игра уже пережила миграцию на новые архитектурные рельсы. Когда мы начали делать вторую игру, к бэкенду и CI/CD подошли уже более фундаментально, и старый добрый фургончик переехал на эту взрослую инфраструктуру.

Недавно мы запустили следующий проект — Match World. Это уже не раннер, а знакомая всем механика три-в-ряд. Она получилась ярче, сложнее и технически совершеннее первой части. Там мы решали уже новые задачи по оптимизации и геймдизайну, но об этом, как и обещали, расскажем в следующий раз.

Этот проект не состоялся бы без команды, которая решила выйти за рамки привычных баннеров. Спасибо Алексею Пивцову за идею, Александру Белову — за блестящую реализацию, Тимуру Садыкову и Антону Калмыку — за то, что дали этой авантюре зелёный свет. Отдельный респект нашим тестировщикам — Анне Семененко и Диане Полозовой, которые в поисках багов наездили сотни километров, и всем коллегам, кто помогал советом и критикой.

  • DevOps
  • frontend

Будьте в курсе всех событий