Sistema di entità componente: aggiornamenti e ordini di chiamata


10

Al fine di ottenere componenti in grado di aggiornare ogni frame (e lasciare questa funzionalità fuori dai componenti che non ne hanno bisogno), ho avuto l'idea di creare un componente UpdateComponent. Altri componenti come MovableComponent(che detiene la velocità) erediterebbero dalla IUpdatableclasse astratta. Questo costringe MovableComponenta implementare un Update(gametime dt)metodo e un altro RegisterWithUpdater()che fornisce UpdateComponentun puntatore al MovableComponent. Molti componenti potrebbero farlo e quindi UpdateComponentchiamare tutti i loro Update(gametime dt)metodi senza preoccuparsi di chi o cosa siano.

Le mie domande sono:

  1. Sembra qualcosa di normale o utilizzato da qualcuno? Non riesco a trovare nulla sull'argomento.
  2. Come posso mantenere un ordine per i componenti come la fisica e quindi il cambio di posizione? È anche necessario?
  3. Quali sono altri modi per assicurare che i componenti che dovrebbero essere processati in ogni frame vengano effettivamente elaborati?

EDIT
Penso che mi occuperò di come fornire al gestore entità un elenco di tipi che sono aggiornabili. Quindi TUTTI i componenti di quel tipo possono aggiornarsi piuttosto che gestirli per entità (che comunque sono solo indici nel mio sistema).

Ancora. Le mie domande rimangono valide per me. Non so se questo sia ragionevole / normale o cosa gli altri tendano a fare.

Inoltre, le persone di Insomniac sono fantastiche! /MODIFICARE

Codice ridotto per l'esempio precedente:

class IUpdatable
{
public:
    virtual void Update(float dt) = 0;
protected:
    virtual void RegisterAsUpdatable() = 0;
};

class Component
{
    ...
};

class MovableComponent: public Component, public IUpdatable
{
public:
    ...
    virtual void Update(float dt);
private:
    ...
    virtual void RegisterWithUpdater();
};

class UpdateComponent: public Component
{
public:
    ...
    void UpdateAll();
    void RegisterUpdatable(Component* component);
    void RemoveUpdatable(Component* component);
private:
    ...
    std::set<Component*> updatables_;
};

Hai un esempio per un componente che non è aggiornabile? Sembra abbastanza inutile. Inserire la funzione di aggiornamento come funzione virtuale nel componente. Quindi aggiorna tutti i componenti nell'entità, non è necessario "UpdateComponent"
Maik Semder,

Alcuni componenti sono più simili ai titolari dei dati. Come PositionComponent. Molti oggetti possono avere una posizione, ma se sono fermi, che potrebbero essere la maggioranza, potrebbero accumularsi tutte quelle chiamate virtuali extra. O dire un componente sanitario. Questo non ha bisogno di fare nulla per ogni fotogramma, basta modificarlo quando è necessario. Forse l'overhead non è poi così male ma il mio UpdateComponent è stato un tentativo di non avere Update () in OGNI componente.
ptpaterson,

5
Un componente che contiene solo dati e nessuna funzionalità per modificarli nel tempo non è per definizione un componente. Devono esserci alcuni funzionalmente nel componente che cambia nel tempo, quindi ha bisogno di una funzione di aggiornamento. Se il tuo componente non ha bisogno di una funzione di aggiornamento, sai che non è un componente e dovresti ripensare il progetto. Una funzione di aggiornamento nel componente di integrità ha senso, ad esempio se si desidera che l'NPC guarisca dai danni nel tempo.
Maik Semder,

@Maik Il mio punto NON era che sono componenti che non cambieranno mai. Sono d'accordo che è sciocco. Semplicemente non hanno bisogno di aggiornare ogni frame, ma devono essere avvisati per cambiare le loro informazioni quando necessario. Nell'istanza della salute nel tempo, ci sarebbe un componente di bonus salute che ha un aggiornamento. Non credo ci sia motivo di combinare i due.
ptpaterson,

Vorrei anche notare che il codice che ho pubblicato ha solo ciò che è necessario per spiegare il concetto di UpdateComponent. Esclude qualsiasi altra forma di comunicazione tra i componenti.
ptpaterson,

Risposte:


16

Uno dei principali vantaggi di un sistema a componenti è la capacità di sfruttare i modelli di cache: buona icache e previsione perché si esegue lo stesso codice più e più volte, buona dcache perché è possibile allocare gli oggetti in pool omogenei e perché itabili, se qualsiasi, stai caldo.

Il modo in cui hai strutturato i tuoi componenti, questo vantaggio scompare completamente e, di fatto, può diventare una responsabilità in termini di prestazioni rispetto a un sistema basato sull'ereditarietà, poiché stai effettuando molte più chiamate virtuali e più oggetti con vtables.

Quello che dovresti fare è archiviare i pool per tipo e iterare ogni tipo in modo indipendente per eseguire gli aggiornamenti.

Sembra qualcosa di normale o utilizzato da qualcuno? Non riesco a trovare nulla sull'argomento.

Non è così comune nei giochi di grandi dimensioni perché non è vantaggioso. È comune in molti giochi, ma non è tecnicamente interessante, quindi nessuno ne scrive.

Come posso mantenere un ordine per i componenti come la fisica e quindi il cambio di posizione? È anche necessario?

Il codice in linguaggi come C ++ ha un modo naturale per eseguire l'esecuzione: digita le istruzioni in quell'ordine.

for (PhysicsComponent *c : physics_components)
    c->update(dt);
for (PositionComponent *c : position_components)
    c->update(dt);

In realtà, ciò non ha senso perché nessun sistema fisico robusto è strutturato in questo modo: non è possibile aggiornare un singolo oggetto fisico. Invece, il codice sarebbe più simile a:

physics_step();
for (PositionComponent *c : position_components)
    c->update(dt);
// Updates the position data from the physics data.

Grazie. Ho iniziato l'intero progetto pensando ai dati (non come i dati guidati), prevenendo mancati errori nella cache e simili. Ma una volta che ho iniziato a scrivere codice ho deciso di creare qualcosa che funzionasse come un punto. Si scopre che ha reso le cose più difficili per me. E sul serio, quell'insonnia è stato fantastico!
ptpaterson,

@Joe +1 per un'ottima risposta. Anche se mi piacerebbe sapere cosa sono un icache e un dcache. Grazie
Ray Dey il

Cache istruzioni e cache dati. Qualsiasi testo introduttivo sull'architettura dei computer dovrebbe coprirli.

@ptpaterson: nota che c'è un piccolo errore nella presentazione di Insomniac: la classe del componente deve contenere il suo indice del roster, o hai bisogno di una mappatura separata degli indici del pool per il roster degli indici.

3

Quello di cui stai parlando è ragionevole e abbastanza comune, penso. Questo potrebbe darti qualche informazione in più.


Penso di essermi imbattuto in quell'articolo un paio di settimane fa. Sono stato in grado di codificare insieme alcune versioni molto semplici di un sistema di entità basato su componenti, ognuna molto diversa mentre provo cose diverse. Cercare di riunire tutti gli articoli e gli esempi in qualcosa che voglio e sono in grado di fare. Grazie!
ptpaterson,

0

Mi piacciono questi approcci:

In breve: evitare di mantenere il comportamento di aggiornamento all'interno dei componenti. I componenti non sono comportamenti. Il comportamento (inclusi gli aggiornamenti) può essere implementato in alcuni sottosistemi a istanza singola. Tale approccio potrebbe anche aiutare a elaborare in batch comportamenti simili per più componenti (magari utilizzando parallel_for o istruzioni SIMD sui dati dei componenti).

L'idea di IUpdatable e il metodo di aggiornamento (gametime dt) sembrano un po 'troppo restrittivi e introducono ulteriori dipendenze. Potrebbe andare bene se non si utilizza l'approccio dei sottosistemi, ma se li si utilizza, IUpdatable è un livello di gerarchia ridondante. Dopotutto, MovingSystem dovrebbe sapere che deve aggiornare i componenti Location e / o Velocity direttamente per tutte le entità che hanno questi componenti, quindi non è necessario alcun componente IUpdatable intermedio.

Ma potresti utilizzare il componente aggiornabile come meccanismo per saltare l'aggiornamento di alcune entità nonostante abbiano componenti Location e / o Velocity. Il componente aggiornabile potrebbe avere un flag bool che può essere impostato su falsee che segnalerebbe a ogni sottosistema compatibile con aggiornabile che questa particolare entità attualmente non dovrebbe essere aggiornata (sebbene in tale contesto, Freezable sembra essere il nome più appropriato per il componente).

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.