Название намеренно гиперболично, и это может быть просто моя неопытность с шаблоном, но вот мои рассуждения:
«Обычный» или, возможно, простой способ реализации сущностей состоит в том, чтобы реализовать их как объекты и создать подклассы общего поведения. Это приводит к классической проблеме «является 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 похожие проблемы?
источник
Ответы:
ECS полностью разрушает сокрытие данных. Это компромисс модели.
ECS отлично справляется с развязкой. Хороший ECS позволяет системе перемещения объявлять, что она работает с любым объектом, имеющим компонент скорости и положения, без необходимости заботиться о том, какие типы объектов существуют, или какие другие системы имеют доступ к этим компонентам. По крайней мере, это эквивалентно разделению мощности на то, что игровые объекты реализуют определенные интерфейсы.
Две системы, имеющие доступ к одним и тем же компонентам, являются функцией, а не проблемой. Это вполне ожидаемо и никак не связывает системы. Это правда, что у систем будет неявный граф зависимостей, но эти зависимости присущи моделируемому миру. Сказать, что система повреждений не должна иметь явной зависимости от системы бонусов, значит утверждать, что бонусы не влияют на повреждения, и это, вероятно, неправильно. Однако, хотя зависимость существует, системы не связаны между собой - вы можете удалить систему powerup из игры, не затрагивая систему повреждений, потому что связь происходила через компонент stat и была полностью неявной.
Устранение этих зависимостей и систем упорядочения может быть выполнено в едином центральном месте, подобно тому, как работает разрешение зависимостей в системе DI. Да, сложная игра будет иметь сложный граф систем, но эта сложность присуща, и, по крайней мере, она содержится.
источник
Практически невозможно обойти тот факт, что системе необходим доступ к нескольким компонентам. Для того, чтобы что-то вроде VelocitySystem работало, ему, вероятно, потребуется доступ к VelocityComponent и PositionComponent. Между тем система RenderingSystem также должна иметь доступ к этим данным. Независимо от того, что вы делаете, в какой-то момент система рендеринга должна знать, где визуализировать объект, а VelocitySystem должна знать, куда перемещать объект.
Для этого вам нужна явная зависимость. Каждая система должна четко указывать, какие данные она будет читать и какие данные она будет записывать. Когда система хочет получить определенный компонент, она должна делать это только явно . В своей простейшей форме он просто имеет компоненты для каждого требуемого типа (например, для RenderSystem нужны RenderComponents и PositionComponents) в качестве аргументов и возвращает все, что было изменено (например, только для RenderComponents).
Вы можете заказать в таком дизайне. Ничто не говорит о том, что для ECS ваши системы должны быть независимы от порядка или чего-либо подобного.
Использование этого Entity-component-system design и FRP не является взаимоисключающим. Фактически, системы можно рассматривать как ничто иное, как не имеющие состояния, просто выполняющие преобразования данных (компоненты).
FRP не решит проблему необходимости использования информации, которая вам необходима для выполнения какой-либо операции.
источник