Wie kann ich die Anzeigeeinstellungen über einen Optionsbildschirm aktualisieren, ohne sie neu zu starten?

9

Ich erstelle gerade ein 2D-RPG in C ++ 11 mit Allegro 5 und Boost.

Mein Ziel ist es, meine Spieleinstellungen irgendwie zu aktualisieren, wenn eine Option im Optionsmenü geändert wird. Ich möchte den Benutzer nicht zwingen, mein Spiel neu zu starten. Andere Spiele erfordern keinen Neustart, wenn Sie die Auflösung ändern oder vom Vollbildmodus zum Fenster wechseln. Daher sollte mein Spiel dies auch nicht tun. Nachfolgend finden Sie eine vereinfachte Ansicht des Systems.

Bitte beachten Sie, dass ich mein Spielobjekt nicht unbedingt direkt über den Optionsbildschirm aufrufen möchte. Die gestrichelte Linie soll lediglich den Effekt veranschaulichen, den ich erreichen möchte. um irgendwie ein Update des Spiels zu verursachen, wenn eine Option in einem anderen Teil des Systems geändert wird.

Ausführliche Erklärung

Der ScreenManager enthält eine Liste aller GameScreenaktuell vorhandenen Objekte. Dies sind verschiedene Bildschirme im Spiel, einschließlich Popups. Dieses Design entspricht mehr oder weniger dem Game State Management-Beispiel in C # / XNA .

Das ScreenManagerenthält einen Verweis auf mein GameObjekt. Das GameObjekt initialisiert und ändert die Spieleinstellungen. Wenn ich die Auflösung ändern, den Vollbildmodus aktivieren oder die Lautstärke stummschalten möchte, würde ich dies in der GameKlasse tun .

Der OptionsScreen kann derzeit jedoch nicht auf die Spielklasse zugreifen. Siehe unteres Diagramm:

Ein GameScreen kann drei Ereignisse signalisieren onFinished, onTransitionStartund onTransitionEnd. Es gibt keine, onOptionsChangedweil das nur ein Bildschirm tut. Der ScreenManager kann die Ereignisbehandlung dafür nicht einrichten, da er alle Bildschirme als GameScreens behandelt.

Meine Frage ist, wie kann ich mein Design so ändern, dass eine Änderung im Optionsmenü keinen Neustart erfordert, sondern sofort geändert wird? Ich würde mein GameObjekt vorzugsweise zur Aktualisierung auffordern, sobald auf die Schaltfläche "Anwenden" geklickt wird.

IAE
quelle
Diese Frage (obwohl sie spielbezogen ist)
scheint
Woher hast du so toll aussehende Grafiken?
Joltmode
@psycketom: Der erste stammt aus Visio 2013 und der zweite wird von VS2012 aus Code generiert. Ich hoffe, das hilft!
IAE

Antworten:

1

Nach dem, was ich gesehen habe, ist es am einfachsten, beim Start eine Optionsdatei zu lesen, um die aktuellen Anzeigeeinstellungen zu ermitteln. Laden Sie dann, wenn Ihr Optionsbildschirm angezeigt wird, alle aktuellen Optionen aus einer Datei.

Wenn Änderungen über eine Schaltfläche applyoder abgeschlossen werden ok, werden sie wieder in einer Datei gespeichert. Wenn sich Änderungen auf die Anzeige auswirken, benachrichtigen Sie den Benutzer, dass das Spiel neu gestartet werden muss, damit er wirksam wird.

Beim Neustart des Spiels werden die (jetzt neuen) Anzeigeeinstellungen erneut aus der Datei gelesen.

--BEARBEITEN--

Annd ... es hätte geholfen, wenn ich diesen letzten Satz bemerkt hätte. Sie möchten nicht neu starten müssen. Dies erschwert die Arbeit je nach Implementierung und Back-End-Grafikbibliothek etwas.

IIRC, Allegro hat einen Funktionsaufruf, mit dem Sie die Anzeigeeinstellungen im laufenden Betrieb ändern können. Ich bin noch nicht auf Allegro 5, aber ich weiß, dass Sie in 4 könnten.

Casey
quelle
Ich glaube nicht, dass das Problem mit Allegro und seinen Fähigkeiten zusammenhängt. "Meine Frage ist, wie kann ich mein Design so ändern, dass eine Änderung im Optionsmenü keinen Neustart erfordert, sondern sofort geändert wird?" Der Neustart ist darauf zurückzuführen, dass der "OptionsScreen derzeit nicht auf die Spielklasse zugreifen kann" und diese aus der Einstellungsdatei laden muss, wenn ich das richtig verstehe.
ClassicThunder
@Casey: Hallo Casey! :) Ja, ich verwende eine Konfigurationsdatei zum Speichern der relevanten Anzeigedaten, möchte aber den Neustart vermeiden können. Es ist ein kleines Spiel, und andere Spiele können die Auflösung ändern, ohne neu starten zu müssen. Daher verstehe ich nicht, warum ich den Benutzer zwingen sollte, mit meinem neu zu starten. Es ist ein Usability-Problem. Ich könnte zwar nur die Allegro-Display-Funktionen aufrufen, aber ich versuche, OOP zu bleiben und meine Grafikanrufe nicht zu mischen, nur weil ich kann. Ich halte das nicht für gutes Design.
IAE
Ich habe das Bit "ohne Neustart" hervorgehoben, damit andere nicht auf den gleichen letzten Satz stoßen. Eine so wichtige Tatsache sollte nicht am Ende stehen, ich entschuldige mich):
IAE
@SoulBeaver Wer sagt, dass Sie es schaffen müssen , damit Sie nicht neu starten? Es ist sinnvoll, dies zu fordern, und es wird heutzutage immer häufiger . Einige neuere Versionen (XCOM: Enemy Unknown, Skyrim usw.) von Spielen mit großem Budget erfordern einen Neustart. (Die Tatsache, dass sie auf Steam sind, kann der Schuldige sein, da die Optionen serverseitig synchronisiert werden, aber gehen wir nicht dorthin ...)
Casey
1
@Casey: Stimmt, und für bestimmte Optionen kann ich sehen, warum das überhaupt notwendig ist, aber für Dinge wie Vollbild / Fenster oder die Bildschirmauflösung? Ich habe noch nie ein Spiel gesehen, bei dem für diese Einstellungen ein Neustart erforderlich war. Meine grundlegende Frage lautet: Warum sollte ich meinen Benutzer zum Neustart zwingen, wenn das Spiel die Einstellungen automatisch ändern kann?
IAE
1

Das mache ich für mein Spiel. Ich habe 2 separate Funktionen zum Initialisieren von Sachen, 'init' und 'reset'. Init wird beim Start nur einmal aufgerufen und führt Dinge aus, die nicht auf Einstellungen beruhen, z. B. das Laden von Hauptressourcen. Beim Zurücksetzen wird beispielsweise die Benutzeroberfläche basierend auf der Bildschirmauflösung angeordnet. Sie wird daher jedes Mal aufgerufen, wenn sich die Einstellungen ändern.

init();
bool quit = false;
while( !quit )
{
    createWindow();
    reset();

    bool settings_changed = false;
    while( !quit && !settings_changed )
    {
        ... main loop
        // set quit=true if user clicks 'quit' in main menu
        // set settings_changed=true if user clicks 'apply' in options
    }

    destroyCurrentWindow();
}

Ich bin mit Allegro nicht vertraut, aber meine Antwort ist ziemlich allgemein, also hoffe ich, dass sie Ihnen oder anderen mit einem ähnlichen Problem hilft.

DaleyPaley
quelle
Das ist eine interessante Lösung. Ich habe versucht, die Kontrolle zu behalten, wann immer ein Reset aufgerufen wird, aber Sie tun dies unabhängig von einer Änderung. Das ist definitiv etwas, das ich implementieren könnte, und ich werde es untersuchen, danke!
IAE
1

Ohne Ihre aktuelle Architektur durcheinander zu bringen, sehe ich zwei Möglichkeiten. Zunächst können Sie einen Zeiger auf die GameInstanz in der OptionsScreenKlasse speichern . Zweitens könnte die GameKlasse aktuelle Einstellungen in einem bestimmten Intervall abrufen, beispielsweise jede Sekunde.

Um sich tatsächlich an die neuen Einstellungen anzupassen, muss die GameKlasse eine Art Rücksetzfunktion implementieren, die die aktuellen Einstellungen abruft und basierend auf diesen neu initialisiert.

Für eine saubere Lösung benötigen Sie einen globalen Manager. Die Implementierung ist daher aufwändiger. Zum Beispiel ein Ereignissystem oder ein Nachrichtensystem. Es ist sehr nützlich, Klassen ohne so starke Bindungen wie Aggregation oder Komposition usw. kommunizieren zu lassen.

Mit einem globalen Event-Manager kann der OptionsScreeneinfach ein Redraw-Ereignis global auslösen, das Gamesich zum Abhören registriert hat.

Im Allgemeinen können Sie eine Manager-Klasse implementieren, in der Ereignisse und Rückrufe, die sie abhören, in einer Hash-Map gespeichert werden. Anschließend können Sie eine einzelne Instanz dieses Managers erstellen und Zeiger darauf an Ihre Komponenten übergeben. Die Verwendung von neuerem C ++ ist recht einfach, da Sie es std::unordered_mapals Hash-Map verwenden und std::functionRückrufe speichern können. Es gibt verschiedene Ansätze, die Sie als Schlüssel verwenden können. Sie können beispielsweise die Zeichenfolge des Ereignismanagers festlegen, wodurch Komponenten noch unabhängiger werden. In diesem Fall würden Sie std::stringals Schlüssel in der Hash-Map verwenden. Ich persönlich mag das und es ist definitiv kein Leistungsproblem, aber die meisten traditionellen Ereignissysteme arbeiten mit Ereignissen als Klassen.

Danijar
quelle
Hallo danijar. Ich verwende bereits boost :: -Signale, um ein rudimentäres Ereignisbehandlungsschema einzurichten. Die starke Linie vom GameScreen zum ScreenManager ist eigentlich die Ereignisbehandlung. Haben Sie Ressourcen, die die Architektur eines globalen Event-Managers beschreiben? Mehr Arbeit oder nicht, das klingt nach einer sauberen Implementierung.
IAE
Obwohl ich keine Recherchen dazu gelesen habe, habe ich einen globalen Event-Event-Manager für meine eigene kleine Engine implementiert. Wenn Sie an der Implementierung interessiert sind, sende ich Ihnen einen Link. Ich habe meine Antwort aktualisiert, um mehr Details zu erfahren.
Danijar
1

Nun, dies ist ein spezieller Fall des Observer-Musters.

Es gibt eine Lösung, die Rückrufe beinhaltet. Dies ist der beste Weg, dies zu tun, wenn Sie eine lose Kupplung wünschen, und ich denke, es ist auch der sauberste. Dies beinhaltet keine globalen Manager oder Singletons.

Grundsätzlich müssen Sie eine Art von haben SettingsStore. Dort speichern Sie die Einstellungen. Wenn Sie einen neuen Bildschirm erstellen, benötigen sie einen Zeiger auf das Geschäft. Im Falle des OptionsScreenwird ein Teil des Einstellungswerts selbst geändert. Im Falle der wird GameScreenes nur lesen. In Ihrem Spiel würden Sie also nur eine Instanz erstellen, die an alle Bildschirme weitergeleitet wird, für die eine erforderlich ist.

Nun wird diese SettingsStoreKlasse eine Liste von haben notifiables. Sie sind Klassen, die eine bestimmte ISettingChangedSchnittstelle implementieren . Die Schnittstelle wäre einfach und enthält die folgende Methode:

void settingChanged(std::string setting);

Anschließend implementieren Sie auf Ihrem Bildschirm die Logik für jede Einstellung, die Sie interessiert. Fügen Sie sich dann dem zu benachrichtigenden Geschäft hinzu : store->notifyOnChange(this);. Wenn eine Einstellung geändert wird, wird der Rückruf mit dem Einstellungsnamen aufgerufen. Der neue Einstellwert kann dann aus dem abgerufen werden SettingsStore.

Dies kann nun durch die folgenden Ideen weiter ergänzt werden:

  • Verwenden Sie eine Klasse, in der die Einstellungszeichenfolgen gespeichert sind - möglicherweise sogar die SettingsStore(const-Zeichenfolgen), um das Einfügen von Zeichenfolgen durch Kopieren zu verhindern.
  • Noch besser ist es, anstelle einer Zeichenfolge eine Aufzählung zu verwenden, um anzugeben, bei welchen Einstellungen Sie sich befinden
  • Sie könnten sogar die alten und neuen Werte als Argumente im Rückruf angeben, aber das wäre komplizierter, da Sie String- oder Int-Werte oder so weiter haben können. Es liegt an dir.
Timotei
quelle
0

Lesen Sie Ihre Einstellungen aus einer Datei in Variablen. Lassen Sie Ihren Bildschirmmanager verfolgen, ob der Bildschirm, von dem er gerade kam, der Bildschirm "Optionen" war, und laden Sie gegebenenfalls Ihre Einstellungen aus den Variablen neu. Wenn der Benutzer Ihr Spiel beendet, schreiben Sie die Einstellungen in den Variablen zurück in die Datei.

131nary
quelle