Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Verwenden des zusammengesetzten Entwurfsmusters für ein RPG-Attributsystem

by
Difficulty:IntermediateLength:LongLanguages:

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

Intelligenz, Willenskraft, Charisma, Weisheit: Abgesehen davon, dass es wichtige Eigenschaften sind, die man als Spieleentwickler haben sollte, sind dies auch übliche Attribute, die in RPGs verwendet werden. Das Berechnen der Werte solcher Attribute - das Anwenden zeitgesteuerter Boni und das Berücksichtigen der Wirkung von ausgerüsteten Gegenständen - kann schwierig sein. In diesem Tutorial zeige ich Ihnen, wie Sie ein leicht modifiziertes Composite-Pattern verwenden können, um dieses Problem zu lösen.

Hinweis: Obwohl dieses Tutorial mit Flash und AS3 geschrieben wurde, sollten Sie die gleichen Techniken und Konzepte in fast jeder Spieleentwicklungsumgebung verwenden können.


Einführung

In RPGs werden Attributsysteme sehr häufig verwendet, um die Stärken, Schwächen und Fähigkeiten von Charakteren zu quantifizieren. Wenn Sie nicht mit ihnen vertraut sind, überfliegen Sie die Wikipedia-Seite für einen anständigen Überblick.

Um sie dynamischer und interessanter zu machen, verbessern Entwickler diese Systeme oft durch Hinzufügen von Fähigkeiten, Gegenständen und anderen Dingen, die die Attribute beeinflussen. Wenn Sie dies tun möchten, benötigen Sie ein gutes System, das die endgültigen Attribute berechnen kann (unter Berücksichtigung aller anderen Effekte) und das Hinzufügen oder Entfernen verschiedener Arten von Boni übernimmt.

In diesem Lernprogramm werden wir eine Lösung für dieses Problem untersuchen, indem Sie eine leicht modifizierte Version des Composite-Entwurfsmusters verwenden. Unsere Lösung wird in der Lage sein, Boni zu handhaben, und wird an jedem Satz von Attributen arbeiten, den Sie definieren.


Was ist das zusammengesetzte Muster?

Dieser Abschnitt enthält eine Übersicht über das Composite-Designmuster. Wenn Sie bereits damit vertraut sind, können Sie auf Modellieren unseres Problems überspringen.

Das zusammengesetzte Muster ist ein Entwurfsmuster (eine allgemein bekannte, wiederverwendbare, allgemeine Entwurfsvorlage) zum Unterteilen von etwas Großem in kleinere Objekte, um eine größere Gruppe zu erzeugen, indem nur die kleinen Objekte gehandhabt werden. Es macht es einfach, große Informationsbrocken in kleinere, leichter behandelbare Brocken zu zerlegen. Im Wesentlichen ist es eine Vorlage für die Verwendung einer Gruppe eines bestimmten Objekts, als ob es ein einzelnes Objekt selbst wäre.

Wir werden ein weit verbreitetes Beispiel verwenden, um dies zu veranschaulichen: Denken Sie an eine einfache Zeichenanwendung. Sie möchten, dass Sie Dreiecke, Quadrate und Kreise zeichnen und sie anders behandeln können. Sie möchten aber auch, dass es Gruppen von Zeichnungen handhaben kann. Wie können wir das leicht machen?

Das zusammengesetzte Muster ist der perfekte Kandidat für diesen Job. Indem man eine "Gruppe von Zeichnungen" als Zeichnung selbst behandelt, könnte man leicht jede Zeichnung innerhalb dieser Gruppe hinzufügen, und die Gruppe als Ganzes würde immer noch als eine einzelne Zeichnung betrachtet werden.

In Bezug auf die Programmierung hätten wir eine Basisklasse, Zeichnung, die das Standardverhalten einer Zeichnung hat (Sie können sie verschieben, Ebenen ändern, drehen usw.) und vier Unterklassen, Dreieck, Quadrat, Kreis und Gruppe.

In diesem Fall haben die ersten drei Klassen ein einfaches Verhalten und erfordern nur die Benutzereingabe der grundlegenden Attribute jeder Form. Die Gruppenklasse verfügt jedoch über Methoden zum Hinzufügen und Entfernen von Formen sowie zum Ausführen einer Operation für alle (z. B. Ändern der Farbe aller Formen in einer Gruppe auf einmal). Alle vier Unterklassen werden immer noch als Zeichnung behandelt, sodass Sie sich keine Gedanken darüber machen müssen, ob Sie bestimmten Code hinzufügen möchten, wenn Sie in einer Gruppe arbeiten möchten.

Um dies in eine bessere Darstellung zu bringen, können wir jede Zeichnung als Knoten in einem Baum betrachten. Jeder Knoten ist ein Blatt, mit Ausnahme von Gruppenknoten, die untergeordnete Elemente haben können - die wiederum Zeichnungen innerhalb dieser Gruppe sind.


Eine visuelle Darstellung des Musters

In Anlehnung an das Beispiel der Zeichnungsapp ist dies eine visuelle Darstellung der "Zeichenanwendung", an die wir gedacht haben. Beachten Sie, dass es im Bild drei Zeichnungen gibt: ein Dreieck, ein Quadrat und eine Gruppe bestehend aus einem Kreis und einem Quadrat:

Using the Composite Design Pattern for an RPG Attributes System

Und dies ist die Baumdarstellung der aktuellen Szene (die Wurzel ist die Stufe der Zeichnungsanwendung):

Using the Composite Design Pattern for an RPG Attributes System

Was wäre, wenn wir innerhalb der Gruppe, die wir gerade haben, eine weitere Zeichnung, eine Gruppe aus einem Dreieck und einem Kreis, hinzufügen möchten? Wir würden es einfach hinzufügen, da wir jede Zeichnung innerhalb einer Gruppe hinzufügen würden. So würde die visuelle Darstellung aussehen:

Using the Composite Design Pattern for an RPG Attributes System

Und so würde der Baum werden:

Using the Composite Design Pattern for an RPG Attributes System

Nun stellen Sie sich vor, dass wir eine Lösung für das Attributproblem erstellen, das wir haben. Offensichtlich werden wir keine direkte visuelle Repräsentation haben (wir können nur das Endergebnis sehen, welches das berechnete Attribut ist, das die Rohwerte und die Boni angibt), so dass wir anfangen, im zusammengesetzten Muster mit der Baumdarstellung zu denken .


Unser Problem modellieren

Um es zu ermöglichen, unsere Attribute in einem Baum zu modellieren, müssen wir jedes Attribut in die kleinsten Teile zerlegen, die wir können.

Wir wissen, dass wir Boni haben, die dem Attribut entweder einen Rohwert hinzufügen oder es um einen Prozentsatz erhöhen können. Es gibt Boni, die zum Attribut hinzugefügt werden, und andere, die berechnet werden, nachdem alle diese ersten Boni angewendet wurden (zB Boni von Skills).

Also können wir haben:

  • Rohboni (hinzugefügt zum Rohwert des Attributs)
  • Endgültige Boni (die dem Attribut hinzugefügt werden, nachdem alles andere berechnet wurde)

Sie haben vielleicht bemerkt, dass wir Boni, die dem Attribut einen Wert hinzufügen, nicht von Boni trennen, die das Attribut um einen Prozentsatz erhöhen. Das liegt daran, dass wir jeden Bonus modellieren, um beide gleichzeitig ändern zu können. Das bedeutet, dass wir einen Bonus haben könnten, der den Wert um 5 erhöht und das Attribut um 10% erhöht. Dies wird alles im Code behandelt.

Diese zwei Arten von Boni sind nur die Blätter unseres Baumes. Sie sind in unserem Beispiel ziemlich ähnlich zu den Klassen Dreieck, Quadrat und Kreis.

Wir haben noch immer keine Entität geschaffen, die als Gruppe dient. Diese Entitäten werden die Attribute selbst sein! Die Gruppenklasse in unserem Beispiel ist einfach das Attribut selbst. Wir werden also eine Attributklasse haben, die sich wie jedes Attribut verhält.

So könnte ein Attributbaum aussehen:

Using the Composite Design Pattern for an RPG Attributes System

Jetzt, wo alles entschieden ist, sollen wir unseren Code beginnen?


Erstellen der Basisklassen

Wir werden in diesem Tutorial ActionScript 3.0 als Sprache für den Code verwenden, aber keine Sorge! Der Code wird anschließend vollständig kommentiert und alles, was für die Sprache (und die Flash-Plattform) einzigartig ist, wird erklärt und es werden Alternativen zur Verfügung gestellt. Wenn Sie also mit einer OOP-Sprache vertraut sind, können Sie dies nachvollziehen Tutorial ohne Probleme.

Die erste Klasse, die wir erstellen müssen, ist die Basisklasse für alle Attribute und Boni. Die Datei wird BaseAttribute.as heißen und ihre Erstellung ist sehr einfach. Hier ist der Code, mit Kommentaren hinterher:

Wie Sie sehen können, sind die Dinge in dieser Basisklasse sehr einfach. Wir erstellen einfach die Felder _value und _multiplier, weisen sie im Konstruktor zu und führen zwei Getter-Methoden aus, eine für jedes Feld.

Jetzt müssen wir die RawBonus und FinalBonus Klassen erstellen. Dies sind einfache Unterklassen von BaseAttribute, denen nichts hinzugefügt wurde. Sie können es so weit erweitern, wie Sie wollen, aber für den Moment werden wir nur diese zwei leeren Unterklassen von BaseAttribute machen:

RawBonus.as:

FinalBonus.as

Wie Sie sehen können, haben diese Klassen nichts in ihnen außer einem Konstruktor.


Die Attributklasse

Die Attributklasse entspricht einer Gruppe im zusammengesetzten Muster. Es kann beliebige rohe oder endgültige Boni enthalten und verfügt über eine Methode zur Berechnung des endgültigen Werts des Attributs. Da es sich um eine Unterklasse von BaseAttribute handelt, ist das Feld _baseValue der Klasse der Startwert des Attributs.

Wenn wir die Klasse erstellen, haben wir ein Problem, wenn wir den endgültigen Wert des Attributs berechnen: Da wir Rohboni nicht von Endboni unterscheiden, können wir den endgültigen Wert nicht berechnen, weil wir nicht wissen, wann Verwende jeden Bonus.

Dies kann durch eine geringfügige Änderung des grundlegenden zusammengesetzten Musters gelöst werden. Anstatt ein Kind zu demselben "Container" innerhalb der Gruppe hinzuzufügen, erstellen wir zwei "Container", einen für die Rohbonusse und andere für die Endboni. Jeder Bonus ist immer noch ein Kind des Attributs, wird sich jedoch an verschiedenen Stellen befinden, um die Berechnung des endgültigen Werts des Attributs zu ermöglichen.

Mit dem erklärt, kommen wir zum Code!

Die Methoden addRawBonus(), addFinalBonus(), removeRawBonus() und removeFinalBonus() sind sehr klar. Alles, was sie tun, ist das Hinzufügen oder Entfernen ihres spezifischen Bonus-Typs zu oder von dem Array, das alle Boni dieses Typs enthält.

Der schwierige Teil ist die Methode calculateValue(). Zuerst werden alle Werte zusammengefasst, die die Rohboni dem Attribut hinzufügen, und außerdem werden alle Multiplikatoren summiert. Danach addiert er die Summe aller rohen Bonuswerte zum Startattribut und wendet dann den Multiplikator an. Später führt er den gleichen Schritt für die endgültigen Boni durch, wobei diesmal jedoch die Werte und Multiplikatoren auf den halb berechneten endgültigen Attributwert angewendet werden.

Und wir sind fertig mit der Struktur! Überprüfen Sie die nächsten Schritte, um zu sehen, wie Sie es verwenden und erweitern würden.


Zusätzliches Verhalten: Zeitliche Boni

In unserer derzeitigen Struktur haben wir nur einfache Roh- und Schlussboni, die derzeit überhaupt keinen Unterschied machen. In diesem Schritt fügen wir der FinalBonus-Klasse zusätzliches Verhalten hinzu, damit es mehr wie Boni aussieht, die durch aktive Fertigkeiten in einem Spiel angewendet werden.

Da, wie der Name schon sagt, solche Fähigkeiten nur für einen bestimmten Zeitraum aktiv sind, werden wir ein Timing-Verhalten für die letzten Boni hinzufügen. Die rohen Boni können zum Beispiel für Boni verwendet werden, die durch Ausrüstung hinzugefügt werden.

Um dies zu tun, werden wir die Timer-Klasse verwenden. Diese Klasse stammt aus ActionScript 3.0 und verhält sich wie ein Timer, der bei 0 Sekunden beginnt und dann eine angegebene Funktion nach einer bestimmten Zeit aufruft, die auf 0 zurückgesetzt wird und die Zählung erneut startet, bis sie den angegebenen Wert erreicht Anzahl der Zählungen erneut. Wenn Sie sie nicht angeben, läuft der Timer weiter, bis Sie ihn stoppen. Sie können wählen, wann der Timer startet und wann er stoppt. Sie können sein Verhalten einfach replizieren, indem Sie bei Bedarf das Timing-System Ihrer Sprache mit entsprechendem zusätzlichen Code verwenden.

Lass uns zum Code springen!

Im Konstruktor besteht der erste Unterschied darin, dass finale Boni jetzt einen Zeitparameter benötigen, der anzeigt, wie lange sie dauern. Innerhalb des Konstruktors erstellen wir einen Timer für diesen Zeitraum (vorausgesetzt, die Zeit ist in Millisekunden) und fügen einen Ereignis-Listener hinzu.

(Event-Listener sind im Grunde das, was den Timer dazu bringt, die richtige Funktion aufzurufen, wenn er diese bestimmte Zeit erreicht - in diesem Fall lautet die aufgerufene Funktion onTimerEnd().)

Beachten Sie, dass wir den Timer noch nicht gestartet haben. Dies geschieht in der startTimer() -Methode, die auch einen Parameter parent erfordert, der ein Attribut sein muss. Diese Funktion benötigt das Attribut, das den Bonus hinzufügt, um diese Funktion aufzurufen, um sie zu aktivieren; Dies wiederum startet den Timer und teilt dem Bonus mit, welche Instanz um die Entfernung des Bonus bitten soll, wenn der Timer sein Limit erreicht hat.

Der Entfernungsteil wird in der onTimerEnd() -Methode durchgeführt, die nur den übergeordneten Elternteil auffordert, ihn zu entfernen und den Timer zu stoppen.

Jetzt können wir endgültige Boni als zeitlich festgelegte Boni verwenden, die anzeigen, dass sie nur für eine bestimmte Zeit andauern.


Zusätzliches Verhalten: Abhängige Attribute

Eine Sache, die häufig in RPG-Spielen zu sehen ist, sind Attribute, die von anderen abhängig sind. Nehmen wir zum Beispiel das Attribut "Angriffsgeschwindigkeit". Es hängt nicht nur von der Art der verwendeten Waffe ab, sondern fast immer auch von der Geschicklichkeit des Charakters.

In unserem aktuellen System erlauben wir nur, dass Boni Kinder von Attribut-Instanzen sind. In unserem Beispiel müssen wir jedoch zulassen, dass ein Attribut ein Kind eines anderen Attributs ist. Wie können wir das machen? Wir können eine Unterklasse des Attribute namens DependantAttribute erstellen und dieser Unterklasse das gesamte von uns benötigte Verhalten zuweisen.

Das Hinzufügen von Attributen als untergeordnete Elemente ist sehr einfach: Wir müssen lediglich ein weiteres Array erstellen, das Attribute enthält, und spezifischen Code zur Berechnung des endgültigen Attributs hinzufügen. Da wir nicht wissen, ob alle Attribute auf die gleiche Weise berechnet werden (Sie möchten vielleicht zuerst die Angriffsgeschwindigkeit durch geschicktes Verhalten ändern und dann die Boni überprüfen, aber zuerst Boni verwenden, um den magischen Angriff zu ändern und dann zum Beispiel Intelligenz), müssen wir auch die Berechnung des finalen Attributs in der Attribute-Klasse in verschiedenen Funktionen trennen. Lass uns das zuerst machen.

In Attribut.as:

Wie Sie an den hervorgehobenen Zeilen sehen können, haben wir lediglich applyRawBonuses() und applyFinalBonuses() erstellt und sie aufgerufen, wenn Sie das final-Attribut in calculateValue() berechnen. Wir haben auch _finalValue geschützt, so dass wir es in den Unterklassen ändern können.

Jetzt können wir die DependantAttribute-Klasse erstellen. Hier ist der Code:

In dieser Klasse sollten die Funktionen addAttribute() und removeAttribute() Ihnen vertraut sein. Sie müssen auf die überschriebene Funktion calculateValue() achten. Hier verwenden wir nicht die Attribute für die Berechnung des endgültigen Wertes - Sie müssen dies für jedes abhängige Attribut tun!

Dies ist ein Beispiel dafür, wie Sie das für die Berechnung der Angriffsgeschwindigkeit tun würden:

In dieser Klasse gehen wir davon aus, dass Sie das Attribut Geschicklichkeit bereits als Kind von AttackSpeed hinzugefügt haben und dass es das erste Element im Array _otherAttributes ist (das sind viele zu machende Annahmen; überprüfen Sie die Schlussfolgerung auf weitere Informationen). Nachdem wir die Geschicklichkeit abgerufen haben, verwenden wir sie einfach, um mehr zum Endwert der Angriffsgeschwindigkeit hinzuzufügen.


Fazit

Mit allem fertig, wie würdest du diese Struktur in einem Spiel verwenden? Es ist sehr einfach: Sie müssen nur verschiedene Attribute erstellen und jedem Attribut eine Attribut-Instanz zuweisen. Danach geht es darum, Boni hinzuzufügen und durch die bereits erstellten Methoden zu entfernen.

Wenn ein Gegenstand ausgerüstet oder verwendet wird und jedem Attribut ein Bonus hinzugefügt wird, müssen Sie eine Bonusinstanz des entsprechenden Typs erstellen und diese dann zum Attribut des Charakters hinzufügen. Danach berechnen Sie einfach den endgültigen Attributwert neu.

Sie können auch die verschiedenen Arten von verfügbaren Boni erweitern. Zum Beispiel könnten Sie einen Bonus haben, der den Mehrwert oder Multiplikator im Laufe der Zeit ändert. Sie können auch negative Boni verwenden (die der aktuelle Code bereits verarbeiten kann).

Mit jedem System können Sie immer mehr hinzufügen. Hier sind ein paar Verbesserungsvorschläge, die Sie machen könnten:

  • Identifizieren Sie Attribute nach Namen
  • Machen Sie ein "zentralisiertes" System zur Verwaltung der Attribute
  • Optimieren Sie die Performance (Hinweis: Sie müssen den endgültigen Wert nicht immer vollständig berechnen)
  • Machen Sie es möglich, dass einige Boni andere Boni abschwächen oder verstärken

Danke fürs Lesen!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.