Ich mache ein Spiel, das komponentenbasierte Spielobjekte verwendet, und es fällt mir schwer, eine Möglichkeit für jede Komponente zu implementieren, mit ihrem Spielobjekt zu kommunizieren. Anstatt alles auf einmal zu erklären, werde ich jeden Teil des relevanten Beispielcodes erklären:
class GameObjectManager {
public:
//Updates all the game objects
void update(Time dt);
//Sends a message to all game objects
void sendMessage(Message m);
private:
//Vector of all the game objects
std::vector<GameObject> gameObjects;
//vectors of the different types of components
std::vector<InputComponent> input;
std::vector<PhysicsComponent> ai;
...
std::vector<RenderComponent> render;
}
Das GameObjectManager
enthält alle Spielobjekte und deren Komponenten. Es ist auch für die Aktualisierung der Spielobjekte verantwortlich. Dazu werden die Komponentenvektoren in einer bestimmten Reihenfolge aktualisiert. Ich verwende Vektoren anstelle von Arrays, so dass die Anzahl der Spielobjekte, die gleichzeitig existieren können, praktisch unbegrenzt ist.
class GameObject {
public:
//Sends a message to the components in this game object
void sendMessage(Message m);
private:
//id to keep track of components in the manager
const int id;
//Pointers to components in the game object manager
std::vector<Component*> components;
}
Die GameObject
Klasse kennt ihre Komponenten und kann Nachrichten an sie senden.
class Component {
public:
//Receives messages and acts accordingly
virtual void handleMessage(Message m) = 0;
virtual void update(Time dt) = 0;
protected:
//Calls GameObject's sendMessage
void sendMessageToObject(Message m);
//Calls GameObjectManager's sendMessage
void sendMessageToWorld(Message m);
}
Die Component
Klasse ist rein virtuell, sodass Klassen für die verschiedenen Komponententypen den Umgang mit Nachrichten und Aktualisierungen implementieren können. Es ist auch in der Lage, Nachrichten zu senden.
Nun stellt sich das Problem, wie die Komponenten die sendMessage
Funktionen in GameObject
und aufrufen können GameObjectManager
. Ich habe zwei mögliche Lösungen gefunden:
- Geben Sie
Component
einen Zeiger auf seineGameObject
.
Da sich die Spielobjekte jedoch in einem Vektor befinden, können die Zeiger schnell ungültig werden (das Gleiche gilt für den Vektor in GameObject
, aber hoffentlich kann die Lösung dieses Problems auch diesen lösen). Ich könnte die Spielobjekte in ein Array einfügen, aber dann müsste ich eine beliebige Zahl für die Größe eingeben, die leicht unnötig hoch sein und Speicher verschwenden könnte.
- Geben Sie
Component
einen Zeiger auf dieGameObjectManager
.
Ich möchte jedoch nicht, dass Komponenten die Aktualisierungsfunktion des Managers aufrufen können. Ich bin die einzige Person, die an diesem Projekt arbeitet, aber ich möchte nicht die Gewohnheit haben, potenziell gefährlichen Code zu schreiben.
Wie kann ich dieses Problem beheben, während mein Code sicher und cachefreundlich bleibt?
"Cache-freundlich" zu sein, ist ein Hauptanliegen großer Spiele . Dies scheint mir eine vorzeitige Optimierung zu sein.
Eine Möglichkeit, dies zu lösen, ohne "Cache-freundlich" zu sein, besteht darin, Ihr Objekt auf dem Heap anstatt auf dem Stapel zu erstellen: Verwenden Sie
new
und (intelligente) Zeiger für Ihre Objekte. Auf diese Weise können Sie auf Ihre Objekte verweisen, und ihre Referenz wird nicht ungültig.Für eine cachefreundlichere Lösung können Sie die De- / Zuweisung von Objekten selbst verwalten und Handles für diese Objekte verwenden.
Grundsätzlich reserviert ein Objekt bei der Initialisierung Ihres Programms einen Speicherblock auf dem Heap (nennen wir es MemMan). Wenn Sie dann eine Komponente erstellen möchten, teilen Sie MemMan mit, dass Sie eine Komponente der Größe X benötigen. Ich werde es für Sie reservieren, ein Handle erstellen und intern behalten, wo in seiner Zuordnung das Objekt für dieses Handle ist. Das Handle wird zurückgegeben, und das einzige, was Sie über das Objekt behalten, ist niemals ein Zeiger auf seine Position im Speicher.
Wenn Sie die Komponente benötigen, werden Sie MemMan bitten, auf dieses Objekt zuzugreifen, was es gerne tut. Aber behalten Sie den Verweis nicht bei, weil ...
Eine der Aufgaben von MemMan ist es, die Objekte im Speicher nahe beieinander zu halten. Einmal alle paar Spielrahmen können Sie MemMan anweisen, Objekte im Speicher neu anzuordnen (oder dies kann automatisch erfolgen, wenn Sie Objekte erstellen / löschen). Es wird seine Handle-to-Memory-Standortzuordnung aktualisieren. Ihre Handles sind immer gültig, aber wenn Sie einen Verweis auf den Speicherplatz (einen Zeiger oder einen Verweis ) behalten , werden Sie nur Verzweiflung und Trostlosigkeit finden.
Laut Lehrbüchern hat diese Art der Speicherverwaltung mindestens zwei Vorteile:
Beachten Sie, dass die Art und Weise, wie Sie MemMan verwenden und wie Sie den Speicher intern organisieren, wirklich davon abhängt, wie Sie Ihre Komponenten verwenden. Wenn Sie sie basierend auf ihrem Typ durchlaufen, möchten Sie Komponenten nach Typ beibehalten. Wenn Sie sie basierend auf ihrem Spielobjekt durchlaufen, müssen Sie einen Weg finden, um sicherzustellen, dass sie nahe beieinander liegen eine andere basierend darauf, etc ...
quelle