- 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
Нажмите здесь для печати.