J'écris un jeu de tir (comme 1942, des graphismes 2D classiques) et j'aimerais utiliser une approche basée sur les composants. Jusqu'à présent, j'ai pensé à la conception suivante:
Chaque élément de jeu (dirigeable, projectile, power-up, ennemi) est une entité
Chaque entité est un ensemble de composants qui peuvent être ajoutés ou supprimés au moment de l'exécution. Les exemples sont Position, Sprite, Santé, IA, Dommages, BoundingBox etc.
L'idée est que les dirigeables, les projectiles, les ennemis et les bonus ne sont PAS des classes de jeu. Une entité n'est définie que par les composants qu'elle possède (et qui peuvent changer avec le temps). Le joueur Airship commence donc avec les composants Sprite, Position, Santé et Input. Un powerup a le Sprite, Position, BoundingBox. Etc.
La boucle principale gère le jeu "physique", c'est-à-dire comment les composants interagissent entre eux:
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());
...
Les composants sont codés en dur dans l'application C ++ principale. Les entités peuvent être définies dans un fichier XML (la partie IA dans un fichier lua ou python).
La boucle principale ne se soucie pas beaucoup des entités: elle gère uniquement les composants. La conception du logiciel doit permettre:
Étant donné un composant, obtenez l'entité à laquelle il appartient
Étant donné une entité, obtenez le composant de type "type"
Pour toutes les entités, faites quelque chose
Pour tous les composants de l'entité, faites quelque chose (par exemple: sérialiser)
Je pensais à ce qui suit:
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;
Avec cette conception, je peux obtenir # 1, # 2, # 3 (grâce aux algorithmes boost :: fusion :: map) et # 4. De plus, tout est O (1) (ok, pas exactement, mais c'est toujours très rapide).
Il existe également une approche plus "commune":
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
...
};
Une autre approche consiste à se débarrasser de la classe Entity: chaque type Component vit dans sa propre liste. Il y a donc une liste de sprites, une liste de santé, une liste de dégâts, etc. Je sais qu'ils appartiennent à la même entité logique en raison de l'ID d'entité. C'est plus simple, mais plus lent: les composants IA doivent avoir accès essentiellement à tous les composants des autres entités et cela nécessiterait de rechercher la liste des autres composants à chaque étape.
Quelle approche pensez-vous est la meilleure? la carte boost :: fusion est-elle adaptée pour être utilisée de cette manière?