company_banner

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



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

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

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

    Симуляция


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



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

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

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

    Клиент


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

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

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


    В коде клиента активно используется инъекция зависимостей. В качестве 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#. В качестве сетевого протокола используется упомянутая выше udp-based Photon Network Library. На текущий момент правки в сервер вносятся очень редко, т.к. вся игровая логика пишется клиентскими программистами и она же крутится на сервере.

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



    Мета-сервер


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

    • 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, как мы ее писали и как она работает, или пишите в комментариях, о чем еще из описанного выше вы хотели бы прочитать в ближайшее время.
    Pixonic 166,89
    Международная компания по разработке мобильных игр
    Поделиться публикацией
    Комментарии 43
    • +1
      Спасибо за статью. После такого заголовка ожидалось увидеть описание клиент-серверного взаимодействия или может быть трудности, с которыми столкнулись. Тут же больше просто перечисление технологий и плагинов проекта во многом совпадающих с WarRobots. К концу прочтения даже было ощущение, что статья больше для поиска кандидатов в команду.
      • +2
        Это первая, обзорная статья, а вот потом мы будем смотреть про какие направления интереснее всего будет рассказать)
        • 0
          Просто пока не понятно, чем это отличается от проекта WarRobots (не по геймплею, а по технологиям), по которому уже куча подробных технических статей.
          • 0
            Начинка и архитектура клиента и подходы очень отличаются от WR, серверные решения похожие. Стараемся реиспользовать лучшее.
        • +4

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


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


          Кстати, не знал про volatile physics. Это выглядит просто шикарно!

        • +3
          хорошая статья. хотелось бы узнать как вы реализовали клиент — сервер логику. интересно было бы прочитать как реализуется RPC, чат на примере кода. как создавать свой dedicated server если у вас такое имеется и т.д. одним словом все про multiplayer. я вот пытался сделать мультиплеер игру, но застрял на реализации клент — сервера. нет приемлимого материала о том как сделать сервер и т.д. все существующие аналоги платные с кучей специфичного своего кода
          • 0

            Не нашёл в статье словосочетания unit test. Соответственно, возникает вопрос, как у вас с этим? Является наличие тестов частью definition of done? Внедрена ли методика TDD?

            • +2
              TDD нет. Т.е. мы в первую очередь пишем код игры. Но о юнит-тестах думаем. В планах подключить в первую очередь тест-сценарии на реальных устройствах типа «игрок загрузил игру, нажал туда-то, потом сюда, получил такой-то результат и тд». У нас есть отдельная команда по автотестам на разные проекты, сами тест-сценарии пишут QA.
              • –6
                TDD нет.

                Печаль.


                Но о юнит-тестах думаем.

                То есть, не только TDD нет, но и юнит тесты не пишите? Интересно почему. Те, кто мета-сервер разрабатывают — тоже юнит тесты не пишут?


                В планах подключить в первую очередь тест-сценарии на реальных устройствах типа «игрок загрузил игру, нажал туда-то, потом сюда, получил такой-то результат и тд».

                Это вот эта отдельная команда по автотестам будет делать? А что за сценарии они делают сейчас?

                • +2
                  Изначально юнит-тесты не задумывались и сейчас пока не видится это критичным. Сейчас игра на стадии проверки гипотез, а для этого юнит-тесты не нужны, к тому же код достаточно качественный. (но на другом проекте, например, юнит-тесты есть).
                  Отдельная команда будет писать тесты по дизайн-документу. В основном это тесты UI и меты, пока что. Им для этого не нужно ничего знать о коде, они работают на верхнем уровне, уровне приложения, с точки зрения игрока.
                  • –5
                    Изначально юнит-тесты не задумывались и сейчас пока не видится это критичным.

                    Как я понимаю, вы считаете юнит тесты такой плюшкой, добавление которой требует временных затрат и наличие которой надо как-то обосновать? Это в геймдеве повсеместно распространённая точка зрения?


                    Сейчас игра на стадии проверки гипотез, а для этого юнит-тесты не нужны

                    А для чего они, по вашему мнению, нужны?

                    • 0

                      Скажу честно, довольно странным кажется что при настроенном TeamCity и автоматизированном билде у вас совсем нет Unit Тестов (поправьте если я вас неправильно понял).


                      Как минимум, дельта-компрессию и ECS очень выгодно покрывать тестами, т.к. баги могут оказывать влияние на всю игру, а простора для ошибок много.

                      • 0
                        Как минимум, дельта-компрессию и ECS очень выгодно покрывать тестами, т.к. баги могут оказывать влияние на всю игру, а простора для ошибок много.

                        Я думаю, может впринципе в игровой индустрии тесты писать не принято?

                        • +8

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


                          И на то есть масса причин, первопричина которых лежит самой сути игры:


                          1) Высокая производительность.
                          Игра должна выполнять симуляцию 60 кадров в секунду, т.е. 0.016 мс на кадр. Если вы подумаете обо всем том, что происходит в современных AAA играх, это обязано взорвать вам мозг.


                          Из этого вытекают такие отклонения как отсутствие DI контейнеров; Недостаточное разделение логики.


                          2) Игра — это Data-Driven приложение.
                          Код составляет лишь часть (и не всегда бОльшую часть) игры. И поведение зачастую определяется преимущественно настройками, а не кодом.


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


                          3) Низкая стабильность ТЗ.
                          Очень редко можно предсказать качество геймплейного элемента до его воплощения в жизнь программистом.


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


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


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

                          • –2
                            В результате TDD подход, где тесты пишутся до логики, заставляет программиста писать тесты, которые будут выкинуты в тот же день.

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

                            • 0
                              Есил тесты будут выкинуты, это означает, что будет выкинут и код, который они тестировали. Получается, в тот же день, в который его написали.

                              Верно!
                              В том-то и дело что фича, казавшеяся разумной притерпевает значительные изменения в процессе исполнения.


                              В геймплее не так прозрачны бизнес-требования, как энтерпрайзе:)

                              • 0

                                Я правильно понимаю, что вы считаете, что тесты нужны только для того, чтобы избежать регрессий?

                                • 0

                                  Я считаю что любые тесты — попытка доказательства корректности кода.


                                  И если формальный критерий корректности меняется слишком часто, тесты могут быть не выгодны.

                                  • 0

                                    Каким образом частота смены формальных критериев корректности кода связана с свыгодностью доказательства корректности текущего варианта? Получается, не очень важно, корректен он, или нет?


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

                                    • +2

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


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

                                      • 0
                                        В общем, наверное вы правы и я считаю что в простых геймплейных фичах выгода тестов проявляется только в поиске регрессий.

                                        А вот такой момент, что юнит тесты прогнать быстрее, чем собрать код и вручную погонять программу? Он в геймдеве не выстреливает? Не получается так, что на отладку уходит много времени чисто за счёт накладных расходов на сборку и всё такое?

                                        • 0

                                          Из-за низкой стабильности ТЗ, появляется требование к высокой скорости итераций. Так что современные движки (Unity, Unreal,...) поддерживают hot reload кода и через несколько секунд можно посмотреть на результат своей работы.


                                          Да, полноценный билд проекта в standalone приложение занимает много времени. Но это требуется довольно редко и зачастую настраивается регулярная автоматическая сборка (например, раз в день).

                        • +1
                          Да, говоря про юнит-тесты, я больше имел ввиду клиента и геймплей. У нас есть тесты на стороне meta-сервера, мы подумываем про юнит-тесты на системные классы/задачи в клиенте. На геймплей пока нет, по причинам, которые вы уже сами раскрыли. А вот системные/интеграционные тесты на мета-часть игры (загрузка профиля, покупки, смена снаряжения, нажатие кнопок и тд) задача стоит на специальный отдел, который делает это для всех проектов в компании. Как они это делают — можем тоже отдельную статью написать.
                          • 0

                            Да, это довольно интересно.
                            Вопрос тестирования стоит остро в геймдеве, как вы и сами знаете:)
                            И я не знаю никого (включая нас), кто умеет это дело правильно готовить. Так что было бы круто прочитать вашу точку зрения об этом.

                • 0

                  del

                  • 0
                    Я вот так и подумал что вы Роботов в итоге забросите. Там все обновления к какому-то унынию свелись, не реализуются ожидаемые годами фичи, старые баги все на месте.
                    • +1
                      Мы судим по отзывам и аналитикам в абсолютных значениях. По нашим данным, у проекта все хорошо, более того, новые обновления аналогично отлично заходят)
                      • 0
                        Ну, с аналитикой спорить бесполезно, конечно.

                        Я вам просто чисто по человечески говорю, как человек, которому игра нравилась на самом деле — суть последних обновлений это банальное «добавить пару новых несбалансированных пушек, пару карт и „новый“ конкурс». Всё. Буквально.

                        Видимо есть ещё люди, в это не игравшие, потому аналитика считает что есть приток новых пользователей и пока у проекта всё хорошо. Но факт в том, что без дальнейшего развития его ждёт медленное угасание, как сотни других проектов. Это вопрос времени теперь уже, не более.
                        • +4
                          Спасибо за фидбек!
                          На самом деле большая часть компании работает над War Robots и команда проекта только растет. Проекту уже 4 года, но нового запланировано еще на годы вперед :)
                          А на новый проект мы набрали новую команду, потому что проект с технической точки зрения отличается от WR. Мы придерживаемся принципа „Нет опыта в чем-то — найми тех, кто уже такое делал и научись у них».
                    • +2
                      > нам удалось

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

                      Дополните?
                      • 0
                        Пока проект в бета, скриншоты, к сожалению, выкладывать не могу. Но обязательно выложу, как только можно будет.
                      • 0
                        Спасибо за статью и полезный обзор технологий! Здесь Вы снова упоминаете Volatile Physics, где не реализован детерминизм — не могли бы рассказать, как при удалось реализовать локальную симуляцию и ролбэки?
                        И другой вопрос, связанный с этой библиотекой. То что она исключительно двухмерная накладывает ограничения и на размерность игры? Или это уже вопрос геймдизайна?
                        • 0
                          Честно говоря, выбор Volatile Physics был плохим решением. На момент решения других альтернатив на C# особо не было, а еще один проект в компании уже использовал эту библиотеку и тогда еще особо проблем не было. Вообще, Volatile Physics — это больше «проба пера» одного программиста. Использованы самые неоптимальные алгоритмы, много багов. Например, в одном месте вместо sin использован cos(или наоборот, не помню). Мы уже форкнулись и сделали пуллриквесты с фиксами. Подумываем о замене физического движка, благо с нашей архитектурой это не будет большой проблемой.
                        • 0
                          Что касается 2d — да, это накладывает ограничения на некоторые дизайнерские решения. И там, где нам нужен 3d, мы пишем кастомную обработку физики.
                          • 0
                            Про prediction и rollback будет отдельная большая статья. В целом, недетерминизм физики на них особо сильно не влияет. Во многих «взрослых» играх это работает и без детерминированной физики, у нас тоже.
                          • +1
                            Достойно!
                            • 0
                              >Сокращаем в разы размеры билда за счет компрессии текстур, сборки их в атласы

                              Можете поделиться эмпирическими данным по этому вопросу?(хотелось бы циферок и графиков побольше)
                              Ну и до кучи вопросик, после компановки в атласы какой эффект на рантайм производительность?
                              • +1
                                После объединения спрайтов в атласы, мы стараемся получить квадратную текстуру степени двойки, это позволяет применять большое кол-во форматов сжатия с потерями и достичь наилучшего результата. Результат зависит от выбранного сжатия, какое сжатие использовать зависит от наличия альфа канала, девайса, содержимого и др.
                                Про цифры — dxt5 сжимает в 4 раза, и раз и два
                                При использовании сжатия уменьшается размер – со всеми вытекающими (меньше весит пакет установки, быстрее грузиться) и возможно улучшиться производительность рендеринга =)
                                • +1
                                  Надо поднять старые записи и задачи, посмотрю. Как найду, отпишусь тут.
                                • +2
                                  Огромное спасибо за ваш пост!
                                  ECS-архитектура отличный подход для создания архитектуры, о котором я ранее не знал.

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое