Cela se fait généralement à l'aide de messages. Vous pouvez trouver beaucoup de détails dans d'autres questions sur ce site, comme ici ou là - bas .
Pour répondre à votre exemple spécifique, une solution consiste à définir une petite Message
classe que vos objets peuvent traiter, par exemple:
struct Message
{
Message(const Objt& sender, const std::string& msg)
: m_sender(&sender)
, m_msg(msg) {}
const Obj* m_sender;
std::string m_msg;
};
void Obj::Process(const Message& msg)
{
for (int i=0; i<m_components.size(); ++i)
{
// let components do some stuff with msg
m_components[i].Process(msg);
}
}
De cette façon, vous ne "polluez" pas votre Obj
interface de classe avec les méthodes liées aux composants. Certains composants peuvent choisir de traiter le message, d'autres peuvent simplement l'ignorer.
Vous pouvez commencer par appeler cette méthode directement à partir d'un autre objet:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
Dans ce cas, obj2
's Physics
choisira le message et effectuera le traitement nécessaire. Une fois terminé, il sera soit:
- Envoyez un message "SetPosition" à vous-même, que le
Position
composant choisira;
- Ou accédez directement au
Position
composant pour les modifications (ce qui est tout à fait faux pour une conception basée uniquement sur des composants, car vous ne pouvez pas supposer que chaque objet a un Position
composant, mais le Position
composant peut être une exigence de Physics
).
C'est généralement une bonne idée de retarder le traitement réel du message à la mise à jour du composant suivant . Le traiter immédiatement pourrait signifier l'envoi de messages à d'autres composants d'autres objets, donc l'envoi d'un seul message pourrait rapidement signifier une pile de spaghetti inextricable.
Vous devrez probablement opter pour un système plus avancé plus tard: files d'attente de messages asynchrones, envoi de messages à un groupe d'objets, enregistrement / désinscription par composant des messages, etc.
La Message
classe peut être un conteneur générique pour une chaîne simple comme indiqué ci-dessus, mais le traitement des chaînes au moment de l'exécution n'est pas vraiment efficace. Vous pouvez opter pour un conteneur de valeurs génériques: chaînes, entiers, flottants ... Avec un nom ou mieux encore, un identifiant, pour distinguer différents types de messages. Ou vous pouvez également dériver une classe de base pour répondre à des besoins spécifiques. Dans votre cas, vous pouvez imaginer un EmitForceMessage
qui dérive Message
et ajoute le vecteur de force souhaité, mais méfiez-vous du coût d' exécution de RTTI si vous le faites.
dynamic_cast
peut devenir un goulot d'étranglement, mais je ne m'en inquiéterai pas pour l'instant. Vous pouvez toujours optimiser cela plus tard si cela devient un problème. Les identificateurs de classe basés sur CRC fonctionnent comme un charme.Ce que j'ai fait pour résoudre un problème similaire à ce que vous montrez, c'est d'ajouter des gestionnaires de composants spécifiques et d'ajouter une sorte de système de résolution d'événements.
Ainsi, dans le cas de votre objet "Physique", une fois initialisé, il s'ajouterait à un gestionnaire central d'objets physiques. Dans la boucle du jeu, ces types de gestionnaires ont leur propre étape de mise à jour, donc lorsque ce PhysicsManager est mis à jour, il calcule toutes les interactions physiques et les ajoute dans une file d'attente d'événements.
Après avoir produit tous vos événements, vous pouvez résoudre votre file d'attente d'événements en vérifiant simplement ce qui s'est passé et en prenant des mesures en conséquence, dans votre cas, il devrait y avoir un événement indiquant que les objets A et B ont interagi d'une manière ou d'une autre, vous appelez donc votre méthode emitForceOn.
Avantages de cette méthode:
Les inconvénients:
J'espère que ça aide.
PS: Si quelqu'un a une solution plus propre / meilleure pour résoudre ce problème, j'aimerais vraiment l'entendre.
la source
Quelques points à noter sur cette conception:
la source