Dieser Artikel bietet einen Überblick über die Erstellung eines JRPG (Japanese Role-Playing Game) wie der frühen Final Fantasy-Spiele. Wir betrachten Architektur und Systeme, die das Grundgerüst eines JRPG ausmachen, wie Spielemodi verwaltet werden, wie Tilemaps verwendet werden, um die Welt anzuzeigen, und wie man ein RPG-Kampfsystem codiert.
Hinweis: Dieser Artikel wurde unter Verwendung einer Java-ähnlichen Pseudo-Code-Sprache geschrieben. Die Konzepte sind jedoch auf jede Spieleentwicklungsumgebung anwendbar.
Im Jahr 1983 flogen Yuji Horii, Koichi Nakamura und Yukinobu Chida nach Amerika und nahmen an AppleFest '83 teil, einem Treffen von Entwicklern, die ihre neuesten Kreationen für den Apple II vorführen. Sie wurden von der neuesten Version eines RPG mit dem Namen Wizardry überwältigt.
Als sie nach Japan zurückkehrten, beschlossen sie, Dragon Warrior zu entwickeln, ein ähnliches RPG, das jedoch für das NES gestrafft wurde. Es war ein großer Hit, der das JRPG-Genre prägte. Dragon Warrior ging es in Amerika nicht so gut, aber ein paar Jahre später tat dies ein anderes Spiel.
1987 wurde das ursprüngliche Final Fantasy veröffentlicht, aus dem eines der meistverkauften Videospiel-Franchises der Erde hervorging, das zumindest im Westen zur legendären JRPG wurde.
Spielgenres sind nie genau definiert - sie sind eher eine unscharfe Sammlung von Konventionen. RPGs haben in der Regel ein Leveling-System, einen oder mehrere Spielercharaktere mit Fähigkeiten und Statistiken, Waffen und Rüstungen, Kampf- und Erkundungsmodi sowie starke Erzählungen. Der Fortschritt des Spiels wird oft durch das Vorrücken einer Karte erreicht.
Japanische RPGs sind RPGs, die in der Form von Dragon Warrior erstellt wurden. Sie sind linearer, der Kampf ist oft rundenbasiert und es gibt normalerweise zwei Kartentypen: eine Weltkarte und eine lokale Karte. Archetypische JRPGs umfassen Dragon Warrior, Final Fantasy, Wild Arms, Phantasy Star und Chrono Trigger. Die Art von JRPG, über die wir in diesem Artikel sprechen werden, ähnelt einer frühen Final Fantasy.
Spiele wie Final Fantasy VI und Chrono Trigger sind immer noch sehr angenehm zu spielen. Wenn Sie ein JRPG erstellen, lernen Sie ein zeitloses Spielformat, für das moderne Spieler immer noch sehr aufgeschlossen sind. Sie bilden einen großartigen Rahmen, um Ihre eigene Wendung und Experimente hinzuzufügen - sei es in der Erzählung, der Präsentation oder der Mechanik. Es ist eine großartige Sache, wenn Sie ein Spiel erstellen können, das noch Jahrzehnte nach seiner ersten Veröffentlichung gespielt wurde!
Call of Duty, eines der beliebtesten FPS-Spiele der Welt, verwendet RPG-Elemente. Der Social Game-Boom um Farmville war im Wesentlichen ein Klon des SNES-RPG-Harvest Moon. und selbst Rennspiele wie Gran Turismo verfügen über Level und Erfahrung.
So wie ein Schriftsteller durch ein leeres Blatt Papier eingeschüchtert werden kann, kann sich ein Spieleentwickler bei der Gestaltung eines neuen Spiels durch die große Anzahl möglicher Entscheidungen als gelähmt fühlen. Mit einem JRPG wurden viele Entscheidungen für Sie entschieden, sodass Sie diese Wahllähmung nicht haben. Sie können die Konventionen für die meisten Entscheidungen befolgen und von den Konventionen an den für Sie wichtigen Punkten abweichen.
Final Fantasy wurde fast vollständig von einem einzigen Programmierer, Nasir Gebelli, programmiert, und er machte es in der Montage! Mit modernen Tools und Sprachen ist es einfacher, diese Art von Spiel zu erstellen. Der größte Teil der meisten RPGs ist nicht die Programmierung, sondern der Inhalt. Dies muss jedoch nicht für Ihr Spiel der Fall sein. Wenn Sie den Inhalt ein wenig zurückwählen und sich auf Qualität statt Quantität konzentrieren, ist ein JRPG ein großartiges Soloprojekt.
Ein Team kann bei jedem Spiel helfen, und Sie möchten vielleicht die Kunst und Musik auslagern oder einige der hervorragenden Creative Commons-Ressourcen von Orten wie opengameart.org nutzen. (Anmerkung des Herausgebers: Unsere Schwestersite GraphicRiver verkauft auch Sprite-Sheets.)
JRPGs haben eine engagierte Anhängerschaft, und eine Reihe von Indie-JRPGs (wie die unten abgebildeten) haben sich kommerziell gut bewährt und sind auf Plattformen wie Steam verfügbar.
JRPGs haben so viele Konventionen und Mechanismen, dass es möglich ist, ein typisches JRPG in eine Reihe von Systemen zu unterteilen:
In der Softwareentwicklung wird immer wieder ein Muster gesehen: Schichtung. Dies bezieht sich darauf, wie die Systeme eines Programms aufeinander aufbauen, wobei breit angelegte Schichten am unteren Rand und Schichten, die sich näher mit dem vorliegenden Problem beschäftigen, im oberen Bereich liegen. JRPGs unterscheiden sich nicht und können als mehrere Ebenen betrachtet werden - untere Ebenen befassen sich mit grundlegenden Grafikfunktionen und obere Ebenen mit Quests und Charakterstatistiken.
Spitze: Bei der Entwicklung eines neuen Systems sollten Sie zuerst die unteren Ebenen erstellen und dann Schicht für Schicht nach oben verschieben. Durch die Verwendung von Middleware können Sie einige der unteren Schichten überspringen, die bei vielen Spielen üblich sind. Im obigen Architekturdiagramm werden alle Ebenen unterhalb der gestrichelten Linie von einer 2D-Spiel-Engine verarbeitet.Wie Sie aus dem Architekturdiagramm oben sehen können, gibt es viele Systeme, aus denen ein JRPG besteht. Die meisten Systeme können jedoch als separate Gruppen zusammengefasst werden Modi des Spiels. JRPGs haben sehr unterschiedliche Spielmodi. Sie haben eine Weltkarte, eine lokale Karte, einen Kampfmodus und mehrere Menümodi. Diese Modi sind fast vollständig separate, in sich geschlossene Codeteile, die die Entwicklung jedes einzelnen Moduls erleichtern.
Modi sind wichtig, aber ohne Spielinhalt wären sie nutzlos. Ein RPG enthält viele Kartendateien, Monsterdefinitionen, Dialogzeilen, Skripts zum Ausführen von Zwischensequenzen und Gameplay-Code, um den Fortschritt des Spielers zu steuern. Wenn Sie sich ausführlich mit dem Aufbau einer JRPG beschäftigen würden, würde dies ein ganzes Buch füllen. Wir werden uns daher auf einige der wichtigsten Aspekte konzentrieren. Der saubere Umgang mit den Spielmodi ist entscheidend für die Erstellung eines überschaubaren JRPGs. Das ist also das erste System, das wir untersuchen werden.
Das Bild unten zeigt den Abpumpen der Spielschleife und ruft bei jedem Frame eine Aktualisierungsfunktion auf. Dies ist der Herzschlag des Spiels und fast alle Spiele sind so strukturiert.
Haben Sie schon einmal ein Projekt gestartet, aber ins Stocken geraten, weil Ihnen das Hinzufügen neuer Funktionen zu schwer fiel oder Sie von mysteriösen Fehlern geplagt wurden? Vielleicht haben Sie versucht, den gesamten Code mit wenig Struktur in die Aktualisierungsfunktion zu stopfen, und der Code wurde zu einem kryptischen Chaos. Eine ausgezeichnete Lösung für diese Arten von Problemen besteht darin, den Code in verschiedene Teile zu trennen Spielzustände, eine viel klarere Sicht auf das, was passiert.
Ein allgemeines gamedev-Werkzeug ist das Zustandsmaschine; Es wird überall verwendet, um Animationen, Menüs, Spielablauf, KI zu handhaben. Es ist ein unverzichtbares Werkzeug in unserem Kit. Für das JRPG können wir eine Zustandsmaschine verwenden, um die verschiedenen Spielmodi zu handhaben. Wir werden uns eine normale Zustandsmaschine ansehen und dann ein wenig aufmischen, um sie für die JRPG besser geeignet zu machen. Aber zunächst nehmen wir uns etwas Zeit, um den allgemeinen Spielablauf unten zu betrachten.
Bei einem typischen JRPG beginnen Sie wahrscheinlich mit dem lokalen Kartenspielmodus, in dem Sie sich frei in einer Stadt bewegen und mit ihren Bewohnern interagieren können. Von der Stadt aus können Sie gehen - hier wechseln Sie in einen anderen Spielemodus und sehen die Weltkarte.
Die Weltkarte verhält sich ähnlich wie die lokale Karte, jedoch in größerem Maßstab. Sie können Berge und Städte anstelle von Bäumen und Zäunen sehen. Wenn Sie sich auf der Weltkarte befinden, kehrt der Modus zur lokalen Karte zurück.
Entweder auf der Weltkarte oder auf der lokalen Karte können Sie ein Menü aufrufen, um Ihre Charaktere zu überprüfen, und manchmal werden Sie auf der Weltkarte in den Kampf geworfen. Das Diagramm oben beschreibt diese Spielmodi und Übergänge. Dies ist der grundlegende Ablauf des JRPG-Spiels, aus dem wir unsere Spielzustände erstellen werden.
Eine Zustandsmaschine ist für unsere Zwecke ein Stück Code, das alle verschiedenen Modi unserer Spiele enthält, mit dem wir von einem Modus zum anderen wechseln können und der den aktuellen Modus aktualisiert und rendert.
Abhängig von der Implementierungssprache besteht eine Zustandsmaschine normalerweise aus a Zustandsmaschine
Klasse und eine Schnittstelle, Ich behaupte
, dass alle Staaten umsetzen.
Eine Zustandsmaschine lässt sich am besten beschreiben, indem ein Basissystem im Pseudocode skizziert wird:
Klasse StateMachine MapmStates = neue Karte (); IState mCurrentState = EmptyState; public void Update (float elapsedTime) mCurrentState.Update (elapsedTime); public void Render () mCurrentState.Render (); public void Change (Zeichenfolge stateName, optionale var-Parameter) mCurrentState.OnExit (); mCurrentState = mStates [stateName]; mCurrentState.OnEnter (Parameter); public void Add (String-Name, IState-Status) mStates [Name] = Status;
Dieser Code oben zeigt eine einfache Zustandsmaschine ohne Fehlerprüfung.
Schauen wir uns an, wie der obige Statusmaschinencode in einem Spiel verwendet wird. Zu Beginn des Spiels a Zustandsmaschine
erstellt werden, alle verschiedenen Zustände des Spiels hinzugefügt und der Anfangszustand festgelegt. Jeder Zustand ist eindeutig mit einem gekennzeichnet String
Name, der beim Aufruf der Änderungsstatusfunktion verwendet wird. Es gibt immer nur einen aktuellen Zustand, mCurrentState
, und es wird in jeder Spielschleife gerendert und aktualisiert.
Der Code könnte so aussehen:
StateMachine gGameMode = new StateMachine (); // Ein Status für jeden Spielmodus gGameMode.Add ("mainmenu", new MainMenuState (gGameMode)); gGameMode.Add ("localmap", neuer LocalMapState (gGameMode)); gGameMode.Add ("worldmap", neuer WorldMapState (gGameMode)); gGameMode.Add ("battle", neuer BattleState (gGameMode)); gGameMode.Add ("ingamemenu", neuer InGameMenuState (gGameMode)); gGameMode.Change ("Hauptmenü"); // Main Game Update-Schleife public void Update () float elapsedTime = GetElapsedFrameTime (); gGameMode.Update (elapsedTime); gGameMode.Render ();
Im Beispiel erstellen wir alle erforderlichen Zustände und fügen sie dem hinzu Zustandsmaschine
und stellen Sie den Startzustand auf das Hauptmenü. Wenn wir diesen Code ausgeführt haben MainMenuState
würde zuerst gerendert und aktualisiert werden. Dies ist das Menü, das in den meisten Spielen beim ersten Start angezeigt wird, mit Optionen wie Spiel beginnen und Spiel laden.
Wenn ein Benutzer auswählt Spiel beginnen, das MainMenuState
ruft so etwas an gGameMode.Change ("localmap", "map_001")
und das LocalMapState
wird zum neuen aktuellen Zustand. Dieser Zustand würde dann die Karte aktualisieren und rendern, sodass der Spieler das Spiel erkunden kann.
Das folgende Diagramm zeigt eine Visualisierung einer Zustandsmaschine, die sich zwischen dem System bewegt WorldMapState
und BattleState
. In einem Spiel wäre dies gleichbedeutend damit, dass ein Spieler um die Welt wandert, von Monstern angegriffen wird, in den Kampfmodus wechselt und dann zur Karte zurückkehrt.
Werfen wir einen kurzen Blick auf die Zustandsschnittstelle und eine EmptyState
Klasse, die es implementiert:
öffentliche Schnittstelle IState public virtual void Update (float elapsedTime); Öffentliches virtuelles void Render (); öffentliches virtuelles void OnEnter (); public virtual void OnExit (); public EmptyState: IState public void Update (float elapsedTime) // Im leeren Zustand ist nichts zu aktualisieren. public void Render () // Im leeren Zustand nichts darzustellen public void OnEnter () // Keine Aktion beim Eintreten des Status Public void OnExit () // Keine Aktion beim Status wird beendet
Die Schnittstelle Ich behaupte
Jeder Status muss über vier Methoden verfügen, bevor er als Status in der Statusmaschine verwendet werden kann: Aktualisieren()
, Machen()
, OnEnter ()
und OnExit ()
.
Aktualisieren()
und Machen()
werden jeden Frame für den momentan aktiven Status aufgerufen; OnEnter ()
und OnExit ()
werden beim Statuswechsel aufgerufen. Ansonsten ist alles ziemlich unkompliziert. Jetzt wissen Sie, dass Sie alle Arten von Status für alle Teile Ihres Spiels erstellen können.
Das ist die grundlegende Zustandsmaschine. Es ist in vielen Situationen nützlich, aber wenn wir mit Spielmodi umgehen, können wir es verbessern! Mit dem aktuellen System kann ein Statuswechsel viel Aufwand verursachen - manchmal beim Wechsel zu einem BattleState
wir wollen das verlassen WorldState
, führe die Schlacht aus und kehre dann zum zurück WorldState
in der genauen Aufstellung war es vor der Schlacht. Diese Art von Vorgang kann mit der beschriebenen Standardzustandsmaschine unbeholfen sein. Eine bessere Lösung wäre die Verwendung von a Stapel von Staaten.
Wir können die Standardzustandsmaschine in einen Stapel von Zuständen umwandeln, wie das folgende Diagramm zeigt. Zum Beispiel die MainMenuState
wird zu Beginn des Spiels zuerst auf den Stapel geschoben. Wenn wir ein neues Spiel beginnen, wird die LocalMapState
wird darüber hinaus geschoben. An diesem Punkt der MainMenuState
wird nicht mehr gerendert oder aktualisiert, wartet jedoch auf uns, um wieder dorthin zurückzukehren.
Wenn wir eine Schlacht beginnen, die BattleState
wird oben geschoben; Wenn die Schlacht endet, wird sie vom Stapel genommen und wir können auf der Karte genau dort weitermachen, wo wir aufgehört haben. Wenn wir dann im Spiel sterben LocalMapState
wird abgeknallt und wir kehren zurück zu MainMenuState
.
Das Diagramm unten zeigt eine Visualisierung eines Zustandsstapels mit der Darstellung InGameMenuState
auf den Stapel geschoben und dann abgeknallt werden.
Nun haben wir eine Idee, wie der Stack funktioniert. Schauen wir uns den Code an, um ihn zu implementieren:
öffentliche Klasse StateStack MapmStates = neue Karte (); Liste mStack = Liste (); public void Update (float elapsedTime) IState nach oben = mStack.Top () nach oben.Update (elapsedTime) public void Rendern () IState nach oben = mStack.Top () nach oben.Render () public void Push (Name der Zeichenfolge) IState state = mStates [Name]; mStack.Push (Zustand); public IState Pop () return mStack.Pop ();
Dieser obige Zustandsstapelcode hat keine Fehlerprüfung und ist ziemlich einfach. Zustände können mit dem auf den Stapel geschoben werden Drücken()
anrufen und abspringen mit a Pop()
call und der Status ganz oben im Stack ist der, der aktualisiert und gerendert wird.
Die Verwendung eines stapelbasierten Ansatzes eignet sich gut für Menüs und kann mit ein paar Änderungen auch für Dialogfelder und Benachrichtigungen verwendet werden. Wenn Sie abenteuerlustig sind, können Sie beides kombinieren und haben eine Zustandsmaschine, die auch Stapel unterstützt.
Verwenden Zustandsmaschine
, StateStack
, oder eine Kombination aus beiden schafft eine hervorragende Struktur, auf der Ihr RPG aufgebaut werden kann.
MenuMenuState
und GameState
erben von Ich behaupte
.
Karten beschreiben die Welt; Wüsten, Raumschiffe und Dschungel können alle mithilfe einer Tilemap dargestellt werden. Mit einer Tilemap können Sie mit einer begrenzten Anzahl kleiner Bilder ein größeres erstellen. Das folgende Diagramm zeigt Ihnen, wie es funktioniert:
Das obige Diagramm besteht aus drei Teilen: der Kachelpalette, einer Visualisierung, wie die Tilemap erstellt wird, und der endgültigen Karte, die auf dem Bildschirm angezeigt wird.
Die Kachelpalette ist eine Sammlung aller Kacheln, die zum Erstellen einer Karte verwendet werden. Jede Kachel in der Palette wird durch eine ganze Zahl eindeutig identifiziert. Zum Beispiel ist Kachel Nummer 1 Gras; Beachten Sie die Orte, an denen es in der Tilemap-Visualisierung verwendet wird.
Eine Tilemap ist nur ein Array von Zahlen, wobei sich jede Nummer auf eine Kachel in der Palette bezieht. Wenn wir eine Karte voller Gras erstellen wollten, könnten wir einfach ein großes Feld mit der Nummer 1 füllen, und wenn wir diese Kacheln rendern, sehen wir eine Karte aus Gras, die aus vielen kleinen Graskacheln besteht. Die Kachelpalette wird normalerweise als eine große Textur geladen, die viele kleinere Kacheln enthält. Jeder Eintrag in der Palette kann jedoch genauso einfach eine eigene Grafikdatei sein.
Spitze: Warum nicht ein Array von Arrays verwenden, um die Tilemap darzustellen? Das erste Array könnte durch ein Array von Kacheln dargestellt werden.Der Grund, warum wir dies nicht tun, ist einfach und effizient. Wenn Sie ein Array von Ganzzahlen haben, ist dies ein fortlaufender Speicherblock. Wenn Sie ein Array von Arrays haben, ist dies ein Speicherblock für das erste Array, das Zeiger enthält, wobei jeder Zeiger auf eine Reihe von Kacheln zeigt. Diese Indirektion kann die Dinge verlangsamen - und da wir die Karte jedes Bild zeichnen, ist es umso schneller, je schneller sie ist!
Schauen wir uns einen Code zur Beschreibung einer Kachelkarte an:
// // Nimmt eine Texturkarte aus mehreren Kacheln auf und zerlegt sie in // Einzelbilder von 32 x 32. // Das endgültige Array sieht wie folgt aus: // gTilePalette [1] = Image // Unser erstes Grasplättchen // gTilePalette [2] = Image // Zweite Grasfliesenvariante //… // gTilePalette [15] = Image // Stein- und Grasfliese // Array gTilePalette = SliceTexture ("grass_tiles.png", 32, 32) gMap1Width = 10 gMap1Height = 10 Array gMap1Layer1 = new Array () [2, 2, 7, 3, 11, 11, 11, 12, 2, 1, 1, 10, 11, 11, 4, 11, 12, 2, 2, 2, 1, 13, 5, 11, 11, 11, 4, 8, 2, 1, 2, 1, 10, 11, 11, 11, 11, 11, 11, 9, 10, 11, 12, 13, 5, 11, 11, 11, 11, 4, 13, 14, 15, 1, 10, 11, 11, 11, 11, 6, 2, 2, 2, 2, 13, 14, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 2, 2, 2, 2, 5, 11, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]];
Vergleichen Sie den obigen Code mit dem Diagramm und es ist ziemlich klar, wie eine Tilemap aus einer kleinen Reihe von Kacheln aufgebaut wird. Sobald eine Karte so beschrieben ist, können wir eine einfache Renderfunktion schreiben, um sie auf dem Bildschirm zu zeichnen. Die genauen Details der Funktion ändern sich je nach den Einstellungen für das Ansichtsfenster und den Zeichnungsfunktionen. Unsere Renderfunktion ist unten dargestellt.
statisch int TilePixelSize = 32; // Zeichnet eine Tilemap von links oben an der Pixelposition x, y // x, y - Die Pixelposition, in der die Karte von // map wiedergegeben wird - die zu rendernde Map // width - die Breite der Map in Kacheln public void RenderMap (int x, int y, Array-Map, int mapWidth) // Beginne mit der Indizierung der obersten linken Kachel int tileColumn = 1; int tileRow = 1; for (int i = 1; map.Count (); i ++) // Minus 1, so dass die erste Kachel bei 0, 0 int pixelPosX = x + (tileColumn - 1) zeichnet * TilePixelSize; int pixelPosY = y + (tileRow - 1) * TilePixelSize; RenderImage (x, y, gTilePalette [gMap1Layer1 [i]]); // Weiter zur nächsten Kachel tileColumn + = 1; if (tileColumn> mapWidth) tileColumn = 1; tileRow + = 1; - So wird es in der Hauptaktualisierungsschleife verwendet public void Update () // Zeichne tatsächlich eine Karte auf dem Bildschirm RenderMap (0, 0, gMap1Layer1, gMap1Width)
Die Karte, die wir bisher verwendet haben, ist ziemlich einfach. Die meisten JRPGs verwenden mehrere Ebenen von Tilemaps, um interessantere Szenen zu erstellen. Das Diagramm unten zeigt unsere erste Karte mit drei weiteren Ebenen, was zu einer weitaus erfreulicheren Karte führt.
Wie wir zuvor gesehen haben, ist jede Tilemap nur ein Array von Zahlen. Daher kann aus einem Array dieser Arrays eine vollständige Layer-Map erstellt werden. Natürlich ist das Rendern der Tilemap wirklich nur der erste Schritt, um Ihr Spiel zu erkunden. Karten benötigen außerdem Informationen über Kollisionen, Unterstützung für das Verschieben von Entitäten und grundlegende Interaktivität löst aus.
Ein Trigger ist ein Code, der nur ausgelöst wird, wenn der Spieler ihn durch eine Aktion "auslöst". Es gibt viele Aktionen, die ein Auslöser erkennt. Wenn Sie zum Beispiel den Spieler auf ein Plättchen bewegen, kann dies eine Aktion auslösen. Dies geschieht normalerweise, wenn Sie sich auf eine Tür, einen Teleporter oder das Randplättchen einer Karte bewegen. Auslöser können auf diesen Plättchen platziert werden, um den Charakter zu einer Indoor-Karte, Weltkarte oder verwandten lokalen Karte zu teleportieren.
Ein anderer Auslöser kann davon abhängen, dass die Schaltfläche "Verwenden" gedrückt wird. Wenn der Spieler beispielsweise zu einem Zeichen geht und auf "Verwenden" drückt, wird ein Auslöser ausgelöst und ein Dialogfeld mit dem Text des Zeichens angezeigt. Überall werden Trigger verwendet, um Karten zusammenzufügen und Interaktivität zu bieten.
JRPGs enthalten oft sehr detaillierte und komplizierte Karten. Ich empfehle Ihnen daher, sie nicht manuell zu erstellen. Es empfiehlt sich daher, einen Tilemap-Editor zu verwenden. Sie können eine der hervorragenden kostenlosen vorhandenen Lösungen verwenden oder Ihre eigene rollen. Wenn Sie ein vorhandenes Tool ausprobieren möchten, empfehle ich auf jeden Fall, Tiled auszuprobieren. Dieses ist das Tool, mit dem Sie diese Beispielkarten erstellt haben.
zusammenhängende PostsEndlich zu den Kämpfen! Was nützt eine JRPG ohne Kampf? In vielen Spielen entscheiden sich viele Spiele für Innovationen, es werden neue Fertigkeitssysteme, neue Kampfstrukturen oder andere Zaubersysteme eingeführt - es gibt viele Variationen.
Die meisten Kampfsysteme verwenden eine rundenbasierte Struktur, wobei jeweils nur ein Kämpfer eine Aktion ausführen darf. Die allerersten auf Runden basierenden Kampfsysteme waren einfach: Jeder Spieler hatte einen Zug in der richtigen Reihenfolge: der Zug des Spielers, der Zug des Gegners, der Spielzug des Spielers, der Zug des Gegners und so weiter. Dies führte schnell zu komplexeren Systemen, die mehr Spielraum für Taktik und Strategie bieten.
Wir werden uns das genauer ansehen Aktivzeit basierte Kampfsysteme, bei denen nicht alle Kämpfer gleich viele Züge bekommen. Schnellere Entitäten erhalten möglicherweise mehr Umdrehungen, und die Art der durchgeführten Maßnahmen beeinflusst auch, wie lange eine Abzweigung dauert. Zum Beispiel kann ein Krieger, der mit einem Dolch zerschlägt, 20 Sekunden dauern, aber ein Zauberer, der ein Monster beschwört, kann zwei Minuten dauern.
Der obige Screenshot zeigt den Kampfmodus in einem typischen JRPG. Spielergesteuerte Charaktere befinden sich auf der rechten Seite, gegnerische Charaktere auf der linken Seite und ein Textfeld unten zeigt Informationen über die Kämpfer an.
Zu Beginn des Kampfes werden die Monster- und Spieler-Sprites der Szene hinzugefügt, und es gibt eine Entscheidung darüber, in welcher Reihenfolge die Entitäten ihre Züge einnehmen. Diese Entscheidung kann zum Teil davon abhängen, wie der Kampf gestartet wurde: Wenn der Spieler in einen Hinterhalt geraten war, können alle Monster zuerst angreifen, andernfalls basieren sie normalerweise auf einer der Entitätsstatistiken wie Geschwindigkeit.
Alles, was der Spieler oder die Monster tun, ist eine Aktion: Angreifen ist eine Aktion, die Verwendung von Magie ist eine Aktion. Sogar zu entscheiden, welche Aktion als nächstes ausgeführt wird, ist eine Aktion! Die Reihenfolge der Aktionen lässt sich am besten anhand einer Warteschlange verfolgen. Die Aktion an der Spitze ist die Aktion, die als Nächstes ausgeführt wird, sofern keine schnellere Aktion dies nicht vorsieht. Jede Aktion hat einen Countdown, der mit jedem Frame abnimmt.
Der Kampfstrom wird unter Verwendung einer Zustandsmaschine mit zwei Zuständen gesteuert; Ein Status zum Aktivieren der Aktionen und ein anderer Status zum Ausführen der obersten Aktion, wenn die Zeit gekommen ist. Wie immer ist der beste Weg, etwas zu verstehen, den Code zu betrachten. Das folgende Beispiel implementiert einen grundlegenden Kampfstatus mit einer Aktionswarteschlange:
Klasse BattleState: IState ListMActions = Liste (); Liste mEntities = Liste (); StateMachine mBattleStates = new StateMachine (); public static bool SortByTime (Aktion a, Aktion b) return a.TimeRemaining ()> b.TimeRemaining () public BattleState () mBattleStates.Add ("tick", neuer BattleTick (mBattleStates, mActions)); mBattleStates.Add ("Ausführen", neue BattleExecute (mBattleStates, mActions)); public void OnEnter (var params) mBattleStates.Change ("tick"); // // Eine Entscheidungsaktion für jede Entität in der Aktionswarteschlange abrufen // Die Sortierung so, dass die schnellsten Aktionen die obersten sind // mEntities = params.entities; foreach (Entität e in mEntities) if (e.playerControlled) PlayerDecide action = new PlayerDecide (e, e.Speed ()); mActions.Add (Aktion); else AIDecide action = neuer AIDecide (e, e.Speed ()); mActions.Add (Aktion); Sort (mActions, BattleState :: SortByTime); public void Update (Gleitkommazahl für abgelaufene Zeit) mBattleStates.Update (elapsedTime); public void Render () // Zeichne Szene, Gui, Charaktere, Animationen usw. mBattleState.Render (); public void OnExit ()
Der obige Code demonstriert die Steuerung des Kampfmodus-Flusses mithilfe einer einfachen Zustandsmaschine und einer Warteschlange von Aktionen. Zu Beginn haben alle an der Schlacht beteiligten Einheiten eine Entscheidungs-Aktion zur Warteschlange hinzugefügt.
Eine Entscheidungsaktion für den Spieler zeigt ein Menü mit den stabilen RPG-Optionen Attacke, Zauber, und Artikel; Sobald der Spieler sich für eine Aktion entschieden hat, wird die Entscheidungsaktion aus der Warteschlange entfernt und die neu gewählte Aktion wird hinzugefügt.
Eine Entscheidungsaktion für die KI wird die Szene inspizieren und entscheiden, was als nächstes zu tun ist (unter Verwendung eines Verhaltensbaums, eines Entscheidungsbaums oder einer ähnlichen Technik), und dann wird auch diese Entscheidungsaktion entfernt und die neue Aktion in die Warteschlange eingefügt.
Das BattleTick
Die Klasse steuert die Aktualisierung der Aktionen (siehe unten):
class BattleTick: IState StateMachine mStateMachine; ListeMActions; öffentlicher BattleTick (StateMachine StateMachine, List actions): mStateMachine (stateMachine), mActions (action) // In diesen Funktionen können Dinge passieren, aber nichts, an dem wir interessiert sind. public void OnEnter () public void OnExit () public void Render () public void Update (float elapsedTime) foreach (Aktion a in mActions) a.Update (elapsedTime); if (mActions.Top (). IsReady ()) Aktion nach oben = mActions.Pop (); mStateMachine: Ändern ("Ausführen", oben);
BattleTick
ist ein untergeordneter Status des BattleMode-Status. Er wird nur angezeigt, wenn der Countdown der obersten Aktion gleich Null ist. Dann wird die oberste Aktion aus der Warteschlange gezogen und in die geändert ausführen Zustand.
Das Diagramm oben zeigt eine Aktionsschlange zu