Ich schreibe einen Shooter (wie 1942, klassische 2D-Grafik) und möchte einen komponentenbasierten Ansatz verwenden. Bisher habe ich über folgendes Design nachgedacht:
Jedes Spielelement (Luftschiff, Projektil, Powerup, Feind) ist eine Entität
Jede Entität besteht aus einer Reihe von Komponenten, die zur Laufzeit hinzugefügt oder entfernt werden können. Beispiele sind Position, Sprite, Gesundheit, IA, Schaden, BoundingBox usw.
Die Idee ist, dass Luftschiff, Projektil, Feind und Powerup KEINE Spielklassen sind. Eine Entität wird nur durch die Komponenten definiert, deren Eigentümer sie ist (und die sich im Laufe der Zeit ändern können). Das Spieler-Luftschiff beginnt also mit Sprite-, Positions-, Gesundheits- und Eingabekomponenten. Ein Powerup hat das Sprite, Position, BoundingBox. Und so weiter.
Die Hauptschleife verwaltet das Spiel "Physik", dh wie die Komponenten miteinander interagieren:
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());
...
Komponenten werden in der Hauptanwendung von C ++ fest codiert. Entitäten können in einer XML-Datei definiert werden (der IA-Teil in einer Lua- oder Python-Datei).
Die Hauptschleife kümmert sich nicht viel um Entitäten: Sie verwaltet nur Komponenten. Das Software-Design sollte Folgendes ermöglichen:
Erhalten Sie für eine gegebene Komponente die Entität, zu der sie gehört
Erhalten Sie bei einer gegebenen Entität die Komponente vom Typ "type"
Tun Sie für alle Entitäten etwas
Tun Sie für alle Entitätskomponenten etwas (z. B. serialisieren)
Ich habe über folgendes nachgedacht:
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;
Mit diesem Design kann ich # 1, # 2, # 3 (dank boost :: fusion :: map Algorithmen) und # 4 erreichen. Auch alles ist O (1) (ok, nicht genau, aber es ist immer noch sehr schnell).
Es gibt auch einen "allgemeineren" Ansatz:
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
...
};
Ein weiterer Ansatz besteht darin, die Entity-Klasse zu entfernen: Jeder Komponententyp lebt in seiner eigenen Liste. Es gibt also eine Sprite-Liste, eine Health-Liste, eine Damage-Liste usw. Ich weiß, dass sie aufgrund der Entity-ID derselben logischen Entität angehören. Dies ist einfacher, aber langsamer: Die IA-Komponenten müssen grundsätzlich auf alle Komponenten der anderen Entität zugreifen können und müssen daher bei jedem Schritt die Liste der anderen Komponenten durchsuchen.
Welcher Ansatz ist Ihrer Meinung nach besser? ist die boost :: fusion map dafür geeignet?