In che modo i componenti fisici o grafici sono generalmente integrati in un sistema orientato ai componenti?


19

Ho trascorso le ultime 48 ore a leggere sui sistemi Object Component e mi sento abbastanza pronto per iniziare a implementarlo. Ho creato le classi di oggetti e componenti di base, ma ora che ho bisogno di iniziare a creare i componenti effettivi sono un po 'confuso. Quando penso a loro in termini di HealthComponent o qualcosa che sarebbe sostanzialmente una proprietà, ha perfettamente senso. Quando è qualcosa di più generale come componente di Fisica / Grafica, mi confondo un po '.

La mia classe di oggetti è così simile (se noti cambiamenti che dovrei fare per favore fammi sapere, ancora nuovo a questo) ...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

Ora il problema che sto incontrando è che cosa metterebbe la popolazione in componenti come Fisica / Grafica? Per Ogre (il mio motore di rendering) gli oggetti visibili saranno costituiti da più Ogre :: SceneNode (possibilmente multipli) per collegarlo alla scena, Ogre :: Entity (possibilmente multipli) per mostrare le mesh visibili, e così via. Sarebbe meglio aggiungere più GraphicComponent all'Oggetto e lasciare che ogni GraphicComponent gestisca un SceneNode / Entity o è necessaria l'idea di avere uno di ciascun Component?

Per la fisica sono ancora più confuso. Suppongo che forse crei un RigidBody e tenga traccia di massa / interia / ecc. avrebbe senso. Ma ho problemi a pensare a come inserire effettivamente i dettagli in un componente.

Una volta che avrò fatto un paio di questi componenti "richiesti", penso che avrà molto più senso. A partire da ora, anche se sono ancora un po 'perplesso.

Risposte:


32

Prima di tutto, è quando i sistemi basati su componenti di compilazione, non si deve prendere l'approccio di trasformare tutto ciò in un componente. In effetti, in genere non dovresti - è una specie di errore neofita. Nella mia esperienza, il modo migliore per collegare i sistemi di rendering e fisica in un'architettura basata su componenti è quello di rendere quei componenti poco più che contenitori stupidi per il rendering reale o l'oggetto primitivo della fisica.

Ciò ha l'ulteriore vantaggio di mantenere i sistemi di rendering e fisica disaccoppiati dal sistema componente.

Per i sistemi fisici, ad esempio, il simulatore fisico tiene generalmente una grande lista di oggetti del corpo rigido che manipola ogni volta che spunta. Il tuo sistema di componenti non scherza con questo: il tuo componente di fisica memorizza semplicemente un riferimento al corpo rigido che rappresenta gli aspetti fisici dell'oggetto a cui appartiene il componente e traduce tutti i messaggi dal sistema di componenti in opportune manipolazioni del corpo rigido.

Allo stesso modo con il rendering - il tuo renderer avrà qualcosa che rappresenta i dati dell'istanza da rendere, e il tuo componente visivo semplicemente tiene un riferimento a quello.

È relativamente semplice: per qualsiasi motivo sembra confondere le persone, ma spesso perché ci pensano troppo. Non c'è molta magia lì. E poiché è molto importante fare le cose piuttosto che agonizzarsi per il design perfetto, dovresti considerare fortemente un approccio in cui almeno alcuni dei componenti hanno interfacce definite e sono membri diretti dell'oggetto di gioco, come ha suggerito momboco. È un approccio altrettanto valido e tende a essere più facile da capire.

Altri commenti

Questo non è correlato alle tue domande dirette, ma queste sono cose che ho notato guardando il tuo codice:

Perché stai mescolando Ogre :: String e std :: string nel tuo oggetto di gioco, che è apparentemente un oggetto non grafico e quindi non dovrebbe avere una dipendenza da Ogre? Suggerirei di rimuovere tutti i riferimenti diretti a Ogre tranne nel componente di rendering stesso, dove dovresti fare la tua trasformazione.

update () è un puro virtuale, il che implica che bisogna sottoclassare GameObject, che in realtà è esattamente l'opposto della direzione in cui si vuole andare con un'architettura componente. Il punto principale dell'utilizzo dei componenti è preferire l'aggregazione delle funzionalità piuttosto che l'ereditarietà.

Potresti beneficiare di un certo uso di const(in particolare dove stai tornando per riferimento). Inoltre, non sono sicuro che tu voglia davvero che la velocità sia uno scalare (o se esso, e la posizione, dovessero essere presenti nell'oggetto di gioco nel tipo di metodologia di componente eccessivamente generica che sembra stia cercando).

Il tuo approccio all'utilizzo di una grande mappa di componenti e una update()chiamata nell'oggetto di gioco è piuttosto subottimale (e una trappola comune per coloro che costruiscono prima questo tipo di sistemi). Garantisce una scarsa coerenza della cache durante l'aggiornamento e non consente di sfruttare la concorrenza e la tendenza verso il processo in stile SIMD di grandi quantità di dati o comportamenti contemporaneamente. Spesso è meglio usare un design in cui l'oggetto di gioco non aggiorna i suoi componenti, ma piuttosto il sottosistema responsabile del componente stesso li aggiorna tutti in una volta. Questo potrebbe valere la pena di essere letto.


Questa risposta è stata eccellente. Non ho ancora trovato un modo per distanziare i paragrafi nei commenti (il tasto invio invia solo) ma affronterò queste risposte. La tua descrizione del sistema di oggetti / componenti ha molto senso. Quindi, solo per chiarire, nel caso di un componente Physics, nel costruttore del componente, potrei semplicemente chiamare la funzione CreateRigidBody () del mio PhysicsManager e quindi memorizzare un riferimento a quello nel componente?
Aidan Knight,

Per quanto riguarda la parte "Altri commenti", grazie per questo. Ho mischiato le stringhe perché generalmente preferisco usare tutte le funzioni di Ogre come base per i miei progetti che usano Ogre, ma ho iniziato a cambiarlo nel sistema Object / Component poiché mancava map / pair / etc. Hai ragione però e li ho convertiti tutti in membri standard per separarli da Ogre.
Aidan Knight,

1
+1 Questa è la prima risposta sana riguardo a un modello basato su componenti che ho letto qui un po ', un bel contrasto con un eccesso di generalizzazione
Maik Semder,

5
Consente una migliore coerenza (sia dei dati che nella cache delle istruzioni) e offre la possibilità di scaricare quasi banalmente gli aggiornamenti su thread o processi di lavoro distinti (SPU, ad esempio). In alcuni casi i componenti non dovrebbero nemmeno essere aggiornati comunque, il che significa che è possibile evitare il sovraccarico di una chiamata no-op a un metodo update (). Ti aiuta anche a costruire sistemi orientati ai dati: è tutto basato sull'elaborazione di massa, che sfrutta le tendenze moderne nell'architettura della CPU.

2
+1 per "è più importante fare le cose piuttosto che agonizzarsi per il design perfetto"
Trasplazio Garzuglio,

5

L'architettura dei componenti è un'alternativa per evitare le grandi gerarchie ottenute ereditando tutti gli elementi da uno stesso nodo e i problemi di accoppiamento che portano.

Stai mostrando un'architettura di componente con componenti "generici". Hai la logica di base per collegare e scollegare componenti "generici". Un'architettura con componenti generici è più difficile da realizzare perché non sai quale componente sia. I vantaggi sono che questo approccio è estensibile.

Un'architettura di componente valida si ottiene con componenti definiti. Con definito intendo, l'interfaccia è definita, non l'implementazione. Ad esempio, GameObject può avere un IPhysicInstance, Transformation, IMesh, IAnimationController. Questo ha il vantaggio di conoscere l'interfaccia e GameObject sa come gestire ogni componente.

Rispondere al termine di generalizzazione eccessiva

Penso che i concetti non siano chiari. Quando dico componente, non significa che tutti i componenti di un oggetto gioco debbano ereditare da un'interfaccia componente o qualcosa di simile. Tuttavia, questo è l'approccio per i componenti generici, penso che sia il concetto che si chiama come componente.

L'architettura del componente è un'altra delle architetture esistenti per il modello a oggetti di runtime. Non è l'unico e non è il migliore per tutti i casi, ma l'esperienza ha dimostrato che ha molti vantaggi, come un basso accoppiamento.

La caratteristica principale che distingue l'architettura del componente è convertire la relazione is-a in has-a. Ad esempio un'architettura a oggetti è-a:

è un'architettura

Invece di un'architettura a oggetti has-a (componenti):

ha un'architettura

Esistono anche architetture incentrate sulla proprietà:

Ad esempio, una normale architettura di oggetti è così:

  • Object1

    • Posizione = (0, 0, 0)
    • Orientamento = (0, 43, 0)
  • Object2

    • Posizione = (10, 0, 0)
    • Orientamento = (0, 0, 30)

Un'architettura incentrata sulla proprietà:

  • Posizione

    • Object1 = (0, 0, 0)
    • Object2 = (10, 0, 0)
  • Orientamento

    • Object1 = (0, 43, 0)
    • Object2 = (0, 0, 30)

È meglio l'architettura modello a oggetti? Dal punto di vista delle prestazioni, è meglio incentrato sulle proprietà perché è più compatibile con la cache.

Il componente è riferito alla relazione is-a anziché has-a. Un componente può essere una matrice di trasformazione, un quaternione, ecc. Ma la relazione con l'oggetto di gioco è una relazione è -a, l'oggetto di gioco non ha la trasformazione perché ereditata. L'oggetto gioco ha (relazione has-a) la trasformazione. La trasformazione è quindi un componente.

L'oggetto di gioco è come un hub. se vuoi un componente che è sempre lì, allora forse il componente (come la matrice) dovrebbe essere all'interno dell'oggetto e non un puntatore.

Non esiste un oggetto di gioco perfetto per tutti i casi, dipende dal motore, dalle librerie che il motore utilizza. Forse voglio una macchina fotografica che non ha una maglia. Il problema con is-a architecture è che se l'oggetto game ha una mesh, la telecamera che eredita dall'oggetto game deve avere una mesh. Con i componenti, la fotocamera ha una trasformazione, un controller per il movimento e tutto ciò di cui hai bisogno.

Per ulteriori informazioni, il capitolo "14.2. Runtime Object Model Architecture" dal libro "Game Engine Architecture" di "Jason Gregory" tratta specificamente questo argomento.

I diagrammi UML vengono estratti da lì


Non sono d'accordo con l'ultimo paragrafo, c'è una tendenza a generalizzare eccessivamente. Il modello basato su componenti non significa che ogni singola funzionalità deve essere un componente, tuttavia è necessario considerare le relazioni di funzionalità e dati. Un AnimationController senza mesh è inutile, quindi inseriscilo nella mesh a cui appartiene. Un oggetto di gioco senza trasformazione non è un oggetto di gioco, appartiene per definizione al mondo 3D, quindi inseriscilo come un membro normale nell'oggetto di gioco. Si applicano ancora le normali regole di funzionalità e dati di incapsulamento.
Maik Semder,

@Maik Semder: sono d'accordo con te in termini di generalizzazione eccessiva in termini generali, ma penso che il termine componente non sia chiaro. Ho modificato la mia risposta. Leggilo e dammi le tue impressioni.
momboco,

@momboco bene, hai ripetuto meno gli stessi punti;) Transform e AnimationController sono ancora descritti come componenti, che dal mio punto di vista è un uso eccessivo del modello di componente. La chiave è definire cosa dovrebbe essere inserito in un componente e cosa no:
Maik Semder,

Se è necessario qualcosa per far funzionare Game-Object (GO) in primo luogo, in altre parole se non è facoltativo, ma obbligatorio, non è un componente. I componenti dovrebbero essere opzionali. La trasformazione è un buon esempio, non è affatto opzionale per un GO, un GO senza una trasformazione non ha senso. D'altra parte, non tutti i GO hanno bisogno di fisica, quindi può essere un componente.
Maik Semder,

Se esiste una forte relazione tra 2 funzionalità, come tra AnimationController (AC) e Mesh, allora non interrompere in alcun modo questa relazione premendo AC in un componente, al contrario la Mesh è un componente perfetto, ma un AC appartiene nella maglia.
Maik Semder,
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.