Comments 51
Перезалейте, пожалуйста, картинки на habrastorage.org. Vk у части читателей может быть заблокированным.
При таком «сильно связанном» подходе, особенно, при наличии большого количества компонентов, довольно просто запутаться и поддерживать целостность такой связи. К примеру, если изменится название свойства или метода в одном компоненте, то придется исправлять во всех компонентах, использующих этот. И это гемор.
Как текущий подход помог избавиться от этого гемора?
За то добавилось много нового:
- компилятор больше не помощник, он не скажет где вы ошиблись, где использовали не тот
- ide больше не помощник, он вам просто так уже не переименует переменные
- производительность?
- порядок OnStart должен быть определен строго, если вдруг кто-то уже захочет взять данные из другого компонента в своем OnStart
- создаем новый объект, добавили в него FirstComponent. А он не работает, потому что ожидает «count». Куда идти, где искать?
А вообще, если появилась такая необходимость завязать много компонентов друг на друга, может что-то не так с архитектурой?
Компонент будет работать, просто он не будет получать значение и ошибки не будет, если вы пробовали запускать решение, то убедились бы в этом.
Такая архитектура из коробки предоставляется. И дальше, кто во что горазд. Я смотрел в сторону ECS, и у меня есть своя реализация, что я опишу в следующих статьях.
Так а в чем удобство? Вы одним махом обрубаете кучу прелестей современных IDE, при этом связность кода понижается совсем слегка.
Когда одному из компонентов понадобится изменить функционал так, что некая его «переменная» приобретёт совершенно иной смысл, то вы оставите ей прежнее имя (которое более не соответствует действительности) или будете все же переименовывать? Разумеется под именем я подразумеваю ключ переменной в вашем словаре.
И раз уж вы тут производительность упомянули: доступ по строке в словаре отнюдь не бесплатный, в вашем решении имеет место быть боксинг (возможен даже в очень больших количествах, если например в апдейтах обращаться к общему состоянию), зачем-то определён Update в базовом классе (кстати зачем?).
Для уменьшения связности кода я бы в первую очередь предложил пересмотреть архитектуру. Ну а далее ECS и/или DI.
Про боксинг согласен, я подумаю как использовать дженерик. ECS это то к чему я собираюсь прийти, просто я не могу сурию туторов в одну статью вместить, а DI контейнер я тоже заимплементил и он работает на уровне всего приложения, а не для разрешения зависимостей между компонентами в геймобджекте, для этого тут и RequiredComponent удачно вписывается. А про собственную реализацию контейнера я позжеопубликую.
Производительность: если дорого проверять в каждом апдейте, то можно сделать явную подписку на событие, а без вот этих вот словарей/боксинга/кастов.
Что если два компонента добавят элемент с одним и тем же именем?
Компонент будет работать, просто он не будет получать значение и ошибки не будетА так ли это хорошо? Может лучше, если будет ошибка, и разработчики узнают о проблеме?
На мой взгляд это writeonly подход, написал — проверил что работает — закрыл навсегда. При каком-либо усложнении логики чтение и даже отладка превратиться в сущий ад.
А какие плюсы дает система, я так и не получил ответа.
Производительность: если дорого проверять в каждом апдейте, то можно сделать явную подписку на событие
О подписках на событие я писал в статье и объяснил какие трудности могут возникать, и она предполагает сильную связанность компонентов, что противоречит сути статьи.
а без вот этих вот словарей/боксинга/кастов
По поводу боксинга и анбоксинга, я согласен и придумаю как этого избежать, есть несколько идей.
А так ли это хорошо? Может лучше, если будет ошибка, и разработчики узнают о проблеме?
И да и нет. Я не предлагаю это как единственное правильное решение, я описываю свой опыт и свое видение, и лично мне проще и намного быстрее использовать словари, нежели поля.
При каком-либо усложнении логики чтение и даже отладка превратиться в сущий ад
Дело в привычке, на самом деле и во внимательности. Опять же если вы находите такое решение не приемлемым для себя, то я ж не настаиваю.
А какие плюсы дает система, я так и не получил ответа.
Да, я в этой статье не продемонстрировал реальное использование данного подхода в проекте и сделаю это в следующей.
Реализация «состояний» тоже странная.
Тут не нужны весопеды
Для решения проблемы связности используем DI-контейнеры ( см. Zenject )
Для реализации паттерна наблюдателя используем реактивные расширения, которые намного элегантней и функциональнкй (см. UniRx)
Для решения проблемы связности используем DI-контейнеры ( см. Zenject )/blockquote>
Я бы с удовольствием почитал статью на Хабре от человека, который имеет такой опыт. Сам пользуюсь Zenject, но кажется, что в некоторых местах делаю криво.
Вы написали что плохо, по поводу наследований, но не написали почему. Я думаю многим было бы интереснее, и мне в том числе, почему вы так считаете.
По поводу DI котейнера, я использую его на другом уровне, так как считаю что на этом от него мало толка.
И я заимплементил свой, опять же.
Потповоду реактивных расширений. Мне оч интересно, я посмотрю, спасибо.
Так же C# позволяет использовать статические методы, в не статических классах.
Соответсвенно вы делаете базовую реализацию Singleton, который возвращает ссылку на объект в сцене, а если его нет создает и сохраняет. Далее в компонентах просто используете метод по имена класса «Class.Instance.staticMethod()». И у вас появляется возможность использовать статические методы, без использования ссылок.
Ну или не ее, а шину сообщений в контексте GameObject
В zenject для этого есть gameobject context.О! Вы тоже разбираетесь! Как на счет того, чтобы написать статью? Лично я — очень хочу почитать про Zenject от рядового пользователя.
Интересен не сухой мануал, а личный опыт, который зависит от всяких нюансов. Ну вот, к примеру, я играю в игру, в контейнере лежат всякие зависимости, потом выхожу в меню и загружаю другую игру. Что происходит с зависимостями? Старые выгружаются и загружаются новые? И сколько вообще завимостей должны зависеть от стейта? Я сейчас стараюсь сделать побольше Stateless классов, которые получают данные извне, а сам стейт делать без логики — таким образом вьюшка в основном запрашивает Stateless классы, а уже они зависят от стейта. Но я не уверен, что правильно решаю эту задачу
Я вообще даже задавал вопрос на тостере, но не получил на него ответов.
Неразумное использование рефлексии ведёт к просадке перфоманса. Как и неразумное использование вложенных циклов ведёт туда же.
А что касается рефлексии — так она не в каждом фрейме используется.
Если проектировать полностью гибкую систему. То мы можем построить всю систему на сигналах Zenject`a.
Тогда у нас будет связь между компонентами исключительно через его SignalBus.
А Менеджеры и Контроллеры будут обрабатывать эти сигналы.
И тут я пояснил, как ещё одним способом можно решать проблему высокой свзяности кода в проекте
Но если цель, эксперементировать с архитектурой в целях обучения и расширения кругозора. И поиска новых решений. То. Норм подход.
Можно же сделать класс с данными и его брать через интерфейс и кастить.
public interface ISharedData {
public object SharedData { get; }
}
public class Class1 : Monobehaviour : ISharedData {
public class SharedConfig {
public int intValue;
public float floatValue;
public string stringValue;
}
private SharedConfig config;
public object ISharedData.SharedData() {
return config;
}
}
public class Class2 : Monobehaviour {
private Class1.SharedConfig config;
private IEnumerator Start() {
while (GetComponent<ISharedData>() == null || GetComponent<ISharedData>().SharedData == null)
yield return null;
config = (Class1.SharedConfig) GetComponent<ISharedData>().SharedData;
}
}
Управление состоянием и событиями между компонентами в GameObject