Je crée un jeu qui utilise des objets de jeu basés sur des composants, et j'ai du mal à implémenter un moyen pour chaque composant de communiquer avec son objet de jeu. Plutôt que d'expliquer tout à la fois, j'expliquerai chaque partie de l'exemple de code pertinent:
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;
}
Le GameObjectManager
contient tous les objets du jeu et leurs composants. Il est également responsable de la mise à jour des objets du jeu. Il le fait en mettant à jour les vecteurs de composants dans un ordre spécifique. J'utilise des vecteurs au lieu de tableaux afin qu'il n'y ait pratiquement aucune limite au nombre d'objets de jeu pouvant exister à la fois.
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;
}
La GameObject
classe sait quels sont ses composants et peut leur envoyer des messages.
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);
}
le Component
classe est entièrement virtuelle afin que les classes pour les différents types de composants puissent implémenter comment gérer les messages et les mettre à jour. Il est également capable d'envoyer des messages.
Maintenant, le problème se pose sur la façon dont les composants peuvent appeler les sendMessage
fonctions dans GameObject
et GameObjectManager
. J'ai trouvé deux solutions possibles:
- Donnez
Component
un pointeur à sonGameObject
.
Cependant, comme les objets du jeu sont dans un vecteur, les pointeurs pourraient rapidement être invalidés (la même chose pourrait être dite du vecteur dans GameObject
, mais j'espère que la solution à ce problème peut également résoudre celui-ci). Je pourrais mettre les objets du jeu dans un tableau, mais je devrais alors passer un nombre arbitraire pour la taille, qui pourrait facilement être inutilement élevé et gaspiller de la mémoire.
- Donnez
Component
un pointeur sur leGameObjectManager
.
Cependant, je ne veux pas que les composants puissent appeler la fonction de mise à jour du gestionnaire. Je suis la seule personne à travailler sur ce projet, mais je ne veux pas prendre l'habitude d'écrire du code potentiellement dangereux.
Comment puis-je résoudre ce problème tout en gardant mon code sûr et compatible avec le cache?
Être «compatible avec le cache» est une préoccupation des grands jeux . Cela me semble être une optimisation prématurée.
Une façon de résoudre ce problème sans être «compatible avec le cache» serait de créer votre objet sur le tas plutôt que sur la pile: utilisez
new
des pointeurs (intelligents) pour vos objets. De cette façon, vous pourrez référencer vos objets et leur référence ne sera pas invalidée.Pour une solution plus conviviale pour le cache, vous pouvez gérer vous-même la dé / allocation des objets et utiliser des poignées pour ces objets.
Fondamentalement, à l'initialisation de votre programme, un objet réserve un morceau de mémoire sur le tas (appelons-le MemMan), puis, lorsque vous voulez créer un composant, vous dites à MemMan que vous avez besoin d'un composant de taille X, c'est ' Je vais le réserver pour vous, créer une poignée et garder en interne où dans son allocation se trouve l'objet pour cette poignée. Il retournera la poignée, et que la seule chose que vous garderez sur l'objet, jamais un pointeur vers son emplacement en mémoire.
Comme vous avez besoin du composant, vous demanderez à MemMan d'accéder à cet objet, ce qu'il fera volontiers. Mais ne gardez pas la référence car ...
L'une des tâches de MemMan est de garder les objets proches les uns des autres en mémoire. Une fois toutes les quelques images de jeu, vous pouvez demander à MemMan de réorganiser les objets en mémoire (ou il pourrait le faire automatiquement lorsque vous créez / supprimez des objets). Il mettra à jour sa carte d'emplacement de poignée à mémoire. Vos poignées seront toujours valides, mais si vous gardiez une référence à l'espace mémoire (un pointeur ou une référence ), vous ne trouverez que désespoir et désolation.
Les manuels indiquent que cette façon de gérer votre mémoire présente au moins 2 avantages:
Gardez à l'esprit que la façon dont vous utilisez MemMan et la façon dont vous organiserez la mémoire en interne dépendent vraiment de la façon dont vous utiliserez vos composants. Si vous les parcourez en fonction de leur type, vous souhaiterez conserver les composants par type.Si vous les parcourez en fonction de leur objet de jeu, vous devrez trouver un moyen de vous assurer qu'ils sont proches l'un de l'autre. un autre basé sur ça, etc ...
la source