Коли / де оновити компоненти


10

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

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

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

Отже, моє запитання полягає в тому, де я оновлюю компоненти, що є чистим способом отримання їх менеджерам?

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


1
Ви якось використовуєте системи?
Асакерон

Компонентні системи - це звичайний спосіб зробити це. Я особисто просто викликаю оновлення для всіх об'єктів, яке викликає оновлення на всіх компонентах, і має кілька "спеціальних" випадків (наприклад, просторовий менеджер для виявлення зіткнень, який є статичним).
ashes999

Компонентні системи? Я ніколи не чув про них раніше. Почну гуглінг, але бажаю будь-яких рекомендованих посилань.
Рой Т.

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

1
Я написав тут своєрідну вигадливу публікацію в блозі: gamedevrubberduck.wordpress.com/2012/12/26/…
AlexFoxGill

Відповіді:


15

Я б запропонував почати з того, щоб прочитати 3 великих брехні Майка Актона, оскільки ви порушуєте дві з них. Я серйозно, це змінить спосіб оформлення коду: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html

Отже, що ви порушуєте?

Брехня 3 - Код важливіший за дані

Ви говорите про введення залежності, яке може бути корисним у деяких (і лише деяких) випадках, але завжди повинно дзвонити в великий дзвінок, якщо ви його використовуєте, особливо в розробці ігор! Чому? Тому що це часто непотрібна абстракція. А абстракції в неправильних місцях жахливі. Отже, у вас є гра. У грі є менеджери для різних компонентів. Компоненти всі визначені. Тому складіть клас десь у вашому головному коді циклу ігор, який "має" менеджери. Подібно до:

private CollissionManager _collissionManager;
private BulletManager _bulletManager;

Дайте йому кілька функцій отримання для отримання кожного класу менеджерів (getBulletManager ()). Можливо, цей клас сам по собі є Singleton або він доступний з одного (у вас, певно, десь є центральний ігровий синглтон). Немає нічого поганого в чітко визначених жорстких даних і поведінці.

Не створюйте ManagerManager, який дозволяє реєструвати менеджерів за допомогою ключа, який можна отримати за допомогою цього ключа іншими класами, які хочуть використовувати менеджер. Це чудова система і дуже гнучка, але тут говорити про гру. Ви точно знаєте , які системи в грі. Чому робите вигляд, як ні? Тому що це система для людей, які вважають, що код важливіший за дані. Вони скажуть "Код гнучкий, дані просто заповнюють його". Але код - це лише дані. Система, яку я описав, набагато простіша, надійніша, простіша в обслуговуванні та набагато гнучкіша (наприклад, якщо поведінка одного менеджера відрізняється від інших менеджерів, вам доведеться змінити лише кілька рядків замість того, щоб переробляти всю систему)

Брехня №2 - Код повинен бути розроблений навколо моделі світу

Отже, у вас є сутність в ігровому світі. Суб'єкт господарювання має ряд компонентів, що визначають його поведінку. Таким чином, ви створюєте клас Entity зі списком об'єктів Component та функцією Update (), яка викликає функцію Update () кожного компонента. Правильно?

Ні. :) Це розробка навколо моделі світу: у вашій грі є куля, тож ви додаєте клас Bullet. Потім ви оновлюєте кожну кулю і переходите до наступної. Це абсолютно знищить вашу ефективність, і це дасть вам жахливу складну кодову базу з повторюваним кодом скрізь і не має логічної структуризації подібного коду. (Ознайомтеся з моєю відповіддю тут, щоб отримати більш детальне пояснення того, чому традиційний дизайн OO смокче, або шукайте дизайн, орієнтований на дані)

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

  • У вас є купа утворень
  • Суб'єкти господарювання складаються з ряду компонентів, які визначають поведінку суб'єкта господарювання
  • Ви хочете оновлювати кожен компонент у грі кожен кадр, бажано контрольованим способом
  • Окрім ідентифікації компонентів як належних разом, організація сама не повинна робити. Це посилання / ідентифікатор для декількох компонентів.

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

Якщо ви знайомі з архітектурою комп’ютера або з орієнтованим на дані дизайном, ви знаєте, як досягаються найкращі показники: щільно упакована пам'ять та групування виконання коду. Якщо ви виконаєте фрагменти коду A, B і C так: ABCABCABC, ви не отримаєте такої ж продуктивності, як при виконанні його так: AAABBBCCC. Це не лише тому, що кеш інструкцій та даних буде використовуватися ефективніше, а й тому, що якщо ви виконайте всі "A" один за одним, є багато місця для оптимізації: видалення дублікату коду, попередній розрахунок даних, якими користується всі "А" і т.д.

Отже, якщо ми хочемо оновити всі компоненти, не давайте робити їх класами / об’єктами з функцією оновлення. Не будемо називати цю функцію оновлення для кожного компонента в кожній сутності. Це рішення "ABCABCABC". Згрупуємо всі однакові оновлення компонентів разом. Тоді ми можемо оновити всі A-компоненти, а потім B та ін. Що нам потрібно для цього?

По-перше, нам потрібні менеджери компонентів. Для кожного типу компонентів у грі нам потрібен клас менеджера. Він має функцію оновлення, яка буде оновлювати всі компоненти цього типу. Він має функцію створення, яка додасть новий компонент цього типу та функцію видалення, яка знищить вказаний компонент. Можуть бути й інші допоміжні функції для отримання та встановлення даних, характерних для цього компонента (наприклад: встановлення 3D-моделі для компонентної моделі). Зауважте, що менеджер певним чином являє собою чорний ящик для зовнішнього світу. Ми не знаємо, як зберігаються дані кожного компонента. Ми не знаємо, як оновлюється кожен компонент. Нам все одно, доки компоненти поводяться як слід.

Далі нам потрібна сутність. Ви могли б зробити це класом, але це навряд чи потрібно. Суб'єкт може бути не що інше, як унікальний цілий ідентифікатор або штриховий рядок (так само ціле число). Коли ви створюєте компонент для Entity, ви передаєте ідентифікатор як аргумент менеджеру. Коли ви хочете видалити компонент, ви знову передаєте ідентифікатор. Можуть бути деякі переваги в тому, що додавати до Субстанції трохи більше даних, а не просто робити їх ідентифікатором, але це будуть лише допоміжні функції, оскільки, як я перераховував у вимогах, все поведінка сутності визначається самими компонентами. Це ваш двигун, тому робіть те, що має для вас сенс.

Що нам потрібно - це менеджер суб'єктів господарювання. Цей клас або генерує унікальні ідентифікатори, якщо ви використовуєте рішення, призначене лише для ідентифікації, або його можна використовувати для створення / управління об'єктами Entity. Він також може зберігати список усіх об'єктів у грі, якщо вам це потрібно. Entity Manager може бути центральним класом вашої компонентної системи, зберігаючи посилання на всіх ComponentManagers у вашій грі та викликаючи їх функції оновлення в потрібному порядку. Таким чином, весь цикл ігор повинен зробити, це викликати EntityManager.update (), і вся система чудово відокремлена від решти вашого двигуна.

Ось такий погляд з пташиного польоту, давайте подивимось, як працюють менеджери компонентів. Ось що вам потрібно:

  • Створення компонентних даних під час виклику create (entitidID)
  • Видалити дані компонента при виклику видалення (EntityID)
  • Оновити всі дані (застосовно) компонентів, коли викликається update () (тобто не всі компоненти потребують оновлення кожного кадру)

Останній - це визначення поведінки / логіки компонентів і повністю залежить від типу компонента, який ви пишете. AnimationComponent оновлює дані анімації на основі кадру, в якому знаходиться. DragableComponent оновить лише компонент, який перетягується мишкою. PhysicsComponent буде оновлювати дані у фізичній системі. Тим не менше, оскільки ви оновлюєте всі компоненти одного типу за один раз, ви можете зробити деякі оптимізації, які неможливі, коли кожен компонент є окремим об'єктом з функцією оновлення, яку можна було викликати в будь-який час.

Зауважте, що я досі ніколи не закликав створити клас XxxComponent для зберігання компонентних даних. Це залежить від вас. Вам подобається дизайн орієнтований на дані? Потім структуруйте дані в окремі масиви для кожної змінної. Вам подобається об'єктно-орієнтований дизайн? (Я б не рекомендував це, він все одно знищить вашу ефективність у багатьох місцях) Потім створіть об’єкт XxxComponent, який буде зберігати дані кожного компонента.

Чудова річ у менеджерах - інкапсуляція. Зараз інкапсуляція - одна з найбільш жахливо зловживаних філософій у світі програмування. Ось як це слід використовувати. Тільки менеджер знає, які дані компонентів зберігаються де, як працює логіка компонента. Існує кілька функцій для отримання / встановлення даних, але це все. Ви можете переписати весь менеджер та його основні класи, а якщо не змінити загальнодоступний інтерфейс, ніхто навіть не помітить. Змінили двигун фізики? Просто перепишіть PhysicsComponentManager і закінчите.

Тоді є ще одне останнє: спілкування та обмін даними між компонентами. Зараз це складно, і не існує рішення, яке відповідає всім розмірам. Ви можете створити функції get / set у менеджерах, щоб, наприклад, компонент зіткнення отримав позицію з компонента позиції (тобто PositionManager.getPosition (entitid)). Ви можете використовувати систему подій. Ви можете зберігати деякі спільні дані в суті (найгірше рішення на мою думку). Ви можете використовувати (це часто використовується) система обміну повідомленнями. Або використовувати комбінацію декількох систем! У мене немає часу та досвіду, щоб зайнятися кожною з цих систем, але пошук у Google та stackoverflow - ваші друзі.


Я вважаю цю відповідь дуже цікавою. Лише одне запитання (сподіваюся, ви чи хтось зможете відповісти на мене). Як вам вдається усунути сутність у системі на основі компонентів DOD? Навіть Артеміда використовує Entity як клас, я не впевнений, що це дуже хитро.
Wolfrevo Kcats

1
Що ви маєте на увазі під його усуненням? Ви маєте на увазі систему особи без класу Entity? Причина, що в Артеміди є Entity, полягає в тому, що в Artemis клас Entity управляє своїми власними компонентами. У запропонованій мною системі класи ComponentManager керують компонентами. Отже, замість того, щоб потрібен клас Entity, ви можете просто мати унікальний цілий ідентифікатор. Скажімо, у вас є сутність 254, яка має позиційний компонент. Коли ви хочете змінити позицію, ви можете зателефонувати PositionCompMgr.setPosition (int id, Vector3 newPos), з параметром id 254.
Березень

Але як керувати ідентифікаторами? Що робити, якщо ви хочете видалити компонент з об'єкта, щоб потім його призначити іншому? Що робити, якщо ви хочете видалити об'єкт і додати нове? Що робити, якщо ви хочете, щоб один компонент ділився між двома або більше об’єктами? Мене це справді цікавить.
Wolfrevo Kcats

1
EntityManager можна використовувати для видачі нових ідентифікаторів. Він також може бути використаний для створення повноцінних об'єктів на основі заздалегідь визначених шаблонів (наприклад, створити "EnemyNinja", який генерує новий ідентифікатор та створює всі компоненти, що складають ворожа ніндзя, наприклад рендерінг, зіткнення, AI, можливо, якийсь компонент для боротьби в ближньому бою тощо). Він також може мати функцію RemoveEntity, яка автоматично викликає всі функції видалення ComponentManager. ComponentManager може перевірити, чи є в ньому дані про компоненти для даної сутності, і якщо так, видалити ці дані.
Березень

1
Перемістити компонент від однієї сутності до іншої? Просто додайте функцію swapComponentOwner (int oldEntity, int newEntity) до кожного ComponentManager. Ці дані є в ComponentManager, все, що вам потрібно, - це функція, щоб змінити, до якого власника належить. Кожен ComponentManager матиме щось на зразок індексу чи карти для зберігання, які дані належать до якого ідентифікатора об'єкта. Просто змініть ідентифікатор особи зі старого на новий ідентифікатор. Я не впевнений, що обмін компонентами простий у системі, про яку я придумав, але наскільки це важко? Замість одного ідентифікатора сутності <-> Компонент даних в таблиці індексів існує кілька.
Березень

3

Для того, щоб ці компоненти працювали, вони повинні оновлювати кожен кадр, найпростіший спосіб зробити це - пройти по дереву сцен, а потім для кожного об'єкту оновити кожен компонент.

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

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

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

Однотонний тут насправді не потрібен, і тому вам слід уникати цього, оскільки він несе ті згадані вами недоліки. Введення залежності не є надмірним - суть концепції полягає в тому, що ви передаєте речі, які потрібні об'єкту, тому в ідеалі в конструкторі. Для цього вам не потрібна важка рамка DI (наприклад, Ninject ) - просто передайте десь додатковий параметр конструктору.

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

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

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

Цей підхід іноді відомий як підвісний компонентний підхід , якщо ви шукаєте детальніше.

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