Pull to refresh

Comments 23

Выглядит всё это как переизобретение ECS в менее оптимальном виде. А пример с PickUpHealing и "Вынести хард-код в константы" говорят о том что весь этот фреймворк не удобен и лучше делать на обычных интерфейсах: это будет и быстрее работать и проверок во время компиляции больше будет.

Не, у ECS есть свои тараканы и там практически нет полиморфизма.

На обычных интерфейсах делать можно, я уже это проходил, но проблема в том, что придется делать их очень много, на каждый чих + для каждого игрового объекта делать дополнительный фасад . В результате такой дизайн выглядит слишком избыточным... Поэтому айдишники в виде строк — это самый компактный и гибкий вариант

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

Не совсем понял примеры с кодом проброса геттеров из конкретных компонентов, можешь, плиз, привести пример кода?

Ключевое отличие атомарного подхода от ECS заключается в том, что атомарный подход остается в парадигме ООП, а ECS нет. Атомарный подход подчиняется принципам ООП: инкапсуляция, наследование, полиморфизм, а ECS — нет.

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

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

Не знаю насколько в атомарном подходе остаётся жива инкапсуляция, мы же вроде данные и логику разделили, и добавили возможность получать переменные напрямую из объекта, что как-то с инкапсуляцией не вяжется. Логика данными тоже не владеет, мы данные прокидываем извне, и данными по факту владеет сам объект переменной, который их никак не защищает и про который могут знать разные куски логики.

Полиморфизм легко достигается в ECS через компоненты, ну либо в крайнем случае можно также использовать интерфейсы, если реализаций много.
Конструкция объектБезТипа.Is("Хилка") ничем по факту от полиморфизма в ECS не отличается, кроме того, что ECS быстрее, типобезопаснее и поддерживает фильтры/группы/query.

Вкатывание в ECS сильно много времени не занимает. Включиться в работу и делать фичи можно с первых дней.
Так зачем использовать неоптимальный, урезанный и не самый простой подход, когда есть зарекомендовавшая себя альтернатива?

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

Если все это работает на рефлексии, то зачем типизировать через string, ели можно использовать Type?

В атомарном подходе тип — это просто маркер или тэг объекта.

С точки зрения кода любой игровой объект состоит из данных и логики

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

  • Сериализация состояния мира в больших масштабах.

  • Тестирование и воспроизведение конкретного кейса.

  • Декларирование порядка вызовов между атомарными объектами или внешнего кода.

  • Копирование объектов (сложно оценить как скопируются функции и эвенты)

    Это и проблема многих ECS, где нет доступа к блокам памяти, или нельзя выделить размер в памяти нормально.

Не оч понял аргумент, почему сущность теряет гибкость и тестируемость, если объект состоит из данных и логики?

В именно этой реализации, данные и логика не отделены друг от друга полностью, а имеют важные детали в методе Compose(). И этот метод собственно деструктивно инициализирует данные и логику. Нельзя как в ECS взять тот же компонент, но от другой сущности, чтобы всё продолжило работать.

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

Также если нужно, можно добавлять и удалять данные объекта в Run-time по аналогии с добавлением и удалением компонентов в сущности. (см главу про Динамические объекты)

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

Функцией сериализации придется наделить либо все сущности, либо каким то образом шарить дополнительные детали из метода Compose() (о чём я писал выше). Это будет дороговато поддерживать, и в целом это доступ к рандомной памяти в куче, в большом количестве итераций. Например, файтинг или шутер, где нужно делать копию мира по 7-8 раз за кадр, так не сделать.

А зачем сериализовывать все? Если мы говорим про мультиплеер, то достаточно сериализовать состояние объекта (то есть данные) и синхронизировать их с игровым объектом на сервере.

По коду сложно сказать достаточно или нет, тк сам контракт подразумевает что данные можно трактовать по разному. Тут ведь при синхронизации для данных нужен еще факт создания или удаления сущности, и какого она была типа (набора компонентов данных + набора логических систем + связей между объектами). Если бы вся информация об этом была гарантирована в данных, то было бы надёжнее.
Ну или я просто не правильно представляю рабочие примеры, а не синтетические. Мне кажется что подход не полностью датаориентированный, тк тут замешаны ООП свойства, и я перечислил список частых проблем в недатаориентированных средах.

Да, еще забыл сказать, необходимо мочь сериализовать всё, иначе результаты вычислений будут расходиться. Или, например, если игрок только подключился и у него совсем нет информации о мире. Если работать с сущностями по одной, то получится неконсистентность данных, если делать в лоб. Такой подход есть в Unreal, или в NetworkForGameObjects, но в играх так совсем уже не делают. В современных тайтлах всегда где нибудь да есть предсказание, или компенсация лага.

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

Дочитал до "Is". Зачем этот велосипед с константными строками, используйте интерфейсы.

Прикиньте на сколько удобнее будет код если вместо

character.Is("TakeDamagable");

будет

character is IDamagable

Но зачем вообще проверять персонажей на компонент, если можно работать сразу с массивами компонентов и получить бесплатный буст к производительности? Ой, похоже я придумал ECS...

К сожалению не будет удобнее, уже проходил это с интерфейсами. В результате на большом проекте у тебя будет 500+ интерфейсов, которые нужно будет прописывать на каждый чих, и код-база будет расти слишком быстро. Поэтому в атомарном подходе в основном лучше использовать динамическую типизацию вместо статической.

character.Is("TakeDamagable");

Это и есть интерфейс, просто динамически типизированный.

Но сделать вот так на атомарном подходе тоже можно (писал про пример с аптечкой)

character is IDamagable

Сколько нужно интерфейсов столько и нужно создавать. Не знаю насколько у вас большой проект, но в моем где-то на 50к строк кода интерфейсов всего пара десятков.
И строки использовать это вообще вернейший способ выстрелить себе в ногу.

Sign up to leave a comment.

Articles