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...
Кращий скрипт запуску XR
У Налаштування XR ми представили скрипт запуску, який ініціалізує наші налаштування, які ми використовували як наш скрипт на нашому головному вузлі. Цей скрипт виконує мінімальну кількість кроків, необхідних для будь-якого заданого інтерфейсу.
Під час використання OpenXR ми маємо зробити ряд покращень. Для цього ми створили більш складний стартовий скрипт. Ви знайдете їх у наших демонстраційних проектах.
Крім того, якщо ви використовуєте інструменти XR (див. Представляємо інструменти XR), він містить версію цього сценарію, оновлену деякими функціями, пов’язаними з інструментами XR.
Нижче ми докладно розглянемо скрипт, який використовується в наших демонстраціях, і пояснимо частини, які додаються.
Сигнали для нашого сценарію
Ми вводимо 3 сигнали до нашого сценарію, щоб наша гра могла додати додаткову логіку:
focus_lostвидається, коли гравець знімає гарнітуру або коли гравець входить до системи меню гарнітури.focus_gainedвидається, коли гравець знову одягає гарнітуру або виходить із системи меню та повертається до гри.pose_recenteredвипромінюється, коли гарнітура запитує скидання позиції гравця.
Наша гра має відповідним чином реагувати на ці сигнали.
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();
...
Змінні для нашого сценарію
Ми також вводимо кілька нових змінних у наш скрипт:
maximum_refresh_rateкеруватиме частотою оновлення гарнітури, якщо це підтримується гарнітурою.xr_interfaceмістить посилання на наш інтерфейс XR, він уже існував, але тепер ми вводимо його, щоб отримати повний доступ до нашого XRInterface API.xr_is_focussedбуде встановлюватися на true щоразу, коли наша гра має фокус.
...
@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;
...
Наша оновлена готова функція
Ми додаємо кілька речей до готової функції.
Якщо ми використовуємо мобільний рендерер або рендерер Forward+, ми встановлюємо для параметра vrs_mode області перегляду значення VRS_XR. На платформах, які підтримують цю функцію, це ввімкне рендеринг з фокусуванням.
Якщо ми використовуємо рендерер сумісності, ми перевіряємо, чи налаштовано параметри візуалізації OpenXR foveated, і якщо ні, ми виводимо попередження. Перегляньте OpenXR Settings для отримання додаткової інформації.
Ми підключаємо декілька сигналів, які випромінює XRInterface. Ми надамо більше деталей про ці сигнали, коли ми їх запровадимо.
Ми також закриваємо нашу програму, якщо не можемо успішно ініціалізувати OpenXR. Тепер це може бути вибір. Якщо ви створюєте гру в змішаному режимі, ви встановлюєте режим віртуальної реальності вашої гри в разі успіху та встановлюєте режим без віртуальної реальності вашої гри в разі невдачі. Однак під час запуску лише програми VR на автономній гарнітурі зручніше вийти у разі помилки, ніж зависати систему.
...
# 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();
}
}
...
На початку сесії
Цей сигнал випромінює OpenXR під час налаштування нашого сеансу. Це означає, що гарнітура пройшла всі налаштування та готова почати отримувати від нас вміст. Тільки в цей час різноманітна інформація доступна належним чином.
Головне, що ми тут робимо, це перевіряємо частоту оновлення нашої гарнітури. Ми також перевіряємо доступні частоти оновлення, про які повідомляє середовище виконання XR, щоб визначити, чи потрібно нам встановити для нашої гарнітури вищу частоту оновлення.
Нарешті ми порівнюємо швидкість оновлення фізики з частотою оновлення гарнітури. Godot за замовчуванням працює зі швидкістю оновлення фізики 60 оновлень на секунду, тоді як гарнітури працюють із мінімумом 72, а для сучасних гарнітур часто до 144 кадрів на секунду. Невідповідність швидкості оновлення фізики спричинить заїкання, оскільки кадри візуалізуються без рухомих об’єктів.
...
# 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;
}
...
На видимому стані
Цей сигнал випромінюється OpenXR, коли наша гра стає видимою, але не перебуває у фокусі. Це дещо дивний опис в OpenXR, але по суті це означає, що наша гра щойно розпочалася, і ми збираємося перейти до наступного сфокусованого стану, тобто користувач відкрив системне меню або щойно зняв гарнітуру.
Отримавши цей сигнал, ми оновимо наш сфокусований стан, змінимо режим обробки нашого вузла на вимкнено, що призупинить обробку цього вузла та його дочірніх вузлів, і випустимо сигнал focus_lost.
Якщо ви додали цей скрипт до свого кореневого вузла, це означає, що ваша гра буде автоматично призупинятися, коли потрібно. Якщо ви цього не зробили, ви можете підключити до сигналу метод, який виконує додаткові зміни.
Примітка
Поки ваша гра перебуває у видимому стані, оскільки користувач відкрив системне меню, Godot продовжуватиме відображати кадри, а відстеження голови залишатиметься активним, тому ваша гра залишатиметься видимою у фоновому режимі. Однак контролер і відстеження рук будуть вимкнені, доки користувач не вийде із системного меню.
...
# 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);
}
}
...
У зосередженому стані
Цей сигнал випромінює OpenXR, коли наша гра отримує фокус. Це робиться після завершення нашого запуску, але також може видаватись, коли користувач виходить із системного меню або знову вдягає гарнітуру.
Зауважте також, що коли ваша гра починається, коли користувач не носить гарнітуру, гра залишається у «видимому» стані, доки користувач не одягне гарнітуру.
Попередження
Тому важливо тримати гру на паузі, коли вона перебуває у видимому режимі. Якщо ви цього не зробите, гра продовжуватиме працювати, навіть коли користувач не взаємодіє з нею. Також, коли гра повертається у сфокусований режим, відстеження контролера та рук раптово знову вмикається, і це може мати наслідки для гри, якщо ви не відреагуєте на це належним чином. Обов'язково протестуйте цю поведінку у своїй грі!
Під час обробки нашого сигналу ми оновимо стан фокусів, відновимо паузу нашого вузла та випромінюємо сигнал 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);
}
...
У стані зупинки
Цей сигнал випромінює OpenXR, коли ми входимо в стан зупинки. Існують певні відмінності між платформами, коли це відбувається. На деяких платформах це видається лише під час закриття гри. Але на інших платформах це також видаватиметься кожного разу, коли гравець зніматиме гарнітуру.
Наразі цей метод є лише заповнювачем.
...
# 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");
}
...
У позі відцентровано
Цей сигнал випромінює OpenXR, коли користувач запитує повторне центрування свого перегляду. По суті, це повідомляє вашій грі, що користувач зараз дивиться вперед, і ви повинні переорієнтувати гравця, щоб він дивився вперед у віртуальному світі.
Оскільки це залежить від вашої гри, ваша гра повинна відповідним чином реагувати.
Все, що ми робимо тут, це випромінюємо сигнал pose_recentered. Ви можете підключитися до цього сигналу та впровадити фактичний код рецентратора. Часто достатньо викликати 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);
}
}
На цьому наш скрипт закінчився. Він був написаний для того, щоб його можна було повторно використовувати в кількох проектах. Просто додайте його як скрипт на вашому головному вузлі (і розширте його, якщо потрібно) або додайте його на дочірньому вузлі, спеціальному для цього скрипта.