Pull to refresh

Comments 24

PinnedPinned comments

Статью можно дополнить только через привлечение модераторов, поэтому просто отпишу здесь:

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

Спасибо за прекрасную статью, не понял только несколько моментов (не в контексте юнити):


1) Как обрабатывать ивенты. Например спавн юнита с какими-нибудь эффектами (например в кольце огня) по кнопке из UI. Просто делаем evenbus и по нему вводим в игру кучу компонентов, а дальше системы разберутся?


2) Как обрабатывать тесные взаимодействия, например если делаешь сокобан, где можно двигать ящик, который может подвинуть ящик, который может подвинуть ящик, который может упасть и раздавить юнита.


Или это нормально, двигать в одном кадре, а обрабатывать урон в следующем?

1) В принципе так чаще всего и делают. По кнопке UI создаём сущность с компонентами, которые оповестят системы что им необходимо сделать, а дальше системы разберутся.

2) Если я правильно понял, то тут как раз проблема потенциальной рекурсии, которую я указал в недостатках ECS. Это, вцелом, можно решить как зацикливанием группы систем(как в статье описал), так и растянув логику на несколько кадров, если такое позволяется дизайном игры. Буду рад, если кто-то из опытных ECS-адептов подскажет как это ещё можно решить.

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

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

  2. Если взаимодействие тесное, то его обработкой должна заниматься конкретная ситема. В вашем примере Система ввода добавит(обновит) компонет скорости к персонажу, а система передвижения обновит всё, что нужно. В таком виде можно элементарно ввести в игру конвееры. Просто на каждом шаге добавлять(обновлять) компонент скорости у сущностей на конвеере.

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

Лучшая статья по ECS что я видел. Из недостатков, сам натыкался и на порядок систем, и на слабую связность когда надо неудобно проверять наличие компонентов если сущность получена не из фильтра, и про неудобство в дебаге, но абсолютно нигде не видел, чтобы про это кто-то писал.

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

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

Это фундаментальная проблема обработки нескольких одновременных действий над одним объектом, всегда придётся выбирать какое действие будет первым. Например сначала передвинуть игрока, а потом применить выстрел или наоборот. А от этого много чего зависит.

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

В ООП могут быть ошибки, когда у методов неправильный порядок вызова, но это проблема конкретного объекта, и решается она индивидуально в рамках конкретного объекта без большой боязни затронуть что-то ещё. Ты просто открываешь Player.Update() и меняешь вызовы методов местами, пока оно не станет работать правильно.

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

Даже в рамках одной фичи в ECS могут возникнуть различные баги. Чтобы узнать порядок нужно лезть в место, где системы регистрируются, то есть быстренько прокликать откуда вызывается каждый метод или заглянуть в Update конкретного объекта не получится.

Геймдев и юнити не моя специализация, но прочитал с интересом. Одна из лучших статей месяца по C# на хабре, однозначно! Применять в работе вряд ли буду, но для расширения кругозора было очень полезно.

Большое спасибо за такую оценку :)

Как вы решали вопросы ссылочной целостности? Например, нужно удалить родительскую сущность, с которой связаны сущности-дети (у которых тоже могут быть дети, и даже могут быть циклические зависимости), и не допустить появление «сирот» и висячих ссылок.

Возможные подходы: 1) тупо запретить удаление родителя, пока у него есть хотя бы один ребёнок, переложив ответственность на удаление детей/внуков/правнуков перед удалением родителя на того, кто инициирует удаление или 2) пометить родителя как «удалённого», после чего реактивно начать помечть удалёнными его детей, а потом их детей, до исчерпания, и потом однажды сделать сборку мусора.

Какой подход более «каноничен» для ECS? Должна это быть некая «система» типа EntityConstraintsEnforcer, хранящая информацию обо всех зависимостях между сущностями плюс обо всех возможных запросах, потенциально способных нарушить ссылочную целостность, сидящая в пайплайне и перехватывающая все такие запросы?

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

Вопрос про то, какая часть ECS ответственна за поддержание ссылочной целостности и прочих инвариантов стора. Раз в компонентах логики нет, значит полагаться на автоматическое удаление детей при вызове деструктора родителя уже нельзя. Значит нужно выносить логику удаления «детей» куда-то вовне. Должна это быть специально выделенная система, которая знает о всех связях «родитель-ребёнок»?

Как было описано в Недостатках, ECS не особо хорошо дружит с иерархией, рекурсивной и реактивной логикой, так что если делать иерархию прямо в ECS, то придётся покостылить. Поэтому лично я рекомендую иерархию прикручивать немного сбоку в виде сервиса или ещё как-то.

Если говорить про связи, то все связи в ECS живут в компонентах в виде ссылок на другие сущности, то есть фактически об этом знают все системы и каждая может использовать её в своих интересах. Посему "Ссылочная целостность" целиком на плечах разработчика. Надо самому перед использованием проверять, что сущность в поле компонента живая и имеет необходимые данные.

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

Если бы мне пришлось решать такую задачу

Вот я как раз малость удивлён, что бОльшая часть материалов написана так, будто эта задача никогда ни перед кем не стояла. Люди реально никогда не удаляют данные? Или не заботятся о сиротах и висячих ссылках?

Я столкнулся с этим, когда решил таки попробовать `redux`, который по сути тот же ECS с анемичными сущностями, только вместо «system» там «reducer» и «middleware». Везде пишут «применяйте нормализованные сторы», всякие `redux-toolkit` дают инструменты нормализации, но при этом практически во всех статьях про очередной «TodoApp» ноль упоминаний про поддержку ссылочной целостности в этих нормализованных сторах, хотя это первейшая задача для любой базы данных, и должна идти буквально «из коробки». Из-за этого зачастую возникает впечатление, будто все эти статьи про волшебные фреймворки пишут теоретики, не создавшие ничего сложнее TodoApp, и поэтому не дошедшие до явно лежащих граблей.

Я как раз доходил до этой проблемы, поэтому в своей ецс (ME.ECS) как раз и сделал иерархию как часть ецс. То есть можно кладывать сущности друг в друга, удалять их вместе со всей иерархией, читать/писать позиции и прочее.

Может быть поэтому я и не понимаю проблемы, видимо)

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

Если же хочется решения конкретных проблем, то обычный запрос гугла entity component system hierarchy выдает кучку ссылок на различные англоязычные статьи, где разбирается проблема иерархии в ECS, можете ознакомиться с ними.

Целью моей статьи в том числе было показать, что у ECS как подхода есть свои проблемы и грабли, посему он не является "серебрянной пулей" или "волшебным фреймворком" и решение о выборе ECS в качестве подхода надо принимать с учётом этих недостатков. В Недостатках чётко сказано, что будут проблемы при попытке создавать иерархию и необходимости вызывать какую-то логику "здесь и сейчас". Их можно решить, если того требует задача, но это потребует времени.

Заценил как chromealex реализовал иерархию в своём ECS. Мне очень понравилось, советую тоже заценить.

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

В известных мне ECS такого из коробки нет и делается обычно по-разному. Но обычно это выглядит так же как и в реляционной базе данных.

Вводят id для сущностей.

Есть условный ParentEntityIdComponent, который вешается на дочерние сущности. И есть система, которая следит, что в случае, если сущность c ParentId не резолвится, то удаляет текущую сущность.

Так можно сделать иерархию любой сложности.

Можно развернуть и в обратную сторону, что при удалении Parent'a он спрашивает все сущности, которые референсят его id

В итоге, мы получаем каскадное удаление как в базах данных

Отличная статья, спасибо! Надеюсь будет продолжение, есть желание изучить ECS, если напишите какой-то туториал для новичков скажем по Morpeh - с удовольствием бы почитал.

Статью можно дополнить только через привлечение модераторов, поэтому просто отпишу здесь:

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

Sign up to leave a comment.

Articles