Perché è una cattiva idea archiviare metodi in Entità e componenti? (Insieme ad altre domande sul Sistema Entità.)


16

Questo è un seguito a questa domanda, a cui ho risposto, ma questa affronta un argomento molto più specifico.

Questa risposta mi ha aiutato a capire Entity Systems anche meglio dell'articolo.

Ho letto l'articolo (sì, l'articolo) su Entity Systems e mi ha detto quanto segue:

Le entità sono solo un ID e una matrice di componenti (gli articoli affermano che la memorizzazione di entità nei componenti non è un buon modo di fare le cose, ma non fornisce un'alternativa).
I componenti sono pezzi di dati che indicano cosa si può fare con una determinata entità.
I sistemi sono i "metodi", eseguono la manipolazione dei dati sulle entità.

Questo sembra davvero pratico in molte situazioni, ma la parte sul fatto che i componenti siano solo classi di dati mi dà fastidio. Ad esempio, come posso implementare la mia classe Vector2D (posizione) in un sistema di entità?

La classe Vector2D contiene dati: coordinate xey, ma ha anche dei metodi , che sono cruciali per la sua utilità e distinguono la classe da un solo array di due elementi. Metodi di esempio sono: add(), rotate(point, r, angle), substract(), normalize(), e tutti gli altri standard metodi utili, e assolutamente necessario che le posizioni (che sono istanze della classe Vector2D) dovrebbe avere.

Se il componente fosse solo un detentore di dati, non sarebbe in grado di disporre di questi metodi!

Una soluzione che potrebbe probabilmente apparire sarebbe quella di implementarli all'interno dei sistemi, ma sembra molto controintuitivo. Questi metodi sono cose che voglio eseguire ora , che siano completi e pronti per l'uso. Non voglio aspettare MovementSystemche legga un costoso set di messaggi che gli dicono di eseguire un calcolo sulla posizione di un'entità!

E l'articolo afferma chiaramente che solo i sistemi dovrebbero avere alcuna funzionalità, e l'unica spiegazione per quello, che ho potuto trovare, era "evitare OOP". Prima di tutto, non capisco perché dovrei astenermi dall'utilizzare metodi in entità e componenti. L'overhead di memoria è praticamente lo stesso e, se abbinato a sistemi, questi dovrebbero essere molto facili da implementare e combinare in modi interessanti. I sistemi, ad esempio, potrebbero fornire la logica di base solo a entità / componenti, che conoscono l'implementazione stessa. Se me lo chiedi, questo in pratica sta prendendo le chicche sia da ES che da OOP, qualcosa che non può essere fatto secondo l'autore dell'articolo, ma a me sembra una buona pratica.

Pensaci in questo modo; ci sono molti diversi tipi di oggetti disegnabili in un gioco. Vecchie immagini, animazioni ( update(), getCurrentFrame()ecc.) Semplici , combinazioni di questi tipi primitivi e tutte potrebbero semplicemente fornire un draw()metodo al sistema di rendering, che quindi non deve preoccuparsi di come viene implementato lo sprite di un'entità, solo sull'interfaccia (disegno) e la posizione. E poi, avrei solo bisogno di un sistema di animazione che chiamasse metodi specifici dell'animazione che non hanno nulla a che fare con il rendering.

E solo un'altra cosa ... Esiste davvero un'alternativa agli array quando si tratta di immagazzinare componenti? Non vedo altro posto dove archiviare i componenti se non gli array all'interno di una classe Entity ...

Forse, questo è un approccio migliore: archiviare i componenti come semplici proprietà delle entità. Ad esempio, un componente di posizione verrebbe incollato entity.position.

L' unico altro modo sarebbe avere una sorta di strana tabella di ricerca all'interno dei sistemi, che fa riferimento a entità diverse. Ma questo sembra molto inefficiente e più complicato da sviluppare rispetto alla semplice memorizzazione di componenti nell'entità.


Alexandre, stai solo apportando molte modifiche per ottenere un altro badge? Perché questo è cattivo, continua a sbattere un sacco di fili antichi.
jhocking

Risposte:


25

Penso che sia assolutamente corretto disporre di metodi semplici per accedere, aggiornare o manipolare i dati nei componenti. Penso che la funzionalità che dovrebbe rimanere fuori dai componenti sia la funzionalità logica. Le funzioni di utilità vanno bene. Ricorda, il sistema di componenti entità è solo una linea guida, non regole rigide che devi seguire. Non fare di tutto per seguirli. Se pensi che abbia più senso farlo in un modo, allora fallo in quel modo :)

MODIFICARE

Per chiarire, il tuo obiettivo non è quello di evitare OOP . Sarebbe piuttosto difficile nella maggior parte delle lingue comuni utilizzate in questi giorni. Stai cercando di ridurre al minimo l' eredità , che è un aspetto importante di OOP, ma non richiesto. Vuoi eliminare l'oggetto-> MobileObject-> Creatura-> Bipede-> Eredità di tipo umano.

Tuttavia, va bene avere un po 'di eredità! Hai a che fare con un linguaggio fortemente influenzato dall'eredità, è molto difficile non usarlo. Ad esempio, puoi avere unComponent classe o un'interfaccia che tutti gli altri componenti estendono o implementano. Lo stesso affare con la tua Systemclasse. Questo rende le cose molto più facili. Consiglio vivamente di dare un'occhiata al framework Artemis . È open source e ha alcuni progetti di esempio. Apri quelle cose e vedi come funziona.

Per Artemis, le entità sono archiviate in un array, semplice. Tuttavia, i loro componenti sono archiviati in un array o in array (separati dalle entità). L'array di livello superiore raggruppa l'array di livello inferiore per tipo di componente. Quindi ogni tipo di componente ha il suo array. L'array di livello inferiore è indicizzato dall'ID entità. (Ora non sono sicuro che lo farei in questo modo, ma è così che viene fatto qui). Artemis riutilizza gli ID entità, quindi l'ID entità massimo non aumenta rispetto al numero corrente di entità, ma è comunque possibile disporre di array sparsi se il componente non è un componente utilizzato di frequente. Comunque, non lo sceglierei troppo. Questo metodo per archiviare entità e i loro componenti sembra funzionare. Penso che sarebbe un ottimo primo passo per implementare il tuo sistema.

Le entità e i componenti sono memorizzati in un gestore separato.

La strategia che menzioni, facendo in modo che le entità memorizzino i propri componenti ( entity.position), è in qualche modo contro il tema del componente entità, ma è totalmente accettabile se ritieni che abbia più senso.


1
Hmm, questo semplifica notevolmente la situazione, grazie! Pensavo che ci fosse qualcosa di magico "te ne pentirai più tardi", e non riuscivo a vederlo!
jcora,

1
Na, li uso totalmente nel mio sistema di componenti entità. Ho anche alcuni componenti che ereditano da un genitore comune, sussulto . Penso che l'unico rimpianto che faresti è se provassi a evitare di usare metodi del genere. Si tratta di fare ciò che ha più senso per te. Se ha senso usare l'ereditarietà o inserire alcuni metodi in un componente, provalo.
MichaelHouse

2
Ho imparato dalla mia ultima risposta su questo argomento. Disclaimer: non sto dicendo che questo è il modo di farlo. :)
MichaelHouse

1
Sì, so quanto possa essere scoraggiante imparare un nuovo paradigma. Fortunatamente, puoi usare gli aspetti del vecchio paradigma per semplificare le cose! Ho aggiornato la mia risposta con le informazioni di archiviazione. Se guardi Artemis, controlla EntityManagercome è lì che sono archiviate le cose.
MichaelHouse

1
Bello! Sarà un motore abbastanza dolce quando avrà finito. Buona fortuna! Grazie per aver posto domande interessanti.
MichaelHouse

10

L'articolo "Quello" non è particolarmente d'accordo, quindi penso che la mia risposta sarà in qualche modo critica.

Questo sembra davvero pratico in molte situazioni, ma la parte sul fatto che i componenti siano solo classi di dati mi dà fastidio. Ad esempio, come posso implementare la mia classe Vector2D (posizione) in un sistema di entità?

L'idea non è quella di garantire che nulla nel tuo programma sia qualcosa di diverso da un ID entità, componente o sistema - è garantire che i dati e il comportamento dell'entità vengano creati attraverso la composizione di oggetti piuttosto che utilizzare un albero di eredità complesso o, peggio, provare a mettere tutte le funzionalità possibili in un oggetto. Per implementare questi componenti e sistemi avrai sicuramente dati normali come vettori che, nella maggior parte delle lingue, sono meglio rappresentati come una classe.

Ignora la parte dell'articolo che suggerisce che questo non è OOP - è OOP come qualsiasi altro approccio. Quando la maggior parte dei compilatori o dei runtime del linguaggio implementano metodi a oggetti, è sostanzialmente come qualsiasi altra funzione, tranne che c'è un argomento nascosto chiamato thiso self, che è un puntatore a un posto nella memoria in cui sono archiviati i dati di quell'oggetto. In un sistema basato su componenti, l'ID entità può essere utilizzato per trovare dove si trovano i componenti rilevanti (e quindi i dati) per una determinata entità. Pertanto l'ID entità equivale a un puntatore this / self, e i concetti sono sostanzialmente la stessa cosa, appena riorganizzati un po '.

E l'articolo afferma chiaramente che solo i sistemi dovrebbero avere alcuna funzionalità, e l'unica spiegazione per quello, che ho potuto trovare, era "evitare OOP". Prima di tutto, non capisco perché dovrei astenermi dall'utilizzare metodi in entità e componenti.

Buona. I metodi sono un modo efficace per organizzare il tuo codice. La cosa importante da togliere dall'idea "evitare OOP" è evitare di usare l'eredità ovunque per estendere la funzionalità. Invece, suddividere la funzionalità in componenti che possono essere combinati per fare la stessa cosa.

Pensaci in questo modo; ci sono molti diversi tipi di oggetti disegnabili in un gioco. Semplici vecchie immagini, animazioni (update (), getCurrentFrame (), ecc.), Combinazioni di questi tipi primitivi e tutte potrebbero semplicemente fornire un metodo draw () al sistema di rendering [...]

L'idea di un sistema basato su componenti è che non avresti classi separate per queste, ma avresti una singola classe Oggetto / Entità, e l'immagine sarebbe un Oggetto / Entità che ha un ImageRenderer, le Animazioni sarebbero un Oggetto / Entità che ha un AnimationRenderer, ecc. I sistemi rilevanti saprebbero come renderizzare questi componenti e quindi non ci dovrebbe essere alcuna classe base con un metodo Draw ().

[...] che quindi non ha bisogno di preoccuparsi di come viene implementato lo sprite di un'entità, ma solo dell'interfaccia (disegno) e della posizione. E poi, avrei solo bisogno di un sistema di animazione che chiamerebbe metodi specifici dell'animazione che non hanno nulla a che fare con il rendering.

Certo, ma questo non funziona bene con i componenti. Hai 3 scelte:

  • Ogni componente implementa questa interfaccia e ha un metodo Draw (), anche se non viene disegnato nulla. Se lo facessi per ogni funzionalità, i componenti sembrerebbero piuttosto brutti.
  • Solo i componenti che hanno qualcosa da disegnare implementano l'interfaccia, ma chi decide quali componenti chiamare Draw ()? Un sistema deve in qualche modo interrogare ciascun componente per vedere quale interfaccia è supportata? Sarebbe soggetto a errori e potenzialmente difficile da implementare in alcune lingue.
  • I componenti sono sempre e solo gestiti dal loro sistema proprietario (che è l'idea nell'articolo collegato). In tal caso, l'interfaccia è irrilevante perché un sistema sa esattamente con quale classe o tipo di oggetto sta lavorando.

E solo un'altra cosa ... Esiste davvero un'alternativa agli array quando si tratta di immagazzinare componenti? Non vedo altro posto dove archiviare i componenti se non gli array all'interno di una classe Entity ...

È possibile memorizzare i componenti nel sistema. L'array non è il problema, ma è dove si trova il componente.


+1 Grazie per un altro punto di vista. È bello ottenerne alcuni quando si affronta un argomento così ambiguo! Se stai memorizzando componenti nel sistema, significa che i componenti possono essere modificati solo da un sistema? Ad esempio, il sistema di disegno e il sistema di movimento accedono entrambi al componente posizione. Dove lo memorizzi?
MichaelHouse

Bene, memorizzerebbero solo un puntatore a quei componenti, che possono, fintanto che sono preoccupato, essere da qualche parte ... Inoltre, perché conservare i componenti nei sistemi? C'è un vantaggio in questo?
jcora,

Ho ragione, @Kylotan? È così che lo farei, sembra logico ...
jcora,

Nell'esempio di Adam / T-Machine, l'intenzione è che ci sia 1 sistema per componente, ma un sistema potrebbe certamente accedere e modificare altri componenti. (Ciò ostacola i vantaggi del multithreading dei componenti, ma è una questione diversa.)
Kylotan,

1
La memorizzazione di componenti nel sistema consente una migliore località di riferimento per quel sistema - quel sistema funziona (generalmente) solo con quei dati, quindi perché andare su tutta la memoria del computer da un'entità all'altra per ottenerlo? Aiuta anche con la concorrenza perché potresti mettere un intero sistema e i suoi dati su un core o processore (o anche un computer separato, in MMO). Ancora una volta, questi benefici diminuiscono quando 1 sistema accede a più di 1 tipo di componente, quindi dovrebbero essere presi in considerazione quando si decide dove dividere le responsabilità del componente / sistema.
Kylotan,

2

Un vettore è dato. Le funzioni sono più simili alle funzioni di utilità: non sono specifiche per quell'istanza dei dati, possono essere applicate in modo indipendente a tutti i vettori. Un bel modo di pensarci è: queste funzioni possono essere riscritte come metodi statici? Se è così, è solo un'utilità.


Lo so, ma il problema è che i metodi di chiamata sono più veloci e possono essere eseguiti sul posto da un sistema o da qualsiasi altra cosa che potrebbe essere necessaria per manipolare la posizione di un'entità. Ho spiegato che, dai un'occhiata, inoltre, la domanda ha molto di più rispetto a questo, credo.
jcora,
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.