Ein besseres XR-Startskript

In Einrichten von XR haben wir ein Startskript eingeführt, das unsere Einrichtung initialisiert und das wir als Skript auf unserem Haupt-Node verwendet haben. Dieses Skript führt die Mindestschritte aus, die für eine bestimmte Schnittstelle erforderlich sind.

Bei der Verwendung von OpenXR gibt es eine Reihe von Verbesserungen, die wir hier vornehmen sollten. Hierfür haben wir ein aufwändigeres Startskript erstellt. Sie finden diese in unseren Demoprojekten verwendet.

Wenn Sie XR Tools verwenden (siehe Einführung in XR-Tools), enthält es eine Version dieses Skripts, die um einige Funktionen im Zusammenhang mit XR Tools erweitert wurde.

Im Folgenden werden wir das in unseren Demos verwendete Skript detailliert beschreiben und die hinzugefügten Teile erklären.

Signale für unser Skript

Wir führen 3 Signale in unser Skript ein, damit unser Spiel weitere Logik hinzufügen kann:

  • focus_lost wird ausgesendet, wenn der Spieler sein Headset abnimmt oder wenn der Spieler das Menüsystem des Headsets betritt.

  • focus_gained is emitted when the player puts their headset back on or exits the menu system and returns to the game.

  • pose_recentered wird ausgesendet, wenn das Headset die Position des Spielers zurücksetzen will.

Unser Spiel sollte entsprechend auf diese Signale reagieren.

extends Node3D

signal focus_lost
signal focus_gained
signal pose_recentered

...

Variablen für unser Skript

Wir führen auch einige neue Variablen in unser Skript ein:

  • maximum_refresh_rate steuert die Bildwiederholrate des Headsets, wenn dies vom Headset unterstützt wird.

  • xr_interface enthält einen Verweis auf unsere XR-Schnittstelle. Diese existierte bereits, aber wir geben sie jetzt ein, um vollen Zugriff auf unsere XRInterface API zu erhalten.

  • xr_is_focussed wird auf true gesetzt, wenn unser Spiel den Fokus hat.

...

@export var maximum_refresh_rate : int = 90

var xr_interface : OpenXRInterface
var xr_is_focussed = false

...

Unsere aktualisierte ready-Funktion

Wir fügen der ready-Funktion ein paar Dinge hinzu.

Wenn wir den mobilen oder Forward+ Renderer verwenden, setzen wir den Viewport vrs_mode auf VRS_XR. Auf Plattformen, die dies unterstützen, wird dies ein foveated Rendering ermöglichen.

Wenn wir den Kompatibilitätsrenderer verwenden, prüfen wir, ob die OpenXR-Einstellungen für das foveated Rendering konfiguriert sind, und wenn nicht, geben wir eine Warnung aus. Siehe OpenXR Einstellungen für weitere Details.

Wir schließen eine Reihe von Signalen an, die von der XRInterface ausgesendet werden. Wir werden mehr Details zu diesen Signalen liefern, wenn wir sie implementieren.

Wir haben unsere Anwendung auch beendet, wenn wir OpenXR nicht erfolgreich initialisieren konnten. Dies kann nun eine Option sein. Wenn Sie ein Spiel mit gemischtem Modus erstellen, richten Sie bei Erfolg den VR-Modus Ihres Spiels ein und bei Misserfolg den Nicht-VR-Modus Ihres Spiels. Wenn Sie jedoch eine reine VR-Anwendung auf einem Standalone-Headset ausführen, ist es besser, sich bei einem Fehler zu beenden, als das System aufzuhängen.

...

# Called when the node enters the scene tree for the first time.
func _ready():
    xr_interface = XRServer.find_interface("OpenXR")
    if xr_interface and xr_interface.is_initialized():
        print("OpenXR instantiated successfully.")
        var vp : Viewport = get_viewport()

        # Enable XR on our viewport
        vp.use_xr = true

        # Make sure v-sync is off, v-sync is handled by OpenXR
        DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

        # Enable VRS
        if RenderingServer.get_rendering_device():
            vp.vrs_mode = Viewport.VRS_XR
        elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0:
            push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings")

        # Connect the OpenXR events
        xr_interface.session_begun.connect(_on_openxr_session_begun)
        xr_interface.session_visible.connect(_on_openxr_visible_state)
        xr_interface.session_focussed.connect(_on_openxr_focused_state)
        xr_interface.session_stopping.connect(_on_openxr_stopping)
        xr_interface.pose_recentered.connect(_on_openxr_pose_recentered)
    else:
        # We couldn't start OpenXR.
        print("OpenXR not instantiated!")
        get_tree().quit()

...

On session begun

Dieses Signal wird von OpenXR ausgesendet, wenn unsere Sitzung eingerichtet ist. Das bedeutet, dass das Headset alle Einstellungen vorgenommen hat und bereit ist, Inhalte von uns zu empfangen. Erst zu diesem Zeitpunkt sind verschiedene Informationen richtig verfügbar.

Hier überprüfen wir vor allem die Bildwiederholfrequenz unseres Headsets. Wir überprüfen auch die verfügbaren Bildwiederholraten, die von der XR-Runtime gemeldet werden, um festzustellen, ob wir unser Headset auf eine höhere Bildwiederholrate einstellen möchten.

Schließlich passen wir die unsere Physik-Aktualisierungsrate an die Aktualisierungsrate unseres Headsets an. Godot läuft standardmäßig mit einer Physik-Aktualisierungsrate von 60 Updates pro Sekunde, während Headsets mit mindestens 72 und bei modernen Headsets oft mit bis zu 144 Bildern pro Sekunde laufen. Wird die Physik-Aktualisierungsrate nicht angepasst, kommt es zu Stottern, da Bilder gerendert werden, ohne dass sich Objekte bewegen.

...

# Handle OpenXR session ready
func _on_openxr_session_begun() -> void:
    # Get the reported refresh rate
    var current_refresh_rate = xr_interface.get_display_refresh_rate()
    if current_refresh_rate > 0:
        print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
    else:
        print("OpenXR: No refresh rate given by XR runtime")

    # See if we have a better refresh rate available
    var new_rate = current_refresh_rate
    var available_rates : Array = xr_interface.get_available_display_refresh_rates()
    if available_rates.size() == 0:
        print("OpenXR: Target does not support refresh rate extension")
    elif available_rates.size() == 1:
        # Only one available, so use it
        new_rate = available_rates[0]
    else:
        for rate in available_rates:
            if rate > new_rate and rate <= maximum_refresh_rate:
                new_rate = rate

    # Did we find a better rate?
    if current_refresh_rate != new_rate:
        print("OpenXR: Setting refresh rate to ", str(new_rate))
        xr_interface.set_display_refresh_rate(new_rate)
        current_refresh_rate = new_rate

    # Now match our physics rate
    Engine.physics_ticks_per_second = current_refresh_rate

...

On visible state

Dieses Signal wird von OpenXR ausgegeben, wenn unser Spiel sichtbar wird, aber nicht fokussiert ist. Dies ist eine etwas seltsame Beschreibung in OpenXR, aber es bedeutet im Grunde, dass unser Spiel gerade gestartet wurde und wir als nächstes in den fokussierten Zustand wechseln, dass der Benutzer ein Systemmenü geöffnet hat oder dass der Benutzer gerade sein Headset abgenommen hat.

Wenn wir dieses Signal erhalten, aktualisieren wir unseren fokussierten Zustand, ändern den Prozessmodus unseres Nodes auf deaktiviert, was die Verarbeitung dieses Nodes und seiner Child-Nodes unterbricht, und senden unser Signal focus_lost aus.

Wenn Sie dieses Skript zu Ihrem Root-Node hinzugefügt haben, bedeutet dies, dass Ihr Spiel bei Bedarf automatisch pausiert. Wenn nicht, können Sie eine Methode mit dem Signal verbinden, die zusätzliche Änderungen vornimmt.

Bemerkung

Während Ihr Spiel sichtbar ist, weil der Benutzer ein Systemmenü geöffnet hat, rendert Godot weiterhin Frames und die Kopfverfolgung bleibt aktiv, so dass Ihr Spiel im Hintergrund sichtbar bleibt. Controller- und Hand-Tracking werden jedoch deaktiviert, bis der Benutzer das Systemmenü verlässt.

...

# Handle OpenXR visible state
func _on_openxr_visible_state() -> void:
    # We always pass this state at startup,
    # but the second time we get this it means our player took off their headset
    if xr_is_focussed:
        print("OpenXR lost focus")

        xr_is_focussed = false

        # pause our game
        get_tree().paused = true

        emit_signal("focus_lost")

...

On focussed state

Dieses Signal wird von OpenXR ausgesendet, wenn unser Spiel den Fokus erhält. Dies geschieht bei der Beendigung unseres Starts, kann aber auch ausgesendet werden, wenn der Benutzer ein Systemmenü verlässt oder sein Headset wieder aufsetzt.

Beachten Sie auch, dass, wenn Ihr Spiel gestartet wird, während der Benutzer sein Headset nicht trägt, das Spiel im "sichtbaren" Zustand bleibt, bis der Benutzer sein Headset aufsetzt.

Warnung

Daher ist es wichtig, dass Ihr Spiel im sichtbaren Modus angehalten wird. Andernfalls wird das Spiel weiterlaufen, ohne dass der Benutzer mit dem Spiel interagiert. Auch wenn das Spiel in den fokussierten Modus zurückkehrt, wird plötzlich das gesamte Controller- und Hand-Tracking wieder aktiviert, was Folgen für das Spiel haben kann, wenn Sie nicht entsprechend darauf reagieren. Testen Sie dieses Verhalten unbedingt in Ihrem Spiel!

Während der Verarbeitung unseres Signals werden wir den Fokus-Zustand aktualisieren, die Pause unseres Nodes aufheben und unser Signal focus_gained aussenden.

...

# Handle OpenXR focused state
func _on_openxr_focused_state() -> void:
    print("OpenXR gained focus")
    xr_is_focussed = true

    # unpause our game
    get_tree().paused = false

    emit_signal("focus_gained")

...

On stopping state

Dieses Signal wird von OpenXR ausgegeben, wenn wir unseren Stoppzustand erreichen. Es gibt einige Unterschiede zwischen den Plattformen, wann dies geschieht. Auf einigen Plattformen wird dieses Signal nur ausgegeben, wenn das Spiel geschlossen wird. Auf anderen Plattformen wird dieses Signal jedes Mal ausgegeben, wenn der Spieler sein Headset abnimmt.

Im Moment ist diese Methode nur ein Platzhalter.

...

# Handle OpenXR stopping state
func _on_openxr_stopping() -> void:
    # Our session is being stopped.
    print("OpenXR is stopping")

...

On pose recentered

Dieses Signal wird von OpenXR ausgegeben, wenn der Benutzer seine Ansicht neu zentriert haben möchte. Grundsätzlich teilt es dem Spiel mit, dass der Benutzer jetzt nach vorne schaut und Sie den Spieler neu ausrichten sollten, damit er in der virtuellen Welt nach vorne schaut.

Da dies von Ihrem Spiel abhängt, muss Ihr Spiel entsprechend reagieren.

Alles, was wir hier tun, ist, das Signal pose_recentered auszusenden. Sie können sich mit diesem Signal verbinden und den eigentlichen Recenter-Code implementieren. Oft reicht es aus, center_on_hmd() aufzurufen.

...

# Handle OpenXR pose recentered signal
func _on_openxr_pose_recentered() -> void:
    # User recentered view, we have to react to this by recentering the view.
    # This is game implementation dependent.
    emit_signal("pose_recentered")

Und damit war unser Skript fertig. Es wurde so geschrieben, dass es in mehreren Projekten wiederverwendet werden kann. Fügen Sie es einfach als Skript zu Ihrem Haupt-Node hinzu (und erweitern Sie es bei Bedarf) oder fügen Sie es zu einem Child-Node speziell für dieses Skript hinzu.