6.7. Spielemenüs

Die Gemeinsamkeit fast aller Spiele ist die Möglichkeit eigene Optionen festzulegen.

6.7.1. 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

Diese Konfiguartion sieht dann im Scene Manager so aus:

VBox im Editor

Das Main Menu implementiert diese Control Node:

Main Menu

6.7.2. 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

Das Skript config.gd beinhaltet Möglichkeiten zum laden und speichern von Optionen:

Codeabschnitt 6.7.1 config.gd
10extends Node
11
12# Savefile für Optionen
13const SAVEFILE = "user://options.save"
14
15# Dictionary mit geladenen Optionen
16var save_data = get_default_config()
17
18func load_data():
19	var config = ConfigFile.new()
20	var err = config.load(SAVEFILE)
21	if err != OK:
22        # Config file ist wahrscheinlich noch nicht erstellt
23		print("Could not read config file, defaulting.")
24        # Schreibe default config in Datei
25		save()
26		return
27
28    # Config wurde ausgelesen, nun wird das dictionary angepasst
29	for key in save_data.keys():
30		save_data[key] = config.get_value("Options", key)
31
32func save():
33	var config = ConfigFile.new()
34	for key in save_data.keys():
35		config.set_value("Options", key, save_data.get(key))
36	config.save(SAVEFILE)
37
38static func get_default_config():
39	return {
40		"window_mode": 1,
41		"vsync_on": false,
42		"display_fps": false,
43	}

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:

Codeabschnitt 6.7.2 user://options.save
1[Options]
2window_mode=1
3vsync_on=false
4display_fps=false

Dieses Beispiel ist das momentan verwendete Config File, welches Videooptionen speichert.

6.7.2.1. 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:

Codeabschnitt 6.7.3 config.gd
27func apply_config():
28	match save_data["window_mode"]:
29		0: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
30		1: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
31	match save_data["vsync_on"]:
32		true: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
33		false: DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

6.7.2.2. 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:

Codeabschnitt 6.7.4 main_menu.gd
10func _ready():
11	var conf = preload("res://Scripts/config.gd").new()
12	conf.load_data()
13	conf.apply_config()
14	# -- 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:

Codeabschnitt 6.7.5 HUD.gd
 1extends CanvasLayer
 2
 3onready var fps_label = $FPS_Label
 4var save_data = {}
 5
 6func load_config():
 7	var config = preload("res://Scripts/config.gd").new()
 8	config.load_data()
 9	save_data = config.save_data
10
11func _ready():
12	load_config()
13
14func _process(delta):
15	if save_data.get("display_fps", false):
16		fps_label.text = str(Engine.get_frames_per_second())
17	else:
18		fps_label.text = ""
19
20func _on_pause_unpaused():
21	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 VSYNC_OFF

(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.)

6.7.3. Pause Menü

Das Pause Menü hat eine Eigenschaft, dass es die Szene pausieren kann.

Pause Menu

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.

Codeabschnitt 6.7.6 pause.gd
22func toggle_pause():
23	var new_pause_state = not get_tree().paused
24    # Pause state wird umgeschalten
25	get_tree().paused = new_pause_state
26    # Pause Menü soll sichtbar/unsichtbar werden
27	visible = new_pause_state
28	if visible:
29		resume_button.grab_focus()
30	else:
31		emit_signal("unpaused")

Das Problem an pausierten Elementen ist, dass Input ignoriert wird und dass das Pause Menü ebenso Teil des SceneTrees ist. Unter der process Kathegorie im Node Inspector lässt sich allerdings dieses Verhalten umstellen.

Process im Node Inspector

Wenn die Variable Mode auf Always statt Inherit geschaltet wird, kann auch während die Node pausiert ist noch Input entgegengenommen und verarbeitet werden.

6.7.4. 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

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.

Codeabschnitt 6.7.7 game_over.gd
10func _on_player_killed():
11	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.