На ютубе сейчас очень мало действительно информативных русскоязычных роликов по Unity, а потому для меня этот канал на вес золота! Спасибо больше Илья за качественный и полезный контент!
@@chillcompany1028 наоборот, я часто замечал, что как раз информативного контента на англ ютубе (да и не только ютубе) куда больше, поэтому когда я не могу найти ответ на какой-либо вопрос, сразу обращаюсь к англ сообществу, там куда больше инфы, нежели от снг
1 совет абсолютный бред 🙄. То есть если у тебя десятки и сотни объектов которые нужно инициализировать, нужен какой то класс который будет иметь ссылки на них, и вызывать метод Initialize. А если эти объекты создаются новые, а потом уничтожаются, значит их еще надо подписать на события. Совет про паттерны хорош, про scriptable object тоже. Если игра маленькая, то можно использовать MonoBehaviour и не беспокоиться.
Насчёт скриптабл обжектов, дело в том, что все их поля по умолчанию сериализуются. Если вам надо что-то в них менять во время игры через её код, а потом сбрасывать (например, если обжект используется для передачи данных между сценами), ставьте перед нужными полями [System.NonSerialized].
Я занимаюсь разработкой игр на Unity уже два года. И могу полностью подтвердить полезность и практичность этих советов! Удивительно, но на своей практике я сталкивался со всеми проблемами озвученными в этом видео. В конечном итоге, через долгое раздумье и множество попыток рефакторинга я приходил именно к тем решениям про которые и говорит автор. Это отличный канал и я с нетерпением буду ждать новых видео! Удачи с развитием канала :)
Привет, слушай я только начинаю свой путь программиста- разработчика инди игр поэтому я благодарен тебе за данное видео . Это поможет мне со старта избавиться от этих проблем в будущем. Только лайк без обсуждения очень качественно и кратко, сам стремлюсь к этому.
По поводу 1-ого совета, есть для Юнити такая зачательная штука как Zenject, которая решает кучу вопросов по поводу зависимостей компонентов. Очень было бы интересно посмотреть про эту либу. Она кстати не заменяет bootstrap, а скорее дополняет.
@@-it394 чисто для уточнения, нельзя ли условную ловушку (1:56) конфигурировать в "private void Start()"? Вроде как к этому моменту все компоненты будут гарантировано инициализированы.
@@QuizzyBreezy если уверен что это ни к чему не приведет -- можно. А привести может к тому, что кто-то в свою очередь будет ждать инициализации ловушек, тогда потребуется какое то событие приделать, крч ни к чему хорошему это не приведет, потому что если это не контроллировать, эти события придется приделывать везде и ещё и утилизировать подписки, забудешь подписку и будут ещё баги. Плюс, если на порядке инициализации возникнет баг, то нигде не написано что ловушки зависят от чьей то инициализации. Вот на опыте скажу, архитектурные решения это решения которые потом сложно сделать снова, если ты начнешь использовать везде события для порядка инициализации то это изменить можно будет ток рефакторингом, и бывает ситуация что цепочка из событий где то на 10 классов по 100 строк, а ещё они имеют скрытые связи, к примеру один класс ждет когда второй класс что то сделает, второй класс ждет когда первый класс что то сделает и так несколько раз, тут ещё проблемы с srp получается, и с плохой архитектурой проблемы с srp решать тяжело
Я тоже всегда делал какой-то один скрипт, обычно называл его GLOBAL =). И там были ссылки на все нужные объекты и какие-то глобальные переменные, которые мне нужны. Вполне удобно.
Советы толковые, делай еще) По 3 совету - отказ от монобехов. В целом совет хороший, но есть куча вытекающих проблем, о которых нужно думать прежде чем отказаться от монобеха. Например очень сложно дебажить это из эдитора. Банально хочешь узнать состояние Level или того же таймера, или чтобы геймдизайнер мог это все посмотреть и подергать, добавить кнопки в инспектор и так далее. [Serializable] тут не всегда помощник, часто придется писать какие-то костыли чтобы вывести это в эдитор или прокидывать поля в верхнеуровневые классы, либо условный bootstrap в инспекторе превратится в длинный монстрокласс который только и занимается тем чтобы в инспектор вывести инструменты для внутренних сущностей. Поэтому иногда можно пренебречь этим, да архитектура может немного пострадать, но зато не тратишь время на доп инструменты и проще\быстрее с этим работать и геймдизайнерам и программистам, что на самом деле может быть очень важно для бизнеса (не тратить время на супер крутую архитектуру, которая может и совсем не понадобится на проекте)
На ютубе очень не хватает видео про архитектуру Unity-проектов и, в целом, про более сложные вещи. Иной раз бесит, когда ищешь какую-то инфу, а тебе попадаются только тонны тупых видосов для новичков, а-ля "Как сделать инвентарь". Респект! 👍
Я думал сейчас опять будут очередные бесполезные советы для тех, кто работал с юнити больше пары часов. Но нет, советы действительно очень годные. Подтверждаю как разработчик с опытом больше трех лет.
В целом порекомендовал бы еще в качестве зависимостей прокидывать классы не напрямую, а через интерфейсы. Например, при работе в команде бывает так, что ты работаешь над задачей которой требуется некоторые данные из других классов, но разработкой нужного функционала занимается другой человек и не понятно когда он будет готов. Для решения этой задачи ты просто создаешь интерфейс который заставляет реализовать некоторый нужный тебе функционал и далее тестируешь все это через какой нибудь example скрипт ,который реализует этот интерфейс.
Как многие уже сказали, создание точки входа - действительно неплохая практика. Но вот пример как по мне не очень яркий. Я вот например выстроил для себя четкое разделение: Awake() для подготовки данных для роботы, а Start() для работы с данными Применяем данную логику к примеру: Maze, очевидно, некий компонент, который будет иметь в себе некоторую структуру данных для ячеек лабиринта TrapsGenerator - это класс, который генерирует ловушки в ячейках Maze То есть, Maze должен инициализироваться в Awake(), для подготовки ячеек, а TrapsGenerator (который в примере вообще не инициализирует никакие свои данные), в методе Start() будет создавать ловушки, обращаясь к референсу Maze Может у меня мало опыта, но я лично не могу представить ситуации, в которой есть необходимость инициализации в определенном порядке из-за жестких зависимостей. Но даже так, с точки зрения организации архитектуры - это вполне себе
А если тебе в лабиринте надо будет расставить объекты после ловушек ? Вполне нормальный пример привели. старт и авейк вообще бы не юзал. Все инициализации должны проходить свыше, по цепочке...
@@just4funTony В его примере я такого не увидел. Предположить можно что угодно, но я то говорил о конкретном, приведенным им, примере. И как уже и сказал - совет хороший P.S. Хотя я могу представить как это можно реализовать без точки входа, но это уже будет вопрос архитектуры и вкусовщины :D
Не так уж и часто важен порядок выполнения. А на этот случай есть события C#. Подписал методы в классах на событие которое вызывается в нужный момент и проблема решена.
Про первый пункт - это вообще проблема юньки. Вообще одним из способов решения является DI (dependency injection) и для юньки есть готовое решение: zenject. Я пока сам не юзал его, но слышал хорошие отзывы от других разработчиков
Привет! Хороший канал у тебя, не бросай это дело, как сейчас модно у всех нормальных ютуберов по Юньке 🤣 Если будет время и желание, очень хотелось бы увидеть подробный разбор ECS с примерами, а то что-то никак не могу вкурить эту систему, а хочется. Удачи.
Ну 4й совет не до конца корректный. Если сбилдить проект тогда изменения в SO не будут записываться, это работает только из едитора, этот вопрос CodeMonkey поднимал в одном из своих видео. А так видео клевое.
Блин, реально годные советы! Сам разрабатываю и учусь больше 2-х лет, но ты реально крут в своём деле) Кстати насчёт сохранения, я думаю зачастую будет довольно уместно делать через Scriptable Object Лайк и подписка!
Насчет сохранения через Scriptable Object ничего не получится. Показанное изменение и сохранение значений полей работает только при симуляции в редакторе. После закрытия редактора или игры (приложения) Scriptable Object восстановит изначальные значения полей (в редакторе это те, что были при последнем сохранении,)
@@alexzakhРечь идёт о способе, когда Scriptable Object просто совершает действия сохранения (например запись json) без использования промежуточных значений. Я сам так делал, всё прекрасно работало.
А меня по началу сбивало с толку, что если компонент отключен, а я в коде напишу строку его включения и тут же строку выполнения какого-то его метода, то сначала выполнится этот метод, а уже потом обработчик Start(). То есть порядок действий поменяется! Это кажется нелогичным, но методы жизненного цикла выполняются в определённой последовательности, поэтому Start() выполнится только после завершения текущего кадра и методов Update() и FixedUdpate(). А вот метод из компонента выполнится сразу, даже если компонент в редакторе отключен.
Годный видос! Автор правильно и понятно рассказал советы. Но есть вопрос - Можно ли использовать scriptable object как Model в MVP и файл для сохранения (инвентаря, состояния уровня и т.д.)
Вот правда! Понимаю что мне надо это понять, но со второго совета перед глазами как будто белый шум., хоть и пересмотрел уже раз 5. Начинал делать игру ещё до того как узнал о самом понятии архитектуры, наговнокодил около 50 перевязанных между собой как попало скриптов и забросил потому что перестал понимать что сам же понаписал. А архитектуру не знаю даже с чего начать изучать...
@@СергейКильянов-в5к могу посоветовать пописать небольшие игры и делать упор на достаточно простые советы: код стайл, нейминг, научиться избавляться от повторений кода хотя бы в рамках одного класса, научиться разделять и строить алгоритмы внутри метода так, чтобы они были максимально информативны и просты в понимании
@@СергейКильянов-в5к Вот например простой пример класса, которые подсвечивает предмет на который смотрит игрок: [CreateAssetMenu(menuName = "Create SingleSelector", fileName = "SingleSelector", order = 0)] public class SingleSelector : SelectorBehaviour { [SerializeField] private float _maxDistance = 5f; public override ISelectable Select(Camera camera, Transform origin) { var affected = Physics.OverlapSphere(origin.position, _maxDistance * 2); var ray = camera.ScreenPointToRay(GetScreenCenter()); Physics.Raycast(ray, out var hit, _maxDistance); var selectionTarget = hit.transform != null? hit.transform.GetComponent(): null;
affected.Each(a => { if (a.TryGetComponent(out var item)) { item.ChangeSelectedState(selectionTarget == item); } }); return selectionTarget; } }
А это код который его использует: [RequireComponent(typeof(Camera))] public class Selector : MonoBehaviour { [SerializeField, ReadOnly] private Camera _camera; [Space] [SerializeField] private SelectorBehaviour _behaviour;
Что касаемо первого совета он верный, но можно сделать ещё лучше. Завести какой то интерфейс или абстрактный класс с методов Initialize и в буте просто создать очередь из таких объектов. А потом инициализировать в порядке очереди. Так прямо в инспекторе можно будет настраивать очередь
@@-it394 ну тут уже надо думать, для параметров тоже можно заводить интерфейсы и оборачивать все в объект, потом уже разбирать этот объект. Не знаю правда работает ли это в шарпах, но в ТС пользуюсь подобным
@@-it394 по идее набор параметров для конкретного объекта должен определяться как раз в конфигах, в методе Initialize можно из контекста получить нужный ScriptableObject и например по ID чего-либо взять данные. Например при инициализации игрока мы можем получить настройки Character, в зависимости от них получать настройки Weapon, Skins и тд, можно при этом взаимодействовать с моделью, в которой хранятся данные из сохранки. Вариантов много, но вот чтобы прям все объекты так перечислять не очень удобно, в больших играх так точно не делается, тем более все настройки не хранятся в одном месте, проводится декомпозиция. По сути по этому и нужен MVC, мы получаем данные из моделей, на их основе генерируем MonoBehaviour и связываем их с контроллером, а если делать по другому, архитектура достаточно быстро сыпиться.
@@Xummuk97-n1t Да на самом деле если уж так говорить, то нужны фабрики и DI для внедрения нужных зависимостей. В таком случае без проблем можно реализовать эти варианты. Но все таки тут довольно простые советы разбираются, которые сильно помогут при малой затрате сил. Да и для маленьких проектов вполне достаточно такого варианта
Курс будет проходить в виде групповых занятий (подробности в телеге). Поэтому видеокурса пока не будет, но я рассмотрю варианты как это можно реализовать
Вообще для буста активности канала сними видос про твоё участие в геймджеме. Люди любят такое и многие нынешние ютуберы по юнити именно после этих видео начали активно расти.
Насчет первого совета. Разве неправильно проводить первичную инициализацию в Awake() а после работать через Start() с зависимостями? Сама идея звучит привлекательно, но не могу себе представить чтобы в проекте на 200+ скриптов инициализация вызывалась из одного класса с манульаной расстановкой порядка.. Проще же когда ты интуивно знаешь что инициализировать в любом классе в Awake() а что на Start() 🤯
Уж лучше так, чем когда 200+ скриптов управляют собой и потом ты сам запутаешься кто за что отвечает, почему тот объект вызывается позже этого и откуда возникает проблема
тут делается полноценный класс у которого ответственность отображать что-то на UI, логично сделать его монобехом. Безусловно, можно прокидывать все в конструктор, но в таком случае в другом месте кода придется создавать эти поля (для прокидывания из инспектора), что сильно ухудшит читаемость, да и в целом будет размываться ответственность. Это в данном случае просто картинка, но легко могут добавится цвета, анимации, что-то еще
@@-it394 Спасибо. Понял. Ну вот у меня тож всегда делема с этим. Стоит ли убирать монобех, если можно обойтись без него, но он с монобехом логичнее получается.
Всё хорошо, но последний совет - дизинфа. Это работает ТОЛЬКО в эдиторе. Если ты сделаешь это в билде, а затем выключишь игру и запустишь, то SO восстановит свои значения, а не сохранит. Не понимаю, зачем это работает только в эдиторе, но это факт. Будучи зелёным, я пытался сделать сохранения на SO, но оказалось, что это не работает в билде
На счёт последнего - не проще ли использовать инстансы SO'шек вместо дополнительных обёрток, если нужно в них что-либо менять в рантайме. Естественно, не забывая убивать после того, как они уже не нужны. По факту это те же ассеты, что и материалы, префабы, etc.
Вроде нельзя сделать инстанс SO это несовсем тоже самое что и префаб. У материала также есть более гибкие возможности, например работать с ним через property Block. У SO таких вариантов нет, если я правильно понял о чем вы)
У меня вопрос. В моём прототипе игры есть скрипт, который хранит в себе ссылки на все объекты на сцене, сортирует эти ссылки по группам (враги, здания, остальное) и отправляет их нужным объектам. Нужен ли тут MonoBehaviour? Возможно ли сделать так, чтобы этот скрипт хранил и обрабатывал данные, не находясь на объекте?
Ну я так понимаю у тебя тут что то вроде ручного бустрапера, так что почему нет, он же должен откуда то брать ссылки на объекты. Если Di нет никакого, то можно таким монобехом обойтись
Может кто-нибудь подсказать, как сделать такие же скрываемые параметры в инспекторе в зависимости от значений других переменных внутри ScriptableObject, как показано на 8:52?
Это делается через CustomEditor(SO и монобехи) или PropertyDrawer(для обычных классов или если нужно самому настроить вид инспектора) скрипты. Тебе хватит CustomEditor
А зачем таймер создавать с нуля если его можно взять из System.Thearing, Start прописал и пускай считает, надо будет Stop вызвал и готово, время можно получить из timespan
4 совет - не прокидывайте в функциональные классы UI. Это очень банально и я сомневаюсь что кто-то кому нужны какие либо советы в Архитектуре приложения может так делать.
3 совет ещё более странный чем 1, если так рассуждать то можно вообще сделать оба этих класса статическими, ведь все необходимое можно просто прокинуть им в поля.
1 совет. Ха, да. Это одна из проблем которая у меня возникла. Но я её исправил просто создав булевые переменые которые говорят о готовности классов и поместил всё в Update
1 совет выглядит как шутка, ладно идея сама по себе ненужная так как есть Start который включается после Awake так ещё ии реализация очень плохая. Правильная реализация выглядит как MonoBehaviour с интерфейсом IInit, в bootstrap делаем массив MonoBehaviour где в OnValidate проверяем есть ли на всех элементах массива интерфейс, в Awake проходимся по массиву и вызываем метод. Если нужна более производительная версия вместо интерфейса можно сделать абстрактный класс
рубрика Вредные советы, удивительно что кто-то называет это базой) Скрипт экзекьюшен ордер как раз и сделан для таких случаев, когда в старте надо сделать несколько вещей и учесть их порядок например для банального DI
@@GGamess не так то, что если совать туда все подряд, то там окажется весь проект) Эта штука предназначена для более высокоуровневых вещей, например, какой нибудь сервисы внедрения зависимостей (вроде zenject даже при установке плагина сам закидывается туда)
Уже лет 10 делаю игры применяя данные советы. Жаль что большинство разрабов даже в больших западных студиях до сих пор пишут ужасный говнокод и ничего подобного не знают и не применяют. А часто еще и спорят что все это фигня и им проще без этого )
Лайки в студию ))) Требуем продолжения! ))
На ютубе сейчас очень мало действительно информативных русскоязычных роликов по Unity, а потому для меня этот канал на вес золота! Спасибо больше Илья за качественный и полезный контент!
Будем честными, англоязычных ещё меньше .. :(
@@chillcompany1028 наоборот, я часто замечал, что как раз информативного контента на англ ютубе (да и не только ютубе) куда больше, поэтому когда я не могу найти ответ на какой-либо вопрос, сразу обращаюсь к англ сообществу, там куда больше инфы, нежели от снг
есть ещё NTC
@@veiterio Он тоже прекрасен, не спорю, но к сожалению выпускает ролики редко
Пользуйтесь яндекс переводчиком. Англоязычных каналов много
Думал будут какие-то бредовые советы как делают большинство ютуберов, а оказалось вполне годно!
1 совет абсолютный бред 🙄. То есть если у тебя десятки и сотни объектов которые нужно инициализировать, нужен какой то класс который будет иметь ссылки на них, и вызывать метод Initialize. А если эти объекты создаются новые, а потом уничтожаются, значит их еще надо подписать на события. Совет про паттерны хорош, про scriptable object тоже. Если игра маленькая, то можно использовать MonoBehaviour и не беспокоиться.
@@vomgame это нужно для старта больших систем в игре. Игрок он как бы один в игре его можно инициализировать тут
@@vomgame , автор объяснил сам принцип. На деле же можно это в разы упростить, к примеру использовав какой-нибудь ECS.🤷♂️
Неужели нашелся нормальный человек, который говнокод не показывает. Мое почтение
Насчёт скриптабл обжектов, дело в том, что все их поля по умолчанию сериализуются. Если вам надо что-то в них менять во время игры через её код, а потом сбрасывать (например, если обжект используется для передачи данных между сценами), ставьте перед нужными полями [System.NonSerialized].
Оффигеееть, это как раз то что мне было нужно. Благодарю за то, что создаешь такой контент. Жду продолжения 🔥
Очень интересно и понятно, спасибо! Буду ждать продолжение.
Продолжай, спасибо!
Даёшь лайки!!!!
Про Timer круто, я что то не подумал заглянуть в конструктор )) буду только так использовать.
Идея про инициализацию очень прикольная, заюзал в рабочем проекте и доволен как слон)
1:57 есть отличное правило что в awake инициализируется объект а в start идет обращение уже к другим объектам, так что данную проблему легко избежать
Красавчик, советы что надо! Хоть они и кажутся простыми, но на их основе формируются более сложные принципы, которые применяются и в больших проектах
Я занимаюсь разработкой игр на Unity уже два года. И могу полностью подтвердить полезность и практичность этих советов! Удивительно, но на своей практике я сталкивался со всеми проблемами озвученными в этом видео. В конечном итоге, через долгое раздумье и множество попыток рефакторинга я приходил именно к тем решениям про которые и говорит автор.
Это отличный канал и я с нетерпением буду ждать новых видео! Удачи с развитием канала :)
В основном база, но несколько советов для себя взял, спасибо за видео))
Привет, слушай я только начинаю свой путь программиста- разработчика инди игр поэтому я благодарен тебе за данное видео . Это поможет мне со старта избавиться от этих проблем в будущем. Только лайк без обсуждения очень качественно и кратко, сам стремлюсь к этому.
Хороший видос. Дал мне определённую пищу для размышлений)
Очень хорошие советы, спасибо
Как-раз почувствовал проблему с инициализацией сцены, попробую будстрап
По поводу 1-ого совета, есть для Юнити такая зачательная штука как Zenject, которая решает кучу вопросов по поводу зависимостей компонентов. Очень было бы интересно посмотреть про эту либу. Она кстати не заменяет bootstrap, а скорее дополняет.
Да в другом комменте написал про это) Тоже хочу сделать видосик на эту тему
@@-it394 чисто для уточнения, нельзя ли условную ловушку (1:56) конфигурировать в "private void Start()"? Вроде как к этому моменту все компоненты будут гарантировано инициализированы.
@@QuizzyBreezy если уверен что это ни к чему не приведет -- можно. А привести может к тому, что кто-то в свою очередь будет ждать инициализации ловушек, тогда потребуется какое то событие приделать, крч ни к чему хорошему это не приведет, потому что если это не контроллировать, эти события придется приделывать везде и ещё и утилизировать подписки, забудешь подписку и будут ещё баги. Плюс, если на порядке инициализации возникнет баг, то нигде не написано что ловушки зависят от чьей то инициализации. Вот на опыте скажу, архитектурные решения это решения которые потом сложно сделать снова, если ты начнешь использовать везде события для порядка инициализации то это изменить можно будет ток рефакторингом, и бывает ситуация что цепочка из событий где то на 10 классов по 100 строк, а ещё они имеют скрытые связи, к примеру один класс ждет когда второй класс что то сделает, второй класс ждет когда первый класс что то сделает и так несколько раз, тут ещё проблемы с srp получается, и с плохой архитектурой проблемы с srp решать тяжело
За последние годы это единственное видео под которое я поставил лайк!
Я тоже всегда делал какой-то один скрипт, обычно называл его GLOBAL =). И там были ссылки на все нужные объекты и какие-то глобальные переменные, которые мне нужны. Вполне удобно.
Советы толковые, делай еще)
По 3 совету - отказ от монобехов.
В целом совет хороший, но есть куча вытекающих проблем, о которых нужно думать прежде чем отказаться от монобеха.
Например очень сложно дебажить это из эдитора. Банально хочешь узнать состояние Level или того же таймера, или чтобы геймдизайнер мог это все посмотреть и подергать, добавить кнопки в инспектор и так далее. [Serializable] тут не всегда помощник, часто придется писать какие-то костыли чтобы вывести это в эдитор или прокидывать поля в верхнеуровневые классы, либо условный bootstrap в инспекторе превратится в длинный монстрокласс который только и занимается тем чтобы в инспектор вывести инструменты для внутренних сущностей.
Поэтому иногда можно пренебречь этим, да архитектура может немного пострадать, но зато не тратишь время на доп инструменты и проще\быстрее с этим работать и геймдизайнерам и программистам, что на самом деле может быть очень важно для бизнеса (не тратить время на супер крутую архитектуру, которая может и совсем не понадобится на проекте)
На ютубе очень не хватает видео про архитектуру Unity-проектов и, в целом, про более сложные вещи. Иной раз бесит, когда ищешь какую-то инфу, а тебе попадаются только тонны тупых видосов для новичков, а-ля "Как сделать инвентарь".
Респект! 👍
Весьма полезно. Спасибо!
Я работаю в анриле и не знаю как всё устроено в юнити, но было интересно, спасибо рекомендациям ютуба. Если что это не сарказм
Мужик💪
Очень круто, спасибо
Топ видосик!
Я думал сейчас опять будут очередные бесполезные советы для тех, кто работал с юнити больше пары часов. Но нет, советы действительно очень годные. Подтверждаю как разработчик с опытом больше трех лет.
В целом порекомендовал бы еще в качестве зависимостей прокидывать классы не напрямую, а через интерфейсы. Например, при работе в команде бывает так, что ты работаешь над задачей которой требуется некоторые данные из других классов, но разработкой нужного функционала занимается другой человек и не понятно когда он будет готов. Для решения этой задачи ты просто создаешь интерфейс который заставляет реализовать некоторый нужный тебе функционал и далее тестируешь все это через какой нибудь example скрипт ,который реализует этот интерфейс.
Как многие уже сказали, создание точки входа - действительно неплохая практика. Но вот пример как по мне не очень яркий.
Я вот например выстроил для себя четкое разделение: Awake() для подготовки данных для роботы, а Start() для работы с данными
Применяем данную логику к примеру:
Maze, очевидно, некий компонент, который будет иметь в себе некоторую структуру данных для ячеек лабиринта
TrapsGenerator - это класс, который генерирует ловушки в ячейках Maze
То есть, Maze должен инициализироваться в Awake(), для подготовки ячеек, а TrapsGenerator (который в примере вообще не инициализирует никакие свои данные), в методе Start() будет создавать ловушки, обращаясь к референсу Maze
Может у меня мало опыта, но я лично не могу представить ситуации, в которой есть необходимость инициализации в определенном порядке из-за жестких зависимостей.
Но даже так, с точки зрения организации архитектуры - это вполне себе
А если тебе в лабиринте надо будет расставить объекты после ловушек ? Вполне нормальный пример привели. старт и авейк вообще бы не юзал. Все инициализации должны проходить свыше, по цепочке...
@@just4funTony В его примере я такого не увидел. Предположить можно что угодно, но я то говорил о конкретном, приведенным им, примере. И как уже и сказал - совет хороший
P.S. Хотя я могу представить как это можно реализовать без точки входа, но это уже будет вопрос архитектуры и вкусовщины :D
Не так уж и часто важен порядок выполнения. А на этот случай есть события C#. Подписал методы в классах на событие которое вызывается в нужный момент и проблема решена.
Было бы здорово.
Ничего не понял но было очень интересно.
В школе учили Паскалю, вот и люблю его. Синтаксис наикрутейший. Сейчас кроме Делфи ничего другого не признаю.
Ты спецально столько опечаток наделал, чтобы вынудить таких как я на коммент?! Хитрюга))
Scriptable objects можно исползывать как дата но через класс player prefs, так можно json сохранить в стринг как класс и так далее
Про первый пункт - это вообще проблема юньки. Вообще одним из способов решения является DI (dependency injection) и для юньки есть готовое решение: zenject. Я пока сам не юзал его, но слышал хорошие отзывы от других разработчиков
Привет! Хороший канал у тебя, не бросай это дело, как сейчас модно у всех нормальных ютуберов по Юньке 🤣 Если будет время и желание, очень хотелось бы увидеть подробный разбор ECS с примерами, а то что-то никак не могу вкурить эту систему, а хочется. Удачи.
На самом деле рассказал довольно качественные советы! Новичкам в геймдеве/кодинге это видео станет полезным 👍
Ну 4й совет не до конца корректный. Если сбилдить проект тогда изменения в SO не будут записываться, это работает только из едитора, этот вопрос CodeMonkey поднимал в одном из своих видео. А так видео клевое.
Блин, реально годные советы! Сам разрабатываю и учусь больше 2-х лет, но ты реально крут в своём деле)
Кстати насчёт сохранения, я думаю зачастую будет довольно уместно делать через Scriptable Object
Лайк и подписка!
Насчет сохранения через Scriptable Object ничего не получится. Показанное изменение и сохранение значений полей работает только при симуляции в редакторе. После закрытия редактора или игры (приложения) Scriptable Object восстановит изначальные значения полей (в редакторе это те, что были при последнем сохранении,)
@@alexzakhРечь идёт о способе, когда Scriptable Object просто совершает действия сохранения (например запись json) без использования промежуточных значений.
Я сам так делал, всё прекрасно работало.
А меня по началу сбивало с толку, что если компонент отключен, а я в коде напишу строку его включения и тут же строку выполнения какого-то его метода, то сначала выполнится этот метод, а уже потом обработчик Start(). То есть порядок действий поменяется! Это кажется нелогичным, но методы жизненного цикла выполняются в определённой последовательности, поэтому Start() выполнится только после завершения текущего кадра и методов Update() и FixedUdpate(). А вот метод из компонента выполнится сразу, даже если компонент в редакторе отключен.
Годный видос! Автор правильно и понятно рассказал советы.
Но есть вопрос - Можно ли использовать scriptable object как Model в MVP и файл для сохранения (инвентаря, состояния уровня и т.д.)
Очень полезные советы, особенно для новичков - к сожалению, новички обычно не способны оценить такие советы))
Вот правда! Понимаю что мне надо это понять, но со второго совета перед глазами как будто белый шум., хоть и пересмотрел уже раз 5. Начинал делать игру ещё до того как узнал о самом понятии архитектуры, наговнокодил около 50 перевязанных между собой как попало скриптов и забросил потому что перестал понимать что сам же понаписал. А архитектуру не знаю даже с чего начать изучать...
@@СергейКильянов-в5к могу посоветовать пописать небольшие игры и делать упор на достаточно простые советы: код стайл, нейминг, научиться избавляться от повторений кода хотя бы в рамках одного класса, научиться разделять и строить алгоритмы внутри метода так, чтобы они были максимально информативны и просты в понимании
@@СергейКильянов-в5к Вот например простой пример класса, которые подсвечивает предмет на который смотрит игрок:
[CreateAssetMenu(menuName = "Create SingleSelector", fileName = "SingleSelector", order = 0)]
public class SingleSelector : SelectorBehaviour {
[SerializeField] private float _maxDistance = 5f;
public override ISelectable Select(Camera camera, Transform origin) {
var affected = Physics.OverlapSphere(origin.position, _maxDistance * 2);
var ray = camera.ScreenPointToRay(GetScreenCenter());
Physics.Raycast(ray, out var hit, _maxDistance);
var selectionTarget = hit.transform != null? hit.transform.GetComponent(): null;
affected.Each(a => {
if (a.TryGetComponent(out var item)) {
item.ChangeSelectedState(selectionTarget == item);
}
});
return selectionTarget;
}
}
А это код который его использует:
[RequireComponent(typeof(Camera))]
public class Selector : MonoBehaviour {
[SerializeField, ReadOnly] private Camera _camera;
[Space]
[SerializeField] private SelectorBehaviour _behaviour;
private readonly HashSet _listeners = new();
private void OnValidate() => _camera = GetComponent();
public IDisposable Subscribe(ISelectionNoticeable listener) => _listeners.Subscribe(listener);
private void Update() {
_listeners.Each(l => l.ChangeSelectedItem(_behaviour.Select(_camera, transform)));
}
}
Ну 500 лайков есть, ждём новые видео)
Сегодня/завтра выйдет))) Параллельно работаю над курсом для групповых занятий по паттернам, поэтому немного не успеваю моментально реагировать))
@@-it394 о, здорово, а что за курс такой?
@@DELOG244 t.me/yakovlev_gamedev подробности тут)
Что касаемо первого совета он верный, но можно сделать ещё лучше. Завести какой то интерфейс или абстрактный класс с методов Initialize и в буте просто создать очередь из таких объектов. А потом инициализировать в порядке очереди. Так прямо в инспекторе можно будет настраивать очередь
В целом идея хорошая, но у всех может быть разный набор параметров который нужен для инициализации, надо это учитывать
@@-it394 ну тут уже надо думать, для параметров тоже можно заводить интерфейсы и оборачивать все в объект, потом уже разбирать этот объект. Не знаю правда работает ли это в шарпах, но в ТС пользуюсь подобным
@@-it394 по идее набор параметров для конкретного объекта должен определяться как раз в конфигах, в методе Initialize можно из контекста получить нужный ScriptableObject и например по ID чего-либо взять данные. Например при инициализации игрока мы можем получить настройки Character, в зависимости от них получать настройки Weapon, Skins и тд, можно при этом взаимодействовать с моделью, в которой хранятся данные из сохранки. Вариантов много, но вот чтобы прям все объекты так перечислять не очень удобно, в больших играх так точно не делается, тем более все настройки не хранятся в одном месте, проводится декомпозиция. По сути по этому и нужен MVC, мы получаем данные из моделей, на их основе генерируем MonoBehaviour и связываем их с контроллером, а если делать по другому, архитектура достаточно быстро сыпиться.
@@Xummuk97-n1t Да на самом деле если уж так говорить, то нужны фабрики и DI для внедрения нужных зависимостей. В таком случае без проблем можно реализовать эти варианты. Но все таки тут довольно простые советы разбираются, которые сильно помогут при малой затрате сил. Да и для маленьких проектов вполне достаточно такого варианта
Привет! Спасибо что делишься знаниями! Когда ~ будет курс от тебя ? Планируешь ли залетать на Udemy ?
Курс будет проходить в виде групповых занятий (подробности в телеге). Поэтому видеокурса пока не будет, но я рассмотрю варианты как это можно реализовать
Вообще для буста активности канала сними видос про твоё участие в геймджеме. Люди любят такое и многие нынешние ютуберы по юнити именно после этих видео начали активно расти.
Вообще можно будет как вариант))
bootstrap - god object, это не плохо, но если говорить про архитектуру, то это плохо
Как вариант, можем вообще вместе создавать игры. И снимать видео на юткб и многое другое. Как думаешь?
Прости но времени сейчас очень не хватает( Возможно позже что- нибудь придумаем
@@-it394 а как насчет взаимопиарп
Насчет первого совета. Разве неправильно проводить первичную инициализацию в Awake() а после работать через Start() с зависимостями? Сама идея звучит привлекательно, но не могу себе представить чтобы в проекте на 200+ скриптов инициализация вызывалась из одного класса с манульаной расстановкой порядка.. Проще же когда ты интуивно знаешь что инициализировать в любом классе в Awake() а что на Start() 🤯
Уж лучше так, чем когда 200+ скриптов управляют собой и потом ты сам запутаешься кто за что отвечает, почему тот объект вызывается позже этого и откуда возникает проблема
Ну сразу по первому совету есть базовая рекомендация от юнити, что инициализации в эвейке, а обращения в старте
Ну при большом количестве обьектов это не спасёт все равно, будет также ситуация. Лучше всего awake использовать только для каких нибудь getcomponent
8:08 А зачем тебе на баре MonoBehaviour, если можно создать экземпляр класса и прокинуть в конструктор картинку(саму полоску) и таймер?
тут делается полноценный класс у которого ответственность отображать что-то на UI, логично сделать его монобехом. Безусловно, можно прокидывать все в конструктор, но в таком случае в другом месте кода придется создавать эти поля (для прокидывания из инспектора), что сильно ухудшит читаемость, да и в целом будет размываться ответственность. Это в данном случае просто картинка, но легко могут добавится цвета, анимации, что-то еще
@@-it394 Спасибо. Понял. Ну вот у меня тож всегда делема с этим. Стоит ли убирать монобех, если можно обойтись без него, но он с монобехом логичнее получается.
Всё хорошо, но последний совет - дизинфа. Это работает ТОЛЬКО в эдиторе. Если ты сделаешь это в билде, а затем выключишь игру и запустишь, то SO восстановит свои значения, а не сохранит. Не понимаю, зачем это работает только в эдиторе, но это факт. Будучи зелёным, я пытался сделать сохранения на SO, но оказалось, что это не работает в билде
Все верно, со сбрасывается после ресета. В теории можно через костыли загонять инфу, он легче уж тогда с префабами )))
@@just4funTony Scriptable Object вполне можно использовать, просто не в качестве сохранения данных.
Так же я который может сделать управление персонажем, и при том с кучей ошибок
можно подробное видео по Entry Point
На счёт последнего - не проще ли использовать инстансы SO'шек вместо дополнительных обёрток, если нужно в них что-либо менять в рантайме. Естественно, не забывая убивать после того, как они уже не нужны. По факту это те же ассеты, что и материалы, префабы, etc.
Вроде нельзя сделать инстанс SO это несовсем тоже самое что и префаб. У материала также есть более гибкие возможности, например работать с ним через property Block. У SO таких вариантов нет, если я правильно понял о чем вы)
@@-it394 почему, можно: Instantiate(_linkToReferencedSO);
@@-it394 Видимо, неправильно.
Скриптейблы "сохраняются" только в редакторе. В финальном билде все изменения скриптейбла откатятся назад после перезапуска приложения.
У меня вопрос. В моём прототипе игры есть скрипт, который хранит в себе ссылки на все объекты на сцене, сортирует эти ссылки по группам (враги, здания, остальное) и отправляет их нужным объектам. Нужен ли тут MonoBehaviour? Возможно ли сделать так, чтобы этот скрипт хранил и обрабатывал данные, не находясь на объекте?
Ну я так понимаю у тебя тут что то вроде ручного бустрапера, так что почему нет, он же должен откуда то брать ссылки на объекты. Если Di нет никакого, то можно таким монобехом обойтись
Вместо первого совета лучше использовать зенжект и не терять модульность юнити
Может кто-нибудь подсказать, как сделать такие же скрываемые параметры в инспекторе в зависимости от значений других переменных внутри ScriptableObject, как показано на 8:52?
Это делается через CustomEditor(SO и монобехи) или PropertyDrawer(для обычных классов или если нужно самому настроить вид инспектора) скрипты. Тебе хватит CustomEditor
@@bonbad612 благодарю
А зачем таймер создавать с нуля если его можно взять из System.Thearing, Start прописал и пускай считает, надо будет Stop вызвал и готово, время можно получить из timespan
База
4 совет - не прокидывайте в функциональные классы UI.
Это очень банально и я сомневаюсь что кто-то кому нужны какие либо советы в Архитектуре приложения может так делать.
3 совет ещё более странный чем 1, если так рассуждать то можно вообще сделать оба этих класса статическими, ведь все необходимое можно просто прокинуть им в поля.
1 совет. Ха, да. Это одна из проблем которая у меня возникла. Но я её исправил просто создав булевые переменые которые говорят о готовности классов и поместил всё в Update
1 совет выглядит как шутка, ладно идея сама по себе ненужная так как есть Start который включается после Awake так ещё ии реализация очень плохая.
Правильная реализация выглядит как MonoBehaviour с интерфейсом IInit, в bootstrap делаем массив MonoBehaviour где в OnValidate проверяем есть ли на всех элементах массива интерфейс, в Awake проходимся по массиву и вызываем метод. Если нужна более производительная версия вместо интерфейса можно сделать абстрактный класс
Интерсно, но ни слова не понял.
рубрика Вредные советы, удивительно что кто-то называет это базой) Скрипт экзекьюшен ордер как раз и сделан для таких случаев, когда в старте надо сделать несколько вещей и учесть их порядок например для банального DI
Чел, Скрипт экзекьюшен ордер необходимо стараться использовать по минимуму, а не пихать туда всё. Не давай вредные советы тоже.
@@lomion46 я привел конкретный пример когда он нужен, что не так, чел?
@@GGamess не так то, что если совать туда все подряд, то там окажется весь проект) Эта штука предназначена для более высокоуровневых вещей, например, какой нибудь сервисы внедрения зависимостей (вроде zenject даже при установке плагина сам закидывается туда)
Чел ты такую дичь несешь, Di в Awake =D
@@DarkIllusoire что не так чел?
Автор сам плодит костыли, а избыточный код извиняясь называет "классиком".
Уже лет 10 делаю игры применяя данные советы. Жаль что большинство разрабов даже в больших западных студиях до сих пор пишут ужасный говнокод и ничего подобного не знают и не применяют. А часто еще и спорят что все это фигня и им проще без этого )
8:13 не Example, а Eample =)
И так и так правильно, одинаковые по значению слова.
Сомнительные советы.
Слишком бегло