И у меня получилось!
На связи снова я со своим маленьким космо-шутером с видом сверху с честными орбитами, гравитацией и космодробовикам, имя которому HOTLOOP.
На этот раз я решил затронуть тему программной реализации, а именно внедрения контейнера зависимостей, его величества Zenject (т.к. игра на Unity)!
С самого начала разработки основное внимание уделялось только геймплейным механикам, чтоб кораблики летали, пушки стреляли, а гравитация работала как и в реальности. В итоге такой подход привел к тому, что во всей игре было только три уровня: Главное меню, летная школа и орбита планеты. Большего ведь для тестирования геймплея не нужно... И надо признаться они справлялись с задачей, к которой готовились, а именно к участию в фестивале демок в steam "Играм быть"!
Фестиваль прошел, демка игры кое-как отработала, и принесла каких то вишлистов и теперь нужно было двигаться дальше. Но из-за того как именно был реализован весь проект, задача создания новых уровней из максимально приятного ctrl+c ctrl+v превращалась в ужаснейшую рутину по перепривязыванию одних объектов к другим.
Суть проблемы
Предположим есть у нас планетка, при посадке на которую должна происходить починка корабля игрока. Для этого в скрипте планеты наружу выведено поле для ввода, в котором можно указать корабль игрока в качестве целевого объекта для ремонта. Но что же произойдет если заменить планету на космическую станцию? А произойдет то, что корабль игрока придется привязывать заново. И пока таких объектов парочка на весь уровень - это не проблема, но если их десятки, это очень быстро начинает превращаться в дичайшую головную боль.
Для решения именно этой проблемы, умными дядьками была создана концепция автоматического "внедрения зависимостей". Специальный скрипт знает какой объект в сцене является кораблем игрока, и может передать ссылку на него любому другому нуждающемуся объекту. Таким образом, при замене планеты на станцию, больше нет необходимости указывать ей кто тут ̶п̶а̶п̶о̶ч̶к̶а̶ игрок!
Далее хочу привести несколько рекомендаций по разработке проекта с прицелом на внедрение zenject (и возможно аналогов тоже).
1 - Внедряй zenject сразу. Если тебе не хочется внедрять его сразу, то тебе кажется, и на самом деле тебе хочется внедрять его сразу!
2 - Если ты всетаки проигнорировал п.1, и теперь тебе, как и мне нужно перелопачивать почти весь проект, не спеши сразу все делать на живом проекте\в основной ветке разработки. Если используешь Git, сделай отдельную ветку и экспериментируй в ней. Если не используешь гит (осуждаю), то сделай бекап проекта в отдельной папке, чтоб у тебя всегда была возможность вернуться в точку, где zenject-а еще нет.
3 - Сведи использование UnityEvent и рефок на объекты в инспекторе (как на скрине ниже) к минимуму. В них ты будешь прокидывать связи на конкретные объекты, а это как раз случай с планетой, станцией и игроком, описанный мной выше! Это допустимо разве что только для ссылок на самого себя.
4 - Префабь все сверху до низу. В идеале (до которого даже я не дошел пока), вся иерархия сцены должна быть синей. Так ты избежишь ситуации, когда ты добавил пункт меню на одной сцене, но на другой локации в этом же меню этот пункт не появился.
5 - Уже в процессе перевода на зенжект, когда ты заменяешь UnityEvent на инъекцию через контейнер, тебе захочется скрыть эту этот ивент из инспектора, ведь он вроде как теперь бесполезен. НЕ ДЕЛАЙ ЭТОГО. Оставь пока не убедишься что на ВСЕХ сценах ты пересобрал логику из ивента с использованием контейнера!
6 - Так же если зенжект внедряется в уже полу-готовый проект, как в моем случае, ты наверняка узнаешь, что по канону, зенжект должен сам создавать объекты которые покрывают зависимости (в нашем случае это корабль игрока, который являлся зависимостью для планеты и станции), следовательно казалось бы, их можно удалять со сцены после описания инъекции. НЕ ДЕЛАЙ ЭТОГО... просто не делай. Иначе добавишь себе головную боль с порядком регистрации зависимостей. По феншую сделаешь уже на новом проекте!
Ну и по класике!
Поговаривают, что у добавивших мою игру в желаемое FPS во всех играх увеличивается на 10 единиц, а PP получает +1см к длине. Думаю тебе стоит проверить эту гипотезу!
Комментарии
ааа так вот она откуда в вш
Это про что речь? Чот я не в теме. Что за "вш"?
Вишлист
гыг)
Я немножко суть проблемы не понял
В чём проблема сделать что-то вроде
Init
If (player.exists) target = player
?
Проблема в том чтобы найти "player" в игровой сцене
Тег player? Да хоть по имени
Так лучше не делать. Тег может технически висеть на нескольких объектах, и тогда не известно какой именно тебе нужен. Это еще ок если проект крошечный., но если разрастется хотябы до просто небольшого или среднего, то это станет большой проблемой. Так же и с именем. Вопервых это очень медленно, а во вторых может вызвать проблемы если кто то по неосторожности или не знанию переименует сущность. Очень критично если работать в команде. Хоть я и делаю игру один, но стараюсь привить себе привычки командной работы, так, чтобы потом с этим не было проблем.
Так суть тега player в том, что он висит ТОЛЬКО на одном объекте, но хорошо, если это медленнее, тогда в чем проблема передать с игрока реф на себя при посадке?
Я конечно не эксперт, и программированием занимаюсь чисто для себя, но это выглядит как оверинжиниринг для выдуманной проблемы (и как раз в таком случае при работе с командой, вместо того чтобы просто знать как работать с юнити, им приходится изучать какой-то зенжект, что по сути является проблемой)
Тут скорее проблема в том, что при использовании тегов нет "защиты от дурака". Кто то может поставить тег на две сущности, когда это не предусмотрено по программной логике. Тег может быть переименован и тогда везде в коде надо будет тоже это передергивать.
Не всегда можно просто "передать реф". Например некоторые объекты должны быть доступны на самом старте сцены. Более того. некоторые объекты могут быть необходимы для инициализации других объектов также на старте. В этот момент появляется необходимость контролировать порядок выолнения скриптов, т.к. нельзя инциализировать обьект, пока не проиницииализированы объекты от которых он уже сам зависит.
Иньекция же зависимостей это решает автоматически.
А кто-то может стереть весь код, удалить весь проект и т.д. Глупость же
Всё равно не могу представить примера проблемы, где это не решается изначально продуманным дизайном проекта или чуть другим подходом, ну да ладно
если использовать гит, то никто кроме влаельца репозитория не сможет стереть код)
Так Зенжект по сути это и делает. Зенжект это просто реализация DI, с которым разобраться куда проще, чем делать самописный.
Поступить проще здесь, без потери в эффективности - синглтон, а он достаточно проблемный.
Прокидывание зависимостей это один из главных архитектурных вопросов на юньке, насколько могу судить.
Ну, кстати, сейчас почти везде, даже на джунов Unity требуют уметь работать с зенжектом :D
>разобраться куда проще
Кому как, видимо
Я любые фреймворки не перевариваю, из-за них всё становится только сложнее
>Прокидывание зависимостей это один из главных архитектурных вопросов на юньке
Более точно - это решение проблемы, которую изначально можно было легко избежать
Имея пятилетний опыт в продуктовой разработке (на пхп), могу отметить, что DI используется там серьезными дядьками очень плотно, что наводит на мысль, что это один из наиболее удачных способов решения подобных проблем.