Особенности разработки мобильной MMO RTS. Часть 4
Содержание:
- Оптимизация размера игры
- Бандлы и загружаемые ресурсы. Что требуется от системы?
- Дифы манифеста
- Экономия на кодогенерации
Мы прошли экватор цикла о создании MMO RTS. Сегодняшняя статья посвящена оптимизации.
Оптимизации размера игры
Помните предупреждение от AppStore и GooglePlay о том, что вы пытаетесь загрузить приложение больше 100 Мб через мобильную сеть? Это сообщение очень сильно снижает показатели конверсии. Продвигать такие объемные игры очень дорого. Мы провели исследование и выяснили, что конверсия падает почти на 20%. Для бесплатных игр этот показатель на грани выживания, для платных не так критичен.
На iOS всё еще сложнее. Начиная с ARM64, в билд попадают 2 разных исполняемых файла, для 32-битной архитектуры и 64-битной. То есть вместо 70 Мб для вшитых ресурсов остается только 30.
На каждом iOS-билде мы проводим пессимистичное вычисление его размера. Для этого xCode-архив делится на ресурсы и исполняемый файл. Ресурсы жмутся в .zip, и результат суммируется с размером исполняемого файла. Этого показателя достаточно, чтобы узнать, как изменяется размер проекта. Более подробное руководство и формулы есть на сайте Unity.
Если вся графика правильно жмется, меши оптимизированы, а ненужные зависимости устранены, остается не так много решений — например, вынесение контента в бандлы и оптимизация кодогенерации.
Бандлы и загружаемые ресурсы. Что требуется от системы?
Вся базовая графика, необходимая для отображения UI пользователю после логина, остается в билде.
Мы начали работать с бандлами еще на четвертой версии движка. В пятой версии Unity предлагает другой подход, но мы его не используем, потому что у нас уже есть готовая система.
Если вкратце, система работает так:
- В Unity-проекте есть набор папок, названия которых заканчиваются на .bundle. Содержимое этих бандлов — ресурсы, запрашиваемые клиентом в рантайме по имени файла этого ресурса.
- Через специальную утилиту разработчик билдит измененные бандлы и формирует манифест. Это текстовый файл с информацией, необходимой клиенту.
- Сформированные бандлы выливаются на контент-сервер, а манифест на сервер, который возвращает его клиентам во время логина. Бандлы могут обновиться или добавиться без обновления клиента.
- Клиент парсит манифест, загружает и кэширует бандлы, как только их запрашивает код.
В итоге у нас сформировались требования к манифесту.
Работа с кэшем бандлов в Unity осуществляется через численное выражение версии. Мы храним эту информацию в манифесте. Когда нужно обновить файл, из версии формируется путь к бандлу на контент-сервере. Это гарантирует, что любое изменение — это новый файл.
Система поддерживает зависимости бандлов друг от друга. Часто разные 3D модели зависят от общего бандла с текстурами. Информация хранится в манифесте и используется в клиенте для приоритизации загрузки ресурсов.
Мы добавили возможность локализовать бандлы. Это нужно для игровых баннеров с текстами на разных языках и озвучки визарда.
Бандлы формируются для всех сжатий, которые мы поддерживаем. Клиент знает, с какой компрессией он работает, и формирует путь к запрашиваемому бандлу, добавляя в него имя компрессии.
У нас в продакшне могут находиться разные версии приложения. Поэтому возможно использование разных ресурсов. Мы храним информацию для разных версий приложения в одном манифесте, но не в виде кучи наборов данных. Они хранятся в виде дифов от какой-то базовой версии, меньше которой в продакшне уже нет. На клиенте из базовой версии и всех наложенных дифов мы формируем финальный набор данных, который и используем.
Экономия на кодогенерации
Что касается кодогенерации, тут нужно понимать, как работает платформа .NET/Mono и IL2CPP. Обращайте внимание на generic-типы. Для .NET/Mono каждая специализация generic является отдельным типом. Для reference-типов специализации ссылаются на одну — Object. В случае с value-типами компилятору нужно учитывать размер объекта этого типа. Потом он создает отдельные специализации.
Основные проблемные места — классы с большой реализацией и generic-коллекции. Помимо типизированных массивов, неприятным моментом для нас оказалось то, что использование значений перечислений enum в качестве значения ключа в словаре приводит к генерации новой копии кода Dictionary.
Для выявления таких реализаций мы использовали ReSharper и его опцию Find generic substitutions. После того, как мы их обнаружили, стараемся свести количество специализаций к минимуму.
Предыдущие статьи:
- Часть 1;
- Часть 2;
- Часть 3.