Unity компонентно-ориентированный подход часть вторая
Ранее я уже писал статью про компонентно-ориентированный подход в Unity. Та реализация оказалась совершенно не рабочей для любых проектов.
В этой статье я расскажу о новой реализации. Чем она лучше. Когда стоит её использовать. И какие проблемы могут возникнуть.
Время прочтения: 5 мин
Статья подойдёт в основном для начинающих. При этом, читающий должен знать основы языка и Unity. Иначе не будет понимания происходящего.
Ссылка на первую часть: https://habr. com/ru/articles/733288/
Ссылка на проект: https://github. com/Favellangel/TowerDefence2D
В первой части для того чтобы не писать повторно одинаковые компоненты с разными типами(переменными) я отделял их от самого компонента и подставлял в самом редакторе.
В этот раз я вернулся к одному из столбов ООП – инкапсуляции. Всё что может изменить поведение компонента, должно быть закрыто для других компонентов. Пример можно увидеть ниже. Сам код можно посмотреть по ссылке выше.
Теперь в проекте нет отдельных скриптов с данными, который можно подставлять. Это повысило понимание кода и объектов в Unity.
Для проекта который я здесь использую, такой подход уместен. Проект не очень сложный. Для дизайнеров довольно удобно добавлять и изменять префабы.
Далее расскажу проблемы, которые возникнут при таком подходе.
Первая проблема — инициализации скриптов. Unity вызывает сначала awake, start для одного скрипта, а потом только для другого. Это можно проверить выводами на консоль для разных скриптов.
Для решения этой проблемы я написал код использующий рефлексию. Рефлексия, это когда код самостоятельно анализирует себя и выполняет какие-то действия. Рефлексия очень сильно бьёт по производительности. Поэтому её не стоит использовать во время игры. Либо в начале для инициализации, либо в конце.
Я создал несколько интерфейсов IBindable, IAwakable, IEnable. Интерфейсы содержат по одному методу. Идея была в том, чтобы сделать инициализацию в том порядке, который мы изначально ожидали от Unity от методов Awake и Start.
Далее в скрипте Entry Point Game я собираю все MonoBehaviors. И вызываю Awake скрипта EntryPoint.
В скрипте Entry Point я создаю списки. В методе GetLinks я пытаюсь преобразовать каждый MonoBehavior скрипт в конкретный интерфейс. Если преобразование удалось, добавляю его в соответствующий список.
После на конкретных MonoBehavior за место метода Awake или start можно наследоваться от интерфейсов. И эти методы будут вызываться по порядку. В методе Initialize можно собрать ссылки на компоненты Unity или свои компоненты на этом объекте. В методе Bind можно собрать все внешние ссылки.
IEnable в результате лучше удалить и добавить скрипт EntryPointGame в порядок вызовов скриптов (Script Execution Order) вторым в списке после Event System. Тогда этот скрипт будет выполняться самым первым. А значит выполнит все IBindable и IAwakable. И только потом выполнит OnEnable.
Плюсы такой реализации:
Быстро и легко добавлять новые механики;
Легко изменить механику;
Этот подход перестанет работать, как только:
На один Gameobject будет повешено 15 и более скриптов. Начнётся путаница;
Добавятся сложные механики: Инвентарь, система диалогов и прочие;
Начнут появляться баги, а написать тесты для MonoBehavior особо не получится;
В команде появится больше 1-2 программистов;
- На GameObjects появится очень много скриптов, из-за чего возникнут проблемы с производительностью и памятью.
Этот подход хорошо подходит:
Для изучения Unity и С#;
Для создания прототипов;
Для создания простых игр;
В остальных случаях лучше воспользоваться другими подходами. Тем более в компаниях вы ни увидите такую реализацию. Даже для простых hyperCasual.
ИТОГ
Проблемы, которые были описаны данной статье лишь верхушка айсберга.
Если нет понимания как можно разделить код на небольшие блоки и заставить это работать без использования GameManagers и прочих. То этот пример очень хорошо это показывает.
Если хотите начать работать в игровой студии. То для создания больших и средних проектов необходимо писать код не зависимый от Monobehavior. То есть обычные С# скрипты. А Monobehavior использовать только для использования этой игровой логики.
Спасибо за прочтение статьи.