Composizione OOP pesante vs sistemi componenti entità pura? [chiuso]


13

Lo ammetto, ho commesso il peccato di abusare e persino di abusare dell'eredità. Il primo progetto di gioco (testuale) che ho realizzato durante il mio corso OOP è andato fino a "Porta chiusa" e "Porta aperta" da "Porta" e "Stanza con una porta", "Stanza con due porte" e così via da "Room".

Con il gioco (grafico) a cui ho lavorato di recente, pensavo di aver imparato la lezione e di limitare l'uso dell'ereditarietà. Tuttavia ho notato presto che i problemi iniziano ad apparire. La mia classe radice stava iniziando a gonfiarsi sempre di più e le mie classi foglia erano piene di codici duplicati.

Ho pensato che stavo ancora facendo cose sbagliate, e dopo averlo cercato online ho scoperto che non ero l'unico con questo problema. È così che ho scoperto i sistemi Entity dopo alcune ricerche approfondite (leggi: googlefu)

Quando ho iniziato a leggerlo, sono stato in grado di vedere con quanta chiarezza fosse in grado di risolvere i problemi che avevo con la gerarchia OOP tradizionale con i componenti. Questi erano tuttavia nelle prime letture. Quando mi sono imbattuto in altri ... approcci ES "radicali", come quello della T-machine .

Ho iniziato a non essere d'accordo con i metodi che stavano usando. Un sistema a componenti puri sembrava eccessivo, o piuttosto non intuitivo, che è probabilmente il punto di forza di OOP. L'autore si spinge fino a dire che il sistema ES è l'opposto di OOP, e mentre può essere utilizzabile con OOP, in realtà non dovrebbe. Non sto dicendo che è sbagliato, ma non mi sentivo come una soluzione che vorrei implementare.

Quindi, per me, e risolvere i problemi che avevo all'inizio del post, senza andare contro le mie intuizioni, è ancora usare una gerarchia, tuttavia non sarà una gerarchia monolitica come quelle che ho usato prima, ma piuttosto uno polilitico (non riuscivo a trovare una parola opposta al monolitico), che consiste di diversi alberi più piccoli.

L'esempio seguente mostra cosa intendo (questo è ispirato a un esempio che ho trovato in Game Engine Architecture, capitolo 14).

Avrei un piccolo albero per i veicoli. La classe del veicolo radice avrebbe un componente di rendering, un componente di collisione, un componente di posizione ecc.

Quindi un serbatoio, una sottoclasse di veicoli erediterebbe quei componenti da esso, e gli sarebbe stato dato il proprio componente "cannone".

Lo stesso vale per i personaggi. Un personaggio avrebbe i suoi componenti, quindi la Classe giocatore lo erediterebbe e gli sarebbe stato assegnato un controller di input, mentre le altre classi nemiche avrebbero ereditato dalla classe Character e gli sarebbe stato dato un controller AI.

Non vedo davvero alcun problema con questo design. Nonostante non utilizzi un puro Entity Controller System, il problema con l'effetto bubbling e la grande classe root viene risolto utilizzando una gerarchia multi-albero e il problema delle pesanti foglie che duplicano il codice scompare poiché le foglie non lo fanno avere un codice per cominciare, solo componenti. Se è necessario apportare una modifica al livello foglia, è semplice come modificare un singolo componente, anziché copiare e incollare il codice ovunque.

Naturalmente, essendo inesperto quanto me, non ho riscontrato alcun problema quando ho iniziato a utilizzare la singola gerarchia, il modello pesante ereditario, quindi se ci sono problemi con il modello che sto pensando di implementare, non lo farei essere in grado di vederlo.

Le tue opinioni?

PS: sto usando Java, quindi non è possibile utilizzare l'ereditarietà multipla per implementare questo invece di utilizzare componenti normali.

PPS: le comunicazioni intercomponenti verranno eseguite collegando tra loro componenti dipendenti. Questo porterà all'accoppiamento, ma penso che sia un giusto compromesso.


2
Tutto questo mi sembra perfetto. C'è un esempio specifico nella tua gerarchia o nel tuo sistema di entità che ti "annusa"? Alla fine, si tratta di fare il gioco più di un certo senso di purezza.
drhayes,

Non lo so, ma come ho detto, ho a malapena qualche esperienza e sono tutt'altro che il miglior giudice per questo.
Midori Ryuu,

3
Ho votato per chiudere questa domanda perché è soggettiva e aperta a un'ampia discussione. Apprezzo che sia stato chiesto da una posizione sincera, ma scegliere come procedere qui è interamente una questione di opinione personale. L'unico vero svantaggio di fissare i componenti in una sottoclasse è che la disposizione dei componenti non è modificabile in fase di esecuzione - ma questo deve sicuramente essere evidente per te ed è una scelta che fai.
Kylotan,

4
Ho votato anche per chiudere. È una domanda buona e ben scritta, ma non appropriata per GDSE. Potresti provare a ripubblicare su GameDev.net.
Sean Middleditch,

Come scritto, con la domanda "Le tue opinioni?", È davvero aperto e senza risposta. Tuttavia, è possibile rispondere se si risponde come se la domanda fosse: "Ci sono trappole aperte che potrei cadere senza accorgermene?" (Almeno, se pensi che ci sia in effetti una differenza empirica tra le varie architetture.)
John Calsbeek,

Risposte:


8

Considera questo esempio:

Stai facendo un RTS. In un adattamento di logica completa, si decide di creare una classe base GameObject, quindi due sottoclassi Buildinge Unit. Questo funziona bene, ovviamente, e si finisce con qualcosa che assomiglia a questo:

  • GameObject
    • ModelComponent
    • CollisionComponent
    • ...
  • Building
    • ProductionComponent
    • ...
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent
    • ...

Ora ogni sottoclasse Unitche fai ha già tutti i componenti di cui hai bisogno. E come bonus, hai un unico posto dove mettere tutto quel noioso codice di configurazione che newè un a WeaponComponent, lo collega a un AimingAIComponente un - ParticleEmitterComponentmeraviglioso!

E, naturalmente, poiché continua a scomporre la logica in componenti, quando alla fine decidi di voler aggiungere una Towerclasse che è un edificio ma ha un'arma, puoi gestirla. È possibile aggiungere an AimingAIComponent, a WeaponComponente a ParticleEmitterComponentalla sottoclasse di Building. Ovviamente, devi esaminare e estrarre il codice di inizializzazione dalla Unitclasse e inserirlo altrove, ma non è una grande perdita.

E ora, a seconda di quanta lungimiranza hai avuto, potresti o meno finire con bug sottili. Si scopre che potrebbe esserci stato del codice in altre parti del gioco simile a questo:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Non funziona silenziosamente per il tuo Toweredificio, anche se volevi davvero che funzionasse per qualsiasi cosa con un'arma. Ora deve assomigliare a questo:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Molto meglio! Ti viene in mente dopo aver cambiato questo che uno qualsiasi dei metodi di accesso che hai scritto potrebbe finire in questa situazione. Quindi la cosa più sicura da fare è eliminarli tutti e accedere a tutti i componenti tramite questo unico getComponentmetodo, che può funzionare con qualsiasi sottoclasse. (In alternativa, è possibile aggiungere ogni singolo tipo di get*Component()superclasse GameObjecte sovrascriverli in sottoclassi per restituire il componente. Presumo, tuttavia, il primo.)

Ora il tuo Buildinge le Unitclassi sono praticamente solo sacchi di componenti, senza nemmeno accessi su di essi. E nemmeno un'inizializzazione fantasiosa, perché la maggior parte di ciò doveva essere trascinato fuori per evitare la duplicazione del codice con il tuo caso speciale altro oggetto da qualche parte.

Se lo segui all'estremo, finirai sempre per rendere le sottoclassi sacche di componenti completamente inerti, a quel punto non c'è nemmeno alcun punto che abbia le sottoclassi intorno. Puoi ottenere quasi tutti i vantaggi di avere una gerarchia di classi con una gerarchia di metodi che crea i componenti corretti. Invece di avere una Unitclasse, hai un addUnitComponentsmetodo che fa una AnimationComponente chiama anche addWeaponComponents, che fa una AimingAIComponent, una WeaponComponente una ParticleEmitterComponent. Il risultato è che hai tutti i vantaggi di un sistema di entità, tutto il riutilizzo del codice di una gerarchia e nessuna tentazione di controllare instanceofo trasmettere al tuo tipo di classe.


Ben detto. Aggiungendo a questo penso che la soluzione ideale sia inizializzare ogni entità con uno script o con i file descrittori. Uso i file di testo per l'inizializzazione dell'entità. È veloce e consente ai non programmatori di testare diversi parametri.
Coyote,

Wow, leggere il tuo post mi ha dato un incubo! Certo, intendo che in senso buono, visto che hai aperto gli occhi su che tipo di incubo potrei finire! Vorrei poter rispondere di più a una risposta così dettagliata, ma non c'è molto da aggiungere davvero!
Midori Ryuu,

In un modo più semplice. Finisci per usare l'ereditarietà come modello di fabbrica del povero.
Zardoz89,

2

La composizione sull'ereditarietà è la terminologia OOP (almeno il modo in cui l'ho imparato / insegnato). Per scegliere tra composizione ed eredità dici ad alta voce "L'auto è un tipo di ruota" (eredità) e "L'auto ha le ruote" (composizione). Uno dovrebbe suonare più corretto dell'altro (composizione in questo caso) e, se non riesci a decidere, impostare automaticamente la composizione fino a quando non decidi diversamente.


1

Quando si tratta di integrare sistemi basati su componenti con giochi esistenti basati su oggetti di gioco, c'è un articolo sulla programmazione dei cowboy http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ che potrebbe darvi alcuni suggerimenti sul modo in cui può essere effettuata la transizione.

Per quanto riguarda i problemi, il tuo modello dovrà comunque gestire il giocatore in modo diverso da un altro nemico. Non sarai in grado di cambiare stato (cioè il giocatore diventa AI controllato) quando un giocatore si disconnette o in casi speciali senza gestirlo in modo diverso dagli altri personaggi.

A parte il pesante riutilizzo di componenti attraverso entità diverse, l'idea di giochi basati su componenti è l'uniformità dell'intero sistema. Ogni entità ha componenti che possono essere collegati e staccati a piacimento. Tutti i componenti dello stesso tipo vengono gestiti esattamente allo stesso modo con una serie di chiamate stabilite dall'interfaccia (componente di base) di ogni tipo (posizione, rendering, script ...)

La combinazione dell'ereditarietà degli oggetti di gioco con un approccio basato sui componenti offre alcuni vantaggi dell'approccio basato sui componenti con gli svantaggi di entrambi gli approcci.

Può funzionare per te. Ma non mescolerei entrambi al di fuori di un periodo di transizione da un'architettura all'altra.

PS scritto su un telefono, quindi faccio fatica a collegarmi a risorse esterne.


Grazie per la tua risposta anche se eri al telefono! Capisco cosa intendi. Più sono le cose basate sui componenti, maggiore è la flessibilità che devo modificare. È la ragione per cui andrò avanti e proverò un approccio compositivo con eredità minima. (Anche più di quello che ho proposto.)
Midori Ryuu,

@MidoriRyuu evita qualsiasi eredità negli oggetti entità. Sei abbastanza fortunato da usare un linguaggio con la riflessione (e l'introspezione) incorporata. Puoi utilizzare i file (txt o XML) per inizializzare i tuoi oggetti con i parametri giusti. Non è troppo lavoro e consentirà una migliore messa a punto del gioco senza ricompilare. Ma mantieni la classe Entity, almeno per la comunicazione tra i componenti e altre attività di utilità. PS ancora al telefono :(
Coyote
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.