- PVSM.RU - https://www.pvsm.ru -
Год назад у нас в компании был один проект — мобильный шутер War Robots с относительно медленными, но красочными и напряженными боями. Игра продолжает развиваться, у нее десятки миллионов установок и игроков по всему миру, постоянно выходят апдейты. В какой-то момент мы захотели сделать динамичный шутер на Unity со скоростями, сравнимыми с Overwatch, CS:GO или Quake. Но реализовать задуманное для мобильных платформ (в первую очередь iOS и Android) на основе War Robots при текущих архитектуре и подходах было практически нереально.
Мы понимали, как это сделать в теории — есть много статей, презентаций на YouTube, детально рассказывающих о том, как написать шутер, как работать с сетью [1], какие возникают проблемы и как их решать. Здесь нет Rocket Science, все эти подходы придумали еще 30 лет назад и за это время они особо не поменялись. НО: у нас не было практики.
Забегая вперед, скажу — нам удалось реализовать задуманное. Мы создали для мобильных платформ динамичный быстрый шутер, который сейчас находится в бета-тестировании и активно дорабатывается. И мне очень хотелось бы всем этим поделиться. Это первая, обзорная статья с перечислением и кратким описанием практически всего того, что мы используем (прошу не путать с другим [2] нашим проектом в разработке, технологии и подходы в котором похожие, но отличаются в деталях).
Начнем с симуляции геймплея. Мы решили писать её на ECS [3] — это data-oriented подход, в котором данные отделены от логики. Данные представлены как сущности (Entity) и компоненты (Components), принадлежащие сущностям. Логика описана в системах (Systems), которые обычно проходятся по компонентам сущностей и меняют их, создают новые компоненты и сущности.
Мы довольно быстро написали свою ECS, потому что текущие решения нам не подошли. ECS позволило нам отделить логику от данных, а также быстро сериализовать данные о мире и обмениваться ими между клиентом и сервером. У нас есть отдельный проект для генерации ECS-кода в общей библиотеке, т.к. многие методы в нем дублируются от компонента к компоненту, например, сериализация данных, их копирование, сравнение и дельта-компрессия.
Один и тот же код симуляции работает одновременно на клиенте и сервере. Это дало нам следующие преимущества:
Помимо prediction и rollback на клиенте, мы используем интерполяцию. Но не в привычном смысле, т.к. мы симулируем и рисуем в одном кадре, 30 раз в секунду, и по сути, классическая интерполяция нам не нужна.
По возможности мы интерполируем в буфере пришедшие с сервера состояния, т.к. на мобильных платформах потеря пакетов может быть существенная, а нам желательно иметь состояние мира для отрисовки здесь и сейчас.
Архитектура клиента
В коде клиента активно используется инъекция зависимостей. В качестве DI-фреймворка мы используем Zenject [4]. Конкретные настройки связывания описываются в маленьких Composition Root, которые в Zenject называются Installer. В большинстве случаев мы стараемся писать так, чтобы отключив конкретный Installer мы отключили фичу/представление/сетевое взаимодействие.
В игре есть несколько контекстов и объекты там принадлежат конкретному контексту, живут вместе с ним:
Контекст боя
В контексте MatchContext на уровне представления игровой механики мы используем MVP [5]. Моделью выступает данные из ECS, presenter’ы работают с ними и Unity-view частью для отображения. У нас есть презентеры для сущностей и компонент. На уровне представления UI используется MVVM [6], описанный ниже.
Для транспорта данных между клиентом и сервером выступает низкоуровневая библиотека Photon [7], из которой мы используем протокол, основанный на udp. Сериализуем данные сами: мы написали кастомную дельта-компрессию, т.к. мир игры большой, а объем трафика критичен для мобильных устройств и нам хорошо бы уложиться в MTU [8], чтобы состояние мира вместилось в один физический пакет.
Контекст меты
В MetaContext для уровня отображения мы используем MVVM [9] и DataBinding [10] на основе библиотеки Unity-Weld [11]. В результате у нас нет для каждого окна кастомного MonoBehaviour-класса с кучей настроек и логикой UI, как это обычно делается на Unity-проектах. Вместо этого логика UI описана в ViewModel конкретного окна, а уровень View представлен всего одним классом — View (наследует MonoBehaviour) и несколькими стандартными классами для связывания данных (data binding). В нашем случае программист пишет только логику и выдает «наружу» необходимые данные, а UI-дизайнер верстает и настраивает привязку данных к отображению.
В контексте меты мы также используем подход Signal-Command-Service, частично позаимствованный из библиотек StrangeIoC [12] и IoC+ [13]. Сейчас он основан на сигналах Zenject, но написан так, что в любой момент его можно переписать на любой другой вариант. Таким образом связь событие-действие/сервис у нас сейчас настраивается на уровне Installer и описывается в одну строчку.
Протокол общения с мета-серверами основан на Protobuf [14], в качестве транспорта используется WebSockets [15].
Помимо описанных выше мы используем на клиенте множество других сторонних решений и плагинов для ускорения разработки:
У нас есть отдельная команда, которая разрабатывает для проектов общие решения. Например:
У нас большой опыт оптимизации под мобильные платформы. В частности:
Что-то из этого можно прочитать в наших предыдущих статьях «Пост-эффекты в мобильных играх» [24] и «Оптимизация 2d-приложений для мобильных устройств в Unity3d» [25]. Вторая из этих двух статей уже устарела, но многие советы оттуда работают и сейчас.
Игровой сервер, на котором проходят бои, написан на C#. В качестве сетевого протокола используется упомянутая выше upd-based Photon Network Library. На текущий момент правки в сервер вносятся очень редко, т.к. вся игровая логика пишется клиентскими программистами и она же крутится на сервере.
Также мы написали специальный инструмент, который позволяет подключаться к серверу по http и визуально просматривать бои в реальном времени. Кроме того, можно просмотреть все сущности, их компоненты и значения, а также записать бой, чтобы проиграть его позже в тестовых целях. Про игровой сервер мы напишем отдельно в будущих статьях.
Под метой у нас подразумевается система микросервисов, позволяющая реализовать такие фичи, как: профиль игрока, покупки, матчмейкинг, чат, кланы и т.д. Все это пишется отдельными программистами и используется совместно на разных проектах. Технологии, используемые в разработке:
Игра быстро разрастается фичами и мы активно привлекаем новых сотрудников, но в целом команда проекта состоит из ядра и смежных отделов.
В ядро входят клиентские программисты, геймдизайнер, Project Manager, Product Owner и QA (тестировщики). К смежным отделам относятся:
Мы работаем по Scrum [34]. Но стоит понимать, что у всех свой Скрам. У нас есть двухнедельные итерации, покер-планирование, ретроспективы, демо и т.д. Но при этом релизы у нас не привязаны к окончанию спринта и есть дополнительный этап релизного тестирования.
Храним код на GitHub, делаем pull request’ы и используем Upsource [35] для code review. Код пишем в Rider [36] и Visual Studio + Resharper [37].
Для сборки клиента, а также развертки и деплоя серверов мы используем TeamCity и Gradle. Установка клиента на мобильное устройства происходит в один-два клика:
Как видите, в наше время серьезный продукт использует большое кол-во подходов и технологий, а также ресурсов и специалистов, и не все так просто, как может показаться на первый взгляд.
В следующей статье могу рассказать о нашей ECS, как мы ее писали и как она работает, или пишите в комментариях, о чем еще из описанного выше вы хотели бы прочитать в ближайшее время.
Автор: Антон Григорьев
Источник [38]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/280646
Ссылки в тексте:
[1] как работать с сетью: https://gafferongames.com/
[2] другим: https://habr.com/company/pixonic/blog/354336/
[3] ECS: https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system
[4] Zenject: https://github.com/modesttree/Zenject
[5] MVP: https://ru.wikipedia.org/wiki/Model-View-Presenter
[6] MVVM: https://ru.wikipedia.org/wiki/Model-View-ViewModel
[7] Photon: https://doc.photonengine.com/en-us/onpremise/current/reference/dotnet-sdk
[8] MTU: https://ru.wikipedia.org/wiki/Maximum_transmission_unit
[9] MVVM: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel
[10] DataBinding: https://en.wikipedia.org/wiki/Data_binding
[11] Unity-Weld: https://github.com/Real-Serious-Games/Unity-Weld
[12] StrangeIoC: http://strangeioc.github.io/strangeioc/
[13] IoC+: http://iocplus.com/tutorials/inversion-of-control-in-ioc-plus/
[14] Protobuf: https://ru.wikipedia.org/wiki/Protocol_Buffers
[15] WebSockets: http://WebSockets
[16] FMOD: https://www.fmod.com/
[17] Volatile Physics: https://github.com/ashoulson/VolatilePhysics
[18] Lunar Console: https://github.com/SpaceMadness/lunar-unity-console
[19] Helpshift: https://www.helpshift.com/
[20] AppMetr: https://appmetr.com/welcome
[21] Json.net: https://www.newtonsoft.com/json
[22] dotTrace: https://jetbrains.ru/products/dottrace/
[23] dotMemory: https://jetbrains.ru/products/dotmemory/
[24] «Пост-эффекты в мобильных играх»: https://habr.com/company/pixonic/blog/327442/
[25] «Оптимизация 2d-приложений для мобильных устройств в Unity3d»: https://habr.com/post/169451/
[26] Cassandra: http://cassandra.apache.org/
[27] postgres: https://www.postgresql.org/
[28] Consul: https://www.consul.io/
[29] RabbitMQ: https://www.rabbitmq.com/
[30] gRPC: https://grpc.io/
[31] netty: https://netty.io/
[32] akka: https://akka.io/
[33] logback: https://logback.qos.ch/
[34] Scrum: https://ru.wikipedia.org/wiki/Scrum
[35] Upsource: https://www.jetbrains.com/upsource/
[36] Rider: https://www.jetbrains.com/rider/
[37] Resharper: https://www.jetbrains.com/resharper/
[38] Источник: https://habr.com/post/359008/?utm_source=habrahabr&utm_medium=rss&utm_campaign=359008
Нажмите здесь для печати.