Ich mache ein Top-Down-2D-Spiel und möchte viele verschiedene Angriffsarten haben. Ich möchte die Angriffe sehr flexibel und kombinierbar machen, wie die Bindung von Isaac funktioniert. Hier ist eine Liste aller Sammlerstücke im Spiel . Um ein gutes Beispiel zu finden, schauen wir uns den Gegenstand Spoon Bender an .
Spoon Bender gibt Isaac die Möglichkeit, Tränen in die Augen zu schießen.
Wenn Sie sich den Abschnitt "Synergien" ansehen, werden Sie feststellen, dass er mit anderen Sammlerstücken kombiniert werden kann, um interessante und dennoch intuitive Effekte zu erzielen. Wenn es zum Beispiel mit The Inner Eye kombiniert wird , wird es "Isaac ermöglichen, mehrere Homing-Shots gleichzeitig abzufeuern". Das macht Sinn, weil das innere Auge
Gibt Isaac einen dreifachen Schuss
Was ist eine gute Architektur, um solche Dinge zu entwerfen? Hier ist eine Brute-Force-Lösung:
if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...
Aber das wird sehr schnell außer Kontrolle geraten. Wie lässt sich ein solches System besser entwerfen?
quelle
Antworten:
Sie müssen keine Kombinationen von Hand codieren. Sie können sich stattdessen auf die Eigenschaften konzentrieren, die Ihnen die einzelnen Elemente bieten. Zum Beispiel Artikel A setzt
Projectile=Fireball,Targetting=Homing
. Gegenstand B setztFireMode=ArcShot,Count=3
. DieArcShot
Logik ist für das Aussenden derCount
Anzahl vonProjectile
Elementen in einem Bogen verantwortlich.Diese beiden Elemente können mit anderen Elementen kombiniert werden, die diese (oder andere) Eigenschaften frei ändern. Wenn Sie einen neuen Projektiltyp hinzufügen, funktioniert dieser automatisch
ArcShot
, und wenn Sie einen neuen Schussmodus hinzufügen, funktioniert dieser automatisch mitFireball
Projektilen. EbensoTargetting
ist eine Eigenschaft, mit der die Steuerung für die Projektile festgelegt wird, währendFireMode
die Projektile erstellt werden, so dass sie in jeder sinnvollen Kombination einfach und trivial kombiniert werden können.Sie können auch Eigenschaftsabhängigkeiten und dergleichen festlegen.
ArcShot
Erfordert zum Beispiel, dass Sie einen Anbieter von habenProjectile
(was möglicherweise nur der Standard ist). Sie können Prioritäten festlegen, damit bei zwei aktiven ElementenProjectile
der Code weiß, welches verwendet werden soll. Sie können auch eine Benutzeroberfläche bereitstellen, über die der Benutzer auswählen kann, welchen Projektiltyp er verwenden möchte, oder den Player einfach dazu auffordern, nicht benötigte Gegenstände mit hoher Priorität oder den neuesten Gegenstand usw. auszurüsten. Sie können außerdem ein System von Inkompatibilitäten zulassen ZB so, dass zwei Gegenstände, die beide nur modifizieren,Projectile
nicht gleichzeitig ausgerüstet werden können.Im Allgemeinen ziehen Sie, wenn möglich, jede Art von datengesteuertem Ansatz (oder deklarativem Ansatz ) prozeduralen Ansätzen (dem großen Durcheinander) vor, wenn es um die Objekte und dergleichen in Ihrem Spiel geht. Übergeordnete generische Logik, die durch einfache Daten konfiguriert werden kann, ist hartcodierten Listen mit Regeln mit Sonderfällen weit vorzuziehen.
quelle
Wenn Sie eine OOP-Sprache verwenden, ist dies ein guter Ort, um das Decorator-Pattern zu verwenden . Wenn Sie ändern möchten, wie ein Angriff abläuft, verzieren Sie ihn einfach mit der entsprechenden Erweiterung.
Rohes c ++ Beispiel:
Diese Methode ist am besten geeignet, wenn Sie eine sehr große Anzahl von Angriffen ausführen und diese sich alle mehr oder weniger gleich verhalten müssen. Wenn Sie die Art und Weise, wie der Angriff mit dem Modifikator abläuft, grundlegend ändern möchten (z. B. neue Animation mit Modifikator), ist diese Methode nicht für Sie geeignet.
quelle
Attack
Methode des Objekts aufruft, das er aggregiert. DieTripleAttack
Klasse sollte nichts über dieTearAttack
Klasse wissen . Wenn dies wahr wäre, würde es genauso viele Kopfschmerzen verursachen wie dieelse-if
Blockade. Dies bedeutet, dass sich Tränenanimationen imTearAttackBehaviour
Objekt befinden müssen. Dieses Objekt weiß (und sollte) nicht, dass es von einemTripleAttack
Objekt dekoriert wurde . Das Ergebnis ist, dass die 3 Tränenanimationen unabhängig voneinander ablaufen, da sie unabhängig voneinander sind .Als Fan von Binding of Isaac habe ich mich auch gefragt, wie ich so etwas machen soll. Das System im Spiel ist robust genug, um aufkommende Verhaltensweisen durch die Kombination von Effekten hervorzurufen (das, was mir einfällt, ist Spiegel, Löffelbiegung und einige Reichweitenverstärker, die zu einer wirbelnden, tränenreichen Wand um Isaac im Magneto-Stil führen ). Die bloße Anzahl von ihnen würde einen "Wenn" -Block unpraktisch machen.
Mein Fazit ist, dass Isaac und seine Tränen zwei Einheiten sind, die sich im Zentrum eines massiven Component-Entity-Frameworks befinden . Die Entitäten haben einige grundlegende Werte (Bewegungsgeschwindigkeit, Leben, Reichweite, Schaden, Sprite usw.) und jede Komponente würde einen Stat-Modifikator und ein Verb mitbringen.
Im Code hätten Isaac und seine Tränen jeweils eine Liste, die Dinge einer Schnittstelle enthalten würde. Isaac würde eine Liste von Dingen haben, die IsaacMutator abonnieren, und seine Tränen zerreißen Mutator. IsaacMutator hätte Funktionen, um Isaacs Gesundheit, Geschwindigkeit, Reichweite, Aussehen und ein spezielles Verb zu ändern. TearMutator wäre ähnlich. Einmal pro Spielschleife würde Isaac alle IsaacMutators durchlaufen, die er hat, und auch alle lebenden Tränen. Um nach Ihrem englischen Beispiel zu gehen, würde es wie folgt lauten:
und so weiter. Da die Typen additiv sind, können Sie Inhalte stapeln, hinzufügen und entfernen.
quelle
Ich denke, dein Weg funktioniert am besten. Diese Art von Elementen geben jeweils eine Bedingung an. Wenn sie zusammen eine andere Bedingung ergeben, müssten alle drei möglichen Bedingungen definiert werden.
Sie können auch eine neue Art von Definition erstellen, wenn beide Elemente vorhanden sind. Dies trägt jedoch zur Konvolution bei:
quelle