# Spielemenüs Die Gemeinsamkeit fast aller Spiele ist die Möglichkeit eigene Optionen festzulegen. ## Control Nodes In Godot können Menüs mit `Control` Nodes erstellt werden. `Control` Nodes sind im Gegensatz zu `Node2D` und `Node3D` nur UI elemente, wie Buttons, Dropdowns, Label und Container. Besonders `Container` sind wichtige UI elemente, da sie bestimmen, wo Elemente im UI angezeigt und angeordnet werden. `Container` sind parent Nodes von andern UI elementen, die innerhalb eines Containers angelegt werden. Hier ein Beispiel für einen `VBox Container` (vertikales Alignment) im Scene tool: ![VBox im Scene Tool](../img/starroamers/vbox_node.png) Diese Konfiguartion sieht dann im Scene Manager so aus: ![VBox im Editor](../img/starroamers/vbox_ex.png) Das Main Menu implementiert diese Control Node: ![Main Menu](../img/starroamers/main_menu.png) ## Optionen Die Szene, in der die Konfigurationsmöglichkeit für Videooptionen erstellt wurde, wird im folgenden `options.tscn` genannt. Diese Szene beinhaltet Control Nodes und Skripts, mit denen das Spiel angepasst werden kann. ![Option Menu](../img/starroamers/options_ingame.png) Das Skript `config.gd` beinhaltet Möglichkeiten zum *laden* und *speichern* von Optionen: ```{code-block} gdscript --- caption: config.gd lineno-start: 10 --- extends Node # Savefile für Optionen const SAVEFILE = "user://options.save" # Dictionary mit geladenen Optionen var save_data = get_default_config() func load_data(): var config = ConfigFile.new() var err = config.load(SAVEFILE) if err != OK: # Config file ist wahrscheinlich noch nicht erstellt print("Could not read config file, defaulting.") # Schreibe default config in Datei save() return # Config wurde ausgelesen, nun wird das dictionary angepasst for key in save_data.keys(): save_data[key] = config.get_value("Options", key) func save(): var config = ConfigFile.new() for key in save_data.keys(): config.set_value("Options", key, save_data.get(key)) config.save(SAVEFILE) static func get_default_config(): return { "window_mode": 1, "vsync_on": false, "display_fps": false, } ``` Die Methode `load_data` holt die Config Datei `SAVEDATA` und liest es als Konfiguration ein. Config Dateien haben `INI` ähnliche formate und besitzen Sektionen und Key-Value Paare: ```{code-block} ini --- caption: user://options.save lineno-start: 1 --- [Options] window_mode=1 vsync_on=false display_fps=false ``` Dieses Beispiel ist das momentan verwendete Config File, welches Videooptionen speichert. ### Anwendung Natürlich reicht das Speichern dieser Konfiguration nicht aus, um diese Optionen auch im Spiel anwenden zu können. In Godot gibt es den `DisplayServer`, welcher Display optionen anwenden kann. In der Methode `apply_config` wird dieser genutzt, um die Konfigurationen, welche mit der Refresh Rate oder dem Fenstermodus zu tun haben, anzuwenden: ```{code-block} gdscript --- caption: config.gd lineno-start: 27 --- func apply_config(): match save_data["window_mode"]: 0: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN) 1: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) match save_data["vsync_on"]: true: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) false: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) ``` ### Verwendung in anderen Nodes Nicht nur das Optionsmenü braucht Zugriff auf die Konfiguration. Wenn das Spiel gestartet wird, soll das Main Menu die gespeicherten Konfigurationen anwenden. Dafür wird das `config.gd` Skript im `main_menu.gd` geladen: ```{code-block} gdscript --- caption: main_menu.gd lineno-start: 10 --- func _ready(): var conf = preload("res://Scripts/config.gd").new() conf.load_data() conf.apply_config() # -- snip -- ``` Die `_ready` Funktion wird aufgerufen, wenn die Node das erste Mal die Szene betritt. Beim MainMenu ist das direkt, wenn das Spiel gestartet wird. Die Option **display_fps** wurde nicht in der `apply_config` Methode behandelt, da der `DisplayServer` keine Funktion besitzt, die FPS zu zeigen. Deshalb wurde ähnlich wie im Main Menu im HUD (`HUD.gd`) das Config file gelesen und ausgewertet: ```{code-block} gdscript --- caption: HUD.gd lineno-start: 1 --- extends CanvasLayer onready var fps_label = $FPS_Label var save_data = {} func load_config(): var config = preload("res://Scripts/config.gd").new() config.load_data() save_data = config.save_data func _ready(): load_config() func _process(delta): if save_data.get("display_fps", false): fps_label.text = str(Engine.get_frames_per_second()) else: fps_label.text = "" func _on_pause_unpaused(): load_config() ``` In diesem Skript wird wieder in der `_ready` Methode die Konfiguration gelesen und dann anschließend in der `_process` Methode die FPS in dem Label `fps_label` ausgegeben. Die Konfiguration wird in `_on_pause_unpaused` neu eingelesen, welche aufgerufen wird, wenn das Pause Menü den aktiven Spielprozess wieder startet. Wenn also die Konfiguration im Pause Menü angepasst wurde, wird das HUD darüber informiert und kriegt die neuen Daten. Hier noch ein Beispiel für das FPS Label: ![VSYNC_ON](../img/starroamers/vsync_on.png) ![VSYNC_OFF](../img/starroamers/vsync_off.png) (*Die labels VSync: Off und VSync: ON sind nicht teil des Screenshots.* *Das Label rechts oben, welches die FPS anzeigt soll hier dargestellt werden.* *Die VSync Labels sind ausschließlich zu verdeutlichung, dass sich* *die FPS dem Bildschirm anpassen können, gedacht.*) ## Pause Menü Das Pause Menü hat eine Eigenschaft, dass es die Szene pausieren kann. ![Pause Menu](../img/starroamers/pause.png) In Godot ist jedes Element in einer Szene abgeleitet von `Node`. Nodes haben das Attribut "pause", welches einen boolean Wert liefert, ob der Prozess pausiert ist. Bei direkter Änderung dieses Wertes wird also die Node pausiert. By default wird für jede Child Node der `pause` Wert der Parent Node vererbt, bedeutet im Umkehrschluss, dass wenn die `Root Node` pausiert wird, der ganze `SceneTree` pausiert ist. ```{code-block} gdscript --- caption: pause.gd lineno-start: 22 --- func toggle_pause(): var new_pause_state = not get_tree().paused # Pause state wird umgeschalten get_tree().paused = new_pause_state # Pause Menü soll sichtbar/unsichtbar werden visible = new_pause_state if visible: resume_button.grab_focus() else: emit_signal("unpaused") ``` Das Problem an pausierten Elementen ist, dass Input ignoriert wird und dass das Pause Menü ebenso Teil des `SceneTree`s ist. Unter der `process` Kathegorie im Node Inspector lässt sich allerdings dieses Verhalten umstellen. ![Process im Node Inspector](../img/starroamers/process_node.png) Wenn die Variable `Mode` auf `Always` statt `Inherit` geschaltet wird, kann auch während die Node pausiert ist noch Input entgegengenommen und verarbeitet werden. ## Game Over Wenn der Spieler keine Leben mehr hat, soll das Spiel zuende sein und ein **Game Over** Bilschirm zu sehen sein. ![Game Over Menu](../img/starroamers/game_over.png) Der Spieler emitted bei seinem Tod das Signal `killed`, welches hierfür verwendet werden kann. Dazu muss die Game Over Szene sich an diesem Event registrieren und kann dann eigene Funktionen ausführen. Ähnlich wie beim Pause Menü wird auch hier der Spielfluss unterbrochen, indem die Szene pausiert wird. ```{code-block} gdscript --- caption: game_over.gd lineno-start: 10 --- func _on_player_killed(): toggle_pause() ``` `toggle_pause` is hier keine Funktion in `pause.gd` sondern in `game_over.gd`, funktioniert aber gleich: Pausiere den SceneTree und schalte auf *visible*.