Тёмный

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

Sergey Kazantsev
Подписаться 4,7 тыс.
Просмотров 8 тыс.
50% 1

Игры

Опубликовано:

 

27 июл 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 80   
@fairytale_black_cat
@fairytale_black_cat Месяц назад
Делал на этом паттерне большой проект, на прошлом месте работы. Там по сетевому протоколу приходили команды, а куча объектов в сцене должны были на них реагировать и менять свое состояни. Чтобы не плодить классы с событиями, обработчики регистрировались по имени события, а сами обработчики должны были принимать на входе один объект событие. Внутри события была коллекция параметров в словарике имя/значение. Поддерживался приоритет подписки и возможность рассылки событий глобально и внутри иерархии сцены. Не очень по ООП, но главное что работало и позволяло избегать зависимостей между объектами. Все что объекты в сцене знали, это один статик объект для работы с событиями.
@user-rj1bo4gw9o
@user-rj1bo4gw9o Год назад
Серёж, ты очень недооценённый автор образовательного контента, я как мидл и преподаватель в школе программирования очень рад что нашёл твой канал, сейчас я пересматриваю все твои ролики и наслаждаясь создаю по ним свои тестовые проекты в юнити.
@VyacheslavTamplier
@VyacheslavTamplier Год назад
Сергей, спасибо тебе за очередной вдумчивый и очень информативный урок!
@jasim1798
@jasim1798 Год назад
отличнейший канал, огромный рост - вопрос времени, спасибо за собранную и структурировано поданную инфу, и удачи в развитии канала
@alaleksandr.
@alaleksandr. 9 месяцев назад
Короткое и информативное объяснение с примерами кода
@AzDzelo
@AzDzelo Год назад
Очень круто рассказал! Благодарствую! Продолжай делать новые видео, здорово получается!
@PinkPanteRus
@PinkPanteRus Год назад
Спасибо! Разъяснил от простого к сложному!
@user-de1wo4xd4j
@user-de1wo4xd4j 8 месяцев назад
как же я орнул с базированного выражения (13:20): Нормально делай - нормально будет!
@pashafilenko1567
@pashafilenko1567 Год назад
Спасибо за видео!
@MrG12g
@MrG12g Год назад
очень крутое видео! молодец!
@alexsorokin8373
@alexsorokin8373 Год назад
очень интересный паттерн!) спасибо) Хочется сказать, что частично в практике этот паттерн в упрощенном виде начинает приходить в голову, когда много объектов начинают знать друг о друге...у меня сейчас есть ситуация в своем проекте, где я создал один класс, который собирает части игры - игрока, элементы UI, объекты некоторые и т.д и подписывает их друг на друга, по логике очень похоже на этот паттерн. И да - стоит сказать, что объекты теперь понятия друг о друге не имеют, ошибок меньше, но подписок становится так много, что контролировать это дело становится сложнее и сложнее, а вместо ошибок теперь надо разбирать почему где-то событие сработало два раза или почему некоторые действия идут в странной последовательности) Но кажется в программировании никогда не будет идеально решения)
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
ну да, тут надо балансировать, но если аккуратно писать, соблюдать SRP, может вполне хорошо получиться)
@gwynbleinn
@gwynbleinn 6 месяцев назад
Согласен. Колхозная реализация мне пришла в голову на втором или третьем проекте. Как и некоторые другие паттерны. Но вот качество их исполнения стоит улучшать
@user-cb7dk1ow3h
@user-cb7dk1ow3h 11 месяцев назад
Серега лучший!
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
Вовка привет! Рад тебя видеть, как с гуся вода)
@gwynbleinn
@gwynbleinn 6 месяцев назад
можно Сигнал тоже сделать дженериком. Наплодить от 1 до 5 таких классов с разным количеством полей. Подписчик ивента всё-равно всегда будет знать что он должен получить. Но имхо, падает уровень читаемости, плюс нужно будет изменить способ создания ключей
@sergeykazantsev1655
@sergeykazantsev1655 6 месяцев назад
Можно
@forcesoftheevil9252
@forcesoftheevil9252 Год назад
Спасибо за видео! Мне не нравится Event bus в двух версиях, так что я откажусь от подписок :D Но если серьёзно, почему бы шину событий разбивать на классы? Какие-нибудь PlayerEventBus, GameEventBus и прочее
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
Я бы лучше ивенты тогда разбивал. Типа Eventbus.PlayerEvents.PlayerDamaged += DoSomething() А насчёт подписок, она есть во всех трех версиях, просто в первой она спрятана)
@olza4967
@olza4967 Год назад
Отличный контент! Есть где нибудь реальные проекты посмотреть где реализованы различные паттерны?Вроде как говорят что сами Юнити не делают этого,что бы было как можно легче новичкам понимать. Спасибо.
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
Ну я вот этот скроллер на гитхаб выложил, можете посмотреть) Как раз рубрика "паттерны на практике" для этого и делалась) Юнити какие-то демки выкладывают, но я честно говоря не смотрел Хотя я у них на сайте нашёл бесплатную классную книжку, где сами Юнити рассказывают про паттерны, приводят примеры и тд resources.unity.com/games/level-up-your-code-with-game-programming-patterns
@Diyozen
@Diyozen Год назад
Хочется всё-таки сделать этот EventBus сервисом и зарегистрировать в сервис локаторе. А то чего он такой одинокий (хех) синглтон.
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
Поддерживаю! В своей игре, на гитхабе я в последней итерации именно так и сделал) я убрал статическую часть) в зенжекте он тоже не синглтоновый, кстати)
@styleoflazyphotography
@styleoflazyphotography 4 месяца назад
Привет! Объясните, пожалуйста, неофиту что происходит в КолхозникЭдишн при объявлении класса? Вижу приватный конструктор, вижу геттер статический, но не понимаю для чего оно нужно. Чтобы видеть всех подписчиков? Почему мы не можем сделать статическими все Action и весь класс в целом?
@sergeykazantsev1655
@sergeykazantsev1655 4 месяца назад
Шина колхозника основана на паттерне(если его можно так назвать) синглтон, на 4:33 можете справа посмотреть как я триггерю ивенты и как я на них подписываюсь. Статический геттер нужен чтобы мы к классу сигнальной шины могли получить доступ из любого места.
@gamedev_renaissance1075
@gamedev_renaissance1075 11 месяцев назад
А вот к object это дело приводить обязательно? Выстрелить же может в Invoke, когда другой тип постараемся вызвать. (Из дженерик гитхаба код слишком перегруженный)
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
Насчёт приведения к object, скажу честно, я не нашёл другого решения. Тут же к object приводится для того, чтобы сохранить все Action в одном Dictionary, так, чтобы они хранились вне зависимости от того какие у них входные параметры. Если есть более хорошее решение - я с удовольствием с ним ознакомлюсь и переделаю код, потому что каст к object мне тоже не нравится. Насчёт выстрела Invoke - это можно пофиксить если сделать какой-нибудь пустой интерфейс ISignal и всем сигналам от него отнаследоваться. Впрочем, например в том же зенжекте никого это не парит и вы можете Invok-ать в тело что угодно.
@gamedev_renaissance1075
@gamedev_renaissance1075 11 месяцев назад
@@sergeykazantsev1655 Да, просто я перебираю ваши "наивные" реализации и модифицирую под себя (респект за материал). Из-за замечательной контрвариантности Action сделать CallbackWithPriority у меня не вышло, а вот к object кастить - пожалуйста. Поэтому решил спросить.
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
Во-во и я другого решения не нашёл)
@user-sn6xn1zx1v
@user-sn6xn1zx1v 5 месяцев назад
Внимание, вопрос! :) Попытался внедрить шину сыбытий в своём проекте и столкнулся с проблемой. Как у тебя в проекте объекты с монобехами успеваются подписываться на сигналы. Поясню. Если учитываем, что код срабатывает последовательно, то может возникнуть ситуация, когда в одном скрипте создается сигнал, а во втором Start еще не отработал и подписка на сигнал не состоялась. Как быть? Или я что-то упустил? Пока я выкручиваюсь тем, что привязывают все нужные ситсемы друг к другу через Сервис Локатор и инициализирую в одном котроллере. То есть, использую Init вместо Start.
@sergeykazantsev1655
@sergeykazantsev1655 5 месяцев назад
В завершающем видео по вертикальному скроллеру в конце(11:40) как раз с этим сталкивался. Вижу несколько вариантов: 1. Просто следовать правилам. Создать сигнальную шину в Awake, подписаться всеми классами в Start, стрелять сигналам строго после Start. Что собственно и осталось в этом проекте. Можно это рихтовать Script Execution Order или если есть Entry Point 2. Написать более сложную реализацию сигнальной шины с очередью, чтобы при подписке система проверяла не стрелял ли кто сигналами вот только что. Я такого решения не использовал но уверен что он есть.
@chillcompany1028
@chillcompany1028 Год назад
Можно уточнить один момент? Вы всегда говорите "классы слушают", "классы подписываются" и т.д? Но ведь это делают не классы а непосредственно объекты. Это такое речевое допущение или я совсем запутался и понимаю всё не правильно?
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
Скорее моя оговорка. Объекты слушают и подписываются.
@esteticachannel4604
@esteticachannel4604 Год назад
класс это и есть объект если что, это же ооп
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
Класс - тип, описывающий устройство объектов. Объект - это экземпляр класса. Класс можно сравнить с чертежом, по которому создаются объекты
@esteticachannel4604
@esteticachannel4604 Год назад
вывод прост, используйте ecs
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
С моей точки зрения ecs не является универсальным решением на все случаи жизни) с ним много своих проблем)
@esteticachannel4604
@esteticachannel4604 Год назад
@@sergeykazantsev1655 к примеру каких проблем?)
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
С моей точки зрения ECS предназначен только для игр/проектов где крайне важна оптимизация, скорость и производительность. Например игры с огромным количеством игровых сущностей(под миллион различных активных объектов на сцене), сетевые шутаны или те же MMORPG игры. Там DOTS даёт существенный бонус по производительности. Насчёт проблем - вот что знаю: сериализация данных и ресурсов, подгрузка бандлов и ресурсов. Недавно как раз смотрел конференцию игроделов где обсуждался ECS и каждая вторая фирма писала свой, а не использовала готовые решения так как сталкивалась с той самой сериализацией и подгрузкой ресурсов. А также излишне массивный код и непонятная парадигма на первое время для большинства начинающих разработчиков. Тот же match3 или вертикальный скроллер можно написать без ECS - пользователь разницы не увидит, но код будет написан быстрее и условному джуну/мидлу поддерживать его будет легче.
@esteticachannel4604
@esteticachannel4604 11 месяцев назад
@@sergeykazantsev1655 странное мнение, оптимизация это лишь сайд эффект ецс, ецс же является первоначально архитектурным паттерном) дабы избавится от чрезмерной связности в коде и обеспечить гибкость, матч3 и скроллеры это что-то из разряда ГК где вообще архитектуру не соблюдают в принципе. DOTS это вообще не ецс, он там пришит сбоку. Погугли что значит ецс для начала и для чего он предназначен) подрузка бандлов и ресурсов, а также сериализация обеспечивается сервисами как раз таки со стороны ооп, это не проблема, гибридный подход в юнити пока не избежен, но всяко лучше чем потом спаггети легаси разгребать, а для подключения модуля нового какого 100500 зависимостей пробрасывать и думать какой бы паттерн тут бы всё зарешал
@sergeykazantsev1655
@sergeykazantsev1655 11 месяцев назад
Могу я узнать на чём основаны такие заявления? Ты разрабатываешь игры на ECS? Если да, то для какого жанра, какой проект, как долго, какой фреймворк используешь(LeoEcs, Entitas или что-то другое) А если фреймворк свой - на чём он основан? Можешь ли ты скинуть статьи с хабра или откуда-то ещё про ECS которые конкретно ты считаешь базой и основой основ?
@user-de1wo4xd4j
@user-de1wo4xd4j 8 месяцев назад
В конце не совсем понял, а в чем принципиально лучше данные с игрока получать, а не с события?
@sergeykazantsev1655
@sergeykazantsev1655 8 месяцев назад
А я что-то такое разве говорил? Оо
@user-de1wo4xd4j
@user-de1wo4xd4j 8 месяцев назад
@@sergeykazantsev1655 14:31 :)
@sergeykazantsev1655
@sergeykazantsev1655 8 месяцев назад
А, так в том конкретном случае нам надо было показать текущий и предыдущий счёт после события levelFinished. Можно конечно ивент с этими данными отправлять, но какой смысл если это нужно только в одном месте)
@zuzuBoba
@zuzuBoba Год назад
что значит выдаст enr? 1:21
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
NRE, Null reference exception
@zuzuBoba
@zuzuBoba Год назад
@@sergeykazantsev1655 спасибо!
@a.danilenko
@a.danilenko Год назад
1. В проекте используется ServiceLocator - это антипатерн. ServiceLocator это вариант DI с кучей проблем и недостатков. Рекомендую к изучению книгу Марка Симана "Внедрение зависимостей в .Net". Эта книга познакомит с лучшими практиками внедрения зависимостей. Может быть в каких-то простых проектах ServiceLocator допустим, но не в профессиональной командной разработке. Даже учитывая эти оговорки, что ServiceLocator это некая альтернатива DI, я не могу не указать на то, что в совокупности недостатков у ServiceLocator гораздо больше, чем достоинств. ServiceLocator это не альтернатива DI это и есть DI, но в одной из самых плохих реализаций. 2. EventBus это слишком "жирный" класс с точки зрения предоставляемого API - любой объект может подписаться на любое событие и вызвать любое событие. Это антипатерн. Такие решения тянут за собой много проблем, которые сразу видны опытному профессиональному разработчику. Интерфейс (в широком смысле слова) или API класса должен быть строго ограничен потребностями класса. Тут в одном абзаце всю проблематику не изложить. Если совсем кратко: нарушение принципов ISP и DI. 3. Подписки и отписки от событий неконтролируемы, соответственно могут происходить на любом этапе работы приложения. Отсюда следует опасность потенциальной проблемы, т.к. операции подписки и отписки не являются потокобезопасными. Соответственно, если требуется потокобезопасность при подписке и отписке, то следует использовать синхронизацию при доступе разделяемым ресурсам. 3. Часто встречаются мелкие недочёты в качестве кода: пренебрежение ключевым словом readonly, использование публичных полей вместо свойств и т.д. В общем, есть куда расти в качестве кода.
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
По поводу сервис локатора. На моём канале есть отдельное видео про сервис локатор где я говорю, что этот паттерн имеет свои недостатки и 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 Год назад
@@sergeykazantsev1655 Когда ты говоришь, что ServiceLocator это альтернатива DI, то ты имеешь виду, видимо, DI-контейнер. Вот я о чём. DI и DI-конейнер это не одно и тоже. Не следует их путать. ServiceLocator это тоже DI. Это тоже способ внедрения зависимостей. DI-контейнер и ServiceLocator это инструмент (вариант исполнения) используемый при DI. Крестовая отвертка не может быть альтернативой отвертке, быть лучше или хуже отвертки, поэтому что это тоже отвертка - вот такая аналогия, для понимания ситуации.
@sergeykazantsev1655
@sergeykazantsev1655 Год назад
Под DI в этом случае я говорю в целом использование его в архитектуре, без DI-контейнера реализовать DI в проекте наверное можно, но зачем? Тот же Мартин Фаулер кстати в статье про SL сравнивает его именно с DI , но я думаю он тоже имеет DI как общую парадигму martinfowler.com/articles/injection.html
@user-il2bt2kp7g
@user-il2bt2kp7g 21 час назад
Почему ты Мультикаст-делегаты называешь Событиями? Это 2 совершенно разные сущности которые работают по разному. Сначала не понимал что происходит пока не заметил это. События это обертка над делегатом, также как и свойство - обертка над полем. Событие можно вызвать только в классе в котором оно определено. Мультикаст делегаты (то что ты на видео зовешь событиями) нарушают событийную модель, их можно вызвать откуда угодно, даже в обработчике, тогда будет вечный цикл. На них как и на события можно подписать несколько обработчиков, но вызваться будет именно последний, остальные игнорироваться - это еще 1 нюанс про который нужно помнить. Я не говорю что это плохо, всему есть применение, просто это разные вещи и их не стоит путать.
Далее
Каха заблудился в горах
00:57
Просмотров 1,3 млн
Паттерн Observer, С#, unity,  gamedev,
15:04
Просмотров 6 тыс.