Советы по связыванию системы компонентов Entity в C ++

После прочтения нескольких документов о системе сущностей, я решил реализовать свою. До сих пор у меня есть класс World, который содержит сущности и системный менеджер (системы), класс Entity, который содержит компоненты как std :: map и несколько систем. Я держу объекты как std :: vector в World. Пока нет проблем. То, что меня смущает, - это итерация сущностей, я не могу иметь кристально чистого ума, поэтому я все еще не могу реализовать эту часть. Должна ли каждая система иметь локальный список объектов, которые им интересны? Или я должен просто перебирать объекты в классе World и создавать вложенный цикл для итерации через системы и проверить, есть ли у объекта компоненты, которых интересует система? Я имею в виду:

for (entity x : listofentities) {
   for (system y : listofsystems) {
       if ((x.componentBitmask & y.bitmask) == y.bitmask)
             y.update(x, deltatime)
       }
 }

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

9 голосов | спросил deniz 19 J000000Friday13 2013, 14:33:31

3 ответа


6
  

Наличие локальных списков для каждой системы увеличит использование памяти для   классы.

Это традиционный компромисс между пространством и временем .

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

Тем не менее, этот подход может быть достаточно хорош в зависимости от ваших целей.

Хотя, если вы беспокоитесь о скорости, есть, конечно, еще одно решение.

  

Должна ли каждая система иметь локальный список объектов, которые они интересуют?

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

Теперь, как сохранить эти «списки интересов», возможно, не так очевидно. Что касается контейнера данных, то класс std::vector<entity*> targets внутри системы вполне достаточен. Теперь вот что я делаю:

  • Объект пуст при создании и не принадлежит какой-либо системе.
  • Всякий раз, когда я добавляю компонент к сущности:

    • получить текущую битовую подпись ,
    • размер компонента карты в пуле мира соответствующего размера блока (лично я использую boost :: pool) и выделяю компонент там
    • получить новую битовую подпись (которая является только «текущей битовой сигнатурой» плюс новый компонент)
    • итерации по всем мировым системам, и если есть система, чья подпись не соответствует текущей подписи объекта и делает соответствие новой подписи, становится очевидным, что мы должны push_back указывает на наш объект.

          for(auto sys = owner_world.systems.begin(); sys != owner_world.systems.end(); ++sys)
                  if((*sys)->components_signature.matches(new_signature) && !(*sys)->components_signature.matches(old_signature)) 
                          (*sys)->add(this);
      

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

Теперь вы можете рассматривать использование std :: list, потому что удаление из вектора - это O (n), не говоря уже о том, что вам придется перекладывать большой кусок данных каждый раз, когда вы удаляете из середины. На самом деле вам это не нужно - поскольку мы не заботимся о порядке обработки на этом уровне, мы можем просто вызвать std :: remove и жить с тем фактом, что при каждом удалении нам нужно выполнить O (n) поиск нашего подлежащий удалению объект.

std :: list даст вам O (1), но с другой стороны у вас будет немного дополнительных издержек памяти. Также помните, что большую часть времени вы будете обрабатывать объекты и не удалять их - и это, безусловно, выполняется быстрее, используя std :: vector.

Если вы очень критичны по производительности, вы можете рассмотреть еще один шаблон доступа к данным , но в любом случае вы поддерживаете какие-то« списки интересов ». Помните, что если вы будете достаточно тщательно отфильтровывать свой Entity System API, это не должно быть проблемой для улучшения методов обработки сущностей системы, если ваша частота кадров падает из-за них, поэтому на данный момент выберите наиболее простой для вас метод - только для кода затем профиль и улучшите, если необходимо.

ответил Patryk Czachurski 22 J000000Monday13 2013, 19:24:34
5

Существует подход, который стоит рассмотреть, где каждая система владеет компонентами, связанными с самим собой, и сущности ссылаются только на них. В основном, ваш (упрощенный) Entity класс выглядит следующим образом:

class Entity {
  std::map<ComponentType, Component*> components;
};

Когда вы говорите, что компонент RigidBody, прикрепленный к Entity, вы запрашиваете его из своей системы Physics. Система создает компонент и позволяет сущности держать указатель на него. Затем ваша система выглядит следующим образом:

class PhysicsSystem {
  std::vector<RigidBodyComponent> rigidBodyComponents;
};

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

for(auto it = systems.begin(); it != systems.end(); ++it) {
  it->update();
}

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

for(auto it = rigidBodyComponents.begin(); it != rigidBodyComponents.end(); ++it) {
  it->update();
}

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

В этот момент ваш World проходит только через системы и вызывает update, без необходимости итерации объектов. Это (imho) лучший дизайн, потому что тогда обязанности систем намного понятнее.

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

ответил pwny 19 J000000Friday13 2013, 18:46:36
1

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

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

ответил superarce 19 J000000Friday13 2013, 18:40:54

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132