Flexible Alternativen zu vielen kleinen polymorphen Klassen (zur Verwendung als Eigenschaften oder Nachrichten oder Ereignisse) C ++

9

Es gibt zwei Klassen in meinem Spiel, die wirklich nützlich sind, aber langsam zum Schmerz werden. Nachricht und Eigenschaft (Eigenschaft ist im Wesentlichen eine Komponente).

Sie stammen beide von einer Basisklasse und enthalten eine statische ID, sodass Systeme nur auf diejenigen achten können, die sie möchten. Es funktioniert sehr gut ... außer ...

Ich mache ständig neue Nachrichtentypen und Eigenschaftstypen, während ich mein Spiel erweitere. Jedes Mal muss ich 2 Dateien (hpp und cpp) und eine Tonne Boilerplate schreiben, um im Wesentlichen eine classID und einen oder zwei Standarddatentypen oder einen Zeiger zu erhalten.

Es fängt an, das Herumspielen und Ausprobieren neuer Ideen zu einer echten Aufgabe zu machen. Ich möchte, wenn ich eine neue Nachricht oder einen neuen Eigenschaftstyp erstellen möchte, einfach so etwas eingeben können

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

Anstatt einen Header und eine CPP-Datei zu erstellen, schreiben Sie einen Konstruktor, einschließlich der übergeordneten Klasse usw. usw.

Es sind ungefähr 20 - 40 Zeilen (einschließlich Wachen und alles), nur um das zu tun, was in meinem Kopf logischerweise 1 oder 2 Zeilen sind.

Gibt es ein Programmiermuster, um dies zu umgehen?

Was ist mit Skripten (von denen ich nichts weiß) ... gibt es eine Möglichkeit, eine Reihe von Klassen zu definieren, die fast gleich sind?


Genau so sieht eine Klasse aus:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

All dies nur für einen 2D-Vektor und eine ID, die sagen, dass ein 2D-Vektor erwartet werden soll. (Zugegeben, einige Eigenschaften haben kompliziertere Elemente, aber es ist die gleiche Idee)

Bryan
quelle
3
Schauen Sie sich das an, es könnte Ihnen helfen. gameprogrammingpatterns.com/type-object.html
1
All dies sieht so aus, als könnten Vorlagen helfen, nur nicht bei der statischen Initialisierung. In diesem Fall kann dies sicherlich viel einfacher gemacht werden, aber es ist schwer zu sagen, ohne weitere Informationen darüber, was Sie mit diesen IDs tun möchten und warum Sie überhaupt Vererbung verwenden. Die Verwendung der öffentlichen Vererbung, aber ohne virtuelle Funktionen, ist definitiv ein Codegeruch ... Dies ist kein Polymorphismus!
ltjax
Haben Sie die Drittanbieter-Bibliothek gefunden, die sie implementiert?
Boris

Antworten:

1

C ++ ist mächtig, aber es ist ausführlich . Wenn Sie viele kleine polymorphe Klassen wollen, die alle unterschiedlich sind, dann wird es viel Quellcode erfordern, um sie zu deklarieren und zu definieren. Es gibt wirklich nichts zu tun.

Nun, wie ltjax sagte, ist das, was Sie hier tun, nicht gerade Polymorphismus , zumindest für den Code, den Sie bereitgestellt haben. Ich kann keine gemeinsame Schnittstelle sehen, die die spezifische Implementierung von Unterklassen verbergen würde. Außer vielleicht für die Klassen-ID, aber dies ist tatsächlich redundant mit der realen Klassen-ID: Es ist der Name. Dies scheint nur eine Reihe von Klassen zu sein, die einige Daten enthalten, nichts wirklich Kompliziertes.

Dies löst Ihr Problem jedoch nicht: Sie möchten viele Nachrichten und Eigenschaften mit der geringsten Codemenge erstellen. Weniger Code bedeutet weniger Fehler, daher ist dies eine kluge Entscheidung. Leider gibt es für Sie keine einheitliche Lösung. Es ist schwer zu sagen, was Ihren Anforderungen am besten entspricht, ohne genau zu wissen, was Sie mit diesen Nachrichten und Eigenschaften tun möchten . Lassen Sie mich nur Ihre Optionen aufzeigen:

  1. Verwenden Sie C ++ - Vorlagen . Vorlagen sind eine großartige Möglichkeit, den Compiler Code für Sie schreiben zu lassen. Es wird in der C ++ - Welt immer wichtiger, da sich die Sprache weiterentwickelt, um sie besser zu unterstützen (z. B. können Sie jetzt eine variable Anzahl von Vorlagenparametern verwenden ). Es gibt eine ganze Disziplin, die sich der automatisierten Generierung von Code durch Vorlagen widmet: die Metaprogrammierung von Vorlagen . Das Problem ist: Es ist hart. Erwarten Sie nicht, dass Nicht-Programmierer selbst neue Eigenschaften hinzufügen können, wenn Sie eine solche Technik verwenden möchten.

  2. Verwenden Sie einfache alte C-Makros . Es ist alte Schule, leicht zu missbrauchen und fehleranfällig. Sie sind auch sehr einfach zu erstellen. Makros sind nur verherrlichte Kopierpasten, die vom Präprozessor ausgeführt werden. Sie eignen sich also durchaus dazu, Tonnen von Dingen zu erstellen, die mit kleinen Variationen fast gleich sind. Erwarten Sie jedoch nicht, dass andere Programmierer Sie für ihre Verwendung lieben, da Makros häufig verwendet werden, um Fehler im gesamten Programmdesign zu verbergen. Manchmal sind sie jedoch hilfreich.

  3. Eine andere Möglichkeit besteht darin, ein externes Tool zu verwenden, um den Code für Sie zu generieren. Es wurde bereits in einer früheren Antwort erwähnt, daher werde ich darauf nicht näher eingehen.

  4. Es gibt andere Sprachen, die weniger ausführlich sind und es Ihnen ermöglichen, Klassen viel einfacher zu erstellen. Also , wenn Sie bereits Skript Bindungen (oder Sie planen , sie auf mit), definieren diese Klassen in Skript könnte eine Option sein. Der Zugriff über C ++ ist jedoch recht kompliziert, und Sie verlieren den größten Teil des Nutzens der vereinfachten Erstellung von Klassen. Dies ist wahrscheinlich nur dann sinnvoll, wenn Sie vorhaben, den größten Teil Ihrer Spielelogik in einer anderen Sprache auszuführen.

  5. Zu guter Letzt sollten Sie ein datengesteuertes Design in Betracht ziehen . Sie können diese "Klassen" in einfachen Textdateien mit einem Layout definieren, das dem von Ihnen selbst vorgeschlagenen ähnlich ist. Sie müssen ein benutzerdefiniertes Format und einen Parser dafür erstellen oder eine der mehreren bereits verfügbaren Optionen (.ini, XML, JSON und so weiter) verwenden. Auf der C ++ - Seite müssen Sie dann ein System erstellen, das eine Sammlung dieser verschiedenen Arten von Objekten unterstützt. Dies entspricht fast dem Skriptansatz (und erfordert wahrscheinlich noch mehr Arbeit), außer dass Sie ihn genauer auf Ihre Bedürfnisse zuschneiden können. Und wenn Sie es einfach genug machen, können Nicht-Programmierer möglicherweise selbst neue Dinge erstellen.

Laurent Couvidou
quelle
0

Wie wäre es mit einem Tool zur Codegenerierung, das Ihnen hilft?

Sie können beispielsweise Ihren Nachrichtentyp und Ihre Mitglieder in einer kleinen Textdatei definieren und das Code-Gen-Tool diese analysieren lassen und alle C ++ - Dateien auf dem Boilerplate als vorgefertigten Schritt schreiben.

Es gibt bereits Lösungen wie ANTLR oder LEX / YACC, und je nach Komplexität Ihrer Eigenschaften und Nachrichten ist es nicht schwierig, eigene Lösungen zu erstellen.

Chronischer Spielprogrammierer
quelle
Nur eine Alternative zum LEX / YACC-Ansatz: Wenn ich mit nur geringfügigen Änderungen sehr ähnliche Dateien generieren muss, verwende ich ein einfaches und kleines Python-Skript und eine C ++ - Codevorlagendatei mit Tags. Das Python-Skript sucht in der Vorlage nach diesen Tags und ersetzt sie durch die repräsentativen Token des zu erstellenden Elements.
Sieger
0

Ich empfehle Ihnen, sich über die Protokollpuffer von Google ( http://code.google.com/p/protobuf/ ) zu informieren . Sie sind eine sehr clevere Möglichkeit, mit allgemeinen Nachrichten umzugehen. Sie müssen nur die Eigenschaften in einem strukturähnlichen Muster angeben, und ein Codegenerator erledigt die gesamte Aufgabe, die Klassen für Sie zu generieren (Java, C ++ oder C #). Alle generierten Klassen verfügen über Text- und Binärparser, wodurch sie sowohl für die textbasierte Nachrichteninitialisierung als auch für die Serialisierung geeignet sind.

dsilva.vinicius
quelle
Meine Messaging-Zentrale ist der Kern meines Programms. Wissen Sie, ob Protokollpuffer einen hohen Overhead verursachen?
Bryan
Ich glaube nicht, dass sie einen hohen Overhead verursachen, da die API nur eine Builder-Klasse für jede Nachricht und die Klasse für die Nachricht selbst ist. Google verwendet sie für den Kern ihrer Infrastruktur. Beispielsweise werden alle Google App Engine-Entitäten alle in Protokollpuffer konvertiert, bevor sie beibehalten werden. Im Zweifelsfall empfehle ich Ihnen, einen Vergleichstest zwischen Ihrer aktuellen Implementierung und den Protokollpuffern durchzuführen. Wenn Sie der Meinung sind, dass der Overhead akzeptabel ist, verwenden Sie sie.
dsilva.vinicius