GodotVR / godot-xr-tools

Support scenes for AR and VR in Godot
MIT License
525 stars 75 forks source link

Feature request: Allow for both XR and non-XR code to co-exist in an application #501

Open tcrass opened 1 year ago

tcrass commented 1 year ago

I'm working on an application that's supposed to be usable in both an XR and a non-XR mode. However

It would be nice if interaction with the operating system's XR infrastructure only took place when actually required.

GeminiSquishGames commented 1 year ago

If you mean like separate controller modes coexisting using a one-time setting at startup, then it can be handled in your XR init script. I'm doing it with a GameSettings.gd autoload/singleton with a VR bool, set with a UI at startup asking if the player has VR or not, and the following init script on my Main VR scene:

extends Node3D

var xr_interface: XRInterface  

func _ready():
    if !GameSettings.vr_mode:
        print("Skipping VR init")
        return
    else:    
        xr_interface = XRServer.find_interface("OpenXR")
        if xr_interface and xr_interface.is_initialized():
            print("OpenXR initialised successfully")

            # Turn off v-sync!
            DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

            # Change our main viewport to output to the HMD
            get_viewport().use_xr = true
        else:
            print("OpenXR not initialized, please check if your headset is connected")

Then something like this on the scene that loads your first VR/non VR scene so it can be the same scene, be it a level or what not.

extends Node

func _ready():

    if GameSettings.vr_mode:
        $FreeCam.queue_free()
    else:
        $FreeCam.show() 
        $"VR Scene A".queue_free()     

In this case, I'm just using the VR scene that holds the VR controller and a Free Camera mouse/keyboard controller but you can have as many controllers as you need, just free what you don't need, or probably better to spawn/instantiate the controller you want into the level scene(this is just an example and probably not as well thought out yet). It would be nice to maybe have something more built-in when/if this is a more official system or built-in to Godot, but it's a pretty simple solution for it being a plugin.

Now if you have a need to turn VR off and on after startup at different times, I would assume you just free the VR scene when you don't need it and re-instantiate it when you need it, so that it's _ready() contains the init script as needed and reinstates the VR viewport, just handle whatever the new camera is going to be properly if you lose render focus, but I think that usually happens as long as the camera is checked as current on the saved controller scene you are instancing.

tcrass commented 1 year ago

Hi GreenboxGames,

thanks for your suggestion and you sample code, but this is actually quite similar to what I've tried. I currently don't (yet) have a UI for letting the user choose whether to use XR or not, I just have a boolean property in my main scene, which I switch on or off manually, depending on my development requirements. Also in the main scene, I read out this property, and depending on its value, I instantiate either a scene which does XR initialization (and which introduces all the XR origin, camera, player... stuff) or one which doesn't. So when the is_vr property is false, no XR code gets touched. Yet I observer the described XR-related warnings.

You don't see any of this when choosing not to use VR in your UI?

Cheers -- Torsten

GeminiSquishGames commented 1 year ago

I think I saw them when I was first figuring out how to make the hybrid player controller switch work, and I think that it means the XRCamera3d is still in the scene somewhere and perhaps needs to free up and you use a normal camera to render, but I could be wrong in your case. Make sure by using the Remote tab in your scene hierarchy during runtime that nothing has the XR cam on it in the current root scene. That or something else is trying to make the XR viewport render I think...

PS, Also, one of the reasons I'm using an autoload or singleton is to keep that boolean and all GameSettings that only exist in one place in a global scope and are the only properties of that one GameSettings singleton. Perhaps your boolean is failing to be seen for some reason that something else is setting it or it's getting ignored because there is accidentally more than one way for it to get that value? Might have to look at the code for further deduction. Also, make sure to .queue_free() what you don't want and not hide() to get rid of the XR cam.

Also, these are the signals my UI uses before hand:

extends Node

@export var start_scene = "res://scenes/Levels & Areas/StartTerrain.tscn"

func _on_yes_button_up():
    GameSettings.vr_mode = true
    print("VR Game Mode")
    load_game()

func _on_no_button_up():
    $CanvasLayer/Panel/OK.show() # if no VR then it gives the user a info warning and a confirm button to push _on_okay_button_up

func _on_ok_button_up():
    GameSettings.vr_mode = false
    print("First person Game Mode")
    load_game()

func load_game():
    get_tree().change_scene_to_file(start_scene) #this should kill the current UI scene so no use for:
    #queue_free()
tcrass commented 1 year ago

I think I saw them when I was first figuring out how to make the hybrid player controller switch work, and I think that it means the XRCamera3d is still in the scene somewhere and perhaps needs to free up and you use a normal camera to render, but I could be wrong in your case. Make sure by using the Remote tab in your scene hierarchy during runtime that nothing has the XR cam on it in the current root scene. That or something else is trying to make the XR viewport render I think...

I don't have an XRCamera3D in my scene tree right from the beginning -- I'm only instancing a "VRPlayer" node (which contains all the XR stuff as well as the XR setup code) if* in the main scene's _ready function I find the is_vr property to be true. (If not, a "regular" player node featuring a normal 3D camera gets instanciated and inserted.)

But thanks for pointing me into the "Remote tab" direction! I now realized that my scene contains an XRToolsUserSettings node. I suspect this is the culprit uninvitedly trying to mess around with the XR infrastructure. However, I seem to be unable to get rid of it -- it is showing up as an autoload in the project settings, but if I disable it, I still get the warning dialog about XR not working properly, and in addition the player_body.gd script (which is part of the XR Tools addon) complains about the "Identifyer XRToolsUserSettings not [being] declared in the current scope" (line 433).

PS, Also, one of the reasons I'm using an autoload or singleton is to keep that boolean and all GameSettings that only exist in one place in a global scope and are the only properties of that one GameSettings singleton. Perhaps your boolean is failing to be seen for some reason that something else is setting it or it's getting ignored because there is accidentally more than one way for it to get that value? Might have to look at the code for further deduction. Also, make sure to .queue_free() what you don't want and not hide() to get rid of the XR cam.

I'm pretty sure my boolean is working fine, I've been stepping through the corresponding code like a hundred times...

GeminiSquishGames commented 1 year ago

You can unload any autoloads with [AutoloadNodeName].queue_free() as well if you think that's the problem. My example still works with the XR autoload in my non-VR mode and works fine but it's probably a good idea to free any autoloads you aren't using, and it might work in your case. So use XRToolsUserSettings.queue_free() to get rid of it where you need to.

If you are using the stack trace to make sure your boolean is working then it probably is. But it's a good practice to keep things like that in an autoload/singleton node, like game settings, if you need something that would be "static" or "Global" like you find in other languages. You may have read this already: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html but that's good to know. You are probably a step closer, just remember to go into the code and try freeing anything you don't need that you see in Remote mode at runtime and go over objects with the stack trace using your stepping and breakpoints and you should figure it out eventually. If all else fails, start a new project and work from the bottom up and see if you can get a basic prototype working for the VR/non-VR mechanism. I find working with a clean slate helps me figure things out. Good luck.

tcrass commented 1 year ago

You can unload any autoloads with [AutoloadNodeName].queue_free() as well if you think that's the problem. My example still works with the XR autoload in my non-VR mode and works fine but it's probably a good idea to free any autoloads you aren't using, and it might work in your case. So use XRToolsUserSettings.queue_free() to get rid of it where you need to.

If you are using the stack trace to make sure your boolean is working then it probably is. But it's a good practice to keep things like that in an autoload/singleton node, like game settings, if you need something that would be "static" or "Global" like you find in other languages. You may have read this already: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html but that's good to know.

You are probably a step closer, just remember to go into the code and try freeing anything you don't need that you see in Remote mode at runtime and go over objects with the stack trace using your stepping and breakpoints and you should figure it out eventually.

I get the described warning dialog even before the main scene's _init() function gets called. So I barely have a chance to unload the XRUserSettings3D autoload before the dialog pops up.

If all else fails, start a new project and work from the bottom up and see if you can get a basic prototype working for the VR/non-VR mechanism. I find working with a clean slate helps me figure things out. Good luck.

Yes, this might be the way to go...
atiaxi commented 1 year ago

I'm also having similar issues, but I found a workaround for the dialog popping up: this is actually an optional setting.

In Project Settings -> General -> XR -> OpenXR, uncheck "Startup Alert"

Like @tcrass , though, I'm still getting thousands of "OpenXR: No viewport was marked with use_xr, there is no rendered output!" errors despite QueueFreeing both the XROrigin3d and the XRUserSettings as follows:

    protected void SetupXr()
    {
        vr = false;
        Origin = GetNode<Camera3D>("Camera3D");
        if (!EnableVr)
        {
            var xrorigin = GetNode("XROrigin3D");
            xrorigin.QueueFree();
            var usersettings = GetNode("/root/XRToolsUserSettings");
            usersettings.QueueFree();
            return;
        }
...
BastiaanOlij commented 1 year ago

@tcrass you can only obtain the functionality you're looking for in the compatibility renderer by leaving OpenXR disabled in project settings and instead enable OpenXR by calling initialize on the OpenXRInterface instance (and calling uninitialize to turn it back off).

With the Vulkan renderer this is not possible due to a design choice in OpenXR itself where it requires ownership of the Vulkan instance Godot uses and it can only do so at startup of Godot.

The only alternative currently possible is to start a new copy of Godot and use the --xr-mode command line switch to start with OpenXR enabled or disabled.

This is also not an issue with XR Tools.

tcrass commented 1 year ago

you can only obtain the functionality you're looking for in the compatibility renderer by leaving OpenXR disabled in project settings and instead enable OpenXR by calling initialize on the OpenXRInterface instance (and calling uninitialize to turn it back off). [...] This is also not an issue with XR Tools.

OK, thanks for these explanations! So... I guess the "getting flooded with OpenXR warning messages" is also more a core Godot than an XR Tools thing?

dreadpon commented 1 year ago

I am getting flooded with No viewport was marked with use_xr, there is no rendered output! too, and my app relies on being able to switch between VR and regular 3D at runtime This might cause lag when debugging from Godot editor (similar cases lagged Godot 3.5 editor, not sure yet how it affects 4.x), bloats the log file and prevents efficient debugging of the rest of the program

Maybe we can have a boolean that switches off any "active" functions of XR? As in, all initialization and ownership described above would be ran, but if flag is set to false no processing beyond that will be made (thus avoiding the message)