Поради щодо зв’язку між компонентною системою сутності в C ++


10

Прочитавши декілька документації про сутність-компонентну систему, я вирішив застосувати шахту. Поки що у мене є світовий клас, який містить сутності та системний менеджер (системи), клас Entity, який містить компоненти як std :: map, та декілька систем. Я тримаю сутності як std :: vector у світі. Поки що жодних проблем. Що мене бентежить - це ітерація сутностей, я не можу мати про це кришталево ясний розум, тому я досі не можу реалізувати цю частину. Чи повинна кожна система мати локальний перелік організацій, які їх цікавлять? Або я повинен просто повторити об'єкти світового класу та створити вкладений цикл для повторного перегляду через системи та перевірити, чи є у суті компоненти, які цікавлять цю систему? Я маю на увазі :

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

але я думаю, що система растрових масок трохи заблокує гнучкість у разі вбудовування мови сценаріїв. Або наявність локальних списків для кожної системи збільшить використання пам'яті для класів. Я страшенно розгублений.


Чому ви очікуєте, що підхід битмаска перешкоджає прив'язці сценарію? Як сторону, використовуйте посилання (const, якщо можливо) у циклі для кожного, щоб уникнути копіювання сутностей та систем.
Бенджамін Клостер

використовуючи бітову маску, наприклад, int, міститиме лише 32 різні компоненти. Я не маю на увазі, що буде більше 32 компонентів, але що робити, якщо у мене є? мені доведеться створити інший або 64-бітовий int, це не буде динамічно.
деніз

Ви можете використовувати std :: bitset або std :: vector <bool>, залежно від того, чи хочете ви, щоб вона була динамічною під час виконання.
Бенджамін Клостер

Відповіді:


7

Наявність локальних списків для кожної системи збільшить використання пам'яті для класів.

Це традиційний простір-час .

Хоча ітерація через усі сутності та перевірка їх підписів є прямим кодом, вона може стати неефективною, оскільки кількість ваших систем зростає - уявіть собі спеціалізовану систему (нехай це буде введення), яка шукає свою, ймовірно, єдину сутність, яка представляє інтерес серед тисяч непов'язаних об'єктів .

Зважаючи на це, цей підхід все ще може бути досить хорошим залежно від ваших цілей.

Хоча, якщо ви переживаєте за швидкість, то, звичайно, варто розглянути й інші рішення.

Чи повинна кожна система мати локальний перелік організацій, які їх цікавлять?

Саме так. Це стандартний підхід, який повинен забезпечити вам гідну ефективність і досить легко здійснити. На мою думку, накладні витрати на пам'ять є незначними - ми говоримо про зберігання покажчиків.

Тепер, як підтримувати ці "списки інтересів", може бути не так очевидно. Що стосується контейнера даних, то 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 :: delete та жити з тим фактом, що при кожному видаленні нам потрібно лише виконати O (n) пошук нашої підлягає видаленню.

std :: список дозволить вам видалити O (1), але з іншого боку у вас є додаткова накладна пам'ять. Також пам’ятайте, що більшу частину часу ви будете обробляти об'єкти, а не видаляти їх - і це, безумовно, робиться швидше за допомогою std :: vector.

Якщо ви дуже критичні для продуктивності, ви можете розглянути навіть інший шаблон доступу до даних , але в будь-якому випадку ви підтримуєте якийсь "список цікавих". Пам’ятайте, що якщо ви триматимете API Entity System достатньо абстрагованим, це не повинно бути проблемою для вдосконалення методів обробки сутності системи, якщо ваш частота кадрів знижується через них - тому поки що виберіть метод, який вам найпростіше кодувати - лише потім профілі та покращуйте, якщо потрібно.


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їх, не потребуючи також повторення об'єктів. Це (імхо) краще дизайн, тому що тоді обов'язки систем набагато чіткіші.

Звичайно, існує безліч подібних конструкцій, тому вам потрібно ретельно оцінити потреби своєї гри та вибрати найбільш підходящий, але, як ми бачимо тут, іноді маленькі деталі дизайну можуть змінити значення.


хороша відповідь, спасибі але компоненти не мають функцій (наприклад, update ()), лише дані. і система обробляє ці дані. тож згідно з вашим прикладом я повинен додати віртуальне оновлення для класу компонентів та покажчик сутності для кожного компонента, я прав?
деніз

@deniz Все залежить від вашого дизайну. Якщо у ваших компонентів немає ніяких методів, а лише даних, система все одно може переглядати їх і виконувати необхідні дії. Що стосується посилання на об'єкти, так, так, ви можете зберігати вказівник на сутність власника в самому компоненті або змусити вашу систему підтримувати карту між ручками компонентів та сутностями. Як правило, ви хочете, щоб ваші компоненти були максимально автономними. Компонент, який взагалі не знає про свою материнську сутність, є ідеальним. Якщо вам потрібно спілкування в цьому напрямку, віддайте перевагу подіям тощо.
pwny

Якщо ви скажете, що це буде краще для ефективності, я буду використовувати вашу схему.
деніз

@deniz Переконайтеся, що ви насправді профайлюєте свій код рано та часто, щоб визначити, що працює, а не для вашого конкретного engin :)
pwny

гаразд :) Я зроблю своєрідний стрес-тест
деніз

1

На мою думку, хороша архітектура полягає у створенні компонентного шару в сутностях та розділення управління кожною системою в цьому компонентному шарі. Наприклад, логічна система має деякі логічні компоненти, які впливають на їх сутність, і зберігають загальні атрибути, які спільно використовуються для всіх компонентів сутності.

Після цього, якщо ви хочете керувати об'єктами кожної системи в різних точках, або в певному порядку, краще створити список активних компонентів у кожній системі. Усі списки покажчиків, які можна створювати та керувати в системах, містять менше одного завантаженого ресурсу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.