Event Bus, Паттерны на практике, Unity, C#

Поділитися
Вставка
  • Опубліковано 17 лис 2024

КОМЕНТАРІ • 85

  • @fairytale_black_cat
    @fairytale_black_cat 5 місяців тому +4

    Делал на этом паттерне большой проект, на прошлом месте работы. Там по сетевому протоколу приходили команды, а куча объектов в сцене должны были на них реагировать и менять свое состояни. Чтобы не плодить классы с событиями, обработчики регистрировались по имени события, а сами обработчики должны были принимать на входе один объект событие. Внутри события была коллекция параметров в словарике имя/значение. Поддерживался приоритет подписки и возможность рассылки событий глобально и внутри иерархии сцены. Не очень по ООП, но главное что работало и позволяло избегать зависимостей между объектами. Все что объекты в сцене знали, это один статик объект для работы с событиями.

  • @ВасечкаЛазарев
    @ВасечкаЛазарев Рік тому +8

    Серёж, ты очень недооценённый автор образовательного контента, я как мидл и преподаватель в школе программирования очень рад что нашёл твой канал, сейчас я пересматриваю все твои ролики и наслаждаясь создаю по ним свои тестовые проекты в юнити.

  • @Fenneig
    @Fenneig Рік тому

    как же я орнул с базированного выражения (13:20): Нормально делай - нормально будет!

  • @USSR-Lenin-Stalin-Forever
    @USSR-Lenin-Stalin-Forever Місяць тому

    Вот еще годное видео на тему эвентов "ULTIMATE Event System for Unity Beginners". Это обычный наблюдатель но через скриптовые объекты, каждый эвент это скриптовый объект, и если хочешь подписаться на событие ты просто в свой прослушиватель скриптовый объект суешь и все. Т.е. тот кто вызывает событие ничего не знает об прослушивателя так и слушатели ничего не знают об излучателе. А так как это скриптовые объекты к ним легко получить доступ из любой программы

  • @alaleksandr.
    @alaleksandr. Рік тому

    Короткое и информативное объяснение с примерами кода

  • @VyacheslavTamplier
    @VyacheslavTamplier Рік тому +2

    Сергей, спасибо тебе за очередной вдумчивый и очень информативный урок!

  • @zhasim
    @zhasim Рік тому

    отличнейший канал, огромный рост - вопрос времени, спасибо за собранную и структурировано поданную инфу, и удачи в развитии канала

  • @AzDzelo
    @AzDzelo Рік тому

    Очень круто рассказал! Благодарствую!
    Продолжай делать новые видео, здорово получается!

  • @PinkPanteRus
    @PinkPanteRus Рік тому

    Спасибо! Разъяснил от простого к сложному!

  • @ВладимирГоловчанский-й6м

    Серега лучший!

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Вовка привет! Рад тебя видеть, как с гуся вода)

  • @gwynbleinn
    @gwynbleinn 10 місяців тому

    можно Сигнал тоже сделать дженериком. Наплодить от 1 до 5 таких классов с разным количеством полей. Подписчик ивента всё-равно всегда будет знать что он должен получить.
    Но имхо, падает уровень читаемости, плюс нужно будет изменить способ создания ключей

  • @alexsorokin8373
    @alexsorokin8373 Рік тому

    очень интересный паттерн!) спасибо)
    Хочется сказать, что частично в практике этот паттерн в упрощенном виде начинает приходить в голову, когда много объектов начинают знать друг о друге...у меня сейчас есть ситуация в своем проекте, где я создал один класс, который собирает части игры - игрока, элементы UI, объекты некоторые и т.д и подписывает их друг на друга, по логике очень похоже на этот паттерн. И да - стоит сказать, что объекты теперь понятия друг о друге не имеют, ошибок меньше, но подписок становится так много, что контролировать это дело становится сложнее и сложнее, а вместо ошибок теперь надо разбирать почему где-то событие сработало два раза или почему некоторые действия идут в странной последовательности)
    Но кажется в программировании никогда не будет идеально решения)

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +1

      ну да, тут надо балансировать, но если аккуратно писать, соблюдать SRP, может вполне хорошо получиться)

    • @gwynbleinn
      @gwynbleinn 10 місяців тому

      Согласен. Колхозная реализация мне пришла в голову на втором или третьем проекте. Как и некоторые другие паттерны.
      Но вот качество их исполнения стоит улучшать

  • @Diyozen
    @Diyozen Рік тому

    Хочется всё-таки сделать этот EventBus сервисом и зарегистрировать в сервис локаторе. А то чего он такой одинокий (хех) синглтон.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Поддерживаю! В своей игре, на гитхабе я в последней итерации именно так и сделал) я убрал статическую часть) в зенжекте он тоже не синглтоновый, кстати)

  • @pashafilenko1567
    @pashafilenko1567 Рік тому

    Спасибо за видео!

  • @MrG12g
    @MrG12g Рік тому

    очень крутое видео! молодец!

  • @styleoflazyphotography
    @styleoflazyphotography 8 місяців тому

    Привет! Объясните, пожалуйста, неофиту что происходит в КолхозникЭдишн при объявлении класса? Вижу приватный конструктор, вижу геттер статический, но не понимаю для чего оно нужно. Чтобы видеть всех подписчиков? Почему мы не можем сделать статическими все Action и весь класс в целом?

    • @sergeykazantsev1655
      @sergeykazantsev1655  8 місяців тому

      Шина колхозника основана на паттерне(если его можно так назвать) синглтон, на 4:33 можете справа посмотреть как я триггерю ивенты и как я на них подписываюсь.
      Статический геттер нужен чтобы мы к классу сигнальной шины могли получить доступ из любого места.

  • @gamedev_renaissance1075
    @gamedev_renaissance1075 Рік тому

    А вот к object это дело приводить обязательно? Выстрелить же может в Invoke, когда другой тип постараемся вызвать.
    (Из дженерик гитхаба код слишком перегруженный)

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Насчёт приведения к object, скажу честно, я не нашёл другого решения. Тут же к object приводится для того, чтобы сохранить все Action в одном Dictionary, так, чтобы они хранились вне зависимости от того какие у них входные параметры. Если есть более хорошее решение - я с удовольствием с ним ознакомлюсь и переделаю код, потому что каст к object мне тоже не нравится.
      Насчёт выстрела Invoke - это можно пофиксить если сделать какой-нибудь пустой интерфейс ISignal и всем сигналам от него отнаследоваться. Впрочем, например в том же зенжекте никого это не парит и вы можете Invok-ать в тело что угодно.

    • @gamedev_renaissance1075
      @gamedev_renaissance1075 Рік тому

      @@sergeykazantsev1655 Да, просто я перебираю ваши "наивные" реализации и модифицирую под себя (респект за материал). Из-за замечательной контрвариантности Action сделать CallbackWithPriority у меня не вышло, а вот к object кастить - пожалуйста. Поэтому решил спросить.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Во-во и я другого решения не нашёл)

  • @olza4967
    @olza4967 Рік тому

    Отличный контент!
    Есть где нибудь реальные проекты посмотреть где реализованы различные паттерны?Вроде как говорят что сами Юнити не делают этого,что бы было как можно легче новичкам понимать.
    Спасибо.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Ну я вот этот скроллер на гитхаб выложил, можете посмотреть) Как раз рубрика "паттерны на практике" для этого и делалась) Юнити какие-то демки выкладывают, но я честно говоря не смотрел
      Хотя я у них на сайте нашёл бесплатную классную книжку, где сами Юнити рассказывают про паттерны, приводят примеры и тд resources.unity.com/games/level-up-your-code-with-game-programming-patterns

  • @forcesoftheevil9252
    @forcesoftheevil9252 Рік тому

    Спасибо за видео! Мне не нравится Event bus в двух версиях, так что я откажусь от подписок :D
    Но если серьёзно, почему бы шину событий разбивать на классы? Какие-нибудь PlayerEventBus, GameEventBus и прочее

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Я бы лучше ивенты тогда разбивал. Типа Eventbus.PlayerEvents.PlayerDamaged += DoSomething()
      А насчёт подписок, она есть во всех трех версиях, просто в первой она спрятана)

  • @esteticachannel4604
    @esteticachannel4604 Рік тому +1

    вывод прост, используйте ecs

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +1

      С моей точки зрения ecs не является универсальным решением на все случаи жизни) с ним много своих проблем)

    • @esteticachannel4604
      @esteticachannel4604 Рік тому

      @@sergeykazantsev1655 к примеру каких проблем?)

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +2

      С моей точки зрения ECS предназначен только для игр/проектов где крайне важна оптимизация, скорость и производительность. Например игры с огромным количеством игровых сущностей(под миллион различных активных объектов на сцене), сетевые шутаны или те же MMORPG игры. Там DOTS даёт существенный бонус по производительности.
      Насчёт проблем - вот что знаю: сериализация данных и ресурсов, подгрузка бандлов и ресурсов. Недавно как раз смотрел конференцию игроделов где обсуждался ECS и каждая вторая фирма писала свой, а не использовала готовые решения так как сталкивалась с той самой сериализацией и подгрузкой ресурсов.
      А также излишне массивный код и непонятная парадигма на первое время для большинства начинающих разработчиков. Тот же match3 или вертикальный скроллер можно написать без ECS - пользователь разницы не увидит, но код будет написан быстрее и условному джуну/мидлу поддерживать его будет легче.

    • @esteticachannel4604
      @esteticachannel4604 Рік тому

      @@sergeykazantsev1655 странное мнение, оптимизация это лишь сайд эффект ецс, ецс же является первоначально архитектурным паттерном) дабы избавится от чрезмерной связности в коде и обеспечить гибкость, матч3 и скроллеры это что-то из разряда ГК где вообще архитектуру не соблюдают в принципе. DOTS это вообще не ецс, он там пришит сбоку. Погугли что значит ецс для начала и для чего он предназначен) подрузка бандлов и ресурсов, а также сериализация обеспечивается сервисами как раз таки со стороны ооп, это не проблема, гибридный подход в юнити пока не избежен, но всяко лучше чем потом спаггети легаси разгребать, а для подключения модуля нового какого 100500 зависимостей пробрасывать и думать какой бы паттерн тут бы всё зарешал

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +1

      Могу я узнать на чём основаны такие заявления?
      Ты разрабатываешь игры на ECS? Если да, то для какого жанра, какой проект, как долго, какой фреймворк используешь(LeoEcs, Entitas или что-то другое)
      А если фреймворк свой - на чём он основан? Можешь ли ты скинуть статьи с хабра или откуда-то ещё про ECS которые конкретно ты считаешь базой и основой основ?

  • @a.danilenko
    @a.danilenko Рік тому +2

    1. В проекте используется ServiceLocator - это антипатерн. ServiceLocator это вариант DI с кучей проблем и недостатков. Рекомендую к изучению книгу Марка Симана "Внедрение зависимостей в .Net". Эта книга познакомит с лучшими практиками внедрения зависимостей. Может быть в каких-то простых проектах ServiceLocator допустим, но не в профессиональной командной разработке. Даже учитывая эти оговорки, что ServiceLocator это некая альтернатива DI, я не могу не указать на то, что в совокупности недостатков у ServiceLocator гораздо больше, чем достоинств. ServiceLocator это не альтернатива DI это и есть DI, но в одной из самых плохих реализаций.
    2. EventBus это слишком "жирный" класс с точки зрения предоставляемого API - любой объект может подписаться на любое событие и вызвать любое событие. Это антипатерн. Такие решения тянут за собой много проблем, которые сразу видны опытному профессиональному разработчику. Интерфейс (в широком смысле слова) или API класса должен быть строго ограничен потребностями класса. Тут в одном абзаце всю проблематику не изложить. Если совсем кратко: нарушение принципов ISP и DI.
    3. Подписки и отписки от событий неконтролируемы, соответственно могут происходить на любом этапе работы приложения. Отсюда следует опасность потенциальной проблемы, т.к. операции подписки и отписки не являются потокобезопасными. Соответственно, если требуется потокобезопасность при подписке и отписке, то следует использовать синхронизацию при доступе разделяемым ресурсам.
    3. Часто встречаются мелкие недочёты в качестве кода: пренебрежение ключевым словом readonly, использование публичных полей вместо свойств и т.д.
    В общем, есть куда расти в качестве кода.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +2

      По поводу сервис локатора. На моём канале есть отдельное видео про сервис локатор где я говорю, что этот паттерн имеет свои недостатки и DI таки получше будет. В том же видео я говорю что SL может стать антипаттерном. А также есть подведение итогов, на котором я также говорю что сервис локатор многими дядями считается антипаттерном и у него есть проблема вседоступности. Критиковать сервис локатор в видео про сигнальную шину - крайне странно.
      Насчёт того что EventBus слишком "жирный" класс соглашусь, но как я понимаю тот же Zenject такая реализация вполне устраивает. Я согласен что на больших проектах будут проблемы, но без такой логики EventBus это не EventBus. Мне кажется что вседоступность ивентов это не такая большая беда, что как вседоступность классов как в том же SL. Но опять же говорю что да, наверное на больших проектах может вылезти
      Тайминги подписки и отписки и потокобезопасность да, в финальном видео по данной игре - я опять же говорил о том, что в некоторых случаях приходится это контролировать и с этим возникает головная боль.
      Очень интересно - где у меня публичные поля вместо свойств используются, покажите пожалуйста, мог реально промахнуться
      А расти можно бесконечно - я никогда не заявлял что в коде я преисполнился, что я Бог кода и тд

    • @a.danilenko
      @a.danilenko Рік тому

      @@sergeykazantsev1655
      Я не смотрел видео про ServiceLocator.
      ServiceLocator это и есть DI. Наверное, ты путаешь DI с DI-контейнером. DI и DI-контейнер это разные понятия.
      Публичные поля: классы CallbackWithPriority, ScoreChangedSignal, AddScoreSignal, SelectShipSignal и др.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Я знаю разницу между сервис локатором и di контейнером, и в том же видео я про это говорил
      За публичные поля спасибо

    • @a.danilenko
      @a.danilenko Рік тому +1

      @@sergeykazantsev1655
      Когда ты говоришь, что ServiceLocator это альтернатива DI, то ты имеешь виду, видимо, DI-контейнер.
      Вот я о чём. DI и DI-конейнер это не одно и тоже. Не следует их путать.
      ServiceLocator это тоже DI. Это тоже способ внедрения зависимостей. DI-контейнер и ServiceLocator это инструмент (вариант исполнения) используемый при DI.
      Крестовая отвертка не может быть альтернативой отвертке, быть лучше или хуже отвертки, поэтому что это тоже отвертка - вот такая аналогия, для понимания ситуации.

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      Под DI в этом случае я говорю в целом использование его в архитектуре, без DI-контейнера реализовать DI в проекте наверное можно, но зачем? Тот же Мартин Фаулер кстати в статье про SL сравнивает его именно с DI , но я думаю он тоже имеет DI как общую парадигму
      martinfowler.com/articles/injection.html

  • @Fenneig
    @Fenneig Рік тому

    В конце не совсем понял, а в чем принципиально лучше данные с игрока получать, а не с события?

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

      А я что-то такое разве говорил? Оо

    • @Fenneig
      @Fenneig Рік тому

      @@sergeykazantsev1655 14:31 :)

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +2

      А, так в том конкретном случае нам надо было показать текущий и предыдущий счёт после события levelFinished. Можно конечно ивент с этими данными отправлять, но какой смысл если это нужно только в одном месте)

  • @РоманВоронин-н7и
    @РоманВоронин-н7и 9 місяців тому

    Внимание, вопрос! :) Попытался внедрить шину сыбытий в своём проекте и столкнулся с проблемой. Как у тебя в проекте объекты с монобехами успеваются подписываться на сигналы. Поясню. Если учитываем, что код срабатывает последовательно, то может возникнуть ситуация, когда в одном скрипте создается сигнал, а во втором Start еще не отработал и подписка на сигнал не состоялась. Как быть?
    Или я что-то упустил? Пока я выкручиваюсь тем, что привязывают все нужные ситсемы друг к другу через Сервис Локатор и инициализирую в одном котроллере. То есть, использую Init вместо Start.

    • @sergeykazantsev1655
      @sergeykazantsev1655  9 місяців тому +1

      В завершающем видео по вертикальному скроллеру в конце(11:40) как раз с этим сталкивался.
      Вижу несколько вариантов:
      1. Просто следовать правилам. Создать сигнальную шину в Awake, подписаться всеми классами в Start, стрелять сигналам строго после Start. Что собственно и осталось в этом проекте. Можно это рихтовать Script Execution Order или если есть Entry Point
      2. Написать более сложную реализацию сигнальной шины с очередью, чтобы при подписке система проверяла не стрелял ли кто сигналами вот только что. Я такого решения не использовал но уверен что он есть.

  • @liubomyrprotsyk7225
    @liubomyrprotsyk7225 Місяць тому

    Якісний контент

  • @chillcompany1028
    @chillcompany1028 Рік тому +1

    Можно уточнить один момент? Вы всегда говорите "классы слушают", "классы подписываются" и т.д? Но ведь это делают не классы а непосредственно объекты. Это такое речевое допущение или я совсем запутался и понимаю всё не правильно?

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому +1

      Скорее моя оговорка. Объекты слушают и подписываются.

    • @esteticachannel4604
      @esteticachannel4604 Рік тому

      класс это и есть объект если что, это же ооп

    • @sergeykazantsev1655
      @sergeykazantsev1655  Рік тому

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

  • @zuzuBoba
    @zuzuBoba Рік тому

    что значит выдаст enr? 1:21

  • @ГеннадийШушпанов-д1ч
    @ГеннадийШушпанов-д1ч 2 місяці тому

    По поводу недостатков. Здесь мне видится недопонимание назначения шины событий. Вы же начали с того, что поставщики и потребители событий не зависят друг от друга. Если это так, то никакого контроля за событиями не нужно. Вот на шине его и нет. Поставщик создал событие и забыл про него. Подписчик получил событие и обработал его и ему не важно, сколько еще есть подписчиков. Если же Вам контроль понадобился, например порядок обработки, то это означает, что между объектами, использующими шину есть зависимость. Только она здесь неявная.

    • @sergeykazantsev1655
      @sergeykazantsev1655  2 місяці тому

      На моём опыте были проблемы с порядком уведомления подписчиков, они же race-conditions. Когда две системы подписаны на одно и то-же событие, но необходимо чтобы первая система среагировала раньше второй.
      В целом, хорошо написанные сигнальные шины позволяют это сделать.

    • @ГеннадийШушпанов-д1ч
      @ГеннадийШушпанов-д1ч 2 місяці тому

      @@sergeykazantsev1655 ну вот, Вы опять протаскиваете зависимость. На шине все равны, там нет первых, вторых или десятых. А "хорошо написанные сигнальные шины" -- это уже расширение, а не наследование :)

  • @Chepuha-i5g
    @Chepuha-i5g 3 місяці тому

    Почему ты Мультикаст-делегаты называешь Событиями? Это 2 совершенно разные сущности которые работают по разному.
    Сначала не понимал что происходит пока не заметил это. События это обертка над делегатом, также как и свойство - обертка над полем. Событие можно вызвать только в классе в котором оно определено.
    Мультикаст делегаты (то что ты на видео зовешь событиями) нарушают событийную модель, их можно вызвать откуда угодно, даже в обработчике, тогда будет вечный цикл. На них как и на события можно подписать несколько обработчиков, но вызваться будет именно последний, остальные игнорироваться - это еще 1 нюанс про который нужно помнить.
    Я не говорю что это плохо, всему есть применение, просто это разные вещи и их не стоит путать.