Ich arbeite jetzt schon eine Weile an einem 2D-Rollenspiel, und mir ist klar geworden, dass ich einige schlechte Designentscheidungen getroffen habe. Es gibt ein paar Dinge, die mir Probleme bereiten, und ich habe mich gefragt, welche Entwürfe andere Leute verwendet haben, um sie zu überwinden, oder welche sie verwenden würden.
Für einen kleinen Hintergrund habe ich letzten Sommer in meiner Freizeit angefangen, daran zu arbeiten. Ich habe das Spiel anfangs in C # gemacht, aber vor ungefähr 3 Monaten habe ich beschlossen, zu C ++ zu wechseln. Ich wollte C ++ in den Griff bekommen, da es schon eine Weile her ist, dass ich es intensiv genutzt habe, und dachte, ein interessantes Projekt wie dieses wäre ein guter Motivator. Ich habe die Boost-Bibliothek intensiv genutzt und SFML für Grafiken und FMOD für Audio verwendet.
Ich habe ein gutes Stück Code geschrieben, überlege mir aber, ob ich es wegwerfen und von vorne anfangen soll.
Hier sind die Hauptanliegen, die ich habe und die ich ein paar Meinungen dazu einholen möchte, wie andere sie gelöst haben oder lösen würden.
1. Zyklische Abhängigkeiten Als ich das Spiel in C # machte, musste ich mir darüber keine Sorgen machen, da es dort kein Problem gibt. Der Umstieg auf C ++ ist zu einem ziemlich großen Problem geworden und hat mich zu der Annahme gebracht, dass ich die Dinge möglicherweise falsch entworfen habe. Ich kann mir nicht wirklich vorstellen, wie ich meine Klassen entkoppeln kann und sie trotzdem machen lassen, was ich will. Hier einige Beispiele für eine Abhängigkeitskette:
Ich habe eine Statuseffektklasse. Die Klasse verfügt über eine Reihe von Methoden (Anwenden / Unanwenden, Häkchen usw.), um ihre Effekte auf ein Zeichen anzuwenden. Zum Beispiel,
virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);
Diese Funktion wird jedes Mal aufgerufen, wenn der Charakter, dem der Statuseffekt zugefügt wurde, an der Reihe ist. Es wird verwendet, um Effekte wie Regen, Poison usw. zu implementieren. Es werden jedoch auch Abhängigkeiten von der BaseCharacter-Klasse und der BattleField-Klasse eingeführt. Natürlich muss die BaseCharacter-Klasse nachverfolgen, welche Statuseffekte derzeit auf sie aktiv sind, damit dies eine zyklische Abhängigkeit darstellt. Battlefield muss den Überblick über die kämpfenden Gruppen behalten, und die Gruppenklasse verfügt über eine Liste von BaseCharacters, die eine weitere zyklische Abhängigkeit einführen.
2 - Ereignisse
In C # habe ich die Delegierten ausgiebig genutzt, um Ereignisse an Charakteren, Schlachtfeldern usw. anzuhängen (zum Beispiel gab es einen Delegierten, wenn sich der Gesundheitszustand des Charakters änderte, wenn sich ein Status änderte, wenn ein Statuseffekt hinzugefügt / entfernt wurde usw.) .) und die Schlachtfeld- / Grafikkomponenten würden sich in diese Delegierten einhängen, um deren Auswirkungen durchzusetzen. In C ++ habe ich etwas Ähnliches gemacht. Offensichtlich gibt es kein direktes Äquivalent zu C # -Delegierten. Stattdessen habe ich Folgendes erstellt:
typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;
und in meiner Charakterklasse
std::map<std::string, StatChangeFunction> StatChangeEventHandlers;
Wenn sich der Status des Charakters änderte, iterierte ich und rief jede StatChangeFunction auf der Karte auf. Ich mache mir zwar Sorgen, dass dies ein schlechter Ansatz ist, um Dinge zu tun.
3 - Grafiken
Das ist das große Ding. Es hat nichts mit der Grafikbibliothek zu tun, die ich verwende, ist aber eher eine konzeptionelle Sache. In C # habe ich Grafiken mit vielen meiner Kurse gekoppelt, von denen ich weiß, dass es eine schreckliche Idee ist. Diesmal wollte ich es entkoppelt tun und versuchte es mit einem anderen Ansatz.
Um meine Grafiken zu implementieren, stellte ich mir alle Grafiken im Spiel als eine Reihe von Bildschirmen vor. Das heißt, es gibt einen Titelbildschirm, einen Charakterstatusbildschirm, einen Kartenbildschirm, einen Inventarbildschirm, einen Kampfbildschirm und einen Kampf-GUI-Bildschirm. Grundsätzlich könnte ich diese Bildschirme nach Bedarf übereinander stapeln, um die Spielgrafiken zu erstellen. Was auch immer der aktive Bildschirm ist, besitzt die Spieleingabe.
Ich habe einen Bildschirmmanager entworfen, der Bildschirme basierend auf Benutzereingaben verschiebt und öffnet.
Wenn Sie sich beispielsweise in einem Kartenbildschirm (Eingabehandler / Visualisierer für eine Kachelkarte) befinden und die Starttaste drücken, wird der Bildschirmmanager aufgerufen, um einen Hauptmenübildschirm über den Kartenbildschirm zu schieben und die Karte zu markieren Bildschirm nicht gezeichnet / aktualisiert werden. Der Player navigiert durch das Menü, gibt dem Bildschirmmanager je nach Bedarf weitere Befehle aus, um neue Bildschirme auf den Bildschirmstapel zu verschieben, und fügt sie dann ein, wenn der Benutzer Bildschirme / Abbrüche ändert. Schließlich, wenn der Spieler das Hauptmenü verlässt, würde ich es ausklappen und zum Kartenbildschirm zurückkehren, darauf hinweisen, dass es gezeichnet / aktualisiert werden soll, und von dort aus fortfahren.
Kampfbildschirme wären komplexer. Ich hätte einen Bildschirm als Hintergrund, einen Bildschirm zur Visualisierung jeder Partei im Kampf und einen Bildschirm zur Visualisierung der Benutzeroberfläche für den Kampf. Die Benutzeroberfläche würde sich in die Zeichenereignisse einhängen und diese verwenden, um zu bestimmen, wann die Benutzeroberflächenkomponenten aktualisiert / neu gezeichnet werden sollen. Schließlich würde jeder Angriff, für den ein Animationsskript verfügbar ist, eine zusätzliche Ebene aufrufen, um sich selbst zu animieren, bevor der Bildschirmstapel ausgeblendet wird. In diesem Fall wird jede Ebene durchgehend als zeichnbar und aktualisierbar markiert, und ich erhalte einen Stapel von Bildschirmen, auf denen meine Kampfgrafiken angezeigt werden.
Obwohl ich den Bildschirmmanager noch nicht zum einwandfreien Funktionieren gebracht habe, glaube ich, dass ich es mit der Zeit schaffen kann. Meine Frage ist, ist dies überhaupt ein lohnender Ansatz? Wenn es ein schlechtes Design ist, möchte ich jetzt wissen, bevor ich zu viel Zeit in die Herstellung aller Bildschirme investiere, die ich benötigen werde. Wie baust du die Grafik für dein Spiel auf?
quelle
Ihre zyklischen Abhängigkeiten sollten kein Problem sein, solange Sie die Klassen in den Header-Dateien deklarieren und sie tatsächlich in die CPP-Dateien (oder was auch immer) einschließen.
Für das Ereignissystem zwei Vorschläge:
1) Wenn Sie das Muster beibehalten möchten, das Sie jetzt verwenden, sollten Sie zu einer boost :: unordered_map anstelle von std :: map wechseln. Das Mappen mit Strings als Schlüsseln ist langsam, zumal .NET einige nette Dinge unter der Haube tut, um die Dinge zu beschleunigen. Bei Verwendung von unordered_map-Hashes werden die Zeichenfolgen durchsucht, sodass Vergleiche im Allgemeinen schneller sind.
2) Überlegen Sie sich, auf etwas Stärkeres wie boost :: signals umzuschalten. Wenn Sie das tun, können Sie nette Dinge tun, wie Ihre Spielobjekte verfolgbar zu machen, indem Sie von boost :: signals :: trackable ableiten, und den Destruktor sich darum kümmern lassen, alles zu bereinigen, anstatt sich manuell vom Ereignissystem abmelden zu müssen. Sie können auch mehrere Signale zu jedem Schlitz zeigen (oder umgekehrt, ich die genaue Nomenklatur nicht erinnern) , so dass es sehr ähnlich ist zu tun ,
+=
auf einedelegate
in C #. Das größte Problem bei boost :: signals besteht darin, dass es kompiliert werden muss, nicht nur Header. Abhängig von Ihrer Plattform kann es schwierig sein, einsatzbereit zu sein.quelle