Разве Entity-Component System ужасна для развязки / сокрытия информации?


11

Название намеренно гиперболично, и это может быть просто моя неопытность с шаблоном, но вот мои рассуждения:

«Обычный» или, возможно, простой способ реализации сущностей состоит в том, чтобы реализовать их как объекты и создать подклассы общего поведения. Это приводит к классической проблеме «является EvilTreeподклассом Treeили Enemy?». Если мы разрешаем множественное наследование, возникает проблема с алмазом. Вместо этого мы могли бы использовать объединенную функциональность иерархии, которая ведет к классам Бога, Treeи Enemyдалее продвигаться вверх, или мы можем намеренно исключить поведение в наших классах Treeи Entityклассах (делая их интерфейсами в крайнем случае), чтобы они EvilTreeмогли реализовать это сами - что приводит к дублирования кода , если мы когда - либо иметь SomewhatEvilTree.

Entity-компонентные системы пытаются решить эту проблему путем деления Treeи Enemyобъекта на различные компоненты - скажем Position, Healthи AI- и внедрить системы, такие как , AISystemчто изменяет положение Entitiy давал в соответствии с решениями AI. Пока все хорошо, но что, если EvilTreeможно подобрать бонусы и нанести урон? Сначала нам нужны a CollisionSystemи a DamageSystem(они, вероятно, уже есть). В CollisionSystemпотребности общаться с DamageSystem: Каждый раз , когда две вещи столкнуть CollisionSystemпосылает сообщение DamageSystemздоровья , чтобы он мог вычитать. Повреждения также зависят от бонусов, поэтому мы должны их где-то хранить. Создаем ли мы новое, PowerupComponentкоторое мы прикрепляем к сущностям? Но тогдаDamageSystemему нужно знать о чем-то, о чем он скорее ничего не знает - в конце концов, есть вещи, которые наносят урон, которые не могут поднять бонусы (например, а Spike). Разрешаем ли мы PowerupSystemмодифицировать объект, 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 позволяет системе перемещения объявлять, что она работает с любым объектом, имеющим компонент скорости и положения, без необходимости заботиться о том, какие типы объектов существуют, или какие другие системы имеют доступ к этим компонентам. По крайней мере, это эквивалентно разделению мощности на то, что игровые объекты реализуют определенные интерфейсы.

Две системы, имеющие доступ к одним и тем же компонентам, являются функцией, а не проблемой. Это вполне ожидаемо и никак не связывает системы. Это правда, что у систем будет неявный граф зависимостей, но эти зависимости присущи моделируемому миру. Сказать, что система повреждений не должна иметь явной зависимости от системы бонусов, значит утверждать, что бонусы не влияют на повреждения, и это, вероятно, неправильно. Однако, хотя зависимость существует, системы не связаны между собой - вы можете удалить систему powerup из игры, не затрагивая систему повреждений, потому что связь происходила через компонент stat и была полностью неявной.

Устранение этих зависимостей и систем упорядочения может быть выполнено в едином центральном месте, подобно тому, как работает разрешение зависимостей в системе DI. Да, сложная игра будет иметь сложный граф систем, но эта сложность присуща, и, по крайней мере, она содержится.


7

Практически невозможно обойти тот факт, что системе необходим доступ к нескольким компонентам. Для того, чтобы что-то вроде VelocitySystem работало, ему, вероятно, потребуется доступ к VelocityComponent и PositionComponent. Между тем система RenderingSystem также должна иметь доступ к этим данным. Независимо от того, что вы делаете, в какой-то момент система рендеринга должна знать, где визуализировать объект, а VelocitySystem должна знать, куда перемещать объект.

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

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

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

FRP просто лучше подходит для этой проблемы? Эти проблемы возникают из-за сложности, которую я пытаюсь запрограммировать; то есть будут ли у FRP похожие проблемы?

Использование этого Entity-component-system design и FRP не является взаимоисключающим. Фактически, системы можно рассматривать как ничто иное, как не имеющие состояния, просто выполняющие преобразования данных (компоненты).

FRP не решит проблему необходимости использования информации, которая вам необходима для выполнения какой-либо операции.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.