Up to date

This page is up to date for Godot 4.3. If you still find outdated information, please open an issue.

Un meilleur script de démarrage XR

In Setting up 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 Introducing 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

We are introducing 3 signals to our script so that our game can add further logic:

  • focus_lost is emitted when the player takes off their headset or when the player enters the menu system of the headset.

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

  • pose_recentered is emitted when the headset requests the players position to be reset.

Our game should react accordingly to these signals.

extends Node3D

signal focus_lost
signal focus_gained
signal pose_recentered

...

Variables for our script

We introduce a few new variables to our script as well:

  • maximum_refresh_rate will control the headsets refresh rate if this is supported by the headset.

  • 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_focussed will be set to true whenever our game has focus.

...

@export var maximum_refresh_rate : int = 90

var xr_interface : OpenXRInterface
var xr_is_focussed = false

...

Our updated ready function

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

Si nous utilise le rendu Foward+ ou mobile, nous mettons le vrs_mode de la fenêtre d'affichage à VRS_XR. Sur les plates-formes qui supportent cela, cela permettra le rendu fovéal.

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.

La principale chose que nous faisons ici est de vérifier le taux de rafraîchissement de notre casque. Nous vérifions également les taux de rafraîchissements disponibles indiqués par l'environnement d'exécution XR pour déterminer si nous voulons mettre notre casque à un taux de rafraîchissement plus élevé.

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

Ce signal est émis par OpenXR lorsque notre jeu devient visible mais n'est pas focalisé. C'est un peu une description bizarre dans OpenXR mais cela signifie fondamentalement que notre jeu vient de commencer et nous allons passer ensuite à l'état ciblé, que l'utilisateur a ouvert un menu système ou les utilisateurs viennent de retirer leur casque.

On receiving this signal we'll update our focussed state, we'll change the process mode of our node to disabled which will pause processing on this node and it's 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.

Note also that when your game starts while the user is not wearing their headset, the game stays in 'visible' state until the user puts their headset on.

Avertissement

It is thus important to keep your game paused while in visible mode. If you don't the game will keep on running while your user isn't interacting with your game. Also when the game returns to focussed mode, suddenly all controller and hand tracking is re-enabled and could have game breaking consequences if you do not react to this accordingly. Be sure to test this behaviour in your game!

While handling our signal we will update the focusses state, unpause our node and emit our focus_gained signal.

...

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

For now this method is only a place holder.

...

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

As doing so is dependent on your game, your game needs to react accordingly.

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

And that finished our script. It was written so that it can be re-used over multiple projects. Just add it as the script on your main node (and extend it if needed) or add it on a child node specific for this script.