Создание общей библиотеки кода в геймдеве и не только

Создание общей библиотеки кода в геймдеве и не только

455

На днях я пообщалась с Артёмом Воробьёвым, техлидом игровой студии zGames, входящей в группу компаний Softeq, который, ничтоже сумняшеся , поделился опытом собственной команды (а это наиболее 5 лет разработки мобильных игр для мобилок, консолей и остальных престижных гаджетов). Представляем вашему вниманию элегантную аннотацию с определенными практическими советами.

Мотивация: для чего оно нужно 1.
Программеры именуют это «повторным внедрением кода». В данной статье речь пойдёт о том, как сделать повторное внедрение библиотеки кода и отлично её расширять. Мы любим копировать отличные решения.

Задачка сотворения библиотеки кода традиционно усложняется тем, что:
а) Библиотеку употребляют и расширяют несколько человек
б) Библиотека задействована сразу на пары проектах

Наша библиотека общего кода существует на протяжении уже 4 лет. Начиналось всё с пары классов на Objective-C. На данный момент работаем в Unity3d, и библиотека общего кода насчитывает уже около 400 классов. Потом мы перебежали на C++ и в несколько раз прирастили библиотеку.

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

logs) в движке Unity3d. Эти и почти все остальные расширения логов можно вынести в общую библиотеку. Но движок не умеет записывать эти сообщения в данный файл. Есть способ Debug.Log, который показывает сообщения в консоли Unity. Разглядим, к примеру, реализацию логов (англ. Также реализация лога в движке не способна фильтровать сообщения.

3. Заглавие: КСТ — постоянно збс?
Это поможет вашей команде проще разговаривать на тему библиотеки общего кода. Можно, естественно, называть библиотеку как-то вроде «CommonCode», но лучше бы придумать наиболее «собственное» имя для проекта. Начать стоит с наименования.

Звучит прекрасно, хотя слегка совпадает с заглавием небезызвестной библиотеки для сжатия данных. Потому, подбирая имя для собственной библиотеки, удостоверьтесь, что оно не будет пересекаться с наименованиями остальных библиотек, с которыми для вас доведётся работать. Наша библиотека именуется zLib — сокращённый вариант «zGames Library». Мы в своё время как-то проигнорировали этот факт, и, по-отличному, стоило бы придумать что-нибудь наиболее неповторимое.

Репозитории, externals, SVN vs Mercurial 4. Схема интеграции в проект.
Последующий вопросец — как библиотека будет попадать в проект. Здесь возникает несколько заморочек: фиксы багов и новейшие составляющие довольно трудно обменивать меж проектами. Есть обычный метод, которым нередко всё начинается, — копирование кода из проекта в проект.

Внедрение данной техники дозволит для вас сделать репозиторий проекта составным, и в итоге он сумеет включать не лишь собственный код, но и код остальных проектов (к примеру, общей библиотеки). externals), в Mercurial — «субрепозитории» (англ. Метод наиболее продвинутый — интеграция библиотеки через посторонний репозиторий. subrepository, subrepo), в Git — «подмодули» (англ. В SVN таковая схема именуется «внешние включения» (англ. submodule).

У нас все посторонние репозитории располагаются в одной папке «Externals» снутри проекта (Externals/zLib, Externals/NGUI и т.д.) Указать можно не лишь адресок постороннего репозитория, но и определенную ревизию (не непременно HEAD), с которой должен работать ваш проект. Библиотека будет представлять собой самостоятельную папку где-то снутри вашего проекта, обновляющуюся из отдельного репозитория.

Модерация кода. 5. Какой код помещать в библиотеку
Чтоб библиотека заработала, добавлять в неё нужно только код, который можно реально применять повторно. Мне довелось созидать несколько неуспешных попыток организовать общую библиотеку кода. Фактически постоянно причина неудач в том, что в общий код располагают то, что было необходимо лишь в рамках 1-го проекта, и больше никому не понадобится. Один из самых принципиальных вопросцев, возникающих при разработке общей библиотеки кода — какой код туда помещать.

Подойдите к задачке наиболее системно: пытайтесь выделять в решениях общие части. Стоит отметить, что в играх традиционно находится много специфического для данной определенной игры кода. Потому не спешите выносить в библиотеку вашу первую реализацию match-3/раннера/шутера, раз вы не планируете разрабатывать копии конкретно этого геймплея.

Мы следуем обычный схеме:
а) Неважно какая задачка, решаемая в 1-ый раз, выполняется в рамках данного проекта и «затачивается» под его цели
б) Раз мы сталкиваемся с данной задачей повторно, она, по способности, обобщается и выносится в библиотеку
в) До тех пор, пока подход не удаётся обобщить, код решения копируется из проекта в проект, продолжая приспособиться под определенные нужды

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

6. Модерация структуры файлов: не кодом единым
Подход можно выбрать хоть какой, принципиально, чтоб он был определенным, и все члены команды его соображали. Важной задачей также является определение структуры файлов снутри общей библиотеки. У нас корневая папка библиотеки смотрится последующим образом:
zLib/_Externals
zLib/_Resources
zLib/ZGLib
zLib/ZGLib.Collections
и т.д.

Папка _Externals содержит посторонние библиотеки, которые мы интегрируем в нашу. Папка _Resources содержит файлы, относящиеся к библиотеке, но не являющиеся кодом, — к примеру, текстовые файлы либо рисунки.
namespace) в рамках библиотеки и содержат классы, относящиеся к данному месту имён. Снутри места имён структура папок может быть случайной. Папки ZGLib, ZGLib.Collections и остальные обозначают места имён (англ.

Ограничения архитектуры: когда не в фокусе 7.
Вправду, со временем в общей библиотеке возникают составляющие, которые сформировывают основа приложения так, что для вас приходится придерживаться его рамок при написании собственного проекта. Одно из основных возражений, которые мне доводилось слышать по поводу общей библиотеки кода, касалось ограничений архитектуры.
Фокус в том, что при правильной организации основа нисколечко не будет ограничивать ваши способности. Делайте его таковым, чтоб он агрессивно определял интерфейсы, но нисколечко не ограничивал функциональность. Ключ к правильной реализации каркаса — модульность.

Все создаваемые нами синглтоны должны реализовывать определенный интерфейс (ZGlob) и помещаться в особое место в игровой сцене (естественно, это не касается синглтонов, поставляемых совместно со посторонними фреймворками). У нас есть основа глобальных объектов — подсистема загрузки всех синглтонов (англ. singleton). Таковым образом, мы создаём одну точку загрузки и инициализации приложения. Единый интерфейс дозволяет нам гибко управлять очерёдностью загрузки глобальных объектов, никаким образом не ограничивая их функциональность. Приведу пример.

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

Общий код должен быть написан в одном стиле, точка.
Раз у вас ещё нет общего стиля — начните его сформировывать. Потом равномерно договаривайтесь о разработке новейших правил и расширяйте документ. Для удачного формирования общего стиля необходимо назначить в команде человека, который будет принимать решения, раз команда не сумеет придти к одному мнению. Сделайте документ в Google Docs либо любом другом онлайн-сервисе документов и внесите туда по пт общие правила, которым вы уже на данный момент следуете при написании кода.

9. Владение подсистемами: «Очень приятно, царь»
У каждой подсистемы должен быть обладатель — человек, отвечающий за всё, что с ней происходит. Обладатель подсистемы несёт её философию, знает историю её сотворения и лучше всех осознает её цели. Конфигурации в подсистеме должны дискуссироваться с ним, хотя вносить их могут и остальные люди. Раз делать всё верно, библиотека общего кода будет разбита на подсистемы.

10. Зависимость от посторониих фреймворков: Facebook
В какой-то момент вы столкнётесь с необходимостью вынесения в общий код решений, завязанных на остальные посторонние библиотеки. Это типично, к примеру, для общего кода по работе с Facebook, работающего с классами Facebook SDK конкретно.

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

В наши проекты постоянно добавляется ядро zLib, и, по необходимости — доп модули отдельными субрепозиториями. Есть zLibFacebook, где лежит Facebook SDK и наши общие классы для работы с ним. — уже около 10 различных модулей zLib’а. Есть zLibTwitter, zLibChartboost и т.д. У нас есть ядро zLib, в котором содержатся главные классы, не связанные ни с каким специфическим посторонним обслуживанием.

Системы регистрации доп. 11. поведений
Кроме модульности самой общей библиотеки (zLib, zLibFacebook и т.д.) время от времени также требуется сделать модульную архитектуру отдельной подсистемы снутри библиотеки общего кода. Пример модульной архитектуры одной из подсистем приведён выше — это основа глобальных объектов.

Некие действия могут быть также общими, но привязанными к постороннему сервису (у нас они находятся в zLibFacebook). Другие же действия должны быть специфическими для данного приложения (расположены в его коде). Представьте для себя, что вы желаете сделать общую систему аналитики так, чтоб регистраторы неких событий могли быть общими и употребляться повторно на различных проектах (в нашем случае, помещены в zLib). Иным, ещё наиболее массивным примером, может послужить система реализации игровой аналитики.

Чтоб решить такую делему, мы создаём основа подсистемы в общей библиотеке с управляющим синглтоном (в нашем случае, это ZAnalyticsManager). Регистраторы могут быть динамически добавлены в ZAnalyticsManager. Для остальных технологий можно выбрать подход, общепринятый для данных технологий. GameObject). Далее мы создаём базисный класс для регистратора действия (ZAnalyticsTracker). В Unity3d это просто делать через композицию игровых объектов (англ.

Данный интерфейс реализуется для каждого из сервисов аналитики, и мы добавляем в ZAnalyticsManager те объекты отправителей, которые требуются в данном проекте. Для поддержки различных посторониих сервисов аналитики (Flurry, Google Analytics, Localytics и т.д.) мы создаём интерфейс отправителя событий (ZAnalyticsSender).

Таковым образом, для поднятия системы аналитики мы должны сделать объект управляющего синглтона и надобавлять в него регистраторов и отправителей — они могут быть реализованы как снутри общей библиотеки кода, так и снутри специфического кода вашего проекта. Основное — поддержать общий интерфейс.

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

13. Конфигурации Deprecate: плавненько и безболезненно
deprecate), но на некое время оставлять их в общем коде, чтоб дозволить разрабам безболезненно пересесть на новейшие рельсы. При внесении суровых конфигураций стоит подготавливать письменную аннотацию по переходу на новейшую реализацию. Время от времени какие-то части общего кода приходится поменять — придумывается наиболее удачное заглавие для интерфейса либо рефакторится общественная структура подсистемы. В этом случае лучше всего отмечать старенькые интерфейсы как запрещённые (англ. В C# это делается с помощью атрибута Obsolette, в остальных разработках есть подобные механизмы.

Тут придётся вручную контролировать стабилизацию новейшего подхода в каждом из текущих проектов. Интеграцию суровых конфигураций лучше не осуществлять на конечных шагах разработки (перед релизами, майлстоунами, дедлайнами), а откладывать на наиболее размеренные периоды. Бывают случаи, когда требуется ну чрезвычайно кардинальный рефакторинг, и сохранить старенькые интерфейсы не выходит. Тут поможет система бранчевания и версионности общей библиотеки.

14. Покрытие тестами: «дайте два»
Но для библиотеки общего кода автоматизированные испытания практически наверное себя окупят. Все мы знаем о полезности написания автоматизированных тестов. В рамках маленьких проектов стоимость ручного тестирования и стабилизации может оказаться ниже стоимости написания автоматизированных тестов. При этом, раз задуматься, не постоянно оно нужно. А ещё мы знаем, как изредка они пишутся.

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

Для игр автоматизированные испытания — отдельный нездоровой вопросец. Мы на данный момент покрываем Unit-тестами утилитные классы и планируем покрыть интеграционными тестами те объекты общей игровой логики, которые могут работать по отдельности. По сопоставлению с бизнес-приложениями, игровые объекты имеют больше взаимосвязей, и их труднее протестировать по отдельности.

Библиотека общего кода: не время курить тростник 15.
Раз человек не занят в проекте, он может приниматься за развитие общей библиотеки. Увлекательным бонусом сотворения общей библиотеки для нас стало избавление от простоев. Непременно, постоянно есть категория разрабов, которым в удовлетворенность «ничегонеделанье» на работе, — им, естественно, придётся туго. Зато для людей, которые без работы киснут — это всепригодное решение.

Там есть задачки как на пару часов, так и на несколько недель, что дозволяет утилизировать даже недлинные вольные промежутки в работе над основными проектами. Чтоб сделать процесс отлично, у общей библиотеки должен быть менеджер, лучше всего — техлид. На данный момент наш пул задач по общей библиотеке состоит из 60 пт и равномерно возрастает. Как и у хоть какого проекта, у общей библиотеки есть баги, есть запросы на изменение и на фичи.

zLib в разрезе: какие подсистемы есть у нас 16.
В заключение приведём перечень неких подсистем, которые мы вынесли в библиотеку общего кода:
a) Логи и ассерты
b) Обёртка над асинхронными задачками
c) Работа с JSON’ами
d) Загрузчик глобальных объектов
e) Система внутриигрового интерфейса разраба (cheat UI)
f) Система определения жестов
g) Система аналитики
h) Оконная система
i) Система конфигурирования приложения
j) Система событий
k) Обёртки над посторонними фреймворками

habrahabr.ru Нам было бы чрезвычайно любопытно услышать обратную связь от читателей и коллег по цеху. Будем рады комментариям! Полезен ли наш опыт, какие вы сможете отдать советы, как организована библиотека общего кода у вас?