Компоненти гри, менеджери ігор та властивості об’єкта


15

Я намагаюсь обернутись дизайном сукупності на основі компонентів.

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

Наступне, що я зробив - це видалити об'єкт і просто мати кожен компонент з ідентифікатором. Отже, об'єкт визначається компонентами, що мають однакові Ідентифікатори.

Тепер я думаю, що мені не потрібен менеджер для всіх моїх компонентів, наприклад, у мене є SizeComponent, який просто має Sizeвластивість). В результаті у SizeComponentнього немає методу оновлення, а метод оновлення менеджера нічого не робить.

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

Це здається мені надмірною інженерією, але я не думаю, що я можу позбутися компонентів, тому що мені потрібен спосіб, щоб менеджери могли знати, для яких об’єктів потрібна логіка компонента для запуску (інакше я просто видалю компонент повністю і натисніть її логіку оновлення в менеджер).

  1. Є чи це (маючи ObjectProperty, ObjectComponentі ComponentManagerкласи) більш-інженерії?
  2. Яка б була хороша альтернатива?

1
У вас є правильна ідея, намагаючись вивчити модель компонентів, але вам потрібно краще зрозуміти, що їй потрібно робити - і єдиний спосіб зробити це - [переважно] завершити гру, не використовуючи її. Я думаю, що створення a SizeComponentє надмірним - можна припустити, що більшість об'єктів мають розмір - це такі речі, як візуалізація, AI та фізика, де використовується компонентна модель; Розмір завжди буде поводитися однаково - тому ви можете поділитися цим кодом.
Джонатан Дікінсон


@JonathanDickinson, @Den: Я думаю, тоді моя проблема полягає в тому, де я зберігаю загальні властивості. Наприклад, об'єкт як позиція, яку використовують a RenderingComponentі a PhysicsComponent. Чи я надмірно замислююся над рішенням, де розмістити майно? Чи повинен я просто вставити його в будь-який, а потім інший запит об'єкта для компонента, який має необхідну властивість?
Джордж Дакетт

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

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

Відповіді:


6

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

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

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

  • Ложки немає.
    • Це той крок, який ви вже зробили, позбувшись центрального об’єкта. Це знімає всю дискусію про те, що входить в об’єкт Entity і що переходить в компонент, так як зараз у вас є всі компоненти.
  • Компоненти не є структурами
    • Якщо ви розбите щось там, де воно просто містить дані, то воно вже не є компонентом, це лише структура даних.
    • Компонент повинен містити всю функціональність, необхідну для виконання дуже конкретного завдання певним чином.
    • Інтерфейс IRenderable забезпечує загальне рішення для візуального відображення будь-чого в грі. CRenderableSprite та CRenderableModel - це компонентна реалізація цього інтерфейсу, що забезпечує специфіку візуалізації у 2D та 3D відповідно.
    • IUseable - це інтерфейс для того, з чим гравець може взаємодіяти. CUseableItem буде компонентом, який або стріляє з активної гармати, або випиває вибране зілля, тоді як CUseableTrigger може бути там, де гравець збирається стрибати у башту або кинути важіль, щоб скинути підйомний міст.

Тож, коли керівництво компонентів не є структурами, розмір SizeComponent був надто розбитий. Він містить лише дані, і те, що визначає розмір чогось, може змінюватися. Наприклад, у складі візуалізації це може бути скаляр 1d або 2 / 3d. У фізичній складовій це може бути обмежуючий об'єм об'єкта. У товарно-матеріальних цінностях може бути стільки місця, яке займає 2D-сітка.

Спробуйте намалювати хорошу межу між теорією та практичністю.

Сподіваюсь, це допомагає.


Не будемо забувати, що на деяких платформах виклик функції з інтерфейсу довше, ніж виклик з батьківського класу (оскільки у вашій відповіді були згадки про інтерфейси та класи)
ADB

Хороший момент пам’ятати, але я намагався залишатися мовою агностиком і просто використовував їх у загальних проектних умовах.
Джеймс

"Якщо ви розбите щось там, де воно просто містить дані, то це вже не компонент, це просто структура даних." - Чому? "Компонент" - це таке загальне слово, що воно може означати і структуру даних.
Пол Манта

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

1
@James Це було цікаве обговорення. :) chat.stackexchange.com/rooms/2175 Моя найбільша вдячність, якщо ваша реалізація полягає в тому, що компоненти знають занадто багато про те, що цікавить інші компоненти. Я хотів би продовжити обговорення в майбутньому.
Пол Манта

14

Ви вже прийняли відповідь, але ось мій удар в CBS. Я виявив, що загальний Componentклас має деякі обмеження, тому я розробив дизайн, описаний Radical Entertainment в GDC 2009, який запропонував розділити компоненти на Attributesта Behaviors. (" Теорія та практика архітектури компонентів ігрового об'єкта ", Марцін Чаді)

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

http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

Ось уривок із документа:

Атрибути і поведінка коротко

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

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


Редагувати: І ось схема взаємозв'язку, яка показує, як компоненти спілкуються один з одним:

Діаграма зв'язку між атрибутами та поведінкою

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


Редагувати: На інше запитання я дав аналогічну відповідь і на це. Ви можете знайти його тут для, можливо, кращого пояснення системи:
/gamedev//a/23759/6188


2

Це дійсно залежить від властивостей, які вам потрібні, і де вони вам потрібні. Об'єм пам'яті, який ви матимете, і потужність / тип обробки, який ви будете використовувати. Я бачив і намагаюся зробити наступне:

  • Властивості, які використовуються декількома компонентами, але зміненими лише одним, зберігаються в цьому компоненті. Shape - хороший приклад у грі, де системі AI, фізиці та системі візуалізації потрібен доступ до базової форми, це важка властивість, і вона повинна залишатися лише на одному місці, якщо це можливо.
  • Такі властивості, як позиція, іноді потрібно дублювати. Наприклад, якщо ви запускаєте кілька систем паралельно, ви хочете уникнути зазирвання через системи і скоріше будете синхронізувати позицію (скопіюйте з головного компонента або синхронізуйте через дельти або з необхідністю пройти зіткнення).
  • Властивості, що випливають із елементів керування чи «інтелекту» AI, можуть зберігатися у спеціальній системі, оскільки вони можуть бути застосовані до інших систем, не видно зовні.
  • Прості властивості можуть ускладнитися. Іноді для вашої позиції потрібна спеціальна система, якщо вам потрібно поділитися великою кількістю даних (положення, орієнтація, дельта кадру, загальний рух дельта-струму, дельта-рух для поточного кадру та попереднього кадру, обертання ...). У такому випадку вам доведеться перейти до системи та отримати доступ до найновіших даних із виділеного компонента, і, можливо, доведеться змінити їх через акумулятори (дельти).
  • Іноді ваші властивості можуть зберігатися в необробленому масиві (подвійний *), і ваші компоненти просто матимуть вказівники на масиви, що містять різні властивості. Найбільш очевидний приклад - коли вам потрібні масштабні паралельні обчислення (CUDA, OpenCL). Тож наявність однієї системи для правильного керування покажчиками може бути корисною.

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

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

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

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

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


Що стосується компонента розміру, він, ймовірно, може бути вбудований у ваш позиційний компонент:

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

Замість розміру ви можете навіть використовувати модифікатор розміру, він може стати зручнішим.

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


2
Дорога +1 або -1 мені, тому що мій поточний представник 666 з 9 листопада ... Це моторошно.
Койот

1

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

У C ++ або C # це зазвичай означає, що у вас є метод шаблону на предмет T GetComponent<T>() . І як тільки у вас є ця посилання, ви точно знаєте, які дані учасників вони містять, тому просто отримуйте доступ до них безпосередньо.

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

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


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

Просто виберіть існуючий, якщо він підходить, або розподіліть властивість на новий компонент, якщо він не відповідає. Наприклад, у Unity є компонент "Трансформація", який є лише позицією, і якщо щось інше потрібно змінити позицію об'єкта, вони роблять це через цей компонент.
Килотан

1

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

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

"... моє запитання стосується того, куди ці властивості мають переходити, особливо там, де властивість використовується декількома компонентами, і жоден компонент не може бути власником цього. Дивіться мій 3-й та 4-й коментарі до цього питання."

Немає загального правила. Залежить від конкретної властивості (див. Вище).

Створіть гру з потворною, але на основі компонентів архітектурою, а потім переробляйте її.


Я не думаю, що я цілком розумію, що PhysicsComponentробити тоді. Я вважаю це керуванням, що імітує об'єкт у фізичному середовищі, що призводить мене до такої плутанини: Не всі речі, які потрібно винести, потрібно буде імітувати, тому мені здається неправильним додавати, PhysicsComponentколи я додаю, RenderingComponentоскільки він містить позицію що RenderingComponentвикористовує. Я міг легко бачити, як я закінчується мережею взаємопов'язаних компонентів, тобто все / більшість потрібно додавати до кожної сутності.
Джордж Дакетт

Насправді у мене була подібна ситуація :). У мене є PhysicsBodyComponent та SimpleSpatialComponent. Обидва вони забезпечують положення, кут і розмір. Але перший бере участь у фізичному моделюванні та має додаткові відповідні властивості, а другий просто містить ці просторові дані. Якщо у вас є власний фізичний двигун, ви можете навіть успадкувати перший від другого.
День

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

1

Ваша кишка розповідає вам , що маючи ThingProperty, ThingComponentі ThingManagerдля кожногоThing типу компонента трохи надлишкова. Я думаю, що це правильно.

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

TransformPropertyбуде досить звичайним. Але хто це відповідає за систему візуалізації? Фізична система? Звукова система? Чому б аTransform компонент навіть потребує оновлення?

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

Читайте про Артеміду: http://piemaster.net/2011/07/entity-component-artemis/

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

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

Потім під час фази оновлення циклу, ваш Systems тепер має список тих об'єктів, які потрібно оновити. Тепер ви можете мати деталізацію компонентів , так що ви можете розробити маячні системи, будувати об'єкти з ModelComponent, TransformComponent, FliesLikeSupermanComponent, іSocketInfoComponent , щоб зробити річ - щось дивне , як макіяж літаюча тарілка , яка летить між клієнтами підключені до багатокористувацької грі. Гаразд, можливо, не так, але ідея полягає в тому, що він тримає речі нерозділеними та гнучкими.

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

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