Как мы замахнулись на мобильный fast paced шутер: технологии и подходы

в 10:30, , рубрики: android, Gamedev, iOS, mobile development, multiplayer, online, pvp, unity, Блог компании Pixonic, геймдев, мобильные игры, мультиплеер, проектирование, разработка игр, Разработка под android, разработка под iOS, управление разработкой, шутер

Как мы замахнулись на мобильный fast paced шутер: технологии и подходы - 1

Год назад у нас в компании был один проект — мобильный шутер War Robots с относительно медленными, но красочными и напряженными боями. Игра продолжает развиваться, у нее десятки миллионов установок и игроков по всему миру, постоянно выходят апдейты. В какой-то момент мы захотели сделать динамичный шутер на Unity со скоростями, сравнимыми с Overwatch, CS:GO или Quake. Но реализовать задуманное для мобильных платформ (в первую очередь iOS и Android) на основе War Robots при текущих архитектуре и подходах было практически нереально.

Мы понимали, как это сделать в теории — есть много статей, презентаций на YouTube, детально рассказывающих о том, как написать шутер, как работать с сетью, какие возникают проблемы и как их решать. Здесь нет Rocket Science, все эти подходы придумали еще 30 лет назад и за это время они особо не поменялись. НО: у нас не было практики.

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

Симуляция

Начнем с симуляции геймплея. Мы решили писать её на ECS — это data-oriented подход, в котором данные отделены от логики. Данные представлены как сущности (Entity) и компоненты (Components), принадлежащие сущностям. Логика описана в системах (Systems), которые обычно проходятся по компонентам сущностей и меняют их, создают новые компоненты и сущности.

Как мы замахнулись на мобильный fast paced шутер: технологии и подходы - 2

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

Один и тот же код симуляции работает одновременно на клиенте и сервере. Это дало нам следующие преимущества:

  1. Игровые фичи полностью может писать клиентский программист — та же самая логика будет работать на сервере.
  2. Нет задержки между действием игрока и результатом действия в игре, т.к. клиент моментально обрабатывает команду локально (prediction). И только если результат локальной симуляции разошелся с серверной, клиент берет за основу серверное состояние мира и ресимулирует (rollback).
  3. Обучение игрока (tutorial) мы можем симулировать локально на устройстве, без подключения к игровому серверу.
  4. При разработке фичи на проекте не нужен постоянно работающий сервер. Чтобы тестировать логику, графику, гейм-дизайн и т.д. — можно запустить матч локально.
  5. Используя общую библиотеку, мы смогли быстро написать ботов для нагрузочного тестирования, что вовремя выявило различные проблемы производительности и памяти на игровом сервере.

Клиент

Помимо prediction и rollback на клиенте, мы используем интерполяцию. Но не в привычном смысле, т.к. мы симулируем и рисуем в одном кадре, 30 раз в секунду, и по сути, классическая интерполяция нам не нужна.

По возможности мы интерполируем в буфере пришедшие с сервера состояния, т.к. на мобильных платформах потеря пакетов может быть существенная, а нам желательно иметь состояние мира для отрисовки здесь и сейчас.

Архитектура клиента

Как мы замахнулись на мобильный fast paced шутер: технологии и подходы - 3
В коде клиента активно используется инъекция зависимостей. В качестве DI-фреймворка мы используем Zenject. Конкретные настройки связывания описываются в маленьких Composition Root, которые в Zenject называются Installer. В большинстве случаев мы стараемся писать так, чтобы отключив конкретный Installer мы отключили фичу/представление/сетевое взаимодействие.

В игре есть несколько контекстов и объекты там принадлежат конкретному контексту, живут вместе с ним:

  • ProjectContext — объекты, которые живут все время жизни приложения;
  • MetaContext — выбор персонажа, экипировка, покупки и т.д.;
  • MatchContext — контекст PvP-боя.

Контекст боя

В контексте MatchContext на уровне представления игровой механики мы используем MVP. Моделью выступает данные из ECS, presenter’ы работают с ними и Unity-view частью для отображения. У нас есть презентеры для сущностей и компонент. На уровне представления UI используется MVVM, описанный ниже.

Для транспорта данных между клиентом и сервером выступает низкоуровневая библиотека Photon, из которой мы используем протокол, основанный на udp. Сериализуем данные сами: мы написали кастомную дельта-компрессию, т.к. мир игры большой, а объем трафика критичен для мобильных устройств и нам хорошо бы уложиться в MTU, чтобы состояние мира вместилось в один физический пакет.

Контекст меты

В MetaContext для уровня отображения мы используем MVVM и DataBinding на основе библиотеки Unity-Weld. В результате у нас нет для каждого окна кастомного MonoBehaviour-класса с кучей настроек и логикой UI, как это обычно делается на Unity-проектах. Вместо этого логика UI описана в ViewModel конкретного окна, а уровень View представлен всего одним классом — View (наследует MonoBehaviour) и несколькими стандартными классами для связывания данных (data binding). В нашем случае программист пишет только логику и выдает «наружу» необходимые данные, а UI-дизайнер верстает и настраивает привязку данных к отображению.

В контексте меты мы также используем подход Signal-Command-Service, частично позаимствованный из библиотек StrangeIoC и IoC+. Сейчас он основан на сигналах Zenject, но написан так, что в любой момент его можно переписать на любой другой вариант. Таким образом связь событие-действие/сервис у нас сейчас настраивается на уровне Installer и описывается в одну строчку.

Протокол общения с мета-серверами основан на Protobuf, в качестве транспорта используется WebSockets.

Сторонние решения

Помимо описанных выше мы используем на клиенте множество других сторонних решений и плагинов для ускорения разработки:

  • FMOD — для работы со звуком. У нас есть отдельный sound designer, он умеет «готовить» клевые звуки и музыку в FMOD-редакторе.
  • Volatile Physics — физика на клиенте и сервере, написанная на C#.
  • Lunar Console — для просмотра логов на клиенте, а также тестового функционала.
  • Helpshift — для общения с нашими игроками и сбора отзывов.
  • AppMetr — наша собственная система аналитики.
  • Json.net.
  • И др.

Общие решения

У нас есть отдельная команда, которая разрабатывает для проектов общие решения. Например:

  • у нас свой PackageManager (он лучше, чем в Unity 2018 и появился задолго до него), который поддерживает версионность и удаление. Через него мы доставляем в проект все другие наши решения, а также сторонние плагины и библиотеки. У нас нет проблемы удаления и обновления плагинов;
  • свой BuildPipeline — возможность настраивать сборку для разных конфигураций и платформ, т.к. обычно шаги сборки и настройки отличаются + интеграция с TeamCity;
  • модуль авторизации клиента в системе;
  • система автоматического тестирования;
  • адаптированные под нас сторонние плагины (логирование, аналитика; см. выше);
  • общие шейдеры;
  • и др.

Оптимизация клиента

У нас большой опыт оптимизации под мобильные платформы. В частности:

  • Оптимизируем шейдеры.
  • Сокращаем в разы размеры билда за счет компрессии текстур, сборки их в атласы, уменьшения количества variants в шейдерах и не только.
  • Используем свой MeshMerger для объединения статических объектов с одним материалом в один объект, также мержим текстуры.
  • С помощью встроенного в Unity профилировщика, а также dotTrace и dotMemory оптимизируем код.
  • Используем memory pools и preallocated memory, чтобы минимизировать сборку мусора.
  • Используем дельта-компрессию для уменьшения размера отправляемых пакетов.
  • Многое другое.

Что-то из этого можно прочитать в наших предыдущих статьях «Пост-эффекты в мобильных играх» и «Оптимизация 2d-приложений для мобильных устройств в Unity3d». Вторая из этих двух статей уже устарела, но многие советы оттуда работают и сейчас.

Игровой сервер

Игровой сервер, на котором проходят бои, написан на C#. В качестве сетевого протокола используется упомянутая выше upd-based Photon Network Library. На текущий момент правки в сервер вносятся очень редко, т.к. вся игровая логика пишется клиентскими программистами и она же крутится на сервере.

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

Как мы замахнулись на мобильный fast paced шутер: технологии и подходы - 4

Мета-сервер

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

  • Java — основной язык разработки.
  • Cassandra, postgres — для хранения данных игроков.
  • Consul — как service discovery и в том числе хранилище key-value данных для настроек игры и серверов.
  • RabbitMQ — очередь сообщений.
  • Protobuf — протокол между сервисами и клиентом.
  • gRPC — для общения между сервисами и игровым сервером.
  • А также netty, akka, logback и многое другое.

Команда

Игра быстро разрастается фичами и мы активно привлекаем новых сотрудников, но в целом команда проекта состоит из ядра и смежных отделов.

В ядро входят клиентские программисты, геймдизайнер, Project Manager, Product Owner и QA (тестировщики). К смежным отделам относятся:

  • программисты графики;
  • программисты мета-серверов;
  • команда, разрабатывающая общие решения для проектов;
  • художники;
  • аниматоры;
  • community-менеджеры;
  • саппорты;
  • маркетинг и др.

Как мы работаем

Мы работаем по Scrum. Но стоит понимать, что у всех свой Скрам. У нас есть двухнедельные итерации, покер-планирование, ретроспективы, демо и т.д. Но при этом релизы у нас не привязаны к окончанию спринта и есть дополнительный этап релизного тестирования.

Храним код на GitHub, делаем pull request’ы и используем Upsource для code review. Код пишем в Rider и Visual Studio + Resharper.

Для сборки клиента, а также развертки и деплоя серверов мы используем TeamCity и Gradle. Установка клиента на мобильное устройства происходит в один-два клика:

  • собрать клиент в TeamCity, нажав Run (этот шаг можно пропустить, так как в основном сборки происходят в автоматическом режиме);
  • установка по QR-коду, сгенерированному для билда.

Вместо заключения

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

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

Автор: Антон Григорьев

Источник

Поделиться

* - обязательные к заполнению поля