Чи не є система-компонент суттєвою для роз'єднання / приховування інформації?


11

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

"Звичайний" або, мабуть, прямолінійний спосіб реалізації суб'єктів - це реалізація їх як об'єктів та підкласи загальної поведінки. Це призводить до класичної проблеми "є EvilTreeпідкласом Treeабо Enemy?". Якщо дозволити багаторазове успадкування, виникає алмазна проблема. Ми могли б замість цього витягнути комбіновану функціональність Treeі Enemyвдосконалити ієрархію, що веде до класів Бога, або ми можемо навмисно залишити поведінку в наших Treeі Entityкласах (створюючи їх інтерфейси в крайньому випадку), щоб EvilTreeміг реалізувати саме це - що призводить до дублювання коду, якщо у нас коли-небудь є SomewhatEvilTree.

Entity-Component Systems намагаються вирішити цю проблему, розділивши Treeі Enemyоб'єкт на різні компоненти - скажімо Position, Healthта AI- та впровадивши такі системи, як, наприклад, AISystemщо змінює позицію Entitiy відповідно до рішень про інтелектуальне управління. Поки що добре, але що робити, якщо EvilTreeможна забрати живлення та завдати шкоди? Спочатку нам потрібні a CollisionSystemі a DamageSystem(ми, мабуть, їх уже маємо). В CollisionSystemпотреби спілкуватися з DamageSystem: Кожен раз , коли дві речі зіштовхнути CollisionSystemпосилає повідомлення DamageSystemздоров'я , щоб він міг вичитати. На пошкодження також впливають живлення, тому нам потрібно зберігати їх десь. Чи створюємо ми нове, PowerupComponentяке ми приєднуємо до сутностей? Але тодіDamageSystemпотрібно знати про щось, про що він швидше нічого не знає - зрештою, є також речі, які завдають шкоди, які не можуть забрати бонуси (наприклад, a Spike). Чи дозволяємо ми PowerupSystemмодифікувати a, StatComponentякий також використовується для розрахунків пошкоджень, подібних до цієї відповіді ? Але тепер дві системи мають доступ до одних і тих же даних. У міру того, як наша гра стає складнішою, вона стала би нематеріальною графіком залежності, де компоненти поділяються між багатьма системами. У цей момент ми можемо просто використовувати глобальні статичні змінні та позбутися всіх котлів.

Чи є ефективний спосіб вирішити це? Однією з ідей у ​​мене було дозволити компонентам виконувати певні функції, наприклад, дати ті, StatComponent attack()які просто повертають ціле число за замовчуванням, але вони можуть бути складені, коли відбувається живлення:

attack = getAttack compose powerupBy(20) compose powerdownBy(40)

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

// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup

// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage

// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity

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

Чи є рішення щодо цього? Чи є функціональність / зразок, який мені не вистачає для більш чіткого відключення ECS? FRP просто суворо підходить для цієї проблеми? Чи виникають ці проблеми лише з притаманної складності того, що я намагаюся запрограмувати; тобто чи мали б FRP подібні проблеми?



Я дуже сумую за блогом Еріка (з тих пір, коли мова йшла про C #).
OldFart

Відповіді:


21

ECS повністю руйнує приховування даних. Це компроміс цієї схеми.

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

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

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


7

Майже немає можливості обійти той факт, що системі потрібно отримати доступ до декількох компонентів. Для того, щоб щось на кшталт VelocitySystem працювало, можливо, знадобиться доступ до VelocityComponent та PositionComponent. Тим часом RenderingSystem також потребує доступу до цих даних. Незалежно від того, що ви робите, в якийсь момент система візуалізації повинна знати, куди потрібно винести об'єкт, і система VelocitySystem повинна знати, куди перемістити об’єкт.

Для цього вам потрібна явність залежностей. Кожна система повинна мати чітку інформацію про те, які дані вона буде читати та які дані вона запише. Коли система хоче отримати певний компонент, це потрібно робити лише явно . У своїй найпростішій формі він просто містить компоненти для кожного потрібного йому типу (наприклад, RenderSystem потрібні RenderComponents і PositionComponents) як свої аргументи і повертає все, що він змінився (наприклад, лише RenderComponents).

Таким чином я гарантую принаймні правильне впорядкування різних функцій, що додаються системами

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

FRP просто суворо підходить для цієї проблеми? Чи виникають ці проблеми лише з притаманної складності того, що я намагаюся запрограмувати; тобто чи мали б FRP подібні проблеми?

Використання цього дизайну системи Entity-компонентів та FRP не є взаємовиключними. Насправді, системи можна розглядати як нічого іншого як такі, що не мають стану, просто виконують перетворення даних (компоненти).

FRP не вирішить проблему використання необхідної інформації для виконання певної операції.

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