Beste Möglichkeit, spielweite Variablen zu speichern

23

Ich habe einen Optionsbildschirm für Dinge wie Schwierigkeitsgrad, Auflösung, Vollbild usw., aber ich habe Mühe, den "besten" Weg zu finden, um diese Variablen zur Laufzeit zu speichern / abzurufen.

Derzeit habe ich eine ConstantsKlasse implementiert , die alle GameOptionAufzählungen enthält. Wie wähle ich jedoch eine Standardeinstellung für alle diese Optionen aus? Wie erhalte ich außerdem die aktuell ausgewählte Aufzählung?

Insbesondere in Bezug auf die Auflösung habe ich beschlossen, die Werte zu speichern. Ich bin mir jedoch nicht sicher, wie ich die Standardwerte oder die aktuell gespeicherten Werte erhalten soll. Jede Richtung wäre toll; Vielen Dank! :)

namespace V1.test.RPG
{
  public class GameOptions
  {
    public enum Difficulty { EASY, MEDIUM, HARD }
    public enum Sound { ON, QUIET, OFF }
    public enum Music { ON, QUIET, OFF }
    public enum ResolutionWidth
    {
        SMALL      = 1280,
        MEDIUM     = 1366,
        LARGE      = 1920,
        WIDESCREEN = 2560
    }
    public enum ResolutionHeight
    {
        SMALL      = 800,
        MEDIUM     = 768,
        LARGE      = 1080,
        WIDESCREEN = 1080
    }
    public Boolean fullScreen = false;
  }
}

NB: Ich habe bei SO nachgefragt und sie haben mich auf diesen Ort hingewiesen. Es gibt dort einen Kommentar, aber ich würde gerne verschiedene Methoden dazu hören / die am häufigsten verwendeten.

R-Nold
quelle
1
Sie haben an der richtigen Stelle gefragt. Wer auch immer Sie hierher geschickt hat, hat sich geirrt. Ich habe die Frage trotzdem beantwortet, um Ihnen zu helfen, aber dies ist keine spielentwicklungsspezifische Frage, sondern eine allgemeine Programmierfrage.
jhocking
Ich habe gerade den SO-Thread gelesen. Ich mag die Antwort von Scott Chamberlin.
jhocking
@jhocking Ich habe ihn auf diese Weise hingewiesen, falls es Aspekte gibt, die für die Spieleentwicklung spezifisch sind und von einer normalen Anwendung abweichen könnten. Ich dachte mir auch, dass ihr vielleicht schon ein kanonisches Q & A zu diesem Thema habt, da es so üblich ist.
Chris Hayes
Bitte nehmen Sie nicht an, dass es einen festen Satz von Auflösungen gibt, der sich auf die eigentliche Frage nach Globals bezieht.
Lars Viklund

Antworten:

32

Planen des Wachstums:
Fest codierte Konstanten eignen sich gut für kleine Projekte. Mit zunehmender Größe Ihrer Software möchten Sie jedoch, dass Sie diese Einstellungen ändern können, ohne alles neu kompilieren zu müssen. Es gibt viele Male, in denen Sie Einstellungen ändern möchten, während das Spiel ausgeführt wird, und Sie können dies nicht mit fest codierten Konstanten tun.

CVars:
Sobald Ihr Projekt wächst, sollten Sie sich CVARs ansehen . Ein CVAR ist sozusagen eine "intelligente Variable", die Sie zur Laufzeit über eine Konsole, ein Terminal oder eine Benutzeroberfläche ändern können. CVARs werden normalerweise in Form eines Objekts implementiert, das einen zugrunde liegenden Wert umschließt. Das Objekt kann dann den Wert verfolgen sowie ihn in / aus einer Datei speichern / laden. Sie können die CVAR-Objekte in einer Karte speichern, um auf sie mit einem Namen oder einer anderen eindeutigen Kennung zuzugreifen.

Zur weiteren Veranschaulichung des Konzepts ist der folgende Pseudocode ein einfaches Beispiel für einen CVAR-Typ, der einen intWert umschließt :

// just some flags to exemplify:
enum CVarFlags {
    CVAR_PERSISTENT, // saved to file once app exits
    CVAR_VOLATILE    // never saved to file
};

class CVar {
public:
    // the constructor registers this variable with the global list of CVars
    CVar(string name, int value, int defaultValue, int flags);

    int getValue();
    void setValue(int v);
    void reset(); // reset to the default value

    // etcetera...

private:
    int flags; // flags like: save-to-file, etc.
    int value; // the actual value
    int defaultValue; // the default value to reset the variable to
};

// global list of all CVars:
map<string, CVar> g_cvars;

Globaler Zugriff:
Im obigen Beispiel habe ich angenommen, dass der Konstruktor von CVardie Variable immer in der globalen cvarsZuordnung registriert . Dies ist sehr nützlich, da Sie damit eine Variable wie folgt deklarieren können:

CVar my_var = new CVar("my_var", 0, 42, CVAR_PERSISTENT);

Diese Variable wird automatisch in der globalen Karte verfügbar gemacht, und Sie können von jedem Ort aus darauf zugreifen, indem Sie die Karte mit dem Variablennamen indizieren:

CVar v = g_cvars.find("my_var");

Persistenz:
Wenn das Spiel beendet wird, iteriere die Karte und speichere alle Variablen, die als markiert sind CVAR_PERSISTENT, in einer Datei. Wenn das Spiel das nächste Mal startet, lade sie neu.

Rechtsprechung:
Ein genaueres Beispiel für ein robustes CVAR-System finden Sie in der Implementierung von Doom 3 .

glampert
quelle
4

Zunächst definiert eine Aufzählung, was die Werte sein können , nicht was die Werte sind . Daher müssen Sie nach dem Deklarieren der Enumeration noch eine weitere Variable deklarieren. Beispielsweise:

public enum Sound
{
    ON,
    QUIET,
    OFF
}

public Sound soundValue;

In diesem Beispiel können Sie jetzt soundValueON, QUIET oder OFF einstellen .


Dann müssen Sie Ihren Code noch so strukturieren, dass andere Teile Ihres Codes auf dieses "settings" -Objekt zugreifen können. Ich weiß nicht, ob Sie auch bei diesem Teil Hilfe benötigen, aber gängige Muster zur Behebung dieses Problems sind Singletons (die heutzutage verpönt sind) oder Service Locators oder Dependency Injection.

jhocking
quelle
1

Die Lösung von Glampert ist sehr vollständig, aber ich werde meine persönliche Erfahrung hinzufügen.

Ich bin auf dasselbe Problem gestoßen, und meine Lösung bestand darin, eine statische Variablenklasse zu verwenden.

Die Variables-Klasse bewahrt intern eine Zuordnung von Zeichenfolge zu Zeichenfolge auf (bis jetzt sind alle meine Variablen nur Zeichenfolgen) und auf sie wird über Getter und Setter zugegriffen.

Der Punkt ist, dass der Zugriff auf globale Variablen alle möglichen subtilen Fehler verursachen kann, da sich völlig unabhängige Teile des Codes plötzlich gegenseitig stören.

Um dies zu vermeiden, habe ich die folgende Semantik eingeführt: Mit der setMethode wird eine Ausnahme ausgelöst, wenn eine Variable mit diesem Namen bereits im Wörterbuch vorhanden ist, und getdie Variable wird aus dem Wörterbuch gelöscht, bevor sie zurückgegeben wird.

Zwei zusätzliche Methoden bieten das, was Sie erwarten würden, setAndOverwriteund getAndKeep. Der Sinn der Semantik der anderen Methoden besteht darin, dass Sie leicht Fehler der Art erkennen können, die "diese Methode soll diese Variable initialisieren, aber eine andere Methode hat dies bereits getan".

Um das Wörterbuch zu initialisieren, werden anfängliche Variablen in einer JSON-Datei gespeichert und dann gelesen, wenn das Spiel startet.

Leider bin ich mit meinem Spiel noch nicht allzu weit gekommen, sodass ich die Robustheit dieses Ansatzes nicht bezeugen kann. Vielleicht kann es aber auch zusätzlich zu CVARs noch etwas Interessantes bieten.

angarg12
quelle