Всем привет! Меня зовут Григорий Дядиченко, и я технический продюсер. Сегодня хотелось бы обсудить работу с префабами, их организацию и несколько советов по тому, как работать с префабами и с вариантами. Насобирав несколько шишек на проектах у меня сформировалось некоторое число типовых проблем и советов при неправильной организации. Если хотите сделать работу с префабами удобнее, добро пожаловать под кат! В юнити довольно много удобных инструментов работы с данными, один из которых – префаб. По сути в Unity есть два вида конфигов с визуальным интерфейсом для манипулирования ими. Prefab и ScriptableObject. Если Scriptable Object больше про чистое хранение данных, то Prefab по сути конфиг “аналогичный xaml” в UWP, который позволяет реализовывать концепцию MVVM, и является в ней View. Мне в целом нравится архитектура, когда префабы – это View, компоненты – View-Model, а Scriptable Object – модель. Это довольно удобно. Но в любой другой схеме архитектуры по MVP, MVC и т.п. можно префабы считать за View. Под капотом префаб или скриптабл обжект – это YAML конфиг. Если в Unity включена текстовая сериализация в настройках редактора то его даже можно открыть и почитать. Структурно он обычно состоит из ссылок на другие префабы, файлы, скрипты и т.п. и наборы сериализуемых параметров. При этом тут стоит сразу сделать отступление на что такое Prefab Variant. Это такой же YAML конфиг, который похож на префаб в своей сути, но он хранит ссылку на оригинальный префаб m_SourcePrefab и его модификации m_Modification + удалённые компоненты m_RemovedComponents. Понимать структуру префаб  как конфига полезно для пакетной обработки, чтобы обрабатывать его как текст регулярными выражениями и подобными инструментами. При этом варианты хранят только то, что изменилось, что позволяет сериализацию сделать более компактной для различных “модифицированных” версий объектов в отличии от разных префабов. Единственным исключением из этого правила является набор параметров трансформа (что больше похоже на баг Unity, чем на обоснованную фичу). Разберём все рекомендации на простом пользовательском интерфейсе. Предположим что у нас скажем есть: магазин и награда за квест. Подобный пример позволит нам получить случаи использования похожие на правду и некоторые примеры того, какие проблемы могут возникать. Как и театр начинается с вешалки, сборка интерфейса всегда должна начинаться со сборки базовых элементов. Кнопки, задники окон, рамки предметов и т.п. Что считать базовым элементом зависит от конкретного интерфейса. Кнопка как базовый элемент будет использоваться по всему интерфейсу, поэтому полезно сначала собрать типовые кнопки с нужными размерами шрифтов и т.п. Важно: в работе с префабами никогда не ленитесь писать названия, потом это сэкономит уйму времени при решении разных задач. Из-за того что это необязательно не стоит считать что это неважно. Я собрал кнопку из кучи кружков, потому что на самом деле про интерфейс можно сказать так: "Дайте мне один кружок и на нём можно собрать очень много вариантов интерфейса". При этом это очень удобный концепт, так как весить такой интерфейс будет примерно ничего. В дальнейшем кроме каких-то иконок все базовые панели мы так же соберём с помощью одного спрайта-кружка. Вот пример вариантов кнопок: Теперь соберём по аналогичному принципу фон для окна. В данном случае у них есть некоторый “общий элемент” bg-circle-shadow. Его конечно же можно вынести в отдельную компоненту, чтобы пакетно красить тени, менять их реализацию и т.п. Но по опыту в разных сущностях лучше не обобщать такие элементы, так как именно из-за этого потом возникают проблемы “я поменял кнопку, а сломались все окна”. Изменение префабов с помощью инструментов Unity — слишком простое действие. Но тем не менее не хочется терять возможность пакетной обработки. Поэтому лучше заранее продумать контракты названий и манипулировать пакетно либо скриптами, либо в ручную, но осознанно меняя в определённых местах. Такие вещи на самом деле не так сложно читать на ревью в мерж реквестах, когда названия совпадают или имеют нечто общее. Так что разбор всего уж совсем на молекулы – это, как и в коде, излишняя декомпозиция, которая ведёт в будущем к неочевидным проблемам. Сущности должны быть разделены логически. И совсем базовые компоненты не должны пересекаться. Ну почти, но мы этого коснёмся чуть позже.  Из базовых элементов мы собрали всё, кроме “игрового предмета”. Он аналогичен. Так сказать из того, что можно собрать на стоках, собрали для иллюстрации. Перед тем как начать собирать окна, немного ещё по базовым элементам. Базовые элементы интерфейса – это не эффекты или какие-то поведения на мой взгляд, чтобы с ними было удобно работать, а конкретные базовые сущности. Фоны, кнопки, прогресс бары для интерфейса. Персонажи, мечи, игровые объекты – которые могут повторяться и удобно редактировать пакетом. Основной плюс сборки сразу такого UI кита заключается в том, что дальше окна уже собираются довольно быстро, так как по сути это копирование и изменение значений, но основные ингредиенты окна уже собраны. Итак, начнём собирать окна. Перед сборкой стоит рассказать про композицию и Nested Prefabs. В самой сборке нет ничего особо интересного, она скорее представлена для иллюстрации концепта. Префабы как сущность в движке обладают одной проблемой. Из-за того, как просто ими манипулировать, собирать и управлять, то многие очень халатно подходят к сборке и не учитывают насколько это важный элемент, который при правильном структурировании и аккуратной работе с ним может сэкономить в будущем уйму времени. По сути в Unity сейчас есть два механизма для работы с префабами композиция (Nested Prefabs) и наследование (Prefab Variance) и относится к ним нужно так же, как и к тем же механизмам в коде. Только с ещё с большей осторожностью, так как в префабах в принципе нет “защиты от дурака” Префаб просто позволяет определять View и делать биндинги без кода, но это всё ещё View. И всё ещё те же механизмы встречающиеся в разработке и их проблемы. С композицией всё довольно просто. Она не всегда удобна, излишняя композиция ведёт к проблемам которые я описывал для базовых элементов и теней. То есть любой объект должен быть разбит на логические сущности. Если что-то присуще только этому окну – нет смысла выносить это в отдельный префаб. Самый простой способ определить это задать себе вопрос. Если я изменю этот элемент мне придётся менять это окно? Как можно заметить в базовых элементах у нас скажем не было заголовков окон. Если у окон нет некоей обобщённой вёрстки, то чаще всего мы не может рассуждать так:“Итак, у нас есть заголовки. Хорошим тоном при работе шрифтов, что по всему приложению у нас есть несколько размеров шрифта и для заголовочных он свой. Поэтому вынесем это в префаб, чтобы менять размер шрифта по всему приложению для заголовков” Идея, имеет место быть. Но в среднем по опыту такой подход ведёт к тому, что разработка узнаёт от тестеров о том, что “а вот тут сломался такой то экран, текст теперь залезает туда”. И это и есть излишняя декомпозиция. Всё зависит от конкретного случая, иногда окна скажем можно разбить на повторяющиеся виджеты, которые просто складываются друг на друга через horizontal layout, если дизайн такие молодцы и так сделали. Но это скорее исключение чем правило. Соберём из наших компонент префаб окна награды за квест (я бы конечно ещё поиграл со шрифтами и хедером, но это только для статьи так что попрёт) Получается такая композиция базовых элементов. Удобно, что если скажем у нас дизайн решит что кнопки подтверждения теперь должны быть оранжевыми их можно поменять везде через префаб, а если только в окне квестов, то зайти в префаб окна квеста и поменять там. При этом хочется обратить внимание на несколько моментов, которые я так же считаю весьма удобными.  Первое – это добавление в конец названий префаба предполагаемого действия. Это полезно в поиске, в авто-редактировании, в групповом редактировании объектов по принципу их действия и т.п. Тут можно завести удобный для себя контракт. Второе – я специально на скриншотах захватываю весь экран, так как организация папок это важно. Не именно такая, каждому можно быть удобно своё в зависимости от контекста проекта. Но когда хорошая вложенность папок и всё логически разделено + панели отдельно от базовых элементов – это сильно упрощает навигацию в дальнейшем. Так же, как и смена стиля написания названий панелей (слонов) и базовых элементов (мелкими через дефис) Третье – тут это не так хорошо видно, но организация папок с текстурами. Unity sprite atlas позволяет передавать папку в качестве параметра с текстурами. Поэтому если какие-то текстуры принадлежат какому-то игровому экрану, то лучше класть их в отдельную подпапку. Перед релизом игры может встать задача оптимизации интерфейсов. И тогда это так же сэкономит кучу времени, так как допустим одна из оптимизаций – это сгруппировать текстурные атласы по игровым экранам, чтобы уменьшить число draw call на интерфейс. Так как для того, чтобы интерфейс рисовался в скажем 1 dc одно из требований, чтобы все спрайты этого интерфейса лежали в одном атласе. Я в примере буду во всех экранах использовать одни и те же текстуры, так что у меня этого разделения нет. По своей сути же Prefab Variants – это наследование в префабах, которое позволяет расширять функционал наследников со всеми из этого вытекающими. Как базовая рекомендация в таком случае – не делать очень глубокую иерархию наследования. Если относится к префабам, как к коду, где всё довольно просто, чётко и иерархически, то все советы выполняются сами собой. Просто многие обращают на это недостаточно внимания и тратят уйму времени на поддержку подобных решений. Соберём теперь окно для магазина: И вот у нас уже появились префаб варианты. Они пока не обязательные на самом деле, скорее для иллюстрации концепции. Если бы в награде квеста у нас скажем была статичная иконка, а в магазине она периодически мигала или по ней проходил бы блик, чтобы привлекать внимание.  Важно: для окна квеста мы создали новый вариант game-item-quest-reward и он унаследован от game-item, так же как и game-item-shop унаследован от game-item. Очень частая ошибка, что если делается первым скажем окно QuestPanel, то там остаётся базовый game-item, или получается цепочка наследования game-item->game-item-quest-reward->game-item-shop, что в свою очередь очень плохо с точки зрения проектировки. Так как игровой предмет магазина по логике не должен зависеть от предмета в окне награды за квест. Они могут опираться оба на один базовый, но ни в коем случае нельзя строить такую зависимость. Хотя интерфейс Unity к этому подталкивает. И вот у нас появились префаб варианты. Вариант красной кнопки – это удобно, но не обязательно. Просто верстая много окон или собирая их под разные аспекты в адаптивной вёрстке, очень часто нужно использовать кнопки разных цветов. И поэтому чем каждый раз красить зелёную удобнее просто сделать вариант. По сути это отдельный логический объект (именно визуальный), так как это кнопка отмены. Для кнопки которая во всём интерфейсе встречается один раз я бы не стал делать отдельный вариант, а это достаточно типовое разделение. Ещё стоит обратить внимание, что структурно фон карточки сейчас – это тот же фон окна. Но тогда почему это отдельный префаб? Потому что это отдельная логическая сущность никак не связанная с фоном окна, и не должна изменяться вместе с изменением фона окна. А почему это не вариант, как сделано с игровым предметом? Так можно сделать, тут уже вопрос к тому насколько вы считаете эти сущности разными. Я считаю это рискованным при горизонтальном масштабировании, так как когда таких сущностей не 2-3, а 20-30 трудно следить за их изменениями. А при этом для пакетной обработки можно пройтись по ним скриптом, либо руками. Надеюсь данные советы и рекомендации сделают ваш проект лучше и будут полезны. Я лишь обращаю внимание на то, что не стоит к префабам относится халатно, так как изначально хорошо организованная структура экономит уйму времени при разработке проекта в дальнейшем.  В разработке, и префабы не исключение, есть базовый конфликт. С одной стороны хочется, чтобы правки меняли только тот модуль, который исправляется. Это упрощает разработку и не вызывает неочевидных багов, когда правка касается только конкретного модуля. А с другой стороны хочется удобства, чтобы из-за “дублирования” не приходилось одно и тоже при ошибке менять в каждом месте всей системе. И любая разработка – это компромисс между этими двумя стульями. В общем резюмируя: Следите за названиями объектов Не делайте слишком глубокую иерархию вариантов и разделяйте их логически Для базовых элементов, виджетов собираемых в окно лучше использовать Nested Prefabs и композицию Не обманывайтесь интерфейсом и простотой редактирования. За префабами надо следить не меньше чем за кодом Собранные префабы вы найдёте в этом репозитории, где можно посмотреть на организацию. Спасибо за внимание!  Master of Unity3D