Optionsmenü mit Grafikeinstellungen in Godot

Ich bearbeite gerade die 20 Games Challenge, in der man 20 kleine Spiele bauen soll, um verschiedene Aspekte der Spieleentwicklung zu lernen. Das Konzept, einen festen Scope zu haben und an kleinen Projekten zu entwickeln, hat mich überzeugt.

Mein Ansatz weicht aber in einigen Punkten ab, da ich mich gerne in Details verliere und mir gewisse Dinge ins Auge springen, die ich unbedingt umsetzen möchte. Ich bin gerade bei Spiel 3 und baue einen Frogger-Clon. Anders als in der Challenge vorgegeben baue ich allerdings ein 3D-Spiel und wollte ein Options-Menü bauen, in dem zur Laufzeit Qualitätseinstellungen ausgewählt werden können. Diese sollten über mehrere Spielestarts persistiert werden. Ein Beispiel für eine Einstellung ist MSAA 3D (3D Multisample Anti-Aliasing).

Das Optionsmenü für mein Spiel

Die Auswahl einer Option im OptionButton hat dann ein Signal ausgelöst, das im Code des Dialogfensters etwa folgendermaßen behandelt wurde:

# [...]

func _ready() -> void:
    _setup_options()
    # [...]

func _setup_options() -> void:
    %msaa.clear()
    %msaa.add_item("Disabled", Viewport.MSAA_DISABLED)
    %msaa.add_item("2×", Viewport.MSAA_2X)
    %msaa.add_item("4×", Viewport.MSAA_4X)
    %msaa.add_item("8×", Viewport.MSAA_8X)
    %msaa.select(get_viewport().msaa_3d)

func _on_msaa_item_selected(index: int) -> void:
    get_viewport().msaa_3d = (index as MSAA)
    save_settings()

# [...]

Die Auswahl einer Option hatte allerdings keinen Effekt, ich konnte kein Anti-Aliasing im Spiel beobachten, die FPS blieben konstant und auch die Auslastung der Grafikkarte änderte sich nicht. Ich hatte sogar meinen Code mit dem „graphics_settings“ Projekt aus dem godot-demo-projects Repo verglichen und konnte keinen offensichtlichen Unterschied feststellen. Der Code in _on_msaa_option_button_item_selected ruft auch get_viewport().msaa_3d = ... auf. index entsprach dem korrekten Wert aus dem Viewport.MSAA-enum, den ich zum Testen sogar nach MSAA gecastet hatte1.

Nach einer Menge Debugging und dem Ausschluss aller anderen Optionen kam ich dann darauf: Mein Options-Dialog war, im Gegensatz zu dem Demo-Projekt, in einem eigenen Fenster. Die Klasse Window erbt von Viewport, was dazu geführt hat, dass mein Code nur die Einstellungen des Options-Fensters geändert hat. Mein Fix bestand darin, statt get_viewport() eine eigene Funktion zu bauen, die den Root-Viewport ermittelt und diese stattdessen zu verwenden:

func get_root_viewport() -> Viewport:
    return get_tree().get_root().get_viewport()

  1. Was gar nicht nötig gewesen wäre. Man spart sich durch die Konstruktion den verbosen Code (if index == ...: get_viewport().msaa_3d = ... elif ...), man verliert aber durch die Verwendung des Index die Typinformationen und schleppt nur „implizit“ wichtige Informationen mit. Eine sauberere Lösung ist die Verwendung von Item-Metadata (set_item_metadata/get_item_metadata) an den Options-Einträgen, da dort Variants genutzt werden können.