Sto scrivendo uno sparatutto (come il 1942, la classica grafica 2D) e mi piacerebbe usare un approccio basato sui componenti. Finora ho pensato al seguente design:
Ogni elemento di gioco (dirigibile, proiettile, potenziamento, nemico) è un'entità
Ogni entità è un insieme di componenti che possono essere aggiunti o rimossi in fase di esecuzione. Esempi sono Position, Sprite, Health, IA, Damage, BoundingBox ecc.
L'idea è che Airship, Projectile, Enemy, Powerup NON siano classi di gioco. Un'entità è definita solo dai componenti che possiede (e che possono cambiare nel tempo). Quindi il Dirigibile del giocatore inizia con i componenti Sprite, Posizione, Salute e Input. Un powerup ha Sprite, Position, BoundingBox. E così via.
Il ciclo principale gestisce la "fisica" del gioco, ovvero il modo in cui i componenti interagiscono tra loro:
foreach(entity (let it be entity1) with a Damage component)
foreach(entity (let it be entity2) with a Health component)
if(the entity1.BoundingBox collides with entity2.BoundingBox)
{
entity2.Health.decrease(entity1.Damage.amount());
}
foreach(entity with a IA component)
entity.IA.update();
foreach(entity with a Sprite component)
draw(entity.Sprite.surface());
...
I componenti sono codificati nell'applicazione C ++ principale. Le entità possono essere definite in un file XML (la parte IA in un file lua o python).
Il ciclo principale non si preoccupa molto delle entità: gestisce solo i componenti. La progettazione del software dovrebbe consentire di:
Dato un componente, ottieni l'entità a cui appartiene
Data un'entità, ottieni il componente di tipo "tipo"
Per tutte le entità, fai qualcosa
Per tutti i componenti dell'entità, fai qualcosa (ad es. Serializzare)
Stavo pensando a quanto segue:
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };
// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
int id; // entity id
boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
template <class C> bool has_component() { return components.at<C>() != 0; }
template <class C> C* get_component() { return components.at<C>(); }
template <class C> void add_component(C* c) { components.at<C>() = c; }
template <class C> void remove_component(C* c) { components.at<C>() = 0; }
void serialize(filestream, op) { /* Serialize all componets*/ }
...
};
std::list<Entity*> entity_list;
Con questo design posso ottenere # 1, # 2, # 3 (grazie agli algoritmi boost :: fusion :: map) e # 4. Inoltre tutto è O (1) (ok, non esattamente, ma è ancora molto veloce).
Esiste anche un approccio più "comune":
class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };
class Entity
{
int id; // entity id
std::vector<Component*> components;
bool has_component() { return components[i] != 0; }
template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};
Un altro approccio è quello di sbarazzarsi della classe Entity: ogni tipo di componente vive nel proprio elenco. Quindi c'è una lista Sprite, una lista di salute, una lista di danni ecc. So che appartengono alla stessa entità logica a causa dell'ID entità. Questo è più semplice, ma più lento: i componenti IA hanno bisogno di accedere sostanzialmente a tutti i componenti di altre entità e ciò richiederebbe la ricerca dell'elenco di ogni altro componente ad ogni passaggio.
Quale approccio pensi sia migliore? la mappa boost :: fusion è adatta per essere utilizzata in quel modo?