Упрощаем сборку билдов в Unity3D

в 15:35, , рубрики: continuous integration, unity3d, unity3d editor extension, Игры и игровые приставки, разработка игр

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

Выглядит он примерно так:

Упрощаем сборку билдов в Unity3D - 1

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

Боль

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

Потом обнаруживается, что нет возможности сохранять разные настройки в рамках одной платформы. Простой пример: билд один, а площадок, на которых он будет распространяться, много. И под каждую нужно что-то свое. Частично эта проблема решается выносом настроек в рантайм конфигурацию в StreamingAssets. Но лишь частично, ибо так можно конфигурировать только происходящее уже внутри игры. Иконку таким способом не подменишь. Иконку каждый раз нужно менять самому. Кодом ли, руками ли, но самому. Да и в какой-то момент, ответственный менеджер, которого вы месяц обучали работать со StreamingAssets уходит в отпуск, а билды нужны вчера.

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

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

В итоге подготовка каждого нового билда со временем становится похожей на это:

Упрощаем сборку билдов в Unity3D - 2

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

Поначалу еще может и ничего, но быстро утомляет. Да и вспомни все манипуляции через месяц-другой.

Какие ваши варианты?

Интернеты и народная мудрость предлагают нам следующее:

  • Решать проблему на уровне проектов. Реализации этого подхода могут быть разные: отдельные проекты с общим кодом и ассетами, подмодули в гите, ветки в гите и тому подобное.
    Всерьез рассматривать такое я не могу. Городить огород ради подмены иконки, а потом еще и поддерживать. Совсем на любителя. Но видел своими глазами, поэтому не мог не упомянуть.
  • Bash/bat/python/… скрипты.
    Уже теплее. Но требует дополнительных знаний и сложно назвать кроссплатформенным (часто часть команды разрабатывает под макосью, часть под виндой). Опять же скрипты нужно поддерживать, а на это не всегда есть время.
  • Editor скрипт в самом юнити.
    Совсем тепло. Воротим проект внутри юнити как хотим. А если использовать статик метод и BuildPipeline, то можно и CI настраивать. Как правило в статьях про юнити и CI подобное и описывается. Однако здесь сохраняется один недостаток предыдущего подхода: это код и его надо писать, дописывать и поддерживать. Нам нужно нечто подобное, но без необходимости на каждую платформу и конфигурацию лезть в код.

Решено. Пишем свой плагин, с UI и феями.

Нам нужен план

Для начала нужно определиться что мы собственно хотим получить и зачем.

  1. Хранение настроек проекта под каждую конфигурацию (здесь можно перейти на терминологию самого плагина и называть их вариантами). Желательно реализовать наследование вариантов. Например для всех standalone билдов настройки проекта будут общие, а вариантов друг от друга будут отличаться только иконкой и именем проекта. Вполне логично решать это наследованием, а не копипастой.
  2. Наглядное представление, что поменяно в конкретном варианте и чем он отличается от текущих настроек проекта.
  3. Объединение вариантов в коллекции. Чтобы была возможность одной кнопкой или одним статик методом собрать сразу некоторое подмножество билдов.
  4. Упаковка билда в архив. Это опциональное требование. В случае использования плагина в паре с билд сервером этот пункт можно реализовать на его стороне. Но жизнь упростить хочется упростить всем, а билд сервер есть не всегда.
  5. Перемещение файлов на разных стадиях сборки. Например нужно подменить какой-нибудь ассет (иконку, сплэш, сцену и т.д.) перед сборкой (подмена ассетов при этом должна быть обратима, сборка билда не должна изменять текущее состояние проекта). Или переименовать в готовом виндовом билде {exe_name}_Data в Data (весьма раздражающее поведение юнити, без этой манипуляции нельзя переименовать экзешник). Или опять же в виндовых билдах расставить нужные StreamingAssets. Делать это нужно до упаковки архива. А потом например перетащить все архивы куда-то в одно место.
  6. У юнити проектов огромное количество настроек и нам совершенно не хочется писать свой UI для них всех. Тем более, что он уже есть в самом юнити. Поэтому настройки проекта будем менять как обычно, а в плагине просто следить за изменениями настроек и сохранять в нужные варианты.
  7. Хотелось бы хранить каждый вариант в отдельном файле, чтобы была возможность таскать их между проектами.
  8. Желательно чтобы работало с юнити начиная с версии 5.6. Есть некоторое количество легаси проектов с большим количеством возни вокруг билдов.

Погнали

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

Все настройки юнити хранит в директории ProjectSettings в корне проекта. И первым делом при инициализации мы сохраним эти настройки в директории плагина (к слову это BuildVariants в корне проекта). Это потребуется для того чтобы мы могли отслеживать все последующие изменения. Далее нам нужно их как-то хранить. Самым простым было бы просто копировать ProjectSettings в отдельное место под каждый вариант. И в момент его активации просто подменять их целиком. Идея вполне рабочая и поначалу я рассматривал именно ее. Но это полностью лишает нас возможности наследовать варианты. И если мы уже пишем плагин, то будем сразу делать красиво. Значит нам нужно научиться читать и писать содержимое ProjectSettings.

Для этого нужно понять, что же такое там хранится. А хранятся там самые обыкновенные ассеты, которые сериализуются и десереализуются штатным образом. Это значит, что мы можем прочитать их через AssetsDatabase и получить SerializedObject. Это уже что-то. Потому что мы можем пройтись по всем SerializedProperty, отследить их различия с исходным "снимком" и сохранить их в варианте. Потом при сборке или активации варианта мы собираем изменения по всей цепочке наследования, применяем их к "снимку" и подсовываем в ProjectSettings. Вуаля.

Но у SerializedProperty есть некоторые неприятные особенности: способ хранения значений (все эти intValue, floatValue, isArray), отсутствие нормального механизма их сравнения (есть два родственных объекта, как нам понять все ли SerializedProperty у них имеют одинаковые значения) и парадоксальное (а может и нет) отсутствие аттрибута Serializable (а значит для сохранения нам нужен будет какой-то враппер). Не могу сказать, что это какие-то фатальные недостатки, Все это делалось и не раз в многочисленных editor скриптах. Но хочется все же чего-то более простого, понятного и универсального.

А может быть мы немного ограничим применение нашего плагина? Пусть он работает только на проектах с текстовой сериализацией ассетов (а кто-то еще пользуется бинарной?). Тогда все ProjectSettings будут храниться в виде yaml документов. Можно взять библиотеку YamlDotNet и с ее помощью парсить и сохранять настройки. Заодно и свои конфиги хранить в yaml. Так даже нагляднее. Нужно только дописать немного расширений для диффов yaml документов и их объединения.

Немного возни с editor window (даже описывать не хочу, надеюсь, что с UIElements жить станет проще и веселее), сбор всего в кучу и готово.

Результат

Самое время показать, что же у меня получилось. С основными механизмами работы плагина вы уже ознакомились, поэтому далее тезисно о каких-то особенностях:

  • Варианты добавляются в выбранную коллекцию при помощи галочки слева от имени.
  • Build path нужно указывать с расширением. Не нашел полного перечня расширений под все возможные BuildTarget, поэтому пока оставил так.
  • Перемещение файлов наследуется так же как и настройки.
  • Не реализован дифф массивов. Поэтому если в отнаследованном варианте вы что-то изменили в списке сцен, то весь список будет заменен целиком. По хорошему надо будет доработать.
  • Actual project settings diff — это список всех отличий выбранного варианта от текущих настроек проекта. При этом если выбран активный вариант, то изменения в текущих настройках можно отменять.
  • Variant settings — это соответсвенно индивидуальные изменения в выбранном варианте, без учета родительских изменений.
  • При активации варианта все несохраненные изменения в настройках проекта будут утеряны.
  • Если в настройках проекта есть несохраненные изменения, то любая попытка собрать билд через плагин закончится эксепшеном. Потому что см. выше, а как мы помним "сборка билда не должна изменять текущее состояние проекта".
  • Плагин можно использовать вместе с билд сервером. Для этого есть статик методы: BuildVariants.Controller.BuildController.BuildAll, BuildVariants.Controller.BuildController.BuildCollection (с именем коллекции в параметре -collection) и BuildVariants.Controller.BuildController.BuildVariant (с именем варианта в параметре -variant). Выглядеть это будет примерно так:
    $unity_path -batchmode -projectPath $project_path -quit -logfile -executeMethod BuildVariants.Controller.BuildController.BuildCollection -collection standalone
  • Для возможности архивирования плагин имеет еще одну зависимость в виде библиотеки DotNetZip.
  • Плагин написан в условиях небольшого рабочего затишья, поэтому пока еще не представилось возможности испытать и закалить его в ожесточенных боях. Могут быть баги, возможно даже плохие и неприятные.

Планы на будущее

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

Поэтому буду рад любым пожеланиям, замечаниям, вопросам и баг репортам. Легких билдов вам!

Автор: utkaka

Источник


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