Ich habe darüber nachgedacht, wie ich Spielzustände in mein Spiel implementieren kann. Die wichtigsten Dinge, die ich dafür möchte, sind:
Halbtransparente Top-Zustände, die durch ein Pausenmenü auf das dahinterliegende Spiel blicken können
Etwas OO - ich finde es einfacher, die dahinter stehende Theorie zu verwenden und zu verstehen sowie die Organisation aufrechtzuerhalten und mehr hinzuzufügen.
Ich hatte vor, eine verknüpfte Liste zu verwenden und sie als Stapel zu behandeln. Dies bedeutet, dass ich für die Halbtransparenz auf den unten stehenden Status zugreifen konnte.
Plan: Der Statusstapel soll eine verknüpfte Liste von Zeigern auf IGameStates sein. Der oberste Status verarbeitet seine eigenen Aktualisierungs- und Eingabebefehle und hat dann den Member isTransparent, um zu entscheiden, ob der darunter liegende Status gezeichnet werden soll.
Dann könnte ich tun:
states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();
Um das Laden des Players darzustellen, gehen Sie zu Optionen und dann zum Hauptmenü.
Ist das eine gute Idee oder ...? Soll ich mir etwas anderes ansehen?
Vielen Dank.
quelle
new
wie im Beispielcode gezeigt, es fragt nur nach Speicherlecks oder anderen, schwerwiegenderen Fehlern.Antworten:
Ich habe am selben Motor wie Coderanger gearbeitet. Ich habe einen anderen Standpunkt. :)
Erstens hatten wir keinen Stapel FSMs - wir hatten einen Stapel Staaten. Ein Stapel von Zuständen bildet eine einzelne FSM. Ich weiß nicht, wie ein Stapel FSMs aussehen würde. Wahrscheinlich zu kompliziert, um irgendetwas Praktisches damit anzufangen.
Mein größtes Problem mit unserer globalen Zustandsmaschine war, dass es sich um einen Stapel von Zuständen und nicht um eine Reihe von Zuständen handelte. Dies bedeutet, dass z. B. ... / MainMenu / Loading anders war als ... / Loading / MainMenu, je nachdem, ob Sie das Hauptmenü vor oder nach dem Ladebildschirm geöffnet haben (das Spiel ist asynchron und das Laden erfolgt hauptsächlich auf Servern) ).
Als zwei Beispiele von Dingen, die dies hässlich machten:
Trotz des Namens war es nicht sehr "global". Die meisten internen Spielesysteme verwendeten es nicht, um ihre internen Zustände zu verfolgen, weil sie nicht wollten, dass sich ihre Zustände mit anderen Systemen herumschlagen. Andere, z. B. das UI-System, könnten es nur verwenden, um den Status in ihre eigenen lokalen Statussysteme zu kopieren. (Ich warne besonders vor dem System für UI-Status. Der UI-Status ist kein Stack, sondern eine echte DAG. Wenn Sie versuchen, eine andere Struktur zu erzwingen, ist die Verwendung von UIs nur frustrierend.)
Wofür war es gut, Aufgaben für die Integration von Code von Infrastruktur-Programmierern zu isolieren, die nicht wussten, wie der Spielfluss tatsächlich aufgebaut ist, sodass Sie dem Typ, der den Patcher schreibt, mitteilen konnten, dass er den Code in Client_Patch_Update einfügt, und dem Typ, der die Grafiken schreibt Wenn Sie "Setzen Sie Ihren Code in Client_MapTransfer_OnEnter" laden, können Sie bestimmte logische Abläufe problemlos austauschen.
Auf einem Nebenprojekt habe ich mehr Glück mit einem Zustand hatte gesetzt anstatt einen Stapel , nicht Angst zu haben mehrere Maschinen für unabhängige Systeme zu machen, und sich weigern , mich in die Falle fallen lassen einen „globalen Zustand“ zu haben, was wirklich ist Nur ein komplizierter Weg, um Dinge über globale Variablen zu synchronisieren - Sicher, Sie werden es in der Nähe einer bestimmten Frist tun, aber entwerfen Sie das nicht als Ihr Ziel . Grundsätzlich ist ein Zustand in einem Spiel kein Stapel, und Zustände in einem Spiel sind nicht alle miteinander verbunden.
Da Funktionszeiger und nicht lokales Verhalten dazu neigen, erschwerte das GSM das Debuggen von Dingen, obwohl das Debuggen derartiger großer Zustandsübergänge nicht sehr viel Spaß machte, bevor wir es hatten. Zustandsmengen anstelle von Zustandsstapeln helfen dies nicht wirklich, aber Sie sollten sich dessen bewusst sein. Virtuelle Funktionen anstelle von Funktionszeigern können dies etwas abmildern.
quelle
Hier ist eine Beispielimplementierung eines Gamestate-Stacks, die ich als sehr nützlich empfand: http://creators.xna.com/en-US/samples/gamestatemanagement
Es ist in C # geschrieben und zum Kompilieren benötigen Sie das XNA-Framework. Sie können sich jedoch den Code, die Dokumentation und das Video ansehen, um die Idee zu erhalten.
Es kann Zustandsübergänge, transparente Zustände (wie modale Meldungsfelder) und Ladezustände (die das Entladen vorhandener Zustände und das Laden des nächsten Zustands verwalten) unterstützen.
Ich verwende die gleichen Konzepte jetzt in meinen (Nicht-C #) Hobbyprojekten (unter Umständen nicht für größere Projekte geeignet) und für kleine / Hobbyprojekte kann ich den Ansatz definitiv empfehlen.
quelle
Dies ähnelt dem, was wir verwenden, einem Stapel von FSMs. Geben Sie einfach jedem Zustand eine Eingabe-, Ausgabe- und Ankreuzfunktion und rufen Sie sie der Reihe nach auf. Funktioniert sehr gut, um Dinge wie das Laden zu handhaben.
quelle
In einem der "Game Programming Gems" -Volumes war eine Zustandsmaschinenimplementierung für Spielzustände vorgesehen. http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf enthält ein Beispiel für die Verwendung in einem kleinen Spiel. Es sollte nicht zu gamebryospezifisch sein, um lesbar zu sein.
quelle
Um die Diskussion ein wenig zu standardisieren, ist der klassische CS-Begriff für diese Art von Datenstrukturen ein Pushdown-Automat .
quelle
Ich bin mir nicht sicher, ob ein Stack unbedingt erforderlich ist und die Funktionalität des Staatssystems einschränkt. Wenn Sie einen Stapel verwenden, können Sie einen Zustand nicht auf eine von mehreren Möglichkeiten verlassen. Angenommen, Sie starten im "Hauptmenü" und gehen dann zu "Spiel laden". Möglicherweise möchten Sie nach dem erfolgreichen Laden des gespeicherten Spiels in den Status "Pause" wechseln und zum "Hauptmenü" zurückkehren, wenn der Benutzer das Laden abbricht.
Ich würde nur den Zustand angeben lassen, dem zu folgen ist, wenn er beendet wird.
In Fällen, in denen Sie in den Status vor dem aktuellen Status zurückkehren möchten, z. B. "Hauptmenü-> Optionen-> Hauptmenü" und "Pause-> Optionen-> Pause", übergeben Sie einfach als Startparameter den Status Zustand zurück zu gehen.
quelle
Eine andere Lösung für Übergänge und andere derartige Dinge besteht darin, den Ziel- und Quellzustand zusammen mit der Zustandsmaschine bereitzustellen, die mit der "Maschine" verbunden sein könnte, wie auch immer dies sein mag. Die Wahrheit ist, dass die meisten Zustandsautomaten wahrscheinlich auf das jeweilige Projekt zugeschnitten werden müssen. Eine Lösung könnte diesem oder jenem Spiel zugute kommen, andere Lösungen könnten es behindern.
Zustände werden mit dem aktuellen Zustand und der Maschine als Parameter gepusht.
Staaten werden auf die gleiche Weise geknallt. Ob Sie
Enter()
den unteren anrufen,State
ist eine Umsetzungsfrage.Beim Betreten, Aktualisieren oder Verlassen
State
erhält der alle Informationen, die er benötigt.quelle
Ich habe ein sehr ähnliches System für mehrere Spiele verwendet und festgestellt, dass es mit ein paar Ausnahmen als hervorragendes UI-Modell dient.
Die einzigen Probleme, auf die wir gestoßen sind, waren Fälle, in denen es in bestimmten Fällen erwünscht ist, mehrere Status zurückzusetzen, bevor ein neuer Status gesendet wird (wir haben die Benutzeroberfläche erneut geändert, um die Anforderung zu entfernen, da dies normalerweise ein Zeichen für eine fehlerhafte Benutzeroberfläche war) und einen Assistentenstil zu erstellen lineare Flüsse (leicht gelöst durch Weitergabe der Daten an den nächsten Zustand).
Die von uns verwendete Implementierung hat den Stack tatsächlich verpackt und die Logik für das Aktualisieren und Rendern sowie die Operationen auf dem Stack verwaltet. Jede Operation auf dem Stapel löste Ereignisse in den Zuständen aus, um sie über das Auftreten der Operation zu benachrichtigen.
Es wurden auch einige Hilfsfunktionen hinzugefügt, um allgemeine Aufgaben zu vereinfachen, wie z. B. Tauschen (Pop & Push, für lineare Flüsse) und Zurücksetzen (um zum Hauptmenü zurückzukehren oder einen Fluss zu beenden).
quelle
Dies ist der Ansatz, den ich für fast alle meine Projekte verwende, weil er unglaublich gut funktioniert und extrem einfach ist.
Mein letztes Projekt, Sharplike , handhabt den Kontrollfluss genau so. Unsere Zustände sind alle mit einer Reihe von Ereignisfunktionen verkabelt, die aufgerufen werden, wenn sich Zustände ändern, und es gibt ein "benanntes Stapel" -Konzept, bei dem Sie mehrere Stapel von Zuständen innerhalb derselben Zustandsmaschine und Verzweigung haben können - ein Konzept Werkzeug und nicht notwendig, aber handlich zu haben.
Ich würde davor warnen, das von Skizz vorgeschlagene Paradigma "Dem Controller zu sagen, welcher Status nach dem Beenden folgen soll" zu verwenden: Es ist nicht strukturell einwandfrei und es erstellt Dinge wie Dialogfelder (was im Standard-Stack-State-Paradigma nur das Erstellen eines neuen umfasst) State-Unterklasse mit neuen Mitgliedern, und lesen Sie es dann ab, wenn Sie in den aufrufenden Zustand zurückkehren.
quelle
Ich habe im Grunde genommen genau dieses System in mehreren Systemen orthogonal verwendet. So hatten beispielsweise das Frontend und das In-Game-Menü (auch "Pause" genannt) ihre eigenen Status-Stacks. Die spielinterne Benutzeroberfläche verwendete auch so etwas, obwohl sie "globale" Aspekte (wie die Gesundheitsleiste und die Karte / das Radar) aufwies, die möglicherweise durch das Umschalten des Status hervorgerufen wurden, die jedoch in allen Staaten auf eine gemeinsame Weise aktualisiert wurden.
Das Menü im Spiel kann durch eine DAG "besser" dargestellt werden, aber mit einer impliziten Zustandsmaschine (jede Menüoption, die zu einem anderen Bildschirm wechselt, weiß, wie man dorthin geht, und das Drücken der Zurück-Taste hat immer den obersten Zustand erreicht) war der Effekt genauso.
Einige dieser anderen Systeme verfügten ebenfalls über die Funktion "Ersetzen des Top-Status", diese wurde jedoch in der Regel wie
StatePop()
folgt implementiertStatePush(x);
.Die Handhabung von Speicherkarten war ähnlich, da ich tatsächlich eine Tonne "Operationen" in die Operationswarteschlange geschoben habe (die funktionell dasselbe tat wie der Stapel, nur als FIFO und nicht als LIFO); Sobald Sie anfangen, diese Art von Struktur zu verwenden ("es passiert gerade etwas und wenn es fertig ist, erscheint es von selbst"), infiziert es jeden Bereich des Codes. Sogar die KI begann so etwas zu benutzen. Die KI war "ahnungslos" und wurde auf "vorsichtig" umgeschaltet, wenn der Spieler Geräusche machte, aber nicht gesehen wurde, und schließlich auf "aktiv" angehoben, wenn er den Spieler sah (und im Gegensatz zu kleineren Spielen der Zeit konnte man sich nicht verstecken in einer Pappschachtel und lass den Feind dich vergessen! Nicht, dass ich bitter bin ...).
GameState.h:
GameState.cpp:
quelle