@@ -42,10 +42,16 @@ const FADE_TIME : float = 0.2
4242var music_fade_tween : Tween
4343
4444var xr_interface : XRInterface
45+ ## If WebXR, this is equal to [member xr_interface], but cast to a more specific
46+ ## type. If not, null.
47+ var webxr_interface : WebXRInterface
48+ var vr_supported : bool = false
4549
4650@onready var left_hand : XRController3D = $ XROrigin/LeftHand
4751@onready var right_hand : XRController3D = $ XROrigin/RightHand
4852
53+ @onready var web_xr_setup_ui : CanvasLayer = $ WebXRSetupUI
54+
4955func _ready () -> void :
5056 Globals .init_settings (music_volume , sound_effects_volume )
5157 Globals .main = self
@@ -62,7 +68,23 @@ func _ready() -> void:
6268 # Change our main viewport to output to the HMD
6369 get_viewport ().use_xr = true
6470 else :
65- print ("OpenXR not initialized; assuming regular mode" )
71+ webxr_interface = XRServer .find_interface ("WebXR" ) as WebXRInterface
72+ if webxr_interface :
73+ xr_interface = webxr_interface
74+ # Set up WebXR callbacks.
75+ webxr_interface .session_supported .connect (self ._webxr_session_supported )
76+ webxr_interface .session_started .connect (self ._webxr_session_started )
77+ webxr_interface .session_ended .connect (self ._webxr_session_ended )
78+ webxr_interface .session_failed .connect (self ._webxr_session_failed )
79+
80+ # This returns immediately; the result is delivered to the
81+ # _webxr_session_supported callback.
82+ webxr_interface .is_session_supported ("immersive-vr" )
83+
84+ # Show the button to enter WebXR.
85+ web_xr_setup_ui .visible = true
86+ else :
87+ print ("OpenXR/WebXR not initialized; assuming regular mode" )
6688
6789 if override_startup_scene != '' :
6890 # This will be set if another scene was loaded by the editor.
@@ -198,3 +220,61 @@ func _on_audio_volume_changed(type: AudioManager.AudioType, volume: float) -> vo
198220 # Check before assigning, to avoid restarting already-playing music.
199221 if music_player .playing != (volume > 0 ):
200222 music_player .playing = volume > 0
223+
224+ # --- WebXR stuff --- #
225+ # TODO: Move all VR setup to a separate file.
226+
227+ func _webxr_session_supported (session_mode : String , supported : bool ) -> void :
228+ if session_mode == 'immersive-vr' :
229+ vr_supported = supported
230+
231+ func _on_btn_enter_web_xr_pressed () -> void :
232+ if not vr_supported :
233+ OS .alert ("Your browser doesn't support VR" )
234+ return
235+
236+ # We want an immersive VR session.
237+ webxr_interface .session_mode = 'immersive-vr'
238+ # 'bounded-floor' is room scale, 'local-floor' is a standing or sitting
239+ # experience (it puts you 1.6m above the ground if you have 3DoF headset),
240+ # whereas as 'local' puts you down at the XROrigin.
241+ # This list means it'll first try to request 'bounded-floor', then
242+ # fallback on 'local-floor' and ultimately 'local', if nothing else is
243+ # supported.
244+ webxr_interface .requested_reference_space_types = 'bounded-floor, local-floor, local'
245+ # In order to use 'local-floor' or 'bounded-floor' we must also
246+ # mark the features as required or optional. By including 'hand-tracking'
247+ # as an optional feature, it will be enabled if supported.
248+ webxr_interface .required_features = 'local-floor'
249+ webxr_interface .optional_features = 'bounded-floor, hand-tracking'
250+ # TODO: I probably don't want hand tracking. I do want to validate that
251+ # the device actually has two controllers though.
252+
253+ # This will return false if we're unable to even request the session,
254+ # however, it can still fail asynchronously later in the process, so we
255+ # only know if it's really succeeded or failed when our
256+ # _webxr_session_started() or _webxr_session_failed() methods are called.
257+ if not webxr_interface .initialize ():
258+ OS .alert ("Failed to initialize WebXR" )
259+ return
260+
261+ func _webxr_session_started () -> void :
262+ web_xr_setup_ui .visible = false
263+ # This tells Godot to start rendering to the headset.
264+ get_viewport ().use_xr = true
265+ # This will be the reference space type you ultimately got, out of the
266+ # types that you requested above. This is useful if you want the game to
267+ # work a little differently in 'bounded-floor' versus 'local-floor'.
268+ print ("Reference space type: " , webxr_interface .reference_space_type )
269+ # This will be the list of features that were successfully enabled
270+ # (except on browsers that don't support this property).
271+ print ("Enabled features: " , webxr_interface .enabled_features )
272+
273+ func _webxr_session_ended () -> void :
274+ web_xr_setup_ui .visible = true
275+ # If the user exits immersive mode, then we tell Godot to render to the web
276+ # page again.
277+ get_viewport ().use_xr = false
278+
279+ func _webxr_session_failed (message : String ) -> void :
280+ OS .alert ("Failed to initialize WebXR: " + message )
0 commit comments