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.
Checking the stable version of the documentation...
Un migliore script di avvio XR
In Configurazione del XR abbiamo introdotto uno script di avvio che inizializza la nostra configurazione, che abbiamo utilizzato come script sul nostro nodo principale. Questo script esegue i passaggi minimi richiesti per qualsiasi interfaccia.
Quando si utilizza OpenXR, ci sono diversi miglioramenti da fare qui. Per questo abbiamo creato uno script di avvio più elaborato. Lo troverai utilizzato nei nostri progetti demo.
In alternativa, se si utilizza XR Tools (vedere Introduzione agli strumenti XR), è disponibile una versione di questo script aggiornata con alcune funzionalità relative agli strumenti XR.
Di seguito descriveremo nel dettaglio lo script utilizzato nelle nostre demo e spiegheremo le parti aggiunte.
Segnali per il nostro script
Introduciamo 3 segnali nel nostro script in modo che il nostro gioco possa aggiungere ulteriore logica:
focus_lostviene emesso quando il giocatore si toglie il visore o quando accede al menu di sistema del visore.focus_gainedviene emesso quando il giocatore indossa nuovamente il visore o esce dal menu di sistema e torna al gioco.pose_recenteredviene emesso quando il visore richiede che la posizione del giocatore sia ripristinata.
Il nostro gioco dovrebbe reagire di conseguenza a questi segnali.
extends Node3D
signal focus_lost
signal focus_gained
signal pose_recentered
...
using Godot;
public partial class MyNode3D : Node3D
{
[Signal]
public delegate void FocusLostEventHandler();
[Signal]
public delegate void FocusGainedEventHandler();
[Signal]
public delegate void PoseRecenteredEventHandler();
...
Variabili per il nostro script
Introduciamo anche alcune nuove variabili nel nostro script:
maximum_refresh_ratecontrollerà la frequenza di aggiornamento del visore, se supportata dal visore stesso.xr_interfacecontiene un riferimento alla nostra interfaccia XR, che esisteva già ma ora la digitiamo per ottenere l'accesso completo alla nostra API XRInterface.xr_is_focussedverrà impostato su true ogni volta che il nostro gioco avrà il focus.
...
@export var maximum_refresh_rate : int = 90
var xr_interface : OpenXRInterface
var xr_is_focussed = false
...
...
[Export]
public int MaximumRefreshRate { get; set; } = 90;
private OpenXRInterface _xrInterface;
private bool _xrIsFocused;
...
La nostra funzione ready aggiornata
Aggiungiamo alcune cose alla funzione ready.
Se utilizziamo il renderer mobile o forward+, impostiamo vrs_mode della viewport su VRS_XR. Sulle piattaforme che lo supportano, questo abiliterà il rendering foveato.
Se utilizziamo il motore di rendering Compatibilità, verifichiamo se le impostazioni di rendering foveato di OpenXR sono configurate e, se non, mostriamo un avviso. Consulta le Impostazioni di OpenXR per ulteriori dettagli.
Connettiamo una serie di segnali che saranno emessi da XRInterface. Forniremo maggiori dettagli su questi segnali mentre li implementeremo.
Usciamo anche dall'applicazione se non riusciamo a inizializzare OpenXR. Ora, ciò è una scelta. Se stai creando un gioco in modalità mista, imposta la modalità VR del gioco in caso di successo e imposta la modalità non VR del gioco in caso di fallimento. Tuttavia, quando si esegue un'applicazione solo VR su un visore autonomo, è preferibile uscire in caso di fallimento piuttosto che bloccare il sistema.
...
# 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()
...
...
/// <summary>
/// Called when the node enters the scene tree for the first time.
/// </summary>
public override void _Ready()
{
_xrInterface = (OpenXRInterface)XRServer.FindInterface("OpenXR");
if (_xrInterface != null && _xrInterface.IsInitialized())
{
GD.Print("OpenXR instantiated successfully.");
var vp = GetViewport();
// Enable XR on our viewport
vp.UseXR = true;
// Make sure v-sync is off, v-sync is handled by OpenXR
DisplayServer.WindowSetVsyncMode(DisplayServer.VSyncMode.Disabled);
// Enable VRS
if (RenderingServer.GetRenderingDevice() != null)
{
vp.VrsMode = Viewport.VrsModeEnum.XR;
}
else if ((int)ProjectSettings.GetSetting("xr/openxr/foveation_level") == 0)
{
GD.PushWarning("OpenXR: Recommend setting Foveation level to High in Project Settings");
}
// Connect the OpenXR events
_xrInterface.SessionBegun += OnOpenXRSessionBegun;
_xrInterface.SessionVisible += OnOpenXRVisibleState;
_xrInterface.SessionFocussed += OnOpenXRFocusedState;
_xrInterface.SessionStopping += OnOpenXRStopping;
_xrInterface.PoseRecentered += OnOpenXRPoseRecentered;
}
else
{
// We couldn't start OpenXR.
GD.Print("OpenXR not instantiated!");
GetTree().Quit();
}
}
...
On session begun (all'avvio della sessione)
Questo segnale viene emesso da OpenXR quando la nostra sessione è configurata. Ciò significa che il visore ha completato la configurazione ed è pronto a iniziare a ricevere contenuti da noi. Questo è il punto dove diverse informazioni sono disponibili.
La cosa principale che facciamo qui è verificare la frequenza di aggiornamento del nostro visore. Verifichiamo anche le frequenze di aggiornamento disponibili segnalate dal runtime XR per determinare se vogliamo impostare il visore su una frequenza di aggiornamento più alta.
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
...
...
/// <summary>
/// Handle OpenXR session ready
/// </summary>
private void OnOpenXRSessionBegun()
{
// Get the reported refresh rate
var currentRefreshRate = _xrInterface.DisplayRefreshRate;
GD.Print(currentRefreshRate > 0.0F
? $"OpenXR: Refresh rate reported as {currentRefreshRate}"
: "OpenXR: No refresh rate given by XR runtime");
// See if we have a better refresh rate available
var newRate = currentRefreshRate;
var availableRates = _xrInterface.GetAvailableDisplayRefreshRates();
if (availableRates.Count == 0)
{
GD.Print("OpenXR: Target does not support refresh rate extension");
}
else if (availableRates.Count == 1)
{
// Only one available, so use it
newRate = (float)availableRates[0];
}
else
{
GD.Print("OpenXR: Available refresh rates: ", availableRates);
foreach (float rate in availableRates)
{
if (rate > newRate && rate <= MaximumRefreshRate)
{
newRate = rate;
}
}
}
// Did we find a better rate?
if (currentRefreshRate != newRate)
{
GD.Print($"OpenXR: Setting refresh rate to {newRate}");
_xrInterface.DisplayRefreshRate = newRate;
currentRefreshRate = newRate;
}
// Now match our physics rate
Engine.PhysicsTicksPerSecond = (int)currentRefreshRate;
}
...
On visible state (allo stato visibile)
Questo segnale viene emesso da OpenXR quando il nostro gioco diventa visibile ma non è focalizzato. La descrizione in OpenXR è un po' particolare, ma in sostanza significa che o il nostro gioco è appena iniziato e stiamo per passare allo stato focalizzato, o che l'utente ha aperto un menu di sistema, o che ha appena tolto il visore.
Dopo aver ricevuto questo segnale aggiorneremo il nostro stato focalizzato, cambieremo la modalità di elaborazione del nostro nodo in disabilitato, il che metterà in pausa l'elaborazione su questo nodo e sui suoi figli, ed emetteremo il nostro segnale focus_lost.
Se hai aggiunto questo script al nodo radice, il gioco si metterà automaticamente in pausa quando necessario. Se no, puoi collegare un metodo al segnale che effettua ulteriori modifiche.
Nota
Finché il gioco è visibile perché l'utente ha aperto un menu di sistema, Godot continuerà a renderizzare i frame e il tracciamento della testa rimarrà attivo, quindi il gioco rimarrà visibile nello sfondo. Tuttavia, il tracciamento dei controller e delle mani verrà disabilitato finché l'utente non esce dal menu di sistema.
...
# 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")
...
...
/// <summary>
/// Handle OpenXR visible state
/// </summary>
private void OnOpenXRVisibleState()
{
// We always pass this state at startup,
// but the second time we get this it means our player took off their headset
if (_xrIsFocused)
{
GD.Print("OpenXR lost focus");
_xrIsFocused = false;
// Pause our game
GetTree().Paused = true;
EmitSignal(SignalName.FocusLost);
}
}
...
On focused state (allo stato focalizzato)
Questo segnale viene emesso da OpenXR quando il nostro gioco riceve il focus. Ciò avviene al completamento dell'avvio, ma può essere emesso anche quando l'utente esce da un menu di sistema o rimette il visore.
Si noti inoltre che, quando il gioco si avvia mentre l'utente non sta indossando il visore, rimarrà nello stato "visibile" finché l'utente non lo indossa.
Avvertimento
È quindi importante mettere in pausa il gioco quando è in modalità visibile. Altrimenti, il gioco continuerà a funzionare anche se l'utente non interagisce con esso. Inoltre, quando il gioco torna in modalità focalizzata, il tracciamento dei controller e delle mani viene riattivato all'improvviso, il che potrebbe rovinare il gioco se non si reagisce opportunamente. Assicurati di testare questo comportamento nel gioco!
Durante la gestone del nostro segnale, aggiorneremo lo stato focalizzato, toglieremo la pausa dal nostro nodo ed emetteremo il segnale 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")
...
...
/// <summary>
/// Handle OpenXR focused state
/// </summary>
private void OnOpenXRFocusedState()
{
GD.Print("OpenXR gained focus");
_xrIsFocused = true;
// Un-pause our game
GetTree().Paused = false;
EmitSignal(SignalName.FocusGained);
}
...
On stopping state (allo stato di arresto)
Questo segnale viene emesso da OpenXR quando entriamo nello stato di arresto. Ci sono alcune differenze tra le piattaforme quando ciò accade. Su alcune piattaforme viene emesso solo quando il gioco si sta chiudendo. Su altre, invece, viene emesso anche ogni volta che il giocatore toglie il visore.
Per ora questo metodo è solo provvisorio.
...
# Handle OpenXR stopping state
func _on_openxr_stopping() -> void:
# Our session is being stopped.
print("OpenXR is stopping")
...
...
/// <summary>
/// Handle OpenXR stopping state
/// </summary>
private void OnOpenXRStopping()
{
// Our session is being stopped.
GD.Print("OpenXR is stopping");
}
...
On pose recentered (alla posa ricentrata)
Questo segnale viene emesso da OpenXR quando l'utente richiede che la sua visuale venga ricentrata. In pratica, comunica al gioco che l'utente è ora rivolto in avanti e che bisognerebbe riorientarlo in modo che sia rivolto in avanti nel mondo virtuale.
Poiché farlo dipende dal gioco, il gioco deve reagire di conseguenza.
Tutto quello che facciamo qui è emettere il segnale pose_recentered. È possibile collegarsi a questo segnale e implementare il codice effettivo per ricentrare. Spesso basta chiamare 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")
...
/// <summary>
/// Handle OpenXR pose recentered signal
/// </summary>
private void OnOpenXRPoseRecentered()
{
// User recentered view, we have to react to this by recentering the view.
// This is game implementation dependent.
EmitSignal(SignalName.PoseRecentered);
}
}
E questo è il nostro script. È stato scritto in modo da essere riutilizzabile in più progetti. Basta aggiungerlo come script sul nodo principale (ed estenderlo se necessario) o aggiungerlo su un nodo figlio specifico per questo script.