Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Un meilleur script de démarrage XR

In Mise en place de la XR we introduced a startup script that initialises our setup which we used as our script on our main node. This script performs the minimum steps required for any given interface.

Lors de l'utilisation d'OpenXR il y a un certain nombre d'améliorations que nous devrions faire ici. Pour cela, nous avons créé un script de départ plus élaboré. Vous trouverez ceux-ci utilisés dans nos projets de démonstration.

Alternativement, si vous utilisez XR Tools (voir Présentation de XR Tools), il contient une version de ce script mise à jour avec quelques fonctionnalités liées aux outils XR.

Below we will detail out the script used in our demos and explain the parts that are added.

Signaux pour notre script

Nous introduisons 3 signaux à notre script afin que notre jeu puisse ajouter de la logique supplémentaire :

  • focus_lost est émis lorsque le joueur retire son casque ou lorsque le joueur entre dans le système de menu du casque.

  • focus_gained est émis lorsque le joueur remet son casque sur ou sort du système de menu et retourne au jeu.

  • pose_recentered is emitted when the headset requests the player's position to be reset.

Notre jeu devrait réagir en conséquence à ces signaux.

extends Node3D

signal focus_lost
signal focus_gained
signal pose_recentered

...

Variables pour nos scripts

Nous introduisons également quelques nouvelles variables à notre script :

  • maximum_refresh_rate contrôlera le taux de rafraîchissement du casque si cela est supporté par le casque.

  • xr_interface contient une référence à notre interface XR, cela existait déjà, mais nous lui donnons maintenant un type pour obtenir un accès complet à notre API XRInterface.

  • xr_is_focused sera défini à true chaque fois que notre jeu aura le focus.

...

@export var maximum_refresh_rate : int = 90

var xr_interface : OpenXRInterface
var xr_is_focussed = false

...

Notre fonction ready mise à jour

Nous ajoutons quelques éléments à la fonction ready.

If we're using the mobile or forward+ renderer we set the viewport's vrs_mode to VRS_XR. On platforms that support this, this will enable foveated rendering.

Si nous utilisons le moteur de rendu de compatibilité, nous vérifions si les réglages de rendu fovéal OpenXR sont configurés et sinon, nous émettons un avertissement. Voir OpenXR Settings pour plus de détails.

Nous nous connectons à un certain nombre de signaux qui seront émis par le XRInterface. Nous fournirons plus de détails sur ces signaux lorsque nous les implémenterons.

Nous quittons aussi notre application si nous n'avons pas pu initialiser OpenXR avec succès. Cependant, cela peut être un choix. Si vous faites un jeu en mode mixte, vous configurez le mode VR de votre jeu sur le succès, et configurez le mode non-VR de votre jeu sur l'échec. Cependant, lors de l'exécution d'une application VR seulement sur un casque autonome, il est plus agréable de quitter sur l'échec que de suspendre le système.

...

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

...

Lors d'une session démarrée

Ce signal est émis par OpenXR lorsque notre session est paramétrée. Cela signifie que le casque a exécuté tout la mise en place et est prêt à commencer à recevoir notre contenu. Ce n'est qu'à ce moment que diverses informations sont disponibles.

The main thing we do here is to check our headset's refresh rate. We also check the available refresh rates reported by the XR runtime to determine if we want to set our headset to a higher refresh rate.

Finally we match our physics update rate to our headset update rate. Godot runs at a physics update rate of 60 updates per second by default while headsets run at a minimum of 72, and for modern headsets often up to 144 frames per second. Not matching the physics update rate will cause stuttering as frames are rendered without objects moving.

...

# 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

...

Lors d'un état visible

This signal is emitted by OpenXR when our game becomes visible but is not focused. This is a bit of a weird description in OpenXR but it basically means that our game has just started and we're about to switch to the focused state next, that the user has opened a system menu or the user has just took their headset off.

On receiving this signal we'll update our focused state, we'll change the process mode of our node to disabled which will pause processing on this node and its children, and emit our focus_lost signal.

If you've added this script to your root node, this means your game will automatically pause when required. If you haven't, you can connect a method to the signal that performs additional changes.

Note

While your game is in visible state because the user has opened a system menu, Godot will keep rendering frames and head tracking will remain active so your game will remain visible in the background. However controller and hand tracking will be disabled until the user exits the system menu.

...

# 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")

...

Lors d'un état mis en focus

Ce signal est émis par OpenXR lorsque notre jeu obtient le focus. Ceci est fait à la complétion de notre démarrage, mais il peut également être émis lorsque l'utilisateur quitte un menu système, ou remet son casque.

Notez également que lorsque votre jeu démarre alors que l'utilisateur ne porte pas son casque, le jeu reste dans l'état 'visible' jusqu'à ce que l'utilisateur met son casque.

Avertissement

Il est donc important de garder votre jeu en pause lors du mode visible. Si vous ne le faites pas, le jeu continuera à fonctionner pendant que votre utilisateur n'interagit pas avec. Aussi lorsque le jeu revient au mode avec le focus, soudain tous les contrôleurs et le suivi des mains est re-activé, ce qui pourrait casser le jeu si vous ne réagissez pas à cela en conséquence. Assurez-vous de tester ce comportement dans votre jeu !

Tout en manipulant notre signal, nous mettrons à jour l'état du focus, enlèverons de la pause notre nœud et émettrons notre signal focus_gained.

...

# 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

Ce signal est émis par OpenXR lorsque nous entrons dans notre état d'arrêt. Il y a quelques différences entre les plateformes sur quand cela arrive. Sur certaines plateformes, ce n'est émis que lorsque le jeu est fermé. Mais sur d'autres plates-formes cela sera également émis à chaque fois que le joueur enlève son casque.

Pour l'instant, cette méthode n'est qu'un placeholder temporaire.

...

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

...

Lors du recentrage de la pose

Ce signal est émis par OpenXR lorsque l'utilisateur demande à ce que sa vue se recentre. Fondamentalement, cela communique à votre jeu que l'utilisateur est maintenant tourné vers l'avant et vous devriez réorienter le joueur afin qu'ils soient face à l'avant dans le monde virtuel.

Comme faire cela est dépendant de votre jeu, votre jeu doit réagir en conséquence.

Tout ce que nous faisons ici, c'est émettre le signal pose_recentered. Vous pouvez vous connecter à ce signal et implémenter le code effectif de recentrage. Souvent, il suffit d'appeler center_on_hmd().

...

# 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")

Et cela finit notre script. Il a été écrit pour qu'il puisse être réutilisé sur plusieurs projets. Il suffit de l'ajouter comme script sur votre nœud principal (et l'étendre si nécessaire) ou l'ajouter sur un nœud enfant spécifique pour ce script.