Це важкий. Я просто спробую вирішити деякі питання на основі мого конкретного досвіду (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 це не срібна куля, і я все ще відчуваю тенденцію зайти і додати нові системи, видалити деякі, додати нові компоненти, змінити існуючу систему, щоб забрати цей новий тип компонента тощо. Я не розумію все взагалі правильно вперше. Але різниця в моєму випадку полягає в тому, що я не змінюю нічого центрального, коли не можу передбачити певні дизайнерські потреби вперед. Я не отримую ефекту пульсації каскадних поломок, які вимагають від мене повсюди і змінити стільки коду, щоб впоратися з якоюсь новою потребою, яка врожаю, і це цілком економить час. Мені також простіше в моєму мозку, тому що коли я сідаю з певною системою, мені не потрібно знати / пам’ятати про щось інше, крім відповідних компонентів (які є лише даними), щоб працювати над цим.