Il sistema di componenti di entità non è terribile per il disaccoppiamento / l'occultamento delle informazioni?

7

Il titolo è intenzionalmente iperbolico e potrebbe essere solo la mia inesperienza con lo schema, ma ecco il mio ragionamento:

Il modo "normale" o verosimilmente diretto di implementare le entità è implementarle come oggetti e sottoclasse comportamenti comuni. Questo porta al classico problema di "è un EvilTree una sottoclasse di Tree o Enemy ?". Se permettiamo l'ereditarietà multipla, sorge il problema dei diamanti. Potremmo invece tirare la funzionalità combinata di Tree e Enemy più in alto nella gerarchia che porta alle classi di Dio, oppure possiamo intenzionalmente omettere comportamenti nelle nostre classi Tree e Entity (rendendole interfacce nel caso estremo ) in modo che il EvilTree possa implementare quello stesso, il che porta alla duplicazione del codice se mai abbiamo un SomewhatEvilTree .

I sistemi Entity-Component cercano di risolvere questo problema dividendo l'oggetto Tree e Enemy in diversi componenti - diciamo Position , Health e AI - e implementano sistemi, come AISystem che modifica la posizione di una Entità secondo le decisioni dell'IA. Fin qui tutto bene, ma cosa succede se EvilTree può raccogliere un potenziamento e infliggere danno? Per prima cosa abbiamo bisogno di un CollisionSystem e un DamageSystem (probabilmente ne abbiamo già). CollisionSystem deve comunicare con DamageSystem : Ogni volta che due cose si scontrano, CollisionSystem invia un messaggio a DamageSystem in modo da poter sottrarre la salute. Anche il danno è influenzato dai potenziamenti quindi dobbiamo memorizzarlo da qualche parte. Creiamo un nuovo PowerupComponent che attribuiamo alle entità? Ma poi DamageSystem deve sapere qualcosa di cui preferirebbe non sapere nulla - dopo tutto, ci sono anche cose che infliggono danno che non possono raccogliere potenziamenti (ad esempio un Spike ). Consentiamo a PowerupSystem di modificare un StatComponent utilizzato anche per calcoli di danni simili a questa risposta ? Ma ora due sistemi accedono agli stessi dati. Man mano che il nostro gioco diventa più complesso, diventerebbe un grafico di dipendenza immateriale in cui i componenti sono condivisi tra molti sistemi. A quel punto possiamo semplicemente usare le variabili statiche globali e sbarazzarci di tutto il boilerplate.

C'è un modo efficace per risolvere questo? Un'idea che avevo era quella di lasciare che i componenti avessero determinate funzioni, ad es. dai il StatComponent attack() che restituisce un intero di default ma che può essere composto quando si verifica un powerup:

attack = getAttack compose powerupBy(20) compose powerdownBy(40)

Questo non risolve il problema che attack deve essere salvato in un componente a cui si accede da più sistemi, ma almeno potrei digitare correttamente le funzioni se ho una lingua che la supporti sufficientemente:

// 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

In questo modo garantisco almeno il corretto ordinamento delle varie funzioni aggiunte dai sistemi. Ad ogni modo, sembra che mi stia rapidamente avvicinando alla programmazione reattiva funzionale, quindi mi chiedo se non avrei dovuto usarlo all'inizio (ho appena guardato in FRP, quindi potrei sbagliarmi qui). Vedo che ECS è un miglioramento rispetto alle gerarchie di classi complesse, ma non sono convinto che sia l'ideale.

C'è una soluzione intorno a questo? C'è una funzionalità / modello che mi manca per disaccoppiare ECS in modo più pulito? Il FRP è solo strettamente più adatto a questo problema? Questi problemi derivano solo dalla complessità intrinseca di ciò che sto cercando di programmare; Ad esempio, il FRP avrebbe problemi simili?

    
posta PawkyPenguin 13.06.2018 - 15:46
fonte

2 risposte

12

ECS rovina completamente i dati nascosti. Questo è un compromesso del modello.

ECS è eccellente al disaccoppiamento. Un buon ECS consente a un sistema di spostamento di dichiarare che funziona su qualsiasi entità che ha un componente di velocità e posizione, senza doversi preoccupare dei tipi di entità esistenti o di altri sistemi che accedono a tali componenti. Questo è almeno equivalente nel disaccoppiare il potere di avere oggetti di gioco che implementano determinate interfacce.

Due sistemi che accedono agli stessi componenti sono una caratteristica, non un problema. È completamente previsto e non accoppia in alcun modo i sistemi. È vero che i sistemi avranno un grafico di dipendenza implicito, ma tali dipendenze sono inerenti al mondo modellato. Dire che il sistema di danno non dovrebbe avere la dipendenza implicita dal sistema di accensione è sostenere che i potenziamenti non influiscono sul danno, e questo è probabilmente sbagliato. Tuttavia, mentre la dipendenza esiste, i sistemi non sono accoppiati - puoi rimuovere il sistema di potenziamento dal gioco senza intaccare il sistema di danno, perché la comunicazione è avvenuta attraverso il componente stat ed era completamente implicita. / p>

La risoluzione di queste dipendenze e dei sistemi di ordinazione può essere eseguita in un'unica posizione centrale, in modo analogo a come funziona la risoluzione delle dipendenze in un sistema DI. Sì, un gioco complesso avrà un grafico complesso di sistemi, ma questa complessità è intrinseca, e almeno è contenuta.

    
risposta data 14.06.2018 - 10:17
fonte
4

Non c'è quasi modo di aggirare il fatto che un sistema ha bisogno di accedere a più componenti. Affinché qualcosa come un VelocitySystem funzioni, probabilmente avrà bisogno di accedere a VelocityComponent e PositionComponent. Nel frattempo anche il RenderingSystem deve accedere a questi dati. Qualunque cosa tu faccia, a un certo punto il sistema di rendering deve sapere dove eseguire il rendering dell'oggetto e il VelocitySystem deve sapere dove spostare l'oggetto.

Ciò che ti serve per questo è explicitness delle dipendenze. Ogni sistema deve essere esplicito su quali dati leggerà e su quali dati verrà scritto. Quando un sistema vuole recuperare un particolare componente, deve essere in grado di eseguire questo solo esplicitamente . Nella sua forma più semplice, ha semplicemente i componenti per ogni tipo richiesto (ad esempio RenderSystem ha bisogno di RenderComponents e PositionComponents) come suoi argomenti e restituisce tutto ciò che è stato modificato (ad esempio solo RenderComponents).

This way I at least guarantee correct ordering of the various functions added by systems

Puoi avere un ordine in tale design. Niente sta dicendo che per ECS i tuoi sistemi devono essere indipendenti dall'ordine o da qualsiasi cosa del genere.

Is FRP just strictly better suited for this problem? Are these problems just arising out of inherent complexity of what I'm trying to program; i.e. would FRP have similar issues?

L'uso di questo design di sistema di componenti Entity e FRP non si escludono a vicenda. In effetti, i sistemi possono essere visti come nient'altro che non hanno stato, semplicemente eseguendo trasformazioni di dati (i componenti).

FRP non risolverebbe il problema di dover utilizzare le informazioni necessarie per eseguire alcune operazioni.

    
risposta data 13.06.2018 - 16:16
fonte

Leggi altre domande sui tag