Başlık kasıtlı olarak hiperboliktir ve sadece desenle deneyimim olmayabilir, ancak işte mantığım:
Varlıkları uygulamanın "olağan" veya tartışmasız açık yolu, onları nesne olarak uygulamak ve ortak davranışı alt sınıflamaktır. Bu klasik soruna yol açar " EvilTree
bir alt sınıfı Tree
veya Enemy
?". Birden fazla mirasa izin verirsek, elmas problemi ortaya çıkar. Bunun yerine , Tanrı sınıflarına götüren hiyerarşinin birleşik işlevselliğini Tree
ve Enemy
daha da üstünü alabiliriz veya kasıtlı olarak bizim Tree
ve Entity
sınıflarımızdaki davranışları (aşırı durumda arayüzler haline getirerek) dışarıda bırakabiliriz EvilTree
; kod çoğaltma hiç varsa SomewhatEvilTree
.
Varlık-Bileşen Sistemleri bu sorunu, Tree
ve Enemy
nesneyi farklı bileşenlere (örneğin Position
, Health
ve AI
- bölerek) bölerek çözmeye çalışır ve örneğin, AISystem
bir Yetki'nin konumunu AI kararlarına göre değiştiren sistemler gibi . Şimdiye kadar çok iyi ama EvilTree
bir powerup alabilir ve hasar verebilirse ne olur ? Öncelikle a CollisionSystem
ve a'ya ihtiyacımız var DamageSystem
(muhtemelen bunlara zaten sahibiz). CollisionSystem
İhtiyaçları ile iletişim kurmak için DamageSystem
: iki şey çarpışır Her zaman CollisionSystem
bir mesaj gönderir DamageSystem
o çıkarma böylece sağlık. Hasar aynı zamanda güçlendirmelerden de etkilenir, bu yüzden bunu bir yerde saklamamız gerekir. PowerupComponent
Varlıklara eklediğimiz yeni bir öğe mi yaratıyoruz ? Ama sonraDamageSystem
hiçbir şey bilmeyeceği bir şey bilmelidir - sonuçta, powerups'ı alamayan hasar veren şeyler de vardır (örneğin a Spike
). Bu cevaba benzer hasar hesaplamaları için kullanılan PowerupSystem
a'nın değiştirilmesine izin veriyor muyuz ? Ancak şimdi iki sistem aynı verilere erişiyor. Oyunumuz daha karmaşık hale geldikçe, bileşenlerin birçok sistem arasında paylaşıldığı somut olmayan bir bağımlılık grafiği haline gelecektir. Bu noktada global statik değişkenleri kullanabilir ve tüm kazan plakasından kurtulabiliriz.StatComponent
Bunu çözmenin etkili bir yolu var mı? Sahip olduğum bir fikir, bileşenlerin belirli işlevlere sahip olmasına izin vermekti, örneğin StatComponent
attack()
, varsayılan olarak sadece bir tamsayı döndüren ver, ancak bir güçlendirme gerçekleştiğinde oluşturulabilir:
attack = getAttack compose powerupBy(20) compose powerdownBy(40)
Bu, attack
birden çok sistem tarafından erişilen bir bileşende kaydedilmesi gereken sorunu çözmez, ancak en azından onu destekleyen bir dil varsa işlevleri düzgün bir şekilde yazabilirim:
// 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
Bu şekilde en azından sistemler tarafından eklenen çeşitli fonksiyonların doğru sıralanmasını garanti ederim. Her iki durumda da, burada işlevsel reaktif programlamaya hızla yaklaşıyorum gibi görünüyor, bu yüzden kendime bunu başından beri kullanmamam gerektiğini soruyorum (sadece FRP'ye baktım, bu yüzden burada yanlış olabilirim). ECS'nin karmaşık sınıf hiyerarşileri üzerinde bir gelişme olduğunu görüyorum ama bunun ideal olduğuna ikna olmadım.
Bunun etrafında bir çözüm var mı? ECS'yi daha temiz bir şekilde ayırmak için kaçırdığım bir işlevsellik / desen var mı? FRP bu sorun için kesinlikle daha uygun mu? Bu sorunlar sadece programlamaya çalıştığım şeyin doğasında var olan karmaşıklıktan mı kaynaklanıyor; yani FRP'nin benzer sorunları olur mu?