Как организовать объекты в модели ECS?

У меня есть система рендеринга, которая выполняет итерацию по всем компонентам MeshDrawData и пакетно и рисует их. Моя реализация ECS допускает только один тип компонентов для каждой сущности.

Теперь я хочу изобразить ракетку для понга "Игрок". Мое мышление ООП говорит следующее:

Pseudocode:
auto e = createEntity();
createSpriteComponent(e,...); // this creates a MeshDrawData internally for e entity.
createColliderComponent(e,...);

Это работает нормально, НО, допустим, теперь я тоже хочу отрендерить линию. Если я сделаю это:

Pseudocode:
auto e = createEntity();
createSpriteComponent(e,...); // this creates a MeshDrawData internally for the e entity.
createColliderComponent(e,...);
createLineComponent(e,...); // this creates a MeshDrawData internally for the e entity

Здесь вы можете увидеть проблему .. Я создал два компонента MeshDrawData для объекта e ..

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

Как вы организуете свои сущности, чтобы, например, разрешить варианты использования, подобные упомянутому мною ранее? Что именно должен представлять объект?

Возможные решения:

  1. Никогда не создавайте объект с SpriteComponent и LineComponent, которые создают одни и те же MeshDrawData в одном и том же объекте .. (очевидно, но подвержено ошибкам)
  2. Создайте дочернюю сущность для каждого SpriteComponent и / или LineComponent и добавьте их к соответствующему дочернему элементу. Это сработает, но сделает длинные деревья иерархии сущностей ... не уверен, что это более подвержено ошибкам. ...

person FrameBuffer    schedule 03.02.2020    source источник


Ответы (1)


1:1 Entity:Component - скучное ограничение, но также и хорошее ограничение для большинства двигателей ECS.

Чтобы исправить проблемы, я обычно делаю одно из следующих: -

  1. Запретить по умолчанию: во втором случае игровой движок выдаст команду assert-fail.

  2. Добавить проверку, например в createLineComponent, существует ли уже MeshDrawData, и выполните некоторые пользовательские действия.

    В основном мне это нравится: -

    void createLineComponent(Entity e){
        auto meshComponent=engine->addJustInCase<MeshDrawData>(e);
        //^ if the component exists - just request the component, if not, create it
        meshComponent->setFormat ...
    }
    
  3. Разделить объект и добавить отношение «один ко многим»: подходит для очень сложных объектов.

    3.1 Будьте проще - дочерние объекты родительского кэша в качестве их полей.

    3.2 Полная система для определенного отношения 1: N.
    Система дочерних отношений должна быть очень конкретной по типу,
    например 1:N Collider-Sprite, а не 1:N entity-entity.

В своей игре я их смешиваю примерно 60:30:10 (9: 1). Он работает хорошо.
Если он начинает превращаться в беспорядок - используйте фабричный шаблон для его инкапсуляции.

Если у кого-то есть идеи (получше), ответьте. Я тоже хотел бы это услышать.

person cppBeginner    schedule 09.02.2020
comment
Спасибо за Ваш ответ. Прямо сейчас я использую ваш 2) подход. Я перезаписываю MeshDrawData, добавляя последний компонент, который в этом нуждается. Я не буду выбирать этот ответ как правильный, чтобы узнать, есть ли у кого-нибудь еще одна идея. - person FrameBuffer; 20.02.2020
comment
@FrameBuffer Отлично. Если вы найдете что-то полезное или закончите игру с таким подходом, дайте мне знать. :) - person cppBeginner; 24.02.2020
comment
Я больше думал об этом и пришел к выводу, что вы можете решить поместить все свое состояние в ECS или поделиться им с системами. Мой подход теперь состоит в том, чтобы поместить ВСЕ состояние в компоненты, просто для согласованности. Учитывая, что я решил создать дочерний элемент для каждого отдельного компонента рендеринга, который есть у объекта. И в компонентах средства визуализации сущности сохраните ссылку на сущность на случай, если вам понадобится к ней доступ. - person FrameBuffer; 06.03.2020
comment
@FrameBuffer Просто чтобы проверить мое понимание. Вы ... (1) разрешите 1 объекту иметь ›1 renderer component путем улучшения вашего ядра ECS? Или (2) уменьшить / уменьшить renderer component, чтобы он больше не был компонентом ECS, и вместо этого управлять им вручную? Или что-то другое? ... Я сделал (2) только один раз. Это критически важная для производительности область моего Physics Engine (manifold component). - person cppBeginner; 07.03.2020
comment
У меня есть только один компонент для каждого типа в объекте. Что я изменил с точки зрения архитектуры, так это то, что сущность с LineRenderer и SpriteRenderer для меня не имеет смысла, поэтому я закончил использовать ваш первый (1) подход (потому что что-то не так). Например, Sprite - это объект, который имеет Transform и Sprite (который будет создавать и обрабатывать свои собственные MeshDrawData). Таким образом, персонаж, у которого есть спрайт, будет представлен сущностью (игроком) с дочерней сущностью (спрайтом). Если игрок хочет нарисовать линию, Line - это другой объект (преобразование, LineRenderer- ›MeshDrawData) - person FrameBuffer; 08.03.2020