- PVSM.RU - https://www.pvsm.ru -
Ссылка на проект [1]
Как известно всем, более или менее знакомых с платформой Unity, каждый игровой объект GameObject состоит из компонентов (встроенных или пользовательских, который обычно называют «скрипт»). Компоненты наследуются от базового класса MonoBehavior.

И обычно, ну или часто, для связывания компонентов осуществляется прямая связь.

Т.е. в одном компоненте, для получения данных другого компонента, мы получаем последний с помощью метода GetComponent<…>(), например так:

В данном примере в переменную someComponent будет помещена ссылка на компонент типа SomeComponent.
При таком «сильно связанном» подходе, особенно, при наличии большого количества компонентов, довольно просто запутаться и поддерживать целостность такой связи. К примеру, если изменится название свойства или метода в одном компоненте, то придется исправлять во всех компонентах, использующих этот. И это гемор.
Под катом много картинок
Создадим пустой проект для воспроизведения обычной ситуации, когда у нас есть некие компоненты и каждый из них ссылается друг на друга, для получения данных либо для управления.

Я добавил два скрипта FirstComponent и SecondComponent, которые будут использованы как компоненты в игровом объекте:

Теперь я определю простую структуру для каждого из компонентов, необходимую для экспериментов.


Теперь представим ситуацию, при которой нам бы понадобилось, получить значения полей state1 из компонента FirstComponent и вызвать его метод ChangeState(…) в компоненте SecondComponent. Для этого нужно получить ссылку на компонент и запросить нужные данные в компоненте SecondComponent:

После того как мы запустим игру в консоли будет видно, что мы получили данные из FisrtComponent из SecondComponent и изменили состояние первого

Теперь точно также мы можем получить данные и в обратном направлении из компонента FirstComponent получить данные компонента SecondComponent.

После запуска игры также будет видно что данные получаем и можем управлять компонентом SecondComponent из FirstComponent.

Это был довольно простой пример, и чтобы понять что за проблему я хочу описать, понадобилось бы сильно усложнить структуру и связи всех компонентов, но смысл понятен. Сейчас связь между компонентами выглядит следующим образом:


Расширять даже один игровой объект новыми компонентами, если им нужно будет взаимодействовать с уже существующими будет довольно рутинно. А особенно, если, например, название поля state1 в компоненте FirstComponent изменится, например, на state_1 и придется во всех компонентах менять название, где это используется. Или когда полей у компонента становится слишком много, то тогда довольно сложно становится по ним ориентироваться.
Теперь представим, что нам не нужно было бы получать ссылку на каждый интересующий компонент и получения у него данных, а был бы некий объект, который содержит состояния и данные всех компонентов в игровом объекте. На схеме это выглядело бы следующим образом:

Общее состояние или Объект общего состояния (SharedState) это тоже компонент, который будет играть роль служебного компонента и хранить состояния всех компонентов игрового объекта.
Я создам новый компонент и назову его SharedState:

И определю код этого универсального компонента. Он будет хранить закрытый словарь и индексатор для более удобной работы со словарем компонента, также это будет инкапсуляция и напрямую со словарем из других компонент работать не получится.

Теперь этот компонент нужно разместить на игровом объекте, чтобы остальные компоненты могли получить к нему доступ:

Далее нужно внести некоторые правки в компоненты FirstComponent и SecondComponent, чтобы они использовали компонент SharedState для хранения своего состояния или данных:


Как видно по коду компонентов, мы больше не храним поля, вместо этого мы используем общее состояние и имеем доступ к его данным по ключу «state1» или «counter». Теперь эти данные не привязаны ни к одному компоненту, и если появится третий компонент, то получив доступ к SharedState он сможет иметь доступ ко всем этим данным.
Теперь для демонстрации работы этой схемы, нужно изменить методы Update в обоих компонентах. В FisrtComponent:

И в компоненте SecondComponent:

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

Теперь когда известно как это работает, можно вывести основную инфраструктуру для доступа к общему состоянию в базовый класс, чтобы не делать это все в каждом компоненте отдельно:

И сделаю его абстрактным, чтобы случайно не создать его экземпляр… А так же желательно добавить атрибут, указывающий, что данный базовый компонент требует наличия компонента SharedState:

Теперь нужно изменить компоненты FirstComponent и SecondComponent, чтобы они наследовались от SharedStateComponent и убрать все лишнее:


Ок. А как насчет вызова методов? Это предлагается делать так же не напрямую, а через паттерн Publisher-Subscriber. Упрощенный.
Для реализации этого нужно добавить еще один общий компонент, по аналогии с тем, который содержит данные, за тем исключением что этот будет содержать только подписки и будет называться SharedEvents:

Принцип следующий. Компонент, который Хочет вызвать какой то метод у другого компонента, будет это делать не напрямую, а вызовом события, так же по названию, как мы получаем данные из общего состояния.
Каждый компонент, подписывается на некоторые события, которые он готов отслеживать. И если он отлавливает это событие он выполняет обработчик, который определен в самом компоненте.
Создадим компонент SharedEvents:

И определим структуру, необходимую для управления подписками и публикациями

Для обмена данными между подписками-публикациями определен базовый класс, конкретный будет определяться для автора каждого события самостоятельно, далее будут определены несколько для примера:

Теперь нужно добавить новый компонент в игровой объект:

и немного расширить базовый класс SharedStateComponent и добавить требование того чтобы объект содержал и SharedEvents

Так же как и объект общего состояния объект общих подписок нужно получить из игрового объекта:


Теперь определим подписку на событие, которое обработаем в FisrtComponent и класс для передачи данных через этот тип события, а также изменим SecondComponent чтобы событие по этой подписке было опубликовано:


Теперь мы подписались на любое событие в названием «writesomedata» в компоненте FirstComponent и просто выводим сообщение в консоль при его возникновении. А возникает оно в данном примере путем вызова публикации события с именем «writesomedata» в компоненте SecondComponent и передачи некоторой информации, которая может быть использована в компоненте, который отлавливает события по такому названию.
После запуска игры через 5 секунд мы увидим результат обработки события в FirstComponent:

Теперь если нужно расширить компоненты данного игрового объекта, которые тоже будут использовать общее состояние и общие события нужно добавить класс и просто унаследоваться от SharedStateComponent:

Автор: Денис Козлов
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/unity3d/307453
Ссылки в тексте:
[1] Ссылка на проект: https://github.com/deniskozlov/yunka_sharedstate_exaple
[2] Источник: https://habr.com/ru/post/438554/?utm_source=habrahabr&utm_medium=rss&utm_campaign=438554
Нажмите здесь для печати.