Wie entwerfe ich ein mehrstufiges Spielmenüsystem?

7

Ich fange mit der Spielprogrammierung an. Ich entwerfe ein Spiel, das Sie zunächst durch eine Reihe von Menübildschirmen führt. Ich bin daran interessiert zu erfahren, wie dies normalerweise in der professionellen Spieleentwicklung strukturiert ist.

Derzeit verarbeite ich die Controller-Eingaben aus der Hauptdatei und habe individuelle Klassen für jeden Menübildschirm. Wenn der Benutzer eine Auswahl trifft, muss ich zum nächsten Menübildschirm wechseln. Es scheint, als ob ich entweder eine Art "Menübildschirm-Manager" benötige, um die vorherige Auswahl zu verfolgen und zwischen Menübildschirmen zu wechseln, und / oder ich muss die Verarbeitung der Controller-Eingaben an die Menüobjekte weitergeben, da ich sie verbrauchen möchte / unten in einem Menü und links / rechts im anderen Menü.

Wie würde ein professioneller Spielprogrammierer dies entwerfen / entwerfen?

require "components/first_menu"
require "components/second_menu"

first_menu = FirstMenu.new
second_menu = SecondMenu.new
menu = first_menu
menu.render

on(controller: "up")   { menu.prev_option }
on(controller: "left") { menu.prev_option }

on(controller: "down")  { menu.next_option }
on(controller: "right") { menu.next_option }

on(controller: "buttonA") do
  if menu.is_a? FirstMenu
    menu.hide
    selected_option = menu.select_option
    menu = second_menu
    menu.show(selected_option)
  else
    selected_option = menu.select_option
    launch_game(selected_option)
  end
end
Andrew
quelle
Nur aus Neugier, warum Ruby? Ruby ist eine atypische erste Wahl, um Spielprogrammierung zu lernen. Möglicherweise finden Sie in C #, einer ziemlich anfängerfreundlichen Sprache mit kostenlosen Engines wie Unity, viel mehr Ressourcen für die Spieleentwicklung.
@MattJensJensen True. Ich benutze Ruby, weil ich ein erfahrener Ruby-Webentwickler bin und eine Ruby-Game-Engine baue, die SDL unter der Haube verwendet. Aber ich bin neu in einigen Techniken und Designmustern der professionellen Spielprogrammierung.
Andrew
1
Fair genug, ich liebe Ruby für das, was es kann, aber es könnte Ihr Lernen beschleunigen, C ++ oder C # zu testen, nur weil viele der großartigen Ressourcen im Design von Spielesoftware in erster Linie auf die Konzepte in diesen Sprachen und auf diese Sprachen abzielen in Bezug auf Effizienz und Leistung aussetzen. Ruby ist wunderschön, aber es ist schwierig, die Leistung herauszuholen. Zumindest wurde C ++ entwickelt, um die Leistung so schön wie möglich zu gestalten.
@ Andrew, wenn Ihr Ziel darin besteht, einen Motor zu bauen, würde ich mich nicht darum kümmern, ein Menüsystem zu erstellen. Allein die Möglichkeit, Entitäten zu organisieren und zu zeichnen, sowie das Physiksystem in einer GUI sind ein guter Anfang. Im Gegensatz zu typischer Software werden in einem Spiel keine Menüs benötigt.
Evorlor
Das Erstellen einer Spiel-Engine hat nichts mit meiner Frage zum Erstellen eines Menüsystems zu tun. Wenn Sie Ressourcen (in C, C ++, C # usw.) kennen, die das Design von Menüsystemen abdecken, lassen Sie es mich bitte wissen. Ich suche keine Ruby-Antwort. Ich versuche nur, die Techniken und Entwurfsmuster zu verstehen.
Andrew

Antworten:

3

Wenn Sie verschachtelte Menüs haben, können Sie einen Stapel verwenden. Ich kenne Ruby nicht, ich glaube, es gibt kein 'Stack'-Objekt. Ein ähnliches Verhalten kann mit einem Array erstellt werden. Sie fügen nur Elemente am Ende hinzu (Push), entfernen das letzte Element (Pop) und können mit dem letzten Element interagieren (Peek).

Die Grundidee lautet also: Sie definieren eine Menüklasse mit update () und draw (). Dies sollte eine grundlegende Menüimplementierung sein. Anschließend erstellen Sie einen Menumanager, der steuert, welches Menü aktualisiert und im Gameloop angezeigt wird. Dies implementiert den Stapel: Er kann Menüs hinzufügen oder entfernen, zeigt jedoch nur das oberste Element im Stapel an und aktualisiert es. Wenn ein verschachteltes Menü angezeigt werden soll, verschieben Sie die neue Menüklasse auf den Stapel. Ihr Controller und Ihre Anzeigelogik sollten nur das Menü anzeigen, das sich oben auf dem Stapel befindet.

Beispiel:

Basismenü:

  • Spieleinstellungen -> Spieleinstellungsmenü auf Stapel legen
  • Grafikeinstellungen -> Grafikmenü auf Stapel schieben
  • Audioeinstellungen -> Audio-Menü auf Stapel schieben
  • Beenden -> Menü beenden

Spieleinstellungen Menü:

  • Anzahl der Leben [1 | 3 | 5]
  • Schwierigkeit [leicht | normal | schwer]
  • Wählen Sie Sprache -> Sprachmenü auf Stapel schieben
  • Beenden -> Pop self vom Stapel

Sprachmenü:

  • Sprache [Englisch | Deutch | Nederlands | Français]
  • Untertitel [Aus | Weiß | Gelb]

Beginnen Sie mit dem Stapel, indem Sie das Basismenü darauf drücken.

Der Menumanager aktualisiert und zeigt nur die Oberseite des Stapels an (dies ist ein Blick auf die Oberseite des Stapels).

Wenn der Spieler das Spieleinstellungsmenü auswählt; Wir schieben dieses Menü auf den Stapel. Da wir nur die Oberseite des Stapels aktualisieren und anzeigen, wird das Menü "Spieleinstellungen" angezeigt und bearbeitet. Angenommen, das Menü geht tiefer; Sie können einfach das nächste Menü auf den Stapel schieben und dieses aktualisieren und anzeigen.

Der nette Trick ist, dass, wenn das oberste Menü vom Stapel entfernt ("geknallt") wird, das zugrunde liegende Menü angezeigt wird - Sie gehen einfach eine Ebene tiefer. Als zusätzlichen Bonus befindet sich dieses Menü in dem Zustand, in dem der Spieler es verlassen hat!

Nun, um diese Idee zu erweitern; Sie können einen Stapel auch für Ihre Spielzustände verwenden. Auf diese Weise kann ein Menü auf das laufende Spiel verschoben werden, und wenn es geöffnet wird, kann das Spiel fortgesetzt werden, als wäre nichts passiert.

Felsir
quelle
0

Ich werde eine Nicht-Ruby-Antwort geben, weil ich Ruby nicht kenne.

Ich hätte viele Klassen dafür:

MenuOneController
MenuTwoController
MenuOneView
MenuTwoView
InputNames

Die Controller navigieren durch das Menü. Durch Drücken von Auf und Ab wird das ausgewählte Element geändert, durch Drücken der Eingabetaste wird das Element ausgewählt. Wenn Sie das Element auswählen, wird an anderer Stelle Code aufgerufen, um die eigentliche Aktion auszuführen. Währenddessen aktualisiert der Controller lediglich die Ansichten. (Abhängig von Ihrem Menüsystem können Sie einen einzelnen Controller haben.)

In den Ansichten wird das aktuelle Menü gezeichnet, wobei die aktuell ausgewählte Option angezeigt wird, Feedback gegeben wird, wenn eine Option geändert wird usw.

Eingabenamen sind eine Datei mit Zeichenfolgen wie:

string up = "Up"

string select = "Enter"

Auf diese Weise ändern Sie die Zeichenfolge an einer Stelle anstelle aller Ihrer Controller, wenn Sie möchten, dass die Leertaste anstelle der Eingabe ausgewählt wird. Also anstatt on(controller: "buttonA")würden Sie verwenden on(controller: InputNames.select).

Wenn Sie also in Menü eins beginnen, MenuOneViewwürden Sie zeichnen, was Sie sehen. Das MenuOneControllerwäre aktiv. Wenn Sie dann "Menü Zwei" aus Menü Eins auswählen, wird MenuTwoViewstattdessen mit dem Zeichnen begonnen und MenuOneControllerstattdessen angehört.

Hier ist ein Pseudocode:

Class InputNames
{
    string up = "Up";
    string down = "Down";
    string select = "A Button";
}

Class MenuOneController
{
    int selectedMenuOption;

    void GameLoop()
    {
        if(ButtonPressed(InputNames.up)
        {
            selectedMenuOption++;
        }
        if(ButtonPressed(InputNames.down)
        {
            selectedMenuOption--;
        }
        if(ButtonPressed(InputNames.select))
        {
            if(selectedMenuOption == 0)
            {
                MenuOneView.ShowButtonBeingPressed();
                MyCodeElsewhere.DoStuffFromMenuOne();
            }
            if(selectedMenuOption == 1)
            {
                MenuOneView.ShowButtonBeingPressed();
                GameLoop.Remove(this);
                GameLoop.Add(MenuTwoController);
            }
        }
        MenuOneView.DrawUI(selectedMenuOption);
    }
}

Class MenuOneView
{
    void DrawUI(int selectedMenuOption)
    {
        //draw the UI for the currently selected menu option
    }

    void ShowButtonBeingPressed(selectedMenuOption)
    {
        //draw the UI such that the button is pressed
    }
}

Class MenuTwoController
{
    int selectedMenuOption;

    void GameLoop()
    {
        if(ButtonPressed(InputNames.up)
        {
            selectedMenuOption++;
        }
        if(ButtonPressed(InputNames.down)
        {
            selectedMenuOption--;
        }
        if(ButtonPressed(InputNames.select))
        {
            if(selectedMenuOption == 0)
            {
                MenuTwoView.ShowButtonBeingPressed();
                MyCodeElsewhere.DoStuffFromMenuTwo();
            }
            if(selectedMenuOption == 1)
            {
                MenuTwoView.ShowButtonBeingPressed();
                GameLoop.Remove(this);
                GameLoop.Add(MenuOneController);
            }
        }
        MenuTwoView.DrawUI(selectedMenuOption);
    }
}

Class MenuTwoView
{
    void DrawUI(int selectedMenuOption)
    {
        //draw the UI for the currently selected menu option
    }

    void ShowButtonBeingPressed(selectedMenuOption)
    {
        //draw the UI such that the button is pressed
    }
}
Evorlor
quelle
"Durch Auswahl des Elements wird Code an anderer Stelle aufgerufen, um die eigentliche Aktion auszuführen." Können Sie etwas näher erläutern, wie Sie dies sehen? Pseudocode würde auch helfen.
Andrew
Wollen Sie damit sagen, dass MenuOne MenuTwo direkt instanziieren würde? Oder stellen Sie sich ein Ereignissystem vor, bei dem die Steuerung indirekt an MenuTwo übergeben wird? Wenn Sie auch darauf näher eingehen könnten, wäre das hilfreich.
Andrew
@ Andrew Das kommt darauf an. Wenn alle Steuerelemente in allen Menüs gleich sind, können Sie nur einen einzigen Controller verwenden. Wenn sie unterschiedlich sind, können Menü-Controller andere Menü-Controller anrufen, um sie zur Übernahme aufzufordern. Oder wenn Sie eine andere Abstraktionsebene möchten, können Sie einen MenuControllerController verwenden. Persönlich denke ich, dass es übertrieben ist, einen Controller für Controller zu haben, aber ich kann ein Argument dafür sehen.
Evorlor
@ Andrew "Wenn Sie das Element auswählen, wird der Code an anderer Stelle aufgerufen, um die eigentliche Aktion auszuführen." Grundsätzlich können Sie im Menü auswählen, was Sie tun möchten. Nicht dafür. Es sollte so dumm wie möglich sein. Wenn Sie also die Schaltfläche "Lautstärke ausschalten" auswählen, sollte die Lautstärke nicht ausgeschaltet werden. Es sollte Ihren Volume Manager anweisen, das Volume auszuschalten.
Evorlor
Okay, es wird mir etwas klarer, basierend auf Ihrem Pseudocode-Beispiel. Korrigieren Sie mich, wenn ich falsch liege, aber es sieht so aus, als ob Sie ein globales (Singleton-) GameLoop-Objekt haben, auf das von überall aus zugegriffen werden kann und das die einzelnen Menü-Controller verwaltet / koordiniert. Das GameLoop-Objekt übergibt die Eingabeverarbeitung an den aktiven Menü-Controller. Die einzelnen Controller wissen, welche Controller die im vorherigen Menü ausgewählte Aktion ausführen. Habe ich das richtig verstanden?
Andrew