OOP ECS проти чистого ECS


11

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

Протягом минулого місяця я багато читав про Entity-Component-Systems, і зараз цілком задоволений цією концепцією. Однак є один аспект, який, здається, не вистачає чіткого «визначення», і різні статті пропонують докорінно різні рішення:

Це питання про те, чи повинен ЕЦП порушувати інкапсуляцію чи ні. Іншими словами, його ECS-стиль у стилі OOP (компоненти - це об'єкти як із станом, так і з поведінкою, які інкапсулюють специфічні для них дані) проти чистого ECS (компоненти - це структури c стилю, які мають лише загальнодоступні дані, а системи забезпечують функціональність).

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

Проблеми з підходом ООП

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

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

Проблеми з чистим підходом

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

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

  • Потрібні додаткові прапори. Візьмемо, наприклад, компонент карти плитки. Він матиме розмір, розмір плитки та поле списку індексів. Система карти плитки оброблятиме відповідний вершиновий масив та призначає координати текстури на основі даних компонента. Однак перерахувати всю карту плитки кожен кадр коштує дорого. Тому потрібен список, щоб відслідковувати всі внесені зміни, щоб потім оновити їх у системі. У способі OOP це може бути інкапсульовано компонентом карти плитки. Наприклад, метод SetTile () оновлюватиме вершину масиву щоразу, коли він викликається.

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

Крім того, я не так зацікавлений у продуктивності, тому вся ця ідея дизайну, орієнтованого на дані, і недоліки каси не дуже важлива для мене. Я просто хочу гарної архітектури ^^

Але більшість статей та обговорень, які я читаю, пропонують другий підхід. ЧОМУ?

Анімація

Нарешті, я хочу задати питання, як я б обробляв анімацію в чистому ECS. В даний час я визначив анімацію як функтор, який маніпулює сутністю на основі певного прогресу між 0 і 1. У складі анімації є список аніматорів, у яких є список анімацій. Потім у своїй функції оновлення він застосовує будь-які анімації, які зараз активні для сутності.

Примітка:

Я щойно прочитав цей пост Чи орієнтований об'єкт архітектури Entity Component System за визначенням? що пояснює проблему трохи краще, ніж я. Незважаючи на те, що він перебуває в одній темі, він все ще не дає відповідей на те, чому чистіший підхід до даних є кращим.


1
Можливо, просте, але серйозне питання: чи знаєте ви переваги / недоліки ECS? Це здебільшого пояснює "чому".
Караміріель

Ну, я розумію перевагу використання компонентів, тобто складу, а не успадкування, щоб уникнути діаманту смерті через багаторазове успадкування. Використання компонентів дозволяє також маніпулювати поведінкою під час виконання. І вони модульні. Я не розумію, чому потрібен поділ даних і функцій. Моя поточна реалізація знаходиться на github github.com/AdrianKoch3010/MarsBaseProject
Адріан Кох

Ну, я не маю достатньо досвіду роботи з ECS, щоб додати повну відповідь. Але композиція використовується не просто, щоб уникнути DoD; ви також можете створювати (унікальні) сутності під час виконання, які важко (ер) генерувати, використовуючи підхід OO. Однак, розбиття даних / процедур дозволяє простіше обґрунтувати дані. Ви можете легко здійснити серіалізацію, збереження стану, скасування / повтор тощо. Оскільки легко обґрунтувати дані, їх також простіше оптимізувати. Ви, швидше за все, можете розділити об'єкти на партії (багатопотокові) або навіть завантажити його на інше обладнання, щоб отримати повний потенціал.
Caramiriel

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

Відповіді:


10

Це важкий. Я просто спробую вирішити деякі питання на основі мого конкретного досвіду (YMMV):

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

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

введіть тут опис зображення

... і це:

введіть тут опис зображення

... або це:

введіть тут опис зображення

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

Тому? Аналогічний (або буквальний) еквівалент vtable і віртуальної відправки можна викликати через систему, а не об'єкт, що приховує його базовий стан / дані. Поліморфізм все ще дуже практичний і здійсненний із застосуванням "чистого" ЕЦП, коли аналогічний vtable або функціональний покажчик (и) перетворюється на "види" подібних даних для системи.

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

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

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

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

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

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

Однак перерахувати всю карту плитки кожен кадр коштує дорого. Тому потрібен список, щоб відслідковувати всі внесені зміни, щоб потім оновити їх у системі. У способі OOP це може бути інкапсульовано компонентом карти плитки. Наприклад, метод SetTile () оновлюватиме вершину масиву щоразу, коли він викликається.

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

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

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

Залежності між компонентами все ще існують, хоча їх приховують у системах

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

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

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

Залежності від даних

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

Розробники продовжували знаходити причини додавати, видаляти або змінювати функції цього центрального інтерфейсу, і кожна зміна була жахливою і дорогою, оскільки вона, як правило, ламала кожен окремий клас, що реалізовується IMotionразом із кожним місцем у використовуваній системі IMotion. Тим часом увесь час із такою кількістю болісних та каскадних змін усі об'єкти, що реалізуються IMotion, просто зберігали матрицю 4x4 з поплавцями, і весь інтерфейс стосувався лише того, як перетворити та отримати доступ до цих плавців; представлення даних було стабільним з самого початку, і багато болю можна було б уникнути, якби цей централізований інтерфейс, настільки схильний до змін із непередбачуваними потребами дизайну, навіть не існував в першу чергу.

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

введіть тут опис зображення

Нарешті, я хочу задати питання, як я б обробляв анімацію в чистому ECS. В даний час я визначив анімацію як функтор, який маніпулює сутністю на основі певного прогресу між 0 і 1. У складі анімації є список аніматорів, у яких є список анімацій. Потім у своїй функції оновлення він застосовує будь-які анімації, які зараз активні для сутності.

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

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

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