#паттерны_геймдев
Энтерпрайз vs Геймдев
Привет, предлагаю немного поговорить о разнице подходов для энтерпрайз и геймдев разработки.
Отличительная особенность энтерпрайз разработки - высокая стабильность (неизменность) базовых бизнес-процессов.
Например, если основная задача приложения выставлять счета, то в течение продолжительного времени данное требование остается неизменным.
Энтерпрайз характеризуют достаточно стабильные связи между системами и их компонентами.
Здесь хорошо применимы интерфейсы. Все приложение строится на реализации контрактов и связей между ними.
Такой подход обеспечивает слабое зацепление и сильную связность кода.
Для энтерпрайз характерна простота слоя представления.
Здесь хорошо подойдут большинство стандартных принципов ООП и паттернов проектирования, таких как: MVC, MVP, MVVM, и т.д, в том виде, в котором они описаны в специализированной литературе.
В геймдеве же напротив очень часто меняются бизнес-требования. От спринта к спринту гейм-дизайнер или продюсер могут менять свое видение продукта.
Частое изменения сопряжено с проверкой гипотез, анализом конкурентов и волатильностью рынка.
Приведу в качестве примера эволюцию игрового банка. Допустим, стоит задача добавления в игру первой версии банка (это первая реализация в рамках продукта и ранее ничего подобного мы не делали). Задача может звучать: “дать возможность игрокам покупать игровые предметы, выводя их списком”. Это наш первый банк и цель первой итерации - проверить монетизацию на самой простой версии.
Спустя время банк эволюционирует до разбиения игровых предметов по категориям и объединения их в паки.
Часть ранее выполненных задач могут оказаться ненужными или подвергнуться значительным доработкам.
Реализация кода через интерфейсы и связи между ними в геймдеве в большинстве случаев не позволит обеспечить сильную связанность и слабое зацепление.
Применение интерфейсов в связке с паттернами MVC, MVP, MVVM, и т.д, как правило ведет к росту количества связей между системами. Удержать проект в голове становится всё сложнее. При ротации команды сильно увеличивается технический долг.
Поэтому базовые паттерны, такие как: MVC, MVP, MVVM не подходят в чистом виде (то, как их описывают в специализированной литературе) для использования в геймдеве и требуют дополнительной адаптации.
Энтерпрайз vs Геймдев
Привет, предлагаю немного поговорить о разнице подходов для энтерпрайз и геймдев разработки.
Отличительная особенность энтерпрайз разработки - высокая стабильность (неизменность) базовых бизнес-процессов.
Например, если основная задача приложения выставлять счета, то в течение продолжительного времени данное требование остается неизменным.
Энтерпрайз характеризуют достаточно стабильные связи между системами и их компонентами.
Здесь хорошо применимы интерфейсы. Все приложение строится на реализации контрактов и связей между ними.
Такой подход обеспечивает слабое зацепление и сильную связность кода.
Для энтерпрайз характерна простота слоя представления.
Здесь хорошо подойдут большинство стандартных принципов ООП и паттернов проектирования, таких как: MVC, MVP, MVVM, и т.д, в том виде, в котором они описаны в специализированной литературе.
В геймдеве же напротив очень часто меняются бизнес-требования. От спринта к спринту гейм-дизайнер или продюсер могут менять свое видение продукта.
Частое изменения сопряжено с проверкой гипотез, анализом конкурентов и волатильностью рынка.
Приведу в качестве примера эволюцию игрового банка. Допустим, стоит задача добавления в игру первой версии банка (это первая реализация в рамках продукта и ранее ничего подобного мы не делали). Задача может звучать: “дать возможность игрокам покупать игровые предметы, выводя их списком”. Это наш первый банк и цель первой итерации - проверить монетизацию на самой простой версии.
Спустя время банк эволюционирует до разбиения игровых предметов по категориям и объединения их в паки.
Часть ранее выполненных задач могут оказаться ненужными или подвергнуться значительным доработкам.
Реализация кода через интерфейсы и связи между ними в геймдеве в большинстве случаев не позволит обеспечить сильную связанность и слабое зацепление.
Применение интерфейсов в связке с паттернами MVC, MVP, MVVM, и т.д, как правило ведет к росту количества связей между системами. Удержать проект в голове становится всё сложнее. При ротации команды сильно увеличивается технический долг.
Поэтому базовые паттерны, такие как: MVC, MVP, MVVM не подходят в чистом виде (то, как их описывают в специализированной литературе) для использования в геймдеве и требуют дополнительной адаптации.
#паттерны_геймдев #базис_геймдев #структуры_данных
Паттерны, структуры данных, векторная алгебра
Во время интервью часто спрашивают теорию: структуры данных, паттерны проектирования, векторную алгебру и проч.
Часть кандидатов воспринимают данные вопросы с удивлением: "Пишу код - зачем мне теория". Давайте рассмотрим нужны ли эти знания на практике.
Но зайдем издалека
В процессе разработки игры, мы управляем набором критериев:
- скорость разработки
- простота алгоритма
- сложность понимания кода
- скорость работы
- размер потребляемой памяти
- стоимость доработок
- устойчивость кода к изменениям
- и т.д.
Перед разработчиком стоит задача многокритериальной оптимизации. Оптимальное решение - то, к чему мы должны стремиться.
Игровой проект - набор задач, которые могут решаться оптимально или нет.
Процентное соотношение оптимально и неоптимально решенных задач влияет на обозначенные ранее критерии.
Наличие багажа знаний помогает аргументировано и быстро сделать осмысленный выбор.
Структуры данных
Каждая структура данных предназначена под определенных цели.
Знание и уместное применение влияет на:
- простоту алгоритма
- скорость работы кода (например, насколько быстро получим доступ к элементу)
- размер потреблебляемой памяти
- стоимость доработок, и т.д.
Паттерны
Знание основных паттернов проектирования влияет на:
- скорость разработки
- сложность понимания кода
- стоимость доработок
- устойчивость кода к изменениям, и т.д.
Векорная алгебра
Это базис, на котором строится разработка игр. Большая часть игрового мира (как 3D, так и 2D) базируется на понятиях вектора. Это задачи связанные с поворотами, проверкой направленности объектов (их векторов), движение объекта к определенной цели (разница векторов), и т.д.
В рамках текущей публикации ограничусь абстрактным примером, касающимся выбора структуры данных под решаемую задачу.
Допустим есть мир, куда каждый интервал времени спавнятся игровые объекты, которые необходимо хранить. Каждый игровой объект с течением времени может превращаться в другой. Описание каждого игрового объекта (экземпляр класса) находится в коллекции с контентом.
Задачу разобьем на 2 части:
- хранение заспавненных объектов
- превращение одного объекта в другой.
Для хранения заспавненных объектов можно использовать List и LinkedList. Сравним их между собой.
List - базируется на массиве, размер которого определен по умолчанию либо задан разработчиком. Размер массива не может меняться динамически. Если в List добавляется большое количество элементов, при выходе за изначально заданные размеры массива происходит перевыделение памяти под необходимый новый размер и копирование старой области данных в новую. Частое перевыделение памяти может сказаться на производительности работы приложения.
LinkedList лишен этого недостатка. При динамическом добавлении элементов не происходит переаллокация памяти.
В данном случае LinkedList будет более оптимален.
Рассмотрим реализацию превращения одного объекта в другой, сравнив List и Dictionary.
Пусть в момент превращения в контентной коллекции необходимо найти объект (по id), в который будет превращен исходный. При использовании List в худшем случае для поиска придется обойти всю коллекцию. Более эффективно создать дополнительную структуру данных или изначально хранить коллекцию в виде Dictionary, где ключом будет выступать требуемый параметр для поиска, например id. Поиск по словарю осуществляется за константное время и не зависит от размера коллекции.
Паттерны, структуры данных, векторная алгебра
Во время интервью часто спрашивают теорию: структуры данных, паттерны проектирования, векторную алгебру и проч.
Часть кандидатов воспринимают данные вопросы с удивлением: "Пишу код - зачем мне теория". Давайте рассмотрим нужны ли эти знания на практике.
Но зайдем издалека
В процессе разработки игры, мы управляем набором критериев:
- скорость разработки
- простота алгоритма
- сложность понимания кода
- скорость работы
- размер потребляемой памяти
- стоимость доработок
- устойчивость кода к изменениям
- и т.д.
Перед разработчиком стоит задача многокритериальной оптимизации. Оптимальное решение - то, к чему мы должны стремиться.
Игровой проект - набор задач, которые могут решаться оптимально или нет.
Процентное соотношение оптимально и неоптимально решенных задач влияет на обозначенные ранее критерии.
Наличие багажа знаний помогает аргументировано и быстро сделать осмысленный выбор.
Структуры данных
Каждая структура данных предназначена под определенных цели.
Знание и уместное применение влияет на:
- простоту алгоритма
- скорость работы кода (например, насколько быстро получим доступ к элементу)
- размер потреблебляемой памяти
- стоимость доработок, и т.д.
Паттерны
Знание основных паттернов проектирования влияет на:
- скорость разработки
- сложность понимания кода
- стоимость доработок
- устойчивость кода к изменениям, и т.д.
Векорная алгебра
Это базис, на котором строится разработка игр. Большая часть игрового мира (как 3D, так и 2D) базируется на понятиях вектора. Это задачи связанные с поворотами, проверкой направленности объектов (их векторов), движение объекта к определенной цели (разница векторов), и т.д.
В рамках текущей публикации ограничусь абстрактным примером, касающимся выбора структуры данных под решаемую задачу.
Допустим есть мир, куда каждый интервал времени спавнятся игровые объекты, которые необходимо хранить. Каждый игровой объект с течением времени может превращаться в другой. Описание каждого игрового объекта (экземпляр класса) находится в коллекции с контентом.
Задачу разобьем на 2 части:
- хранение заспавненных объектов
- превращение одного объекта в другой.
Для хранения заспавненных объектов можно использовать List и LinkedList. Сравним их между собой.
List - базируется на массиве, размер которого определен по умолчанию либо задан разработчиком. Размер массива не может меняться динамически. Если в List добавляется большое количество элементов, при выходе за изначально заданные размеры массива происходит перевыделение памяти под необходимый новый размер и копирование старой области данных в новую. Частое перевыделение памяти может сказаться на производительности работы приложения.
LinkedList лишен этого недостатка. При динамическом добавлении элементов не происходит переаллокация памяти.
В данном случае LinkedList будет более оптимален.
Рассмотрим реализацию превращения одного объекта в другой, сравнив List и Dictionary.
Пусть в момент превращения в контентной коллекции необходимо найти объект (по id), в который будет превращен исходный. При использовании List в худшем случае для поиска придется обойти всю коллекцию. Более эффективно создать дополнительную структуру данных или изначально хранить коллекцию в виде Dictionary, где ключом будет выступать требуемый параметр для поиска, например id. Поиск по словарю осуществляется за константное время и не зависит от размера коллекции.
#solid #solid_геймдев
Для чего нужен SOLID
Всем привет, сегодня говорим про SOLID (далее по тексту - классический SOLID).
Публикацию разобьем на 2 части: в первой упростим и выделим суть каждого принципа применительно к геймдеву, во второй рассмотрим проблемы.
SOLID - это набор из 5 принципов ООП, которые сформулировал Роберт Мартин. Аббревиатура была предложена Майклом Фэзерсом.
Определение звучит так: "SOLID - это руководства, которые также могут применяться во время работы над существующим программным обеспечением для его улучшения, например, для удаления «дурно пахнущего кода»".
Про SOLID спрашивают, его обсуждают с докладами на конференциях, его относят к правилам хорошего тона. Те, кто поопытнее пытаются применять SOLID на практике.
Но действительно ли разработчики понимают, как правильно применять SOLID принципы в геймдеве?
Практика показывает, что понимание принципов работает, как испорченный телефон.
Ведь каждый с высоты опыта и багажа по-своему интерпретирует, понимает и применяет на практике полученную информацию.
Часто в финале у большинства команд получается эдакий монстр-франкенштейн с кучей интерфейсов и их реализацией.
Код получается слабо связным и с сильной степенью зацепления.
Рассмотрим классический SOLID применительно к геймдеву.
Разберемся, что находится под капотом у принципов.
Я не буду давать классическое определение каждого из принципов, их легко найти, дам лишь свое пояснение и выделю суть.
Поехали 🚀🚀🚀
S - Single responsibility principle (SRP)
❗Суть принципа: написанный вами код (реализация) внутри каждой категории (про категории говорили тут) должен максимально соответствовать этой категории.
На примере связи Игрок - Инвентарь. Если в каком-то классе, относящемся к категории игрока, мы решили что-то сделать с ячейками инвентаря, поменять их местами (написать логику свопа в классе, который относится к категории игрока), такой подход будет нарушать SRP.
Более правильной реализацией будет работа с инвентарем в классах, относящихся к категории инвентаря.
O - Open-closed principle (OCP)
На практике этот принцип соблюсти достаточно тяжело.Так как в геймдеве часто меняются бизнес-требования.
❗Суть принципа: класс открыт для добавлений, но закрыт для изменений.
Моя рекомендация - пишите код так, чтобы его было легко менять и удобно расширять.
L - Liskov substitution principle (LSP)
❗Суть принципа: если у вас в коде есть класс A и класс B, который является наследником A, то при замене всех классов A на B в работе кода ничего не должно поменяться.
Моя рекомендация - не злоупотребляете наследованием. Иногда композиция или имплементация интерфейса сработает гораздо эффективнее.
I - Interface segregation principle (ISP)
❗Суть принципа: если и используете интерфейсы, не тащите в них то, что не нужно.
Пример подхода, нарушающего ISP: если какой-то интерфейс в реализации должен работать с игровым объектом, при этом описание игрового объекта есть в коллекции контента. Нарушением принципа будет передача в интерфейс самой коллекции контента и id того объекта, с которым хотим работать.
Чтобы обойти нарушение ISP, необходимо передавать сам интерфейс объекта, который мы предварительно получаем по id из коллекции контента.
Принцип бритвы Оккама тут может быть хорошей аналогией.
D - Dependency inversion principle (DIP)
❗Суть принципа: когда вы пишете какую-либо логику в классе и у вас есть зависимости, пишите логику не опираясь на конкретные реализации этих зависимостей. Работайте с зависимостями через абстракции.
Моя рекомендация - учитывать, что абстракции - это не только интерфейсы и абстрактные классы.
Продолжение следует…
Для чего нужен SOLID
Всем привет, сегодня говорим про SOLID (далее по тексту - классический SOLID).
Публикацию разобьем на 2 части: в первой упростим и выделим суть каждого принципа применительно к геймдеву, во второй рассмотрим проблемы.
SOLID - это набор из 5 принципов ООП, которые сформулировал Роберт Мартин. Аббревиатура была предложена Майклом Фэзерсом.
Определение звучит так: "SOLID - это руководства, которые также могут применяться во время работы над существующим программным обеспечением для его улучшения, например, для удаления «дурно пахнущего кода»".
Про SOLID спрашивают, его обсуждают с докладами на конференциях, его относят к правилам хорошего тона. Те, кто поопытнее пытаются применять SOLID на практике.
Но действительно ли разработчики понимают, как правильно применять SOLID принципы в геймдеве?
Практика показывает, что понимание принципов работает, как испорченный телефон.
Ведь каждый с высоты опыта и багажа по-своему интерпретирует, понимает и применяет на практике полученную информацию.
Часто в финале у большинства команд получается эдакий монстр-франкенштейн с кучей интерфейсов и их реализацией.
Код получается слабо связным и с сильной степенью зацепления.
Рассмотрим классический SOLID применительно к геймдеву.
Разберемся, что находится под капотом у принципов.
Я не буду давать классическое определение каждого из принципов, их легко найти, дам лишь свое пояснение и выделю суть.
Поехали 🚀🚀🚀
S - Single responsibility principle (SRP)
❗Суть принципа: написанный вами код (реализация) внутри каждой категории (про категории говорили тут) должен максимально соответствовать этой категории.
На примере связи Игрок - Инвентарь. Если в каком-то классе, относящемся к категории игрока, мы решили что-то сделать с ячейками инвентаря, поменять их местами (написать логику свопа в классе, который относится к категории игрока), такой подход будет нарушать SRP.
Более правильной реализацией будет работа с инвентарем в классах, относящихся к категории инвентаря.
O - Open-closed principle (OCP)
На практике этот принцип соблюсти достаточно тяжело.Так как в геймдеве часто меняются бизнес-требования.
❗Суть принципа: класс открыт для добавлений, но закрыт для изменений.
Моя рекомендация - пишите код так, чтобы его было легко менять и удобно расширять.
L - Liskov substitution principle (LSP)
❗Суть принципа: если у вас в коде есть класс A и класс B, который является наследником A, то при замене всех классов A на B в работе кода ничего не должно поменяться.
Моя рекомендация - не злоупотребляете наследованием. Иногда композиция или имплементация интерфейса сработает гораздо эффективнее.
I - Interface segregation principle (ISP)
❗Суть принципа: если и используете интерфейсы, не тащите в них то, что не нужно.
Пример подхода, нарушающего ISP: если какой-то интерфейс в реализации должен работать с игровым объектом, при этом описание игрового объекта есть в коллекции контента. Нарушением принципа будет передача в интерфейс самой коллекции контента и id того объекта, с которым хотим работать.
Чтобы обойти нарушение ISP, необходимо передавать сам интерфейс объекта, который мы предварительно получаем по id из коллекции контента.
Принцип бритвы Оккама тут может быть хорошей аналогией.
D - Dependency inversion principle (DIP)
❗Суть принципа: когда вы пишете какую-либо логику в классе и у вас есть зависимости, пишите логику не опираясь на конкретные реализации этих зависимостей. Работайте с зависимостями через абстракции.
Моя рекомендация - учитывать, что абстракции - это не только интерфейсы и абстрактные классы.
Продолжение следует…
Telegram
GameDev: разработка на Unity3D
Связность (cohesion) и зацепление (coupling).
Привет, сегодня поговорим про связность и зацепление в коде.
Полагаю, что каждый сталкивался со спагетти-кодом. Он плохо поддерживаемый и плохо структурированный.
Спагетти-код получается, когда…
Привет, сегодня поговорим про связность и зацепление в коде.
Полагаю, что каждый сталкивался со спагетти-кодом. Он плохо поддерживаемый и плохо структурированный.
Спагетти-код получается, когда…
#solid #solid_геймдев
Привет, друзья.
Вернемся к SOLID в разрезе геймдева.
Сегодня рассмотрим проблемы применения принципов в чистом виде и сформируем набор рекомендаций.
Итак приступим: какие из принципов и по каким причинам требуют особого внимания? Можно ли их использовать в том виде, как они были задуманы?
Вспомним, что SOLID - это:
- Прежде всего базис, на котором должны разрабатываться приложения, с поправкой на адаптацию под предметную область
- Создание хорошо сопровождаемых продуктов
- Повышение структурированности и читаемости кода
- Прозрачность бизнес-логики
- Простота и взаимозаменяемость отдельных частей продукта
- Снижение кривой сложности доработок в приложении.
В геймдев проектах можно относительно безболезненно реализовать SRP и LSP, сложности возникнут с OCP, ISP и DIP.
Рассмотрим проблемы, характерные для боевых проектов:
- SRP и LSP желателен постоянный код-ревью, при этом условии возможно безболезненное применение принципов
- OCP достаточно часто нарушается из-за переменчивости бизнес-требований, исходящих от гейм-дизайнеров, меняющих ГДД (гейм-дизайн документ). Это влечет за собой выпиливание части кода и будет сопряжено с трудностями, т.к. классы в коде уже плотно зацепились с классами из других категорий
- ISP и DIP часто интерпретируют неверно, связывая их с интерфейсами. Например: DIP - путают с Dependency Injection
- Дополнительную нагрузку при планировании и проектировании реализации DIP добавляют Dependency Injection фреймворки, наподобии Zenject, Ninject или собственные реализации. Обычно к собственной реализации приходят, если готовый фреймворк оказался сложен или избыточен.
В совокупности применение принципов без адаптации к геймдеву приводит к тому, что приложение обрастает интерфейсами и их реализациями. Задумайтесь, чем больше проект, тем неподъемнее конструкция. Каждая реализация обязана содержать public методы, что раздувает объем кодовой базы и снижает инкапсуляцию с механизмом сокрытия (тут я имею в виду private методы).
Опыт показал, что для геймдева нужен немного другой набор принципов.
✅ Сформулирую набор принципов и рекомендаций, которые я выделяю, исходя из своего опыта:
- В приложении должна быть единая точка входа в код
- Приложение должно быть разбито на слои абстракции
- Должна быть возможность пройти по всему дереву приложения из IDE
- Жизненный цикл всех объектов в приложении должен быть строго детерминирован
- Максимально, где это возможно, должна использоваться инкапсуляции с механизмом сокрытия. А именно: объекты слоя бизнес-логики должны иметь минимальное количество публичных методов, в идеале - не иметь вообще
- Использовать динамический полиморфизм. Причем, который не базируется на интерфейсах или абстрактных классах
- Минимизировать связи между объектами слоев абстракций (аналог ISP)
- Возможность легко менять и расширять код
- Использовать принцип подстановки Барбары Лисков (LSP)
- Код для работы с различными зависимостями должен быть написан декларативно (аналог DIP) и отвечать на вопрос: “что делать”. Подход обеспечит сильную связанность и слабое зацепление в сочетании с динамическим полиморфизмом.
- Простая сериализация/десериализация объектов.
Продолжение следует…
Привет, друзья.
Вернемся к SOLID в разрезе геймдева.
Сегодня рассмотрим проблемы применения принципов в чистом виде и сформируем набор рекомендаций.
Итак приступим: какие из принципов и по каким причинам требуют особого внимания? Можно ли их использовать в том виде, как они были задуманы?
Вспомним, что SOLID - это:
- Прежде всего базис, на котором должны разрабатываться приложения, с поправкой на адаптацию под предметную область
- Создание хорошо сопровождаемых продуктов
- Повышение структурированности и читаемости кода
- Прозрачность бизнес-логики
- Простота и взаимозаменяемость отдельных частей продукта
- Снижение кривой сложности доработок в приложении.
В геймдев проектах можно относительно безболезненно реализовать SRP и LSP, сложности возникнут с OCP, ISP и DIP.
Рассмотрим проблемы, характерные для боевых проектов:
- SRP и LSP желателен постоянный код-ревью, при этом условии возможно безболезненное применение принципов
- OCP достаточно часто нарушается из-за переменчивости бизнес-требований, исходящих от гейм-дизайнеров, меняющих ГДД (гейм-дизайн документ). Это влечет за собой выпиливание части кода и будет сопряжено с трудностями, т.к. классы в коде уже плотно зацепились с классами из других категорий
- ISP и DIP часто интерпретируют неверно, связывая их с интерфейсами. Например: DIP - путают с Dependency Injection
- Дополнительную нагрузку при планировании и проектировании реализации DIP добавляют Dependency Injection фреймворки, наподобии Zenject, Ninject или собственные реализации. Обычно к собственной реализации приходят, если готовый фреймворк оказался сложен или избыточен.
В совокупности применение принципов без адаптации к геймдеву приводит к тому, что приложение обрастает интерфейсами и их реализациями. Задумайтесь, чем больше проект, тем неподъемнее конструкция. Каждая реализация обязана содержать public методы, что раздувает объем кодовой базы и снижает инкапсуляцию с механизмом сокрытия (тут я имею в виду private методы).
Опыт показал, что для геймдева нужен немного другой набор принципов.
✅ Сформулирую набор принципов и рекомендаций, которые я выделяю, исходя из своего опыта:
- В приложении должна быть единая точка входа в код
- Приложение должно быть разбито на слои абстракции
- Должна быть возможность пройти по всему дереву приложения из IDE
- Жизненный цикл всех объектов в приложении должен быть строго детерминирован
- Максимально, где это возможно, должна использоваться инкапсуляции с механизмом сокрытия. А именно: объекты слоя бизнес-логики должны иметь минимальное количество публичных методов, в идеале - не иметь вообще
- Использовать динамический полиморфизм. Причем, который не базируется на интерфейсах или абстрактных классах
- Минимизировать связи между объектами слоев абстракций (аналог ISP)
- Возможность легко менять и расширять код
- Использовать принцип подстановки Барбары Лисков (LSP)
- Код для работы с различными зависимостями должен быть написан декларативно (аналог DIP) и отвечать на вопрос: “что делать”. Подход обеспечит сильную связанность и слабое зацепление в сочетании с динамическим полиморфизмом.
- Простая сериализация/десериализация объектов.
Продолжение следует…
Какими механизмами вы обеспечиваете полиморфизм в своих проектах?
Final Results
62%
Интерфейсы
70%
Виртуальные методы
22%
Другие конструкции языка
0%
Нет опыта
Доброе утро, друзья!
Подведем итоги опроса "Какими механизмами вы обеспечиваете полиморфизм?".
Первым делом хочу поблагодарить всех, кто принял участие и дал обратную связь 👍
Проголосовало чуть меньше 50% участников, это хороший результат.
Вижу, что вы знакомы с понятием полиморфизма 👍
Итак, на момент подведения итогов большинство из участников, принявших участие, выбрали виртуальные методы ~ 70% из 100%, далее следуют интерфейсы ~60% из 100% и другие конструкции языка лишь ~ 20% из 100%.
Что касается меня, для решения задач слоя бизнес-логики приложения я отдаю предпочтение "другим конструкциям языка". А именно реактивным объектам (из библиотеки UniRx).
Кроме этого к другим конструкциям языка относятся делегаты, шина сообщений и т.д.
Если речь идет про библиотеки, которые не завязаны на бизнес-логику приложения, то для обеспечения полиморфизма подойдут как виртуальные методы, так и интерфейсы.
Почему для решения задач слоя бизнес-логики я стараюсь избегать использования интерфейсов и виртуальных методов. Обеспечение полиморфизма такими способом, как правило, влечет за собой организацию связей в коде проекта через публичные методы. Наличие большого количества интерфейсов ведет к разрастанию кодовой базы, так как для каждого интерфейса нужна как минимум 1 реализация.
В итоге мы придем к коду, который мало устойчив к изменениям, в котором сложно ориентироваться.
При изменении состава команды у новых участников возникнут сложности с использованием публичного API приложения (которое уже было спроектировано).
Будет усиливаться зацепление кода.
Если есть желание поговорить подробнее про использование других конструкций языка, оставьте комментарий ⬇
Подведем итоги опроса "Какими механизмами вы обеспечиваете полиморфизм?".
Первым делом хочу поблагодарить всех, кто принял участие и дал обратную связь 👍
Проголосовало чуть меньше 50% участников, это хороший результат.
Вижу, что вы знакомы с понятием полиморфизма 👍
Итак, на момент подведения итогов большинство из участников, принявших участие, выбрали виртуальные методы ~ 70% из 100%, далее следуют интерфейсы ~60% из 100% и другие конструкции языка лишь ~ 20% из 100%.
Что касается меня, для решения задач слоя бизнес-логики приложения я отдаю предпочтение "другим конструкциям языка". А именно реактивным объектам (из библиотеки UniRx).
Кроме этого к другим конструкциям языка относятся делегаты, шина сообщений и т.д.
Если речь идет про библиотеки, которые не завязаны на бизнес-логику приложения, то для обеспечения полиморфизма подойдут как виртуальные методы, так и интерфейсы.
Почему для решения задач слоя бизнес-логики я стараюсь избегать использования интерфейсов и виртуальных методов. Обеспечение полиморфизма такими способом, как правило, влечет за собой организацию связей в коде проекта через публичные методы. Наличие большого количества интерфейсов ведет к разрастанию кодовой базы, так как для каждого интерфейса нужна как минимум 1 реализация.
В итоге мы придем к коду, который мало устойчив к изменениям, в котором сложно ориентироваться.
При изменении состава команды у новых участников возникнут сложности с использованием публичного API приложения (которое уже было спроектировано).
Будет усиливаться зацепление кода.
Если есть желание поговорить подробнее про использование других конструкций языка, оставьте комментарий ⬇
Привет, друзья 🙂
Ниже анонс тем для ближайших публикаций 👇
Все они вместе с затронутыми ранее темами являются важными составляющими разработки игр.
UPD: для удобства навигации заголовок рассмотренных тем дополнен ссылкой на публикацию
1 Деление игры на слои абстракции
Поговорим про:
- представление (view, отвечает на вопрос: "как")
- слой бизнес-логики (presentation model, отвечает на вопрос: "что делать")
- построение дерева приложения (иерархия или граф entity)
- контентный слой (набор контента, с которым стартует игра)
- слой сервисов (например, рекламный сервис, сервис аналитики) не влияющий на бизнес-логику игры
- слой состояний игровых объектов (state)
2 С чего должен начинаться Unity проект
Поговорим про нюансы на уровне Unity: количечтво точек входа, последовательность старта объектов
3 MonoBehaviour
Обсудим его роль в проекте
4 Организация префабов
Поговорим про правильную организацию и роль слоя view в ней
5 Жизненный цикл объектов
Обсудим, кто за него должен отвечать
6 Изолированность классов
Поговорим о том, как ее обеспечить
7 Dependency Injection
Обсудим, как применять. Затронем DI фреймворки. Разберем, как правильно внедрять зависимости
8 Абстракции
Как не использовать интерфейсы, при этом писать абстрактный код. Полиморфизм
9 Связи
Разберем, как выстроить связи между классами со слабым зацеплением в коде. Рассмотрим применение реактивных объектов из библиотеки UniRx
10 Контент
Как должен выглядеть контент, используемый игровыми объектами
11 Состояние объектов в игре
Поговорим про то, как и где хранить состояние объектов в игре, также состояние мира/игрока
12 Плагины
Обсудим, как использовать сторонние плагины, чтобы они вписывались в общую архитектуру проекта
13 Сериализация и десериализация
Как правильно сериализовать и десериализовать объекты внутри игры.
Ниже анонс тем для ближайших публикаций 👇
Все они вместе с затронутыми ранее темами являются важными составляющими разработки игр.
UPD: для удобства навигации заголовок рассмотренных тем дополнен ссылкой на публикацию
1 Деление игры на слои абстракции
Поговорим про:
- представление (view, отвечает на вопрос: "как")
- слой бизнес-логики (presentation model, отвечает на вопрос: "что делать")
- построение дерева приложения (иерархия или граф entity)
- контентный слой (набор контента, с которым стартует игра)
- слой сервисов (например, рекламный сервис, сервис аналитики) не влияющий на бизнес-логику игры
- слой состояний игровых объектов (state)
2 С чего должен начинаться Unity проект
Поговорим про нюансы на уровне Unity: количечтво точек входа, последовательность старта объектов
3 MonoBehaviour
Обсудим его роль в проекте
4 Организация префабов
Поговорим про правильную организацию и роль слоя view в ней
5 Жизненный цикл объектов
Обсудим, кто за него должен отвечать
6 Изолированность классов
Поговорим о том, как ее обеспечить
7 Dependency Injection
Обсудим, как применять. Затронем DI фреймворки. Разберем, как правильно внедрять зависимости
8 Абстракции
Как не использовать интерфейсы, при этом писать абстрактный код. Полиморфизм
9 Связи
Разберем, как выстроить связи между классами со слабым зацеплением в коде. Рассмотрим применение реактивных объектов из библиотеки UniRx
10 Контент
Как должен выглядеть контент, используемый игровыми объектами
11 Состояние объектов в игре
Поговорим про то, как и где хранить состояние объектов в игре, также состояние мира/игрока
12 Плагины
Обсудим, как использовать сторонние плагины, чтобы они вписывались в общую архитектуру проекта
13 Сериализация и десериализация
Как правильно сериализовать и десериализовать объекты внутри игры.
#слои_абстракции_геймдев #архитектура
Приветствую, друзья.
Сегодня мы обсудим важную тему - разделение игры на слои абстракции. Вопрос является ключевым для создания гибкой и масштабируемой игры. Хотя объем информации достаточно велик, буду стараться дать его как можно более лаконично и понятно.
Слои абстракции - это способ организации кода, который позволяет разделить функциональность приложения на несколько уровней. Каждый уровень отвечает за конкретный аспект решаемой задачи. Что позволяет упростить код и улучшить его читаемость, расширяемость и масштабируемость.
Правильное разделение существенно облегчает жизнь разработчика.
Минимально я выделяю семь слоев, среди них:
- слой бизнес-логики
- слой представления
- слой связей
- слой жизненного цикла объектов
- слой контента
- слой состояния игровых объектов
- слой сервисов.
На практике слои бизнес-логики и представления часто смешивают, что ведет к проблемам в поддержке и расширении функционала игры. Но делает разработку быстрее и проще из-за меньшего количества слоев.
Рассмотрим каждый из слоев подробнее.
Слой бизнес-логики (или presentation model, pm)
Часть, отвечающая на вопрос: "что делать". Содержит правила и логику игры, необходимые для ее функционирования.
Например: идти вперед, собирать предметы, открыть ящик, и т.д.
Слой представление (или view)
Часть, отвечающая на вопрос: "как". Отвечает за отображение игрового мира и взаимодействия с игроком.
Как именно действие должно представляться в игровом мире.
Например: как герой должен бежать вперед, как собирать предметы, как открывать ящик, и т.д.
Проведем аналогию с поездкой на такси:
Мы планируем доехать на такси из пункта "А" в пункт "Б".
Для решения этой задачи мы говорим такси, что нужно сделать.
Что нужно сделать? Доехать из пункта "А" в пункт "Б".
Такси при этом само знает, как именно оно поедет.
В данном случае слоем бизнес-логики (pm) выступает пассажир, который говорит, что делать.
Слоем представления (view) выступает такси, которое знает, как именно нужно ехать.
Слой связей
Отвечает за взаимодействие между объектами в игре.
Например связи между персонажем и окружающим миром или между различными игровыми объектами (враг и оружие). Как правило, cвязи выстраиваются через реактивные объекты: ReactiveProperty и ReactiveCommand.
Слой жизненного цикла объектов
Управляет созданием, удалением и обновлением объектов в игре.
Условно жизненный цикл можно представить в виде дерева или графа, где в каждый определенный момент будут работать определенные ветки.
Узел дерева - это некоторая игровая сущность (или как договорились называть ранее entity), описывающая набор логик, предоставляющая набор представлений, необходимых ресурсов и подготавливающая набор связей (реактивные объекты), которые будут использоваться слоями entity, view и pm для общения.
Игровая сущность (entity) может создавать другие игровые сущности, для связи используются реактивные объекты. Игровые сущности не находятся в иерархии наследования.
Именно слабое зацепление и высокая связность слоев entity, pm, view являются ключевыми факторами для создания гибкого и расширяемого приложения. Слабое зацепление обеспечивается за счет использования реактивных объектов, таких как ReactiveProperty и ReactiveCommand. Это позволяет связывать данные и логику между слоями, не нарушая их инкапсуляцию. В то же время, высокая связность обеспечивает общую логику приложения и облегчает отладку и поддержку кода. Это дает возможность легко модифицировать и расширять приложение без необходимости вносить изменения в другие части кода.
Реализующие их классы должны быть написаны таким образом, чтобы обеспечить инкапсуляцию с механизмом сокрытия.
Код должен быть написан декларативно, используя полиморфизм подтипов.
Продолжение 👇
Приветствую, друзья.
Сегодня мы обсудим важную тему - разделение игры на слои абстракции. Вопрос является ключевым для создания гибкой и масштабируемой игры. Хотя объем информации достаточно велик, буду стараться дать его как можно более лаконично и понятно.
Слои абстракции - это способ организации кода, который позволяет разделить функциональность приложения на несколько уровней. Каждый уровень отвечает за конкретный аспект решаемой задачи. Что позволяет упростить код и улучшить его читаемость, расширяемость и масштабируемость.
Правильное разделение существенно облегчает жизнь разработчика.
Минимально я выделяю семь слоев, среди них:
- слой бизнес-логики
- слой представления
- слой связей
- слой жизненного цикла объектов
- слой контента
- слой состояния игровых объектов
- слой сервисов.
На практике слои бизнес-логики и представления часто смешивают, что ведет к проблемам в поддержке и расширении функционала игры. Но делает разработку быстрее и проще из-за меньшего количества слоев.
Рассмотрим каждый из слоев подробнее.
Слой бизнес-логики (или presentation model, pm)
Часть, отвечающая на вопрос: "что делать". Содержит правила и логику игры, необходимые для ее функционирования.
Например: идти вперед, собирать предметы, открыть ящик, и т.д.
Слой представление (или view)
Часть, отвечающая на вопрос: "как". Отвечает за отображение игрового мира и взаимодействия с игроком.
Как именно действие должно представляться в игровом мире.
Например: как герой должен бежать вперед, как собирать предметы, как открывать ящик, и т.д.
Проведем аналогию с поездкой на такси:
Мы планируем доехать на такси из пункта "А" в пункт "Б".
Для решения этой задачи мы говорим такси, что нужно сделать.
Что нужно сделать? Доехать из пункта "А" в пункт "Б".
Такси при этом само знает, как именно оно поедет.
В данном случае слоем бизнес-логики (pm) выступает пассажир, который говорит, что делать.
Слоем представления (view) выступает такси, которое знает, как именно нужно ехать.
Слой связей
Отвечает за взаимодействие между объектами в игре.
Например связи между персонажем и окружающим миром или между различными игровыми объектами (враг и оружие). Как правило, cвязи выстраиваются через реактивные объекты: ReactiveProperty и ReactiveCommand.
Слой жизненного цикла объектов
Управляет созданием, удалением и обновлением объектов в игре.
Условно жизненный цикл можно представить в виде дерева или графа, где в каждый определенный момент будут работать определенные ветки.
Узел дерева - это некоторая игровая сущность (или как договорились называть ранее entity), описывающая набор логик, предоставляющая набор представлений, необходимых ресурсов и подготавливающая набор связей (реактивные объекты), которые будут использоваться слоями entity, view и pm для общения.
Игровая сущность (entity) может создавать другие игровые сущности, для связи используются реактивные объекты. Игровые сущности не находятся в иерархии наследования.
Именно слабое зацепление и высокая связность слоев entity, pm, view являются ключевыми факторами для создания гибкого и расширяемого приложения. Слабое зацепление обеспечивается за счет использования реактивных объектов, таких как ReactiveProperty и ReactiveCommand. Это позволяет связывать данные и логику между слоями, не нарушая их инкапсуляцию. В то же время, высокая связность обеспечивает общую логику приложения и облегчает отладку и поддержку кода. Это дает возможность легко модифицировать и расширять приложение без необходимости вносить изменения в другие части кода.
Реализующие их классы должны быть написаны таким образом, чтобы обеспечить инкапсуляцию с механизмом сокрытия.
Код должен быть написан декларативно, используя полиморфизм подтипов.
Продолжение 👇
#слои_абстракции_геймдев #архитектура
Слои абстракции продолжение 👇
Слой контента
Отвечает за описание всех игровых и логических объектов и их характеристики, также за конфигурацию игры. Представляет собой набор классов, экземпляры которых не должны модифицироваться в процессе игры.
Классы находятся в строгой иерархии наследования, общий предок всех контентных классов - класс Content.
Поля классов контентного слоя должны иметь публичные модификаторы.
Контентный слой должен предоставлять доступ к каждому уровню контентного дерева в виде типизированной коллекции.
Рассмотрим пример: предположим в игре есть 2 типа зданий: обычные и те, которые поддерживают апгрейд. Тогда иерархия контентных классов будет состоять из:
Content-Building-UpgradableBuilding.
В этом случае контентные коллекции должны выглядеть следующим образом:
Contents - коллекция содержащие все экземпляры классов, которые могут быть приведены к классу Content (экземпляры классов UpgradableBuilding, Buildings, Content).
Buildings - коллекция содержащая все экземпляры классов, которые могут быть приведены к классу Building (экземпляры классов UpgradableBuilding, Buildings).
UpgradableBuilding - коллекция содержащая все экземпляры классов, которые могут быть приведены к классу Building (экземпляры классов UpgradableBuilding).
Доступ к элементам коллекции должен осуществляться через id.
Слой состояния игровых объектов
Отвечает за хранение и обновление состояния объектов в игре.
Если игровой объект, описанный контентным классом, в процессе игры может менять свое состояние, для него явно должен присутствовать отдельный класс - состояние (state).
Классы состояния могут находится в иерархии наследования и должны иметь поля с публичными модификаторами доступа.
У класса состояния должно быть публичное поле contentId.
Те поля, на изменение которых может реагировать слой представления (view), должны быть оформлены в виде реактивных объектов. У контентного класса такого вида должен быть публичный метод CreateState, который вернет созданный экземпляр класса состояния нужного типа.
Экземпляры классов состояний можно сериализовывать, сохранять на диск, отправлять на сервер и при перезапуске игры восстанавливать состояние игрового мира.
Слой сервисов
Отвечает за набор дополнительных сервисов, например для сбора и анализа данных об игроках и их действиях в игре.
Представляет собой набор классов с публичными модификаторами доступа. Сервисный класс должен предоставлять публичный доступ к API, который реализует сервис.
Сервис должен быть создан 1 раз в нужном узле дерева приложения. Далее в виде явной зависимости передаваться вниз по иерархии.
Слои абстракции продолжение 👇
Слой контента
Отвечает за описание всех игровых и логических объектов и их характеристики, также за конфигурацию игры. Представляет собой набор классов, экземпляры которых не должны модифицироваться в процессе игры.
Классы находятся в строгой иерархии наследования, общий предок всех контентных классов - класс Content.
Поля классов контентного слоя должны иметь публичные модификаторы.
Контентный слой должен предоставлять доступ к каждому уровню контентного дерева в виде типизированной коллекции.
Рассмотрим пример: предположим в игре есть 2 типа зданий: обычные и те, которые поддерживают апгрейд. Тогда иерархия контентных классов будет состоять из:
Content-Building-UpgradableBuilding.
В этом случае контентные коллекции должны выглядеть следующим образом:
Contents - коллекция содержащие все экземпляры классов, которые могут быть приведены к классу Content (экземпляры классов UpgradableBuilding, Buildings, Content).
Buildings - коллекция содержащая все экземпляры классов, которые могут быть приведены к классу Building (экземпляры классов UpgradableBuilding, Buildings).
UpgradableBuilding - коллекция содержащая все экземпляры классов, которые могут быть приведены к классу Building (экземпляры классов UpgradableBuilding).
Доступ к элементам коллекции должен осуществляться через id.
Слой состояния игровых объектов
Отвечает за хранение и обновление состояния объектов в игре.
Если игровой объект, описанный контентным классом, в процессе игры может менять свое состояние, для него явно должен присутствовать отдельный класс - состояние (state).
Классы состояния могут находится в иерархии наследования и должны иметь поля с публичными модификаторами доступа.
У класса состояния должно быть публичное поле contentId.
Те поля, на изменение которых может реагировать слой представления (view), должны быть оформлены в виде реактивных объектов. У контентного класса такого вида должен быть публичный метод CreateState, который вернет созданный экземпляр класса состояния нужного типа.
Экземпляры классов состояний можно сериализовывать, сохранять на диск, отправлять на сервер и при перезапуске игры восстанавливать состояние игрового мира.
Слой сервисов
Отвечает за набор дополнительных сервисов, например для сбора и анализа данных об игроках и их действиях в игре.
Представляет собой набор классов с публичными модификаторами доступа. Сервисный класс должен предоставлять публичный доступ к API, который реализует сервис.
Сервис должен быть создан 1 раз в нужном узле дерева приложения. Далее в виде явной зависимости передаваться вниз по иерархии.
📌 UPD: группа желающих сделать тестовое задание для разбора сформирована 💪 всем спасибо за участие 👍
p/s если вы хотели, поучаствовать, но не успели, напишите, что-нибудь придумаем.
Приветствую, друзья 👋
С началом новой недели!
Пока мы готовим очередной пост, предлагаем вам выполнить и отправить на ревью небольшое тестовое задание)
Мы со своей стороны сделаем и опубликуем обезличенный разбор для первых трех желающих.
❗“Эталонный” вариант реализации опубликуем после того, как дадим основную часть теоретического блока)
Итак тестовое задание 🎮:
Игровая сцена разбита на квадраты 100x100.
При входе в игру в клетки игрового поля спавнятся здания 2х типов: простые (статичные) и те, которые могут апгрейдится до определенного уровня.
Каждый уровень здания - новая модель.
В контенте можно задать цепочки апгрейдов для зданий.
Например здание 1 (level 1) - здание 2 (level 2) - здание 3 (level 3).
Игрок (наблюдатель) может управлять камерой (WASD + мышь), чтобы можно свободно передвигаться по сцене. При перемещении нужно учитывать коллизии на зданиях и террейне.
Критерии оценки:
- Декомпозиция задачи ,
- Связи между слоями,
- Дорабатываемость и устойчивость к изменениям.
❗Визуальная составляющая не важна и полностью на ваше усмотрение.
Желающие принять участие, отпишитесь о своем намерении в комментариях к посту либо в личку Ирине.
Если требуется детализация, ждем вопросы.
По времени выполнения, предлагаю ориентироваться на неделю, чтобы была возможность спланировать и выбрать удобное время.
И чур не сходить с дистанции 🧗♂️
Всем продуктивной недели 💪
p/s если вы хотели, поучаствовать, но не успели, напишите, что-нибудь придумаем.
Приветствую, друзья 👋
С началом новой недели!
Пока мы готовим очередной пост, предлагаем вам выполнить и отправить на ревью небольшое тестовое задание)
Мы со своей стороны сделаем и опубликуем обезличенный разбор для первых трех желающих.
❗“Эталонный” вариант реализации опубликуем после того, как дадим основную часть теоретического блока)
Итак тестовое задание 🎮:
Игровая сцена разбита на квадраты 100x100.
При входе в игру в клетки игрового поля спавнятся здания 2х типов: простые (статичные) и те, которые могут апгрейдится до определенного уровня.
Каждый уровень здания - новая модель.
В контенте можно задать цепочки апгрейдов для зданий.
Например здание 1 (level 1) - здание 2 (level 2) - здание 3 (level 3).
Игрок (наблюдатель) может управлять камерой (WASD + мышь), чтобы можно свободно передвигаться по сцене. При перемещении нужно учитывать коллизии на зданиях и террейне.
Критерии оценки:
- Декомпозиция задачи ,
- Связи между слоями,
- Дорабатываемость и устойчивость к изменениям.
❗Визуальная составляющая не важна и полностью на ваше усмотрение.
Желающие принять участие, отпишитесь о своем намерении в комментариях к посту либо в личку Ирине.
Если требуется детализация, ждем вопросы.
По времени выполнения, предлагаю ориентироваться на неделю, чтобы была возможность спланировать и выбрать удобное время.
И чур не сходить с дистанции 🧗♂️
Всем продуктивной недели 💪
Telegram
Irina Zhi
#архитектура
Приветствую, друзья 👋
Сегодня затронем вторую тему из списка и поговорим о количестве точек входа в код проекта.
Точка входа может быть как одна, так и несколько.
Начнём с единой точки входа.
Единая точка входа - это главный скрипт, который инициализирует и контролирует все другие элементы проекта.
Представляет собой MonoBehaviour класс, который может быть легко просмотрен и понят.
В Unity единая точка входа может быть реализована с помощью скриптов и объектов, таких как менеджер сцен или стартовый класс.
Единая точка входа может включать в себя загрузку ресурсов, инициализацию компонентов, конфигурацию параметров и другие важные действия, необходимые для запуска и функционирования приложения.
Основные преимущества единой точки входа:
- возможность контролировать порядок инициализации элементов проекта. Особенно актуально, когда вы работаете с большим количеством ассетов и скриптов, и необходимо гарантировать, что все элементы будут инициализированы в нужном порядке
- улучшение производительности приложения. Достигается за счет того, что единая точка входа позволяет контролировать загрузку и инициализацию только тех элементов, которые необходимы для данного уровня или экрана, что уменьшает нагрузку на устройство
- повышение читаемости и поддерживаемости кода.
Применение единой точки входа является best practice и позволяет создать качественный и надежный продукт, существенно улучшает общую организацию и структуру кода, уменьшает риск возникновения ошибок и проблем с поддержкой, а также упрощает процесс разработки и тестирования.
Что касается нескольких точек входа в код проекта.
При таком подходе каждая точка входа представляет собой отдельный вход в код и может быть реализована с помощью GameObject на сцене.
На каждом GameObject расположены MonoBehaviour скрипты, в методах Awake или Start которых выполняется определенный код.
Использование нескольких точек входа проще в реализации, подходит для прототипов но неэффективно для крупных проектов.
Плюсы использования нескольких точек входа в код:
- простота использования (если речь о прототипах)
- отпадает необходимость задумываться о жизненном цикле объектов.
Минусы использования нескольких точек входа:
- усложнение отладки и увеличение времени на ее проведение
- снижение читаемости кода и усложнение его понимания.
Что касается меня, я являюсь сторонником единой точки входа.
Рассмотрим на примере:
В иерархии объектов на сцене я создаю GameObject с названием Root, на котором размещаю компонент EntryPoint. В Awake создаю первую Entity - RootEntity, далее код развивается древовидно.
В каждую из веток кода можно попасть через навигатор кода.
Каждая Entity, Pm, View включается в себя структуру Ctx, которая описывает все зависимости текущего класса.
В итоге код можно представить в виде дерева.
Кроме того, наличие Ctx в каждой Entity, Pm и View позволяет вам легко отслеживать все зависимости, что улучшает читаемость и поддерживаемость кода.
Приветствую, друзья 👋
Сегодня затронем вторую тему из списка и поговорим о количестве точек входа в код проекта.
Точка входа может быть как одна, так и несколько.
Начнём с единой точки входа.
Единая точка входа - это главный скрипт, который инициализирует и контролирует все другие элементы проекта.
Представляет собой MonoBehaviour класс, который может быть легко просмотрен и понят.
В Unity единая точка входа может быть реализована с помощью скриптов и объектов, таких как менеджер сцен или стартовый класс.
Единая точка входа может включать в себя загрузку ресурсов, инициализацию компонентов, конфигурацию параметров и другие важные действия, необходимые для запуска и функционирования приложения.
Основные преимущества единой точки входа:
- возможность контролировать порядок инициализации элементов проекта. Особенно актуально, когда вы работаете с большим количеством ассетов и скриптов, и необходимо гарантировать, что все элементы будут инициализированы в нужном порядке
- улучшение производительности приложения. Достигается за счет того, что единая точка входа позволяет контролировать загрузку и инициализацию только тех элементов, которые необходимы для данного уровня или экрана, что уменьшает нагрузку на устройство
- повышение читаемости и поддерживаемости кода.
Применение единой точки входа является best practice и позволяет создать качественный и надежный продукт, существенно улучшает общую организацию и структуру кода, уменьшает риск возникновения ошибок и проблем с поддержкой, а также упрощает процесс разработки и тестирования.
Что касается нескольких точек входа в код проекта.
При таком подходе каждая точка входа представляет собой отдельный вход в код и может быть реализована с помощью GameObject на сцене.
На каждом GameObject расположены MonoBehaviour скрипты, в методах Awake или Start которых выполняется определенный код.
Использование нескольких точек входа проще в реализации, подходит для прототипов но неэффективно для крупных проектов.
Плюсы использования нескольких точек входа в код:
- простота использования (если речь о прототипах)
- отпадает необходимость задумываться о жизненном цикле объектов.
Минусы использования нескольких точек входа:
- усложнение отладки и увеличение времени на ее проведение
- снижение читаемости кода и усложнение его понимания.
Что касается меня, я являюсь сторонником единой точки входа.
Рассмотрим на примере:
В иерархии объектов на сцене я создаю GameObject с названием Root, на котором размещаю компонент EntryPoint. В Awake создаю первую Entity - RootEntity, далее код развивается древовидно.
В каждую из веток кода можно попасть через навигатор кода.
Каждая Entity, Pm, View включается в себя структуру Ctx, которая описывает все зависимости текущего класса.
В итоге код можно представить в виде дерева.
Кроме того, наличие Ctx в каждой Entity, Pm и View позволяет вам легко отслеживать все зависимости, что улучшает читаемость и поддерживаемость кода.
#разбортестового
Приветствую, друзья 👋
Сегодня публикуем разбор одного из присланных тестовых заданий.
Разбор представлен в 2-х форматах:
- Видео. Постарался сделать сжато, выделив суть.
- Текстовый. Для тех, кто не планирует смотреть видео.
Но я рекомендую выделить время как на прочтение, так и просмотр)
Итак, приступим, начнем с замечаний
1. Отсутствует единая точка входа.
Вход в код осуществляется в 2 классах:
- SceaneLoader, Awake
- Player, Start.
2. Разделение на слои абстракции.
Выполнено, но не до конца.
Есть классы отвечающие за несколько зон.
Так, например, класс UpgradableBuilding содержит в себе следующие ответственности:
- контент
- бизнес-логика смены зданий
- слой представления.
Класс Player содержит избыточную ответственность:
- бизнес-логику (управление и передвижение камеры)
- слой представления.
3. Слой State.
В текущей реализации будет невозможно создать BuildingController из State в случае сохранения игрового состояния мира на диск и его последующего восстановления.
4. Загрузка ресурсов.
Процесс загрузки ресурсов требует особого внимания. Независимо от сложности проекта, следует описать в комментариях способы загрузки и обработки большого количества ресурсов. В противном случае возможно переполнение оперативной памяти устройства, что может привести к негативным последствиям.
5. Организация префабов с UpgradableBuilding.
Префабы изначально содержат все возможные уровни апгрейда зданий, по умолчанию все кроме первого уровня находятся в неактивном состоянии.
В идеале UpgradableBuilding не должен знать свою цепочку апгрейда.
Возможность задавать цепочку апгрейда должна быть доступна через контент. Это необходимо для того, чтобы гейм-дизайнер или контент-менеджер могли управлять апгрейдом самостоятельно при настройке баланса игры без участия разработки.
Для этого необходим отдельный контентный класс, в котором прописывается:
- уровень,
- id здания, которое апгрейдим,
- id здания, на какое апгрейдим.
6. Использование большого количества MonoBehaviour.
7. Утилитарные классы можно не инстанцировать, вместо этого использовать статические классы и методы. Например, сделать статическим класс GridGenerator, метод GetGrid.
8. Использования постфикса Controller в классах.
Уйти от использования постфикса Controller в классах, это может вызвать диссонанс у проверяющего.
Controller из MVC - это слой, который отвечает за пользовательский ввод и уведомляет модель, далее слой представления рендерит изменения.
8. Использовать namespace.
---
Что понравилось в тестовом:
1. Простота кода
2. Единообразие с методом Init.
Тестовое выполнено довольно неплохо
Спасибо автору за проделанную работу и предоставленный для разбора материал 💪
Всем хорошего дня и прекрасных выходных!
Приветствую, друзья 👋
Сегодня публикуем разбор одного из присланных тестовых заданий.
Разбор представлен в 2-х форматах:
- Видео. Постарался сделать сжато, выделив суть.
- Текстовый. Для тех, кто не планирует смотреть видео.
Но я рекомендую выделить время как на прочтение, так и просмотр)
Итак, приступим, начнем с замечаний
1. Отсутствует единая точка входа.
Вход в код осуществляется в 2 классах:
- SceaneLoader, Awake
- Player, Start.
2. Разделение на слои абстракции.
Выполнено, но не до конца.
Есть классы отвечающие за несколько зон.
Так, например, класс UpgradableBuilding содержит в себе следующие ответственности:
- контент
- бизнес-логика смены зданий
- слой представления.
Класс Player содержит избыточную ответственность:
- бизнес-логику (управление и передвижение камеры)
- слой представления.
3. Слой State.
В текущей реализации будет невозможно создать BuildingController из State в случае сохранения игрового состояния мира на диск и его последующего восстановления.
4. Загрузка ресурсов.
Процесс загрузки ресурсов требует особого внимания. Независимо от сложности проекта, следует описать в комментариях способы загрузки и обработки большого количества ресурсов. В противном случае возможно переполнение оперативной памяти устройства, что может привести к негативным последствиям.
5. Организация префабов с UpgradableBuilding.
Префабы изначально содержат все возможные уровни апгрейда зданий, по умолчанию все кроме первого уровня находятся в неактивном состоянии.
В идеале UpgradableBuilding не должен знать свою цепочку апгрейда.
Возможность задавать цепочку апгрейда должна быть доступна через контент. Это необходимо для того, чтобы гейм-дизайнер или контент-менеджер могли управлять апгрейдом самостоятельно при настройке баланса игры без участия разработки.
Для этого необходим отдельный контентный класс, в котором прописывается:
- уровень,
- id здания, которое апгрейдим,
- id здания, на какое апгрейдим.
6. Использование большого количества MonoBehaviour.
7. Утилитарные классы можно не инстанцировать, вместо этого использовать статические классы и методы. Например, сделать статическим класс GridGenerator, метод GetGrid.
8. Использования постфикса Controller в классах.
Уйти от использования постфикса Controller в классах, это может вызвать диссонанс у проверяющего.
Controller из MVC - это слой, который отвечает за пользовательский ввод и уведомляет модель, далее слой представления рендерит изменения.
8. Использовать namespace.
---
Что понравилось в тестовом:
1. Простота кода
2. Единообразие с методом Init.
Тестовое выполнено довольно неплохо
Спасибо автору за проделанную работу и предоставленный для разбора материал 💪
Всем хорошего дня и прекрасных выходных!
#библиотека_программиста #геймдев #слои_абстракции
Привествую! По просьбам читателей и в дополнение к предыдущим постам, сегодняшняя публицация будет посвящена литературе по слоям абстракции.
К сожалению, хорошей литературы по слоям абстракции в геймдеве я не встречал.
Но для понимания на концептуальном уровне могу посоветовать следующие варианты:
📚
1. Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем, Эванс Эрик, стр. 79.
2. Архитектура корпоративных программных приложений, Мартин Фаулер, стр. 43.
3. Программист-прагматик. Путь от подмастерья к мастеру, Эндрю Хант, Дэвид Томас, стр. 32.
Приятного прочтения 🙂
Привествую! По просьбам читателей и в дополнение к предыдущим постам, сегодняшняя публицация будет посвящена литературе по слоям абстракции.
К сожалению, хорошей литературы по слоям абстракции в геймдеве я не встречал.
Но для понимания на концептуальном уровне могу посоветовать следующие варианты:
📚
1. Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем, Эванс Эрик, стр. 79.
2. Архитектура корпоративных программных приложений, Мартин Фаулер, стр. 43.
3. Программист-прагматик. Путь от подмастерья к мастеру, Эндрю Хант, Дэвид Томас, стр. 32.
Приятного прочтения 🙂
Привествую, друзья.
Одна из тем, которой я касаюсь при отборе в команду - использование интерфеса IDisposable.
Перед очередной нашей публикацией предлагаю вам принять участие в опросе 👇
Одна из тем, которой я касаюсь при отборе в команду - использование интерфеса IDisposable.
Перед очередной нашей публикацией предлагаю вам принять участие в опросе 👇
Что случится при вызове метода Dispose в объекте ref типа, который реализует интерфейс IDisposable?
Anonymous Quiz
13%
Освободится память
25%
Освободятся неупраляемые ресурсы
48%
Разрабочик решает, что произойдет
14%
Затрудняюсь ответить
#idisposable #управление_ресурсами
Приветствую, друзья!
Подведем итоги опроса про интерфейс IDisposable 👇
На момент подведения итогов получены следующие результаты:
✅ 44% - разработчик решает, что произойдет
16% - освободится память
26% - освободятся неуправляемые ресурсы
14% - затрудняются ответить.
Как показал опрос использование интерфейса IDisposable в C# часто некорректно связывают только с освобождением памяти или освобождением неуправляемых ресурсов.
‼️ На самом деле сам разработчик может задать сценарий происходящего.
Понимание работы IDisposable понадобится нам для дальнейших публикаций и разбора кода, поэтому приступим.
Один из вариантов использования интерфейса IDisposable в C# - реализация паттерна "ресурсов", суть которого состоит в управлении использованием ресурсов, таких как: файлы, сетевые подключения, базы данных и других неуправляемых ресурсов.
Управляемые ресурсы - это ресурсы, которыми управляет сборщик мусора, такие как объекты ref типа, созданные с использованием оператора new.
Сборщик мусора автоматически освобождает память, выделенную под управляемые ресурсы, когда они больше не используются.
Неуправляемые ресурсы - это ресурсы, которые не попадают под управление сборщика мусора, например: открытые файлы, сетевые подключения, базы данных, оборудование и т.д.
Освобождение неуправляемых ресурсов требует явного вызова методов для их освобождения, что и реализуется через использование интерфейса IDisposable.
Таким образом использование интерфейса IDisposable для реализации паттерна "ресурсов" помогает управлять ресурсами и предотвращать утечки памяти.
Рассмотрим другие сценарии.
Реализация паттерна "заимствования" (borrowing).
Паттерн будет полезен при работе с потоками данных или потоковыми операциями, где необходимо убедиться, что определенные ресурсы используются только одним потоком в определенный момент времени, и избежать ошибок при многопоточной обработке данных.
При данном подходе интерфейс IDisposable используют для создания блоков кода, в которых происходит заимствование (borrowing) ресурса и автоматическое освобождение этого ресурса после завершения блока.
Рассмотрим пример реализации паттерна "заимствования", где использование интерфейса IDisposable позволит гарантировать правильную обработку ресурса и избежать ошибок при работе с потоками данных.
В примере класс StreamResource представляет собой ресурс (поток данных), который может быть заимствован в блоке using, после чего происходит автоматическое освобождение ресурса.
Внутри блока using происходит обработка потока данных.
Таким образом, использование интерфейса IDisposable в рассмотренном примере позволяет гарантировать правильную обработку ресурса и избежать ошибок при работе с потоками данных.
Продолжение 👇
Приветствую, друзья!
Подведем итоги опроса про интерфейс IDisposable 👇
На момент подведения итогов получены следующие результаты:
✅ 44% - разработчик решает, что произойдет
16% - освободится память
26% - освободятся неуправляемые ресурсы
14% - затрудняются ответить.
Как показал опрос использование интерфейса IDisposable в C# часто некорректно связывают только с освобождением памяти или освобождением неуправляемых ресурсов.
‼️ На самом деле сам разработчик может задать сценарий происходящего.
Понимание работы IDisposable понадобится нам для дальнейших публикаций и разбора кода, поэтому приступим.
Один из вариантов использования интерфейса IDisposable в C# - реализация паттерна "ресурсов", суть которого состоит в управлении использованием ресурсов, таких как: файлы, сетевые подключения, базы данных и других неуправляемых ресурсов.
Управляемые ресурсы - это ресурсы, которыми управляет сборщик мусора, такие как объекты ref типа, созданные с использованием оператора new.
Сборщик мусора автоматически освобождает память, выделенную под управляемые ресурсы, когда они больше не используются.
Неуправляемые ресурсы - это ресурсы, которые не попадают под управление сборщика мусора, например: открытые файлы, сетевые подключения, базы данных, оборудование и т.д.
Освобождение неуправляемых ресурсов требует явного вызова методов для их освобождения, что и реализуется через использование интерфейса IDisposable.
Таким образом использование интерфейса IDisposable для реализации паттерна "ресурсов" помогает управлять ресурсами и предотвращать утечки памяти.
Рассмотрим другие сценарии.
Реализация паттерна "заимствования" (borrowing).
Паттерн будет полезен при работе с потоками данных или потоковыми операциями, где необходимо убедиться, что определенные ресурсы используются только одним потоком в определенный момент времени, и избежать ошибок при многопоточной обработке данных.
При данном подходе интерфейс IDisposable используют для создания блоков кода, в которых происходит заимствование (borrowing) ресурса и автоматическое освобождение этого ресурса после завершения блока.
Рассмотрим пример реализации паттерна "заимствования", где использование интерфейса IDisposable позволит гарантировать правильную обработку ресурса и избежать ошибок при работе с потоками данных.
class StreamResource : IDisposable
{
private Stream _stream;
public StreamResource(Stream stream)
{
_stream = stream;
}
public Stream GetStream()
{
return _stream;
}
public void Dispose()
{
_stream = null;
}
}
// Использование ресурса в блоке using
using (var resource = new StreamResource(new FileStream("file.txt", FileMode.Open)))
{
var stream = resource.GetStream();
// обработка потока данных
}
В примере класс StreamResource представляет собой ресурс (поток данных), который может быть заимствован в блоке using, после чего происходит автоматическое освобождение ресурса.
Внутри блока using происходит обработка потока данных.
Таким образом, использование интерфейса IDisposable в рассмотренном примере позволяет гарантировать правильную обработку ресурса и избежать ошибок при работе с потоками данных.
Продолжение 👇
#idisposable #управление_ресурсами
IDisposable продолжение 👇
Другим примером использования интерфейса IDisposable является реализация механизма отмены операции или транзакции.
Например, у вас есть сложная операция, которую нужно выполнить в несколько этапов, и необходима возможность отмены операции в любой момент времени.
Рассмотрим реализацию механизма отмены операции на примере:
В примере класс Operation представляет собой операцию, которая может быть выполнена и отменена.
В блоке using используется операция с возможностью отмены.
Если операция завершается успешно, то она просто выполняется, и интерфейс IDisposable не используется.
Если же происходит ошибка, то операция отменяется с помощью метода Cancel, который также вызывается при автоматическом освобождении ресурса благодаря интерфейсу IDisposable.
Использование интерфейс IDisposable в рассмотренном выше примере позволяет реализовать механизм отмены и гарантировать, что операция будет правильно завершена или отменена в любом случае.
✅ Итак, мы рассмотрели варианты использования интерфейса IDisposable для реализации:
- паттерна "ресурсы" для управления ресурсам и предотвращения утечек памяти
- паттерна "заимствования" (borrowing) для работы с потоками данных
- реализация механизма отмены операции или транзакции.
Всем хорошего дня 👋
Для тех, кто планирует более глубокое погружение, в следующем посте подборка тематической литературы 👇
IDisposable продолжение 👇
Другим примером использования интерфейса IDisposable является реализация механизма отмены операции или транзакции.
Например, у вас есть сложная операция, которую нужно выполнить в несколько этапов, и необходима возможность отмены операции в любой момент времени.
Рассмотрим реализацию механизма отмены операции на примере:
class Operation : IDisposable
{
private bool _isCompleted = false;
public void Execute()
{
// выполнение операции
_isCompleted = true;
}
public void Cancel()
{
if (!_isCompleted)
{
// отмена операции
_isCompleted = true;
}
}
public void Dispose()
{
if (!_isCompleted)
{
Cancel();
}
}
}
// Использование операции с возможностью отмены
using (var operation = new Operation())
{
try
{
operation.Execute();
// выполнение операции
}
catch (Exception ex)
{
// обработка ошибки
operation.Cancel();
}
}
В примере класс Operation представляет собой операцию, которая может быть выполнена и отменена.
В блоке using используется операция с возможностью отмены.
Если операция завершается успешно, то она просто выполняется, и интерфейс IDisposable не используется.
Если же происходит ошибка, то операция отменяется с помощью метода Cancel, который также вызывается при автоматическом освобождении ресурса благодаря интерфейсу IDisposable.
Использование интерфейс IDisposable в рассмотренном выше примере позволяет реализовать механизм отмены и гарантировать, что операция будет правильно завершена или отменена в любом случае.
✅ Итак, мы рассмотрели варианты использования интерфейса IDisposable для реализации:
- паттерна "ресурсы" для управления ресурсам и предотвращения утечек памяти
- паттерна "заимствования" (borrowing) для работы с потоками данных
- реализация механизма отмены операции или транзакции.
Всем хорошего дня 👋
Для тех, кто планирует более глубокое погружение, в следующем посте подборка тематической литературы 👇
#библиотека_программиста #геймдев #idisposable
Подборка литературы по использованию IDisposable.
📚
1. Де Смет, Барт. C# 5.0 Unleashed, глава 20, "Диспозиция и финализация".
2. Скит, Джон. C# in Depth, глава 6, "Размещение объектов и сборка мусора".
3. MSDN. "Использование using в C#", (https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/using-statement).
4. CodeProject. "Implementing IDisposable and the Dispose Pattern Properly", (https://www.codeproject.com/Articles/15360/Implementing-IDisposable-and-the-Dispose-Pattern-P).
5. Albahari, Joseph and Albahari, Ben. C# 9.0 in a Nutshell: The Definitive Reference, глава 9, "Memory Management, Pointers, and Unsafe Code".
6. Nagel, Christian. Professional C# 7 and .NET Core 2.0, глава 21, "Unsafe Code and Pointers".
7. Скит, Джон. C# in Depth, глава 15, "Unsafe code and pointers".
8.Agafonov, Eugene. Mastering C# Concurrency: Write Flawless C# Code for Concurrent and Parallel Programming, глава 7, "Low-level Threading in C#".
9. Miles, Rob. C# Programming Yellow Book, глава 25, "Unsafe Code".
10. Официальная документация Microsoft по C#, раздел "Unsafe Code and Pointers".
11. C# Language Specification, раздел "28.1 Pointer types".
Приятного прочтения 🙂
Подборка литературы по использованию IDisposable.
📚
1. Де Смет, Барт. C# 5.0 Unleashed, глава 20, "Диспозиция и финализация".
2. Скит, Джон. C# in Depth, глава 6, "Размещение объектов и сборка мусора".
3. MSDN. "Использование using в C#", (https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/using-statement).
4. CodeProject. "Implementing IDisposable and the Dispose Pattern Properly", (https://www.codeproject.com/Articles/15360/Implementing-IDisposable-and-the-Dispose-Pattern-P).
5. Albahari, Joseph and Albahari, Ben. C# 9.0 in a Nutshell: The Definitive Reference, глава 9, "Memory Management, Pointers, and Unsafe Code".
6. Nagel, Christian. Professional C# 7 and .NET Core 2.0, глава 21, "Unsafe Code and Pointers".
7. Скит, Джон. C# in Depth, глава 15, "Unsafe code and pointers".
8.Agafonov, Eugene. Mastering C# Concurrency: Write Flawless C# Code for Concurrent and Parallel Programming, глава 7, "Low-level Threading in C#".
9. Miles, Rob. C# Programming Yellow Book, глава 25, "Unsafe Code".
10. Официальная документация Microsoft по C#, раздел "Unsafe Code and Pointers".
11. C# Language Specification, раздел "28.1 Pointer types".
Приятного прочтения 🙂
Docs
оператор using — обеспечение правильного использования удаленных объектов - C# reference
Использование инструкции или объявления C# для обеспечения правильного использования удаленных объектов
GameDev: разработка на Unity3D pinned «Пост - приветствие 👋 Новый год - время новых начинаний, и к наступающему новому 2023 году мы подошли с решением и готовностью создать канал, посвященный разработке игр на Unity. Мы - это Сергей и Ирина Жильниковы. О Сергее: Более 15 лет в разработке…»
#monobehaviour #архитектура
Приветствую, друзья 👋
Сегодняшняя публикация посвящена роли MonoBehaviour в проекте.
MonoBehaviour - это базовый класс в Unity, который используется для написания скриптов, управляющих поведением игровых объектов.
Он предоставляет множество методов жизненного цикла, которые могут быть переопределены для реализации логики игровых объектов.
Некоторые из основных методов, используемые в жизненном цикле объекта, включают в себя:
- Awake(): вызывается при создании объекта после инициализации всех его компонентов, но до того, как он станет активным в иерархии сцены.
- Start(): вызывается после Awake() и до первого обновления фрейма.
✅ В методе можно инициализировать переменные и компоненты, которые зависят от других объектов в сцене.
- Update(): вызывается каждый кадр и используется для обновления поведения объекта.
✅ В методе можно изменять свойства объекта, перемещать его, обрабатывать ввод и т.д.
- FixedUpdate(): вызывается с фиксированной частотой и используется для обновления физики объекта.
✅ Метод должен использоваться всякий раз, когда вам нужно изменять физические свойства объекта, такие как скорость, позиция, силы и т.д.
- LateUpdate(): вызывается после того, как все объекты обновили свои позиции в текущем кадре.
✅ Метод полезен, когда необходимо обновить объект, исходя из его новой позиции после обновления других объектов.
- OnEnable(): вызывается, когда объект становится активным в иерархии сцены.
- OnDisable(): вызывается, когда объект становится неактивным в иерархии сцены.
- OnDestroy(): вызывается перед уничтожением объекта.
В дополнение к методам, MonoBehaviour имеет свойства, ниже некоторые из них:
- transform - для доступа к компоненту Transform объекта,
- gameObject - для доступа к объекту, на котором находится компонент
- и многие другие, которые позволяют получить доступ к различным свойствам и компонентам объекта.
С помощью MonoBehaviour можно реализовать множество функций, например:
- управление движением объектов,
- обработка столкновений,
- взаимодействие с пользователем и многое другое.
⛔На практике разработчики часто смешивают слои бизнес-логики, представления и контента в наследнике MonoBehaviour.
Такое смешение является плохой практикой по нескольким причинам:
1. Нарушение принципа единственной ответственности (SRP).
Каждый класс должен быть ответственным только за одну вещь, и смешение различных слоев функциональности в одном классе приводит к необходимости поддерживать несколько ответственностей одновременно.
Это усложняет код и его тестирование, увеличивает количество ошибок.
2. Сложность переноса и интеграции кода.
Когда вся функциональность приложения сосредоточена в наследнике MonoBehaviour, усложняется перенос в другую среду или интеграции с другими проектами.
Разделение функциональности на различные классы и слои позволяет легче переносить код и переиспользовать его.
3. Сложность тестирования.
Когда код приложения находится в одном монолитном классе, усложняется тестирование. Возникает необходимость создания сложных тестовых сценариев для проверки функциональности, что ведет к росту времени и увеличивает вероятность возниконовения ошибок.
4. Нарушение принципа разделения интерфейса и реализации (ISP).
Классы должны предоставлять только те методы и свойства, которые необходимы для работы с ними, а не всю функциональность приложения.
Смешение различных слоев функциональности в одном классе нарушает ISP и делает код менее гибким и модульным.
5. Усложнение поддержки кода.
Смешение различных слоев функциональности в одном классе делает код менее читабельным и понятным для других разработчиков.
Это усложняет поддержку и дальнейшее развитие приложения.
6. Трудности в отслеживании состояния объектов и их взаимодействия друг с другом.
Часто компоненты MonoBehaviour создаются и удаляются динамически в зависимости от событий в игре, подобное использование MonoBehaviour может сделать код менее гибким и менее управляемым.
Продолжение 👇
Приветствую, друзья 👋
Сегодняшняя публикация посвящена роли MonoBehaviour в проекте.
MonoBehaviour - это базовый класс в Unity, который используется для написания скриптов, управляющих поведением игровых объектов.
Он предоставляет множество методов жизненного цикла, которые могут быть переопределены для реализации логики игровых объектов.
Некоторые из основных методов, используемые в жизненном цикле объекта, включают в себя:
- Awake(): вызывается при создании объекта после инициализации всех его компонентов, но до того, как он станет активным в иерархии сцены.
- Start(): вызывается после Awake() и до первого обновления фрейма.
✅ В методе можно инициализировать переменные и компоненты, которые зависят от других объектов в сцене.
- Update(): вызывается каждый кадр и используется для обновления поведения объекта.
✅ В методе можно изменять свойства объекта, перемещать его, обрабатывать ввод и т.д.
- FixedUpdate(): вызывается с фиксированной частотой и используется для обновления физики объекта.
✅ Метод должен использоваться всякий раз, когда вам нужно изменять физические свойства объекта, такие как скорость, позиция, силы и т.д.
- LateUpdate(): вызывается после того, как все объекты обновили свои позиции в текущем кадре.
✅ Метод полезен, когда необходимо обновить объект, исходя из его новой позиции после обновления других объектов.
- OnEnable(): вызывается, когда объект становится активным в иерархии сцены.
- OnDisable(): вызывается, когда объект становится неактивным в иерархии сцены.
- OnDestroy(): вызывается перед уничтожением объекта.
В дополнение к методам, MonoBehaviour имеет свойства, ниже некоторые из них:
- transform - для доступа к компоненту Transform объекта,
- gameObject - для доступа к объекту, на котором находится компонент
- и многие другие, которые позволяют получить доступ к различным свойствам и компонентам объекта.
С помощью MonoBehaviour можно реализовать множество функций, например:
- управление движением объектов,
- обработка столкновений,
- взаимодействие с пользователем и многое другое.
⛔На практике разработчики часто смешивают слои бизнес-логики, представления и контента в наследнике MonoBehaviour.
Такое смешение является плохой практикой по нескольким причинам:
1. Нарушение принципа единственной ответственности (SRP).
Каждый класс должен быть ответственным только за одну вещь, и смешение различных слоев функциональности в одном классе приводит к необходимости поддерживать несколько ответственностей одновременно.
Это усложняет код и его тестирование, увеличивает количество ошибок.
2. Сложность переноса и интеграции кода.
Когда вся функциональность приложения сосредоточена в наследнике MonoBehaviour, усложняется перенос в другую среду или интеграции с другими проектами.
Разделение функциональности на различные классы и слои позволяет легче переносить код и переиспользовать его.
3. Сложность тестирования.
Когда код приложения находится в одном монолитном классе, усложняется тестирование. Возникает необходимость создания сложных тестовых сценариев для проверки функциональности, что ведет к росту времени и увеличивает вероятность возниконовения ошибок.
4. Нарушение принципа разделения интерфейса и реализации (ISP).
Классы должны предоставлять только те методы и свойства, которые необходимы для работы с ними, а не всю функциональность приложения.
Смешение различных слоев функциональности в одном классе нарушает ISP и делает код менее гибким и модульным.
5. Усложнение поддержки кода.
Смешение различных слоев функциональности в одном классе делает код менее читабельным и понятным для других разработчиков.
Это усложняет поддержку и дальнейшее развитие приложения.
6. Трудности в отслеживании состояния объектов и их взаимодействия друг с другом.
Часто компоненты MonoBehaviour создаются и удаляются динамически в зависимости от событий в игре, подобное использование MonoBehaviour может сделать код менее гибким и менее управляемым.
Продолжение 👇