Betrachten Sie ein Kartenspiel wie Hearthstone .
Es gibt Hunderte von Karten, die eine Vielzahl von Dingen ausführen, von denen einige sogar für eine einzelne Karte einzigartig sind! Zum Beispiel gibt es eine Karte (genannt Nozdormu), die die Anzahl der Spielerumdrehungen auf nur 15 Sekunden reduziert!
Wie vermeidet man bei einer Vielzahl von möglichen Effekten magische Zahlen und einmalige Überprüfungen im gesamten Code? Wie vermeidet man eine "Check_Nozdormu_In_Play" -Methode in der PlayerTurnTime-Klasse? Und wie kann man den Code so organisieren, dass Sie, wenn Sie noch mehr Effekte hinzufügen , Kernsysteme nicht umgestalten müssen, um Dinge zu unterstützen, die sie noch nie zuvor unterstützt haben?
architecture
software-engineering
scripting
Sable Träumer
quelle
quelle
Antworten:
Haben Sie sich mit Entitätskomponentensystemen und Event-Messaging-Strategien befasst?
Statuseffekte sollten Komponenten sein, die ihre dauerhaften Effekte in einer OnCreate () -Methode anwenden, ihre Effekte in OnRemoved () verfallen lassen und Spielereignismeldungen abonnieren können, um Effekte anzuwenden, die als Reaktion auf ein Ereignis auftreten.
Wenn der Effekt dauerhaft bedingt ist (für X Runden gültig, aber nur unter bestimmten Umständen), müssen Sie möglicherweise in verschiedenen Phasen nach diesen Bedingungen suchen.
Dann stellen Sie einfach sicher, dass Ihr Spiel auch keine magischen Standardnummern hat. Stellen Sie sicher, dass alles, was geändert werden kann, eine datengesteuerte Variable ist und keine fest programmierten Standardwerte mit Variablen, die für Ausnahmen verwendet werden.
Auf diese Weise nehmen Sie niemals an, wie lang die Drehung sein wird. Es ist immer eine ständig überprüfte Variable, die durch jeden Effekt geändert und möglicherweise später durch den Effekt rückgängig gemacht werden kann, wenn er abläuft. Sie prüfen niemals auf Ausnahmen, bevor Sie nicht Ihre magische Nummer verwenden.
quelle
RobStone ist auf dem richtigen Weg, aber ich wollte es näher erläutern, da ich genau das getan habe, als ich Dungeon Ho !, ein Roguelike, schrieb, das ein sehr komplexes Effektsystem für Waffen und Zaubersprüche hatte.
Mit jeder Karte sollte eine Reihe von Effekten verbunden sein, die so definiert sind, dass sie anzeigen können, um welchen Effekt es sich handelt, worauf sie abzielt, wie und wie lange. Zum Beispiel könnte ein "Schaden des Gegners" -Effekt ungefähr so aussehen.
Lassen Sie dann, wenn der Effekt ausgelöst wird, eine allgemeine Routine die Verarbeitung des Effekts durchführen. Wie ein Idiot habe ich eine riesige case / switch-Anweisung verwendet:
Eine weitaus bessere und modularere Methode ist der Polymorphismus. Erstellen Sie eine Effect-Klasse, die alle diese Daten umschließt, erstellen Sie eine Unterklasse für jeden Effekttyp und lassen Sie diese Klasse dann eine für die Klasse spezifische onExecute () -Methode überschreiben.
Wir hätten also eine Basic-Effect-Klasse und dann eine DamageEffect-Klasse mit einer onExecute () -Methode.
Um zu wissen, was im Spiel ist, müssen Sie eine Vektor / Array / verknüpfte Liste / etc. Erstellen. von aktiven Effekten (vom Typ Effekt, Basisklasse), die an ein beliebiges Objekt angehängt sind (einschließlich Spielfeld / "Spiel"). Anstatt zu prüfen, ob ein bestimmter Effekt im Spiel ist, durchlaufen Sie einfach alle Effekte, die an ein Objekt angehängt sind die Objekte und lassen Sie sie ausführen. Wenn ein Effekt keinem Objekt zugeordnet ist, ist er nicht aktiv.
quelle
Ich werde eine Handvoll Vorschläge machen. Einige von ihnen widersprechen sich. Aber vielleicht sind einige nützlich.
Betrachten Sie Listen im Vergleich zu Flags
Sie können über die ganze Welt iterieren und bei jedem Gegenstand eine Flagge ankreuzen, um zu entscheiden, ob Sie die Flaggensache ausführen möchten. Oder Sie können eine Liste nur der Elemente führen, die die Flaggensache ausführen sollen.
Berücksichtigen Sie Listen und Aufzählungen
Sie können Ihrer Artikelklasse isAThis und isAThat weiterhin Boolesche Felder hinzufügen. Oder Sie können eine Liste von Zeichenfolgen oder Aufzählungselementen haben, z. B. {“isAThis”, “isAThat”} oder {IS_A_THIS, IS_A_THAT}. Auf diese Weise können Sie der Enumeration (oder den String-Konstanten) neue hinzufügen, ohne Felder hinzuzufügen. Nicht, dass beim Hinzufügen von Feldern wirklich etwas nicht stimmt ...
Betrachten Sie Funktionszeiger
Anstelle einer Liste von Flags oder Aufzählungen könnte eine Liste von Aktionen für dieses Element in verschiedenen Kontexten ausgeführt werden. (Entity-ish ...)
Betrachten Sie Objekte
Einige Leute bevorzugen datengesteuerte oder skriptgesteuerte Ansätze oder Ansätze mit Komponenten. Aber auch altmodische Objekthierarchien sind eine Überlegung wert. Die Basisklasse muss die Aktionen akzeptieren, z. B. "Diese Karte für Phase B spielen" oder was auch immer. Dann kann jede Art von Karte überschreiben und entsprechend reagieren. Es gibt wahrscheinlich auch ein Spielerobjekt und ein Spielobjekt, so dass das Spiel Dinge tun kann wie: if (player-> isAllowedToPlay ()) {mache das Spiel…}.
Betrachten Sie Debug-Fähigkeit
Das Schöne an einem Stapel von Flaggenfeldern ist, dass Sie den Status jedes Elements auf dieselbe Weise prüfen und ausdrucken können. Wenn der Status durch verschiedene Typen oder Beutel mit Komponenten oder Funktionszeigern dargestellt wird oder sich in verschiedenen Listen befindet, reicht es möglicherweise nicht aus, nur die Felder des Elements zu betrachten. Es sind alles Kompromisse.
Letztendlich Refactoring: Betrachten Sie Unit-Tests
Egal wie sehr Sie Ihre Architektur verallgemeinern, Sie können sich Dinge vorstellen, die sie nicht abdeckt. Dann müssen Sie umgestalten. Vielleicht ein bisschen, vielleicht viel.
Eine Möglichkeit, dies sicherer zu machen, sind zahlreiche Unit-Tests. Auf diese Weise können Sie sicher sein, dass die vorhandene Funktionalität auch dann noch funktioniert, wenn Sie die darunter liegenden Elemente (möglicherweise um ein Vielfaches!) Neu angeordnet haben. Jeder Komponententest sieht im Allgemeinen so aus:
Wie Sie sehen können, ist es für die Unit-Test-Strategie von entscheidender Bedeutung, die API-Aufrufe der obersten Ebene für Spiele (oder Spieler, Karten usw.) stabil zu halten.
quelle
Anstatt über jede Karte einzeln nachzudenken, sollten Sie nach Kategorien von Effekten denken, und Karten enthalten eine oder mehrere dieser Kategorien. Um beispielsweise die Zeitdauer eines Spielzugs zu berechnen, können Sie alle im Spiel befindlichen Karten durchlaufen und die Kategorie "Spielzugsdauer ändern" für jede Karte überprüfen, die diese Kategorie enthält. Jede Karte erhöht oder überschreibt dann die Spielzugdauer basierend auf den von Ihnen festgelegten Regeln.
Dies ist im Wesentlichen ein Minikomponentensystem, bei dem jedes "Karten" -Objekt einfach ein Container für eine Reihe von Effektkomponenten ist.
quelle