Чому погана ідея зберігати методи в Entities and Components? (Поряд із деякими іншими питаннями системи Entity.)


16

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

Ця відповідь допомогла мені зрозуміти Entity Systems навіть краще, ніж стаття.

Я прочитав (так,) статтю про Entity Systems, і вона мені сказала наступне:

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

Це здається дійсно практичним у багатьох ситуаціях, але частина про компоненти, які є лише класами даних, мене турбує. Наприклад, як я міг реалізувати свій клас Vector2D (Позиція) в системі Entity?

Клас Vector2D містить дані: координати x і y, але він також має методи , які мають вирішальне значення для його корисності та відрізняють клас лише від масиву з двох елементів. Приклад методу: add(), rotate(point, r, angle), substract(),normalize() , і всі інші стандартний, корисні, і абсолютно необхідні методи , що позиції (які є екземплярами класу Vector2D) повинні мати.

Якби компонент був лише власником даних, він не міг би використовувати ці методи!

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

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

Подумайте про це таким чином; в грі є багато різних видів зображуваних предметів. Звичайні старі зображення, анімації ( update(), getCurrentFrame()і т. Д.), Комбінації цих примітивних типів, і всі вони могли просто надати draw()метод системі візуалізації, який потім не повинен дбати про те, як реалізується спрайт сутності про інтерфейс (малюнок) та положення. І тоді мені знадобиться лише система анімації, яка викликала б конкретні анімаційні методи, що не мають нічого спільного з візуалізацією.

І лише одне інше ... Чи дійсно є альтернатива масивам, коли мова йде про зберігання компонентів? Я не бачу іншого місця для зберігання компонентів, крім масивів всередині класу Entity ...

Можливо, це кращий підхід: зберігайте компоненти як прості властивості сутностей. Наприклад, позиційний компонент буде наклеєний на entity.position.

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


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

Відповіді:


25

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

EDIT

Для уточнення, ваша мета - не уникати OOP . Це було б досить складно в більшості поширених мов, якими користуються в наші дні. Ви намагаєтеся мінімізувати спадщину , що є великим аспектом OOP, але не потрібно. Ви хочете позбутися від Object-> MobileObject-> Creature-> Bipedal-> спадщини людського типу.

Однак нормально мати певну спадщину! Ви маєте справу з мовою, на яку сильно впливає спадщина, дуже важко не використовувати її. Наприклад, ви можете мати Componentклас або інтерфейс, який розширюють або реалізують всі ваші інші компоненти. Те саме стосується вашого Systemкласу. Це значно спрощує справи. Настійно рекомендую поглянути на рамки Artemis . Це відкритий код, і у нього є кілька прикладних проектів. Відкрийте ці речі і подивіться, як це працює.

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

Суб'єкти та компоненти зберігаються в окремому менеджері.

Стратегія, яку ви згадуєте, змушує організації зберігати власні компоненти ( entity.position), є протилежним темі компонентів сутності, але цілком прийнятна, якщо ви вважаєте, що це має найбільше сенс.


1
Хм, це значно спрощує ситуацію, дякую! Я подумав, що відбувається якась магічна річ, "про яку ти пізніше пошкодуєш", і я просто не могла її побачити!
jcora

1
Так, я повністю використовую їх у своїй системі компонентів сутності. У мене навіть є деякі компоненти, які успадковують від звичайного батька, задихаючись . Я думаю, що ви тільки пошкодуєте, якщо б намагалися обійтись, не використовуючи таких методів. Вся справа в тому, щоб зробити те, що має для вас найбільше сенсу. Якщо є сенс використовувати успадкування або ввести якісь методи в компонент, перейдіть до цього.
МайклХаус

2
Я дізнався з останньої відповіді на цю тему. Відмова від відповідальності: Я не кажу , що це спосіб зробити це. :)
МайклХаус

1
Так, я знаю, як страшно може бути вивчення нової парадигми. На щастя, ви можете використовувати аспекти старої парадигми, щоб полегшити справи! Я оновив свою відповідь інформацією про зберігання. Якщо ви дивитесь на Артеміду, перегляньте, EntityManagerяк там зберігаються речі.
MichaelHouse

1
Приємно! Це буде досить солодким двигуном, коли це буде зроблено. Удачі вам! Дякуємо, що задали цікаві запитання.
MichaelHouse

10

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

Це здається дійсно практичним у багатьох ситуаціях, але частина про компоненти, які є лише класами даних, мене турбує. Наприклад, як я міг реалізувати свій клас Vector2D (Позиція) в системі Entity?

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

Ігноруйте біт у статті, який говорить про те, що це не OOP - це так само OOP, як і будь-який інший підхід. Коли більшість компіляторів або мовних програм реалізують об'єктні методи, це, як і будь-яка інша функція, за винятком прихованого аргументу, який називається thisабо self, який є вказівником на місце в пам'яті, де зберігаються дані цього об'єкта. У системі, що базується на компонентах, ідентифікатор сутності може бути використаний для пошуку місця відповідних компонентів (і, таким чином, даних) для даної сутності. Таким чином, ідентифікатор сутності еквівалентний цьому / самовказівці, і поняття в основному те саме, просто трохи переставлене.

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

Добре. Методи - це ефективний спосіб організації вашого коду. Важливо відійти від ідеї "уникати OOP" - уникати використання успадкованих місць для розширення функціональності. Натомість розбийте функціонал на компоненти, які можна комбінувати, щоб зробити те саме.

Подумайте про це таким чином; в грі є багато різних видів зображуваних предметів. Звичайні старі зображення, анімації (update (), getCurrentFrame () тощо), комбінації цих примітивних типів, і всі вони могли просто надати метод draw () для системи візуалізації [...]

Ідея системи, що базується на компонентах, полягає в тому, що ви б не мали для них окремих класів, але мали б один клас Об'єкт / Сутність, а зображення було б Об'єктом / Суб'єктом, що має ImageRenderer, анімації були б Об'єктом / Сутність, яка має AnimationRenderer тощо. Відповідні системи знають, як рендерувати ці компоненти, і тому не потрібно мати базового класу методом Draw ().

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

Звичайно, але це не добре працює з компонентами. У вас є 3 варіанти:

  • Кожен компонент реалізує цей інтерфейс і має метод Draw (), навіть якщо нічого не малюється. Якби ви робили це для кожного біта функціональності, то компоненти виглядали б досить некрасиво.
  • Тільки компоненти, на яких є що намалювати, реалізують інтерфейс - але хто вирішує, на які компоненти слід викликати Draw ()? Чи має система якось запитувати кожен компонент, щоб побачити, який інтерфейс підтримується? Це було б схильним до помилок і потенційно складно реалізувати в деяких мовах.
  • Компоненти коли-небудь обробляються їх власною системою (про що ідея у прив'язаній статті). У цьому випадку інтерфейс не має значення, оскільки система точно знає, з яким класом або типом об'єкта працює.

І лише одне інше ... Чи дійсно є альтернатива масивам, коли мова йде про зберігання компонентів? Я не бачу іншого місця для зберігання компонентів, крім масивів всередині класу Entity ...

Ви можете зберігати компоненти в системі. Масив - це не проблема, але де ви зберігаєте компонент.


+1 Дякую за іншу точку зору. Добре отримати кілька, коли маєш справу з такою неоднозначною темою! Якщо ви зберігаєте компоненти в системі, чи означає це, що компоненти можуть коли-небудь змінюватися однією системою? Наприклад, система малювання та система руху мали б доступ до позиційного компонента. Де ви його зберігаєте?
MichaelHouse

Що ж, вони зберігатимуть лише вказівник на ті компоненти, які, якщо я занепокоєний, десь знаходяться ... Крім того, навіщо ви зберігати компоненти в системах? Чи є в цьому перевага?
jcora

Я прав, @Kylotan? Ось як я це зробив, здається логічним ...
jcora

У прикладі Адама / Т-машини наміром є те, що існує 1 система на компонент, але система, безумовно, може отримати доступ до інших компонентів та змінювати їх. (Це перешкоджає багатопотоковій
перевазі

1
Зберігання компонентів у системі дозволяє краще орієнтуватися на цю систему - ця система працює лише (в цілому) з цими даними, тож навіщо перебирати всю пам'ять комп'ютера від сутності до сутності, щоб отримати її? Це також допомагає з одночасністю, оскільки ви можете розмістити цілу систему та її дані на одному ядрі чи процесорі (або навіть окремому комп'ютері, в MMO). Знову ж таки, ці переваги зменшуються, коли 1 система отримує доступ до більш ніж 1 типу компонентів, так що це слід враховувати при вирішенні питання про розподіл обов'язків компонента / системи.
Кілотан

2

Вектор - це дані. Функції більше схожі на функції утиліти - вони не характерні для цього примірника даних, вони можуть застосовуватися до всіх векторів незалежно. Гарний спосіб мислення про це: Чи можна ці функції переписати як статичні методи? Якщо так, то це просто корисність.


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