Suggerimenti per la gestione dei messaggi del sistema di entità basato su componenti


8

Sto cercando di implementare un sistema di entità basato su componenti ma sono un po 'confuso su come dovrei gestire la messaggistica. Ci sono due problemi che vorrei risolvere in modo da poter testare il sistema. Di seguito è riportato il codice che ho finora,

La classe Entity:

class Entity{
public:
    Entity(unsigned int id):
    id_(id)
    {};
    void handleMessage(BaseMessage &message){
        for(auto element: components_){
            element.second->handleMessage(message);
        }
    }
    template<class T>
    void attachComponent(T *component){
        //Consider making safer in case someone tries to attach same component type twice
        components_[typeid(T).hash_code()] = component;
    }
    template<class T>
    void detachComponent(void){
        components_.erase(typeid(T).hash_code());
    }
    template<class T>
    T* getComponent(void)const{
        return *components_.find(typeid(T).hash_code());
    }
    unsigned int getInstanceID(void)const{
        return id_;
    }
private:
    unsigned int id_;
    std::map<size_t, BaseComponent*> components_;
};

Le classi Componente di base e Messaggio:

class BaseComponent{
public:
    virtual void handleMessage(BaseMessage &message){};
};

class BaseMessage{
public:
    virtual int getType(void) = 0;
};

1. Gestione del tipo di messaggio

La mia prima domanda è come dovrei gestire i diversi tipi di messaggi (derivati ​​da BaseMessage).

Ho pensato a due modi per gestire i tipi di messaggio dei tipi di messaggio derivati. Uno è generare un hash (cioè usando FNV) da una stringa che nomina il tipo di messaggio e usare quell'hash per determinare il tipo di messaggio. Quindi la handleMessage(BaseMessage &message)funzione estrae prima questo hash dal messaggio e quindi esegue un static_cast nel tipo appropriato.

Il secondo metodo consiste nell'utilizzare un modello come segue (simile ai attachComponentmetodi della classe di entità),

template<class T>
handleMessage(T& message){};

e crea specializzazioni per ogni tipo di messaggio che il componente specifico sta per fare.

Ci sono degli svantaggi nel secondo metodo? Che dire delle prestazioni, perché non vedo questo tipo di utilizzo più spesso?

2. Gestione degli input

La mia seconda domanda è quale sarebbe il modo ottimale (in termini di latenza e facilità d'uso) di gestire l'input?

Il mio pensiero era quello di creare un InputHandlerComponentregistro con la classe di tastiera per ascoltare le pressioni di tasti specifiche definite possibilmente in alcuni file. Per esempio

keyboard.register( player.getComponent<InputHandler>() , 'W')

Vorrei che ci fosse una guida più concisa ai sistemi basati su componenti, ma credo che ci siano molti modi diversi di fare le stesse cose. Ho più domande ma penso che sarebbe più saggio provare prima a implementare ciò che posso.

Risposte:


3

In un tipico sistema di entità si lascia che i sistemi abbiano tutta la logica per il gioco e i componenti per memorizzare solo i dati e le entità sono solo un semplice identificatore. La gestione degli input sarebbe molto più semplice in questo modo e per non parlare della flessibilità del tuo gioco.

Esistono molti modi per gestire gli eventi, come ad esempio il modello di osservatore o segnali / slot. Si potrebbe fare la gestione dei messaggi con template / funzioni virtuali o puntatori a funzioni, tuttavia sarebbe probabilmente più facile usare boost :: signal , anche se non è eccezionale per le prestazioni. Se desideri prestazioni *, ti suggerisco di utilizzare modelli e funzioni virtuali o puntatori a funzioni.

* Ho notato che il tuo codice potrebbe davvero essere ottimizzato. Esistono alternative typeidall'operatore, che in realtà sono più veloci dell'uso typeid. Come l'utilizzo di template / macro e interi semplici per definire l'ID di una classe.

Puoi guardare il mio Entity System se hai bisogno di ispirazione (che si ispira al framework Java Artemis ). Anche se non ho implementato un modo per gestire gli eventi (oltre agli eventi relativi alle entità), l'ho lasciato all'utente, ma dopo aver verificato la libreria entityx , che ho trovato su reddit. Ho pensato di poter aggiungere un sistema di eventi alla mia libreria. Si prega di notare che il mio sistema di entità non è completo al 100% su quali funzionalità voglio, ma funziona e ha prestazioni decenti (ma potrei ottimizzare, soprattutto con il modo in cui sto archiviando le entità).

Ecco cosa potresti eventualmente fare per gestire gli eventi (ispirato a entityx ):

BaseReceiver / BaseListener

  • Una classe base in modo da poter memorizzare ascoltatori / ricevitori.

Ricevitore / Listener

  • Questa è una classe modello e richiede di sovrascrivere la funzione virtuale recieve(const T&), dove T è informazione sull'evento.
  • Le classi che desiderano essere avvisate da eventi che inviano informazioni specifiche quando si verifica un evento devono ereditare da questa classe.

Gestore di eventi

  • Emette / spara eventi
  • Ha un elenco di oggetti Ricevitori / Ascoltatori che verranno avvisati dagli eventi attivati

Ho implementato questo progetto in C ++, proprio ora, senza l'uso di boost :: segnali . Puoi vederlo qui .

Professionisti

  • Digitato staticamente
  • Funzioni virtuali (più veloce di boost :: segnali, bene dovrebbe essere)
  • Errori durante la compilazione se non hai emesso correttamente le notifiche
  • C ++ 98 compatibile (credo)

Contro

  • Richiede la definizione di funzione (non è possibile gestire eventi in una funzione globale)
  • Nessuna coda di eventi; basta registrarsi e sparare. (che potrebbe non essere quello che vuoi)
  • Chiamate non dirette (non dovrebbe essere così male per gli eventi)
  • Nessuna gestione di più eventi sulla stessa classe (potrebbe essere modificata in modo da consentire più eventi, tramite ereditarietà multipla, ma potrebbe richiedere del tempo per l'implementazione)

Inoltre, ho un esempio nel mio sistema di entità, che si occupa di rendering e gestione dell'input, è abbastanza semplice ma presenta molti concetti per comprendere la mia libreria.


Puoi dare un esempio di un modo più veloce per scoprire i tipi senza typeid?
Luca B.

"In un tipico sistema di entità si lascia che i sistemi abbiano tutta la logica per il gioco e i componenti per memorizzare solo i dati" - non esiste un sistema di entità "tipico".
Den,

@LukeB. Guarda il codice sorgente del mio sistema di entità, non lo uso typeid(guarda Component e ComponentContainer). Fondamentalmente, memorizzo il "tipo" di un componente come intero, ho un intero globale che incremento per tipo di componente. E conservo i componenti in un array 2d, in cui il primo indice è l'ID dell'entità e il secondo indice è l'ID del tipo di componente, ovvero componenti [entityId] [componentTypeId]
miguel.martin,

@ miguel.martin. La ringrazio per la risposta. Penso che la mia confusione derivi principalmente dal fatto che esistono diversi modi per implementare il sistema di entità e per me tutti hanno i loro svantaggi e benefici. Ad esempio, nell'approccio che usi, devi registrare nuovi tipi di componenti usando CRTP, che personalmente non mi piace poiché questo requisito non è esplicito. Penso che dovrò riconsiderare la mia implementazione.
Grieverheart,

1
@MarsonMao fixed
miguel.martin

1

Sto lavorando su un sistema di entità basato su componenti in C #, ma le idee e gli schemi generali si applicano ancora.

Il modo in cui gestisco i tipi di messaggio è che ogni sottoclasse del componente chiama il RequestMessage<T>(Action<T> action) where T : IMessagemetodo protetto del componente . In inglese, ciò significa che la sottoclasse del componente richiede un tipo specifico di messaggio e fornisce un metodo da chiamare quando il componente riceve un messaggio di quel tipo.

Tale metodo viene memorizzato e quando il componente riceve un messaggio, utilizza la riflessione per ottenere il tipo di messaggio, che viene quindi utilizzato per cercare il metodo associato e invocarlo.

Puoi sostituire il riflesso con qualunque altro sistema tu voglia, gli hash sono la soluzione migliore. Esamina più schemi di invio e visitatori per alcune altre idee.

Per quanto riguarda l'input, ho scelto di fare qualcosa di completamente diverso da quello che stai pensando. Un gestore di input convertirà separatamente l'input da tastiera / mouse / gamepad in un elenco di flag (bitfield) di possibili azioni (MoveForward, MoveBackwards, StrafeLeft, Use, ecc.) In base alle impostazioni dell'utente / a ciò che il giocatore ha collegato al computer. Ogni componente ottiene un UserInputMessageframe ogni contenente sia il bitfield che l'asse del look del giocatore come un vettore 3d (orientando lo sviluppo verso uno sparatutto in prima persona). Ciò rende anche più semplice consentire ai giocatori di cambiare i loro collegamenti chiave.

Come hai detto alla fine della tua domanda, ci sono molti modi diversi di creare un sistema di entità basato su componenti. Ho orientato fortemente il mio sistema verso il gioco che sto creando, quindi naturalmente alcune delle cose che faccio potrebbero non avere alcun senso nel contesto, per esempio, di un RTS, ma è ancora qualcosa che è stato implementato e per cui ha funzionato io finora.

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.