Dies geschieht normalerweise mithilfe von Nachrichten. Sie können viele Details in anderen Fragen auf dieser Site finden, wie hier oder dort .
Um auf Ihr spezielles Beispiel zu antworten, müssen Sie eine kleine Message
Klasse definieren , die Ihre Objekte verarbeiten können, z.
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);
}
}
Auf diese Weise "verschmutzen" Sie Ihre Obj
Klassenschnittstelle nicht mit komponentenbezogenen Methoden. Einige Komponenten können die Nachricht verarbeiten, andere ignorieren sie möglicherweise einfach.
Sie können beginnen, indem Sie diese Methode direkt von einem anderen Objekt aus aufrufen:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
In diesem Fall obj2
wählt s Physics
die Nachricht aus und führt die erforderliche Verarbeitung durch. Wenn Sie fertig sind, wird es entweder:
- Senden Sie eine "SetPosition" -Nachricht an sich selbst, die von der
Position
Komponente ausgewählt wird.
- Oder greifen Sie direkt auf die
Position
Komponente zu, um Änderungen vorzunehmen (völlig falsch für ein reines komponentenbasiertes Design, da Sie nicht davon ausgehen können, dass jedes Objekt eine Position
Komponente hat, die Position
Komponente jedoch möglicherweise eine Anforderung von ist Physics
).
Es ist generell eine gute Idee , verzögern die eigentliche Verarbeitung der Nachricht an die nächste Komponente zu aktualisieren. Die sofortige Verarbeitung kann das Senden von Nachrichten an andere Komponenten anderer Objekte bedeuten. Das Senden von nur einer Nachricht kann also schnell einen untrennbaren Spaghetti-Stapel bedeuten.
Sie müssen sich wahrscheinlich später für ein erweitertes System entscheiden: asynchrone Nachrichtenwarteschlangen, Senden von Nachrichten an Objektgruppen, Registrieren / Aufheben der Registrierung von Nachrichten pro Komponente usw.
Die Message
Klasse kann ein generischer Container für eine einfache Zeichenfolge sein, wie oben gezeigt, aber die Verarbeitung von Zeichenfolgen zur Laufzeit ist nicht wirklich effizient. Sie können einen Container mit generischen Werten verwenden: Zeichenfolgen, Ganzzahlen, Gleitkommazahlen ... Mit einem Namen oder noch besser einer ID, um verschiedene Arten von Nachrichten zu unterscheiden. Sie können auch eine Basisklasse ableiten, die bestimmten Anforderungen entspricht. In Ihrem Fall können Sie sich einen vorstellen EmitForceMessage
, der von Message
dem gewünschten Kraftvektor abgeleitet ist und diesen hinzufügt. Beachten Sie jedoch die Laufzeitkosten von RTTI, wenn Sie dies tun.
dynamic_cast
kann zu einem Engpass werden, aber darüber würde ich mir vorerst keine Sorgen machen. Sie können dies später noch optimieren, wenn es zu einem Problem wird. CRC-basierte Klassenkennungen wirken wie ein Zauber.Um ein ähnliches Problem wie das von Ihnen gezeigte zu lösen, habe ich einige spezifische Komponentenhandler und eine Art Ereignisauflösungssystem hinzugefügt.
Im Fall Ihres "Physik" -Objekts würde es sich bei seiner Initialisierung einem zentralen Manager von Physikobjekten hinzufügen. In der Spielschleife haben diese Art von Managern ihren eigenen Aktualisierungsschritt. Wenn dieser PhysicsManager aktualisiert wird, berechnet er alle physischen Interaktionen und fügt sie einer Ereigniswarteschlange hinzu.
Nachdem Sie alle Ihre Ereignisse erstellt haben, können Sie Ihre Ereigniswarteschlange auflösen, indem Sie einfach überprüfen, was passiert ist, und entsprechende Maßnahmen ergreifen. In Ihrem Fall sollte ein Ereignis vorhanden sein, das besagt, dass Objekt A und B irgendwie interagiert haben, sodass Sie Ihre emitForceOn-Methode aufrufen.
Vorteile dieser Methode:
Nachteile:
Ich hoffe das hilft.
PS: Wenn jemand einen saubereren / besseren Weg hat, dies zu lösen, würde ich es wirklich gerne hören.
quelle
Ein paar Dinge, die Sie bei diesem Design beachten sollten:
quelle