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

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


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



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


Боль


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


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


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


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


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



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


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


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


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


  • Решать проблему на уровне проектов. Реализации этого подхода могут быть разные: отдельные проекты с общим кодом и ассетами, подмодули в гите, ветки в гите и тому подобное.
    Всерьез рассматривать такое я не могу. Городить огород ради подмены иконки, а потом еще и поддерживать. Совсем на любителя. Но видел своими глазами, поэтому не мог не упомянуть.
  • 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 для свежих версий юнити, надеюсь это позволит сделать все более красиво и удобно. Хочется еще прикрутить версионирование и некое подобие переменных окружения. Может быть общественность подскажет что-нибудь еще по функционалу или юзабилити.


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

Поделиться публикацией

Комментарии 10

    0
    Cloud build? =-o
      0
      1. Платно. Не всем подходит.
      2. А он позволяет подменять ассеты и поддерживать разные конфигурации?
      3. Для аутсорсовых проектов удобнее передать все с таким вот плагином, чтобы заказчик сам легко и просто мог собирать нужные ему билды.
      Вот по этим причинам хотелось весь озвученный функционал засунуть в сам проект, чтобы он был самодостаточен в т.ч. по вопросам сборки.
        0
        1. Очень дешево
        2. Есть AssetBundle и директивы препроцессора
        3. Еще больше его запутать кучей настроек в этой надстройке? очень умное решение :-)
          0
          1. Вы правы. Но каждый раз убеждать заказчиков, что им это нужно, не менее утомительно чем собирать самому руками.
          2. Прошу прощения, я действительно по причине из п.1 не работал с cloud build. И сходу не нагуглил. Как там можно поменять иконку для конкретного билда? И там можно наследовать конфигурации друг от друга?
          3. А заказчику и не нужно лезть в настройки. Проект отдается со всеми преднастроенными конфигурациями. Заказчик играется с балансом, меняет арт. А потом одной кнопкой получает все билды. И не отвлекает нас на такие мелочи.
          4. Наша внутренняя инфраструктура во многом завязана на локальный gitlab, быстро и удобно. CI я хотел бы оставить там же.
          5. В самом начале статьи я упомянул, что данная проблема в принципе имеет множество разных решений. Просто по своим причинам я выбрал такое. У кого-то их может и не быть. А у кого-то может. Поэтому решил поделиться. Свобода выбора вроде как.
      0
      проект видимо для тех кто близок к написанию своего такого
      ни подробностей про установку, ни требуемых зависимостей итд
        0
        Прошу прощения, в данный момент все действительно больше похоже на техническое превью. Толковый readme и wiki на гитхабе я только собираюсь оформить. А так же добавить плагин в ассет стор. Сейчас же я с радостью помогу вам. На гитхабе лежит library project, все зависимости там автоматически вытягиваются через nuget, поэтому можно просто клонировать репу и собрать (хотя наверное не без приседаний, нужно настроить BuildVariants.csproj.user, указать где лежит юнити и куда копировать файлы после сборки). Далее скопировать все dll в любую Editor папку в проекте.
        Но в процессе написания этого комментария я понял, что лучше сделать релиз на гитхабе, который просто можно скачать вот здесь. Само окно плагина находится в Window/BuildVariants
          0
          да, так намного проще будет попробовать
          спасибо
        0
        Еще раз. Зачем писать плагин, который сереализует\десереализует ProjectSettings файл на диске.
        Чем неподходит ясное API предоставляемое Unity через PlayerSettings и вложенные классы PlayerSettings.* вкупе с вызовом .unity.exe -batchmode -executeMethod *билд с настройками под каждую платформу*
        ?
          0
          Я понимаю, что со стороны это выглядит так: «чувак наделал костылей и пытается их продвинуть». Костыльность самостоятельной сериализации/десериализации настроек впрочем не отрицаю, но способа лучше я не нашел. Причины:
          1. Основная. Хотелось иметь UI для более менее гибкой конфигурации. Чтобы не вникая в API и не отслеживая изменения в нем (а ребята из Unity любят его менять) можно было добавить какой-то новый вариант билда. Чтобы любой джун, не тратя время на гугление, мог открыть настройки проекта, натыкать необходимое в них, проверить их корректность без сборки билда и сохранить этот вариант. Чтобы для понимания настроек конкретного варианта было достаточно переключиться в него и для этого не нужно было изучать чужой спагетти код (который джуны любят копипастить со стэка прям кусками). В документации юнити пример с кодом подстановки иконок под android и ios едва влезает в экран, натыкать иконки в родном UI от Unity труда не составит. Опять же PlayerSettings это код и его нужно поддерживать. А мне хочется писать код для игр, а не для их сборки.
          2. Когда я пришел к такому решению, я подумал, что может быть и не лишней будет возможность конфигурирования настроек, недоступных через PlayerSettings. Например Fixed timestemp или настройки физики или инпута. Самому никогда подобного не требовалось, но вдруг гипотетически захочется и будут на то причины. Why not?
          Мне самому гораздо больше нравится идея подсовывать настройки без записи на диск, это гораздо правильнее, но вот так…
          Убедительно или по-прежнему не очень?)
            0
            Поддерживаю, самому приходилось заниматься проектами, которые собирались под несколько разных платформ (пк, айос, андроид), разные маркеты (аппстор, гуглстор, самсунгстор), разную монетизацию (премиум/ф2п), разными фичами (с мультиплеером/без). Это все в рамках одного проекта, и API Юнити там явно не хватало. Плюс встречались очень специфичные вещи. Например, одна из СДК для мобильной аналитики тупо не давала собирать билд под ПС4. Ее приходилось физически убирать из проекта перед таким билдом (настройки плагина в инспекторе не помогали). Это, конечно, скорее косяк СДК, но пока они его поправят, могут пройти годы, а билды иногда каждый день нужно собирать, так что собственная автоматизация тут спасает.

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

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