Hallo zusammen,
in meinem kleinen 2D Spiel bzw. Framework möchte ich die Spielobjekte mithilfe von Komponenten realisieren. Zuerst einmal geht es mir nur um die allgemeinen, gameplay-unabhängigen Objekte, wie z.B. Sprites, Tilemaps, Lichter oder Soundquellen. Um die Gegner, Items usw. kümmere ich mich im Detail später, toll wäre aber, wenn das Komponentensystem auch schon diese Objekte berücksichtigen würde.
Ich habe mich bereits in der Umsetzung des Komponentensystems versucht. Mein erster Ansatz sieht folgendermaßen aus:
Das ist die Klasse für die Spielobjekte. Sie stellt hauptsächlich einen Container für die Komponenten dar.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class Entity {
public:
void transform(Transform*); // Legt die Transformationskomponente fest.
auto transform() -> Transform*; // Fragt die Transformationskomponente ab.
// ...
private:
// Die Komponenten.
Transform *m_transform = nullptr;
Sprite *m_sprite = nullptr;
Tilemap *m_tilemap = nullptr;
// ...
};
|
Und das ist die Basisklasse für die Komponenten:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
|
class EntityComponent {
public:
void owner(Entity*); // Legt den Besitzer fest.
auto owner() -> Entity*; // Fragt den Besitzer ab.
private:
Entity *m_owner = nullptr;
};
|
Ich habe also einen sehr einfachen Ansatz gewählt. Eine Entity enthält die Pointer zu allen möglichen Komponenten als Membervariablen. Eine Komponente kann über ihren Besitzer direkt auf andere Komponenten zugreifen. Letzteres habe ich bewusst ermöglicht, damit ich kein aufwendiges Messaging-System für die Komponenten benötige. (Die Sprite-Komponente könnte ich auch mit der Tilemap-Komponente zu einer allgemeinen Graphics-Komponente zusammenfassen, aber das ist wohl eine andere Frage.)
Natürlich sehe ich auch die Schwächen dieser Umsetzung: Ich muss die Entity-Klasse für jede Komponente um einen Member und entsprechende Methoden erweitern. Für die allgemeinen Komponenten (Transform, Sprite, Tilemap ...) sehe ich darin kein allzu großes Problem, weil diese sowieso von fast allen Entities benötigt werden. Wenn ich aber an die Programmierung der Gegner, Items und anderen speziellen Spielobjekte denke, sieht es schon anders aus.
Eine weniger "statische" Implementierung des Komponentensystems könnte vielleicht Abhilfe schaffen, beispielsweise indem ich die Komponenten einfach alle in einen Vector schmeiße:
std::vector<EntityComponent*> m_components;
Das würde allerdings die Abfrage und die Kommunikation zwischen den Komponenten umständlicher machen. Entweder müsste ich dann doch ein Messaging-System hinzufügen oder bei der Abfrage einer Komponente deren Typ ermitteln, um entsprechend casten zu können, z.B. so:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
|
template <class TComponent>
auto Entity::findComponent() -> TComponent* {
for (auto component : m_components) {
auto derivedComponent = dynamic_cast<TComponent*>(component);
if (derivedComponent != nullptr) {
return derivedComponent;
}
}
return nullptr;
}
|
Alternativ könnte ich wohl auch eine Methode zum Abfragen des Typen zur EntityComponent-Klasse hinzufügen und dann statisch casten, aber ich weiß nicht, ob das bei der flachen Klassenhierarchie einen nennenswerten (Geschwindigkeits-)Vorteil bringen würde.
Unschön bei der obigen findComponent-Methode finde ich, dass selbst die Transform-Komponente auf diese Weise abgefragt werden müsste, obwohl ich weiß, dass praktisch alle Spielobjekte diese Komponente verwenden. Dieses Manko könnte ich lösen, indem ich die häufig genutzen Grundkomponenten von den "Spezialkomponenten" (Gegner, Items etc.) trenne: Die Grundkomponenten erhielten dann weiterhin jeweils ihre eigenen Membervariablen in der Entity-Klasse, während die Spezialkomponenten in das Komponentenarray eingefügt würden.
Viele Möglichkeiten also und ich bin mir ziemlich sicher, dass jede davon auf ihre Weise funktionieren würde. Zurzeit aber fällt mir die Entscheidung schwer. Bisher habe ich noch nicht komponentenbasiert programmiert, deswegen kann ich mich auch nicht aus Erfahrung für oder gegen eine dieser Optionen entscheiden. Ich kann nur vermuten ... und natürlich auf eure Hinweise und Vorschläge hoffen.
Was meint also ihr dazu? Komponenten als Membervariablen direkt in die Entity-Klasse? Oder in ein Komponenten-Array? Oder sogar beides (Grund- und Spezialkomponenten)? Soll ich mir die Mühe machen, ein Messaging-System für die Komponenten zu entwerfen oder gehts auch ohne?