Разработка мобильных игр на Unity в современных реалиях. URP, 2D Animation и все-все все на примере игры
Всем привет! Это снова Илья и сегодня мы поговорим о технической реализации мобильной игры в современных реалиях. Статья не претендует на уникальность, однако в ней вы можете найти для себя что-то полезное. А чтобы рассмотреть разработку на реальном проекте - мы возьмем реализацию нашей игры, которая на днях выходит в Soft-Launch.
Итак, запасаемся кофе, открываем Unity и погнали!
Базовая настройка проекта. URP и все-все-все.
Начнем с того, что мы работаем с URP (Universal Render Pipeline). Почему так? Потому что он проще в настройке и обладает более гибким контролем, чем стандартный рендер. Ну и добиться хорошей производительности на тапках исходя из этого - намного проще.
Стоит указать, что ниже пойдет речь о 2D игре. Для 3D игр подходы будут несколько отличаться, как и настройки.
Мы реализовали два уровня графики. Low Level - для деревянных смартфонов и High Level - для флагманов. Уровни графики подключаются при помощи Project Settings.
В нашем проекте стоят следующие настройки (для Quality уровней):
На что здесь следует обратить внимание:
- Texture Quality - качество текстур. Для High - мы берем полный размер текстур, для Low - Четверть. Можно еще внести Middle пресет с дополнительным уровнем.
- Resolution Scaling везде стоит 1 - мы берем это значение из URP Asset.
- VSync на Low уровне стоит отключить.
- Все что связано с реалтаймом - отключаем.
Теперь перейдем к настройкам самих URP Asset. На что следует обратить внимание:
Для разных уровней качества можно установить Render Scale - тем самым снижая разрешение для отрисовки. Также незабываем про Dynamic / Static батчинг.
Adaptive Performance
Отличная штука для автоматической подгонки производительности мобильных игр (в частности для Samsung-устройств):
Другие полезные настройки:
- Отключите 3D освещение, лайтмапы, тени и все что с этим связано.
- Используйте для сборки IL2CPP - ускорьте работу вашего кода.
- Используйте Color Space - Linear.
- По-возможности подключите multithreaded rendering.
Игровой фреймворк
Едем дальше. URP и другие настройки проекта сделали. Теперь настало время поговорить о нашем ядре проекта. Что оно включает в себя?
Само ядро фреймворка включает в себя:
- Игровые менеджеры для управления состояниями игры, аудио, переводов, работы с сетью, аналитикой, рекламными интеграциями и прочим.
- Базовые классы для интерфейсов (компоненты, базовые классы View).
- Классы для работы с контентом, сетью, шифрованием и др.
- Базовые классы для работы с логикой игры.
- Базовые классы для персонажей и пр.
- Утилитарные классы (Coroutine Provider, Unix Timestamp, Timed Event и пр.)
Зачем нужны менеджеры?
Они нужны нам для того, чтобы из контроллеров управлять состояниями и глобальными функциями (к примеру, аналитикой).
Хотя мы и используем внедрение зависимостей, менеджеры состояний реализованы в качестве синглтонов и могут быть (и по их назначению должны быть) инициализированы единожды. А дальше мы просто можем использовать их:
А уже сам менеджер распределяет, в какие системы аналитики, как и зачем мы отправляем эвент.
Базовые классы.
Здесь все просто. Они включают в себя базовую логику для наследования. К примеру, класс BaseView и его интерфейс:
А дальше мы можем использовать его, к примеру таким образом:
Классы для работы с контентом, сетью, шифрованием
Ну здесь все просто и очевидно. Вообще, у нас реализовано несколько классов:
1) Классы шифрования (Base64, MD5, AES и пр.)
2) FileReader - считывающий, записывающий файл, с учетом кодировки, шифрования и других параметров. Также он умеет сразу сериализовать / десериализовать объект в нужном формате и с нужным шифрованием.
3) Network-классы, которые позволяют удобно работать с HTTP-запросами, работать с бандлами / адрессаблс и др.
Утилитарные классы
Здесь у нас хранятся полезные штуки, вроде Unix Time конвертера, а также костыли (вроде Coroutine Provider-а).
Unix Time Converter:
Костыль Coroutine-Provider:
Логика сцен
Наша игра - это по своей сути интерактивная история с различными мини-играми (поиск предметов, простенькие бои, крафтинг, найди пару, а также большое количество головоломок).
Каждая сцена - содержит в себе основной Installer, который помимо различных View, подключает логические блоки - своеобразные куски механик:
Эти куски механик последовательно выполняются, отдавая события OnInitialize, OnProgress, OnComplete. Когда последний блок сыграет свой OnComplete - он завершит работу сцены (закончит уровень).
Зачем это сделано?
- Мы можем собирать каждую сцену из отдельных механик, как конструктор. Это может быть диалог -> поиск предметов -> катсцена -> поиск предметов -> диалог, или любой другой порядок.
- Мы можем сохранять прогресс внутри сцены, привязываясь к определенному блоку.
- Блоки механик удобнее изменять, нежели огромный инсталлер с кучей разных контроллеров.
Работа с контентом
При работе с контентом, мы стараемся делать упор на оптимизацию. В игре содержится много UI, скелетные 2D анимации, липсинк и прочее. Вообще, контента достаточно много, не смотря на простоту игры.
Анимации в игре
Самый удобный для нас вариант - оказался из коробки. Мы используем систему для работы с костной анимацией от самой Unity:
Да, можно взять Spine, но у нас нет настолько большого количества анимаций, поэтому вариант от Unity - весьма оптимален.
Упаковка и сжатие
Все, что можно и нужно запихнуть в атласы - мы запихиваем в атласы и сжимаем. Это могут быть элементы UI, иконки и многое другое. Для упаковки атласов - используем стандартный Unity пакер из Package Manager (V1):
Локализация
Вся локализация базируется на JSON. Мы планируем отказаться от этого в ближайшее время, но пока что на время Soft-Launch этого хватает:
Работа с UI
При работе с UI мы разбиваем каждый View под отдельный Canvas. 99% всех анимаций работает на проверенном временем DOTween и отлично оптимизирован.
View инициализируются и обновляются по запросу через эвенты, которые внедряются в Level Installer, либо в отдельных блоках логики.
Что мы используем еще?
- Salsa - для липсинка;
- 2D Lighting - для освещения. В большинстве сцен используется освещение по маске спрайта;
- DOTween - для анимаций;
Итого
Работа с механиками получается достаточно гибкой за счет блоков логики, внедрение зависимостей - позволяет контролировать оптимизацию кода. У нас собственная реализация внедрения зависимостей, как и системы реактивных событий, дабы не громоздить Zenject + UniRX. Да, мы сделали проще, но нам и не нужно всех возможностей этих огромных библиотек.
Полезные ссылки:
Надеюсь, вам было полезно.
Готов ответить на все вопросы. :)