Название намеренно гиперболично, и это может быть просто моя неопытность с шаблоном, но вот мои рассуждения:
«Обычный» или, возможно, простой способ реализации сущностей состоит в том, чтобы реализовать их как объекты и создать подклассы общего поведения. Это приводит к классической проблеме «является 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 похожие проблемы?