VR development has a default answer, and it’s Unity. It’s a good answer — the tooling is mature and most of the tutorials assume it. It also comes with a company that has changed its licensing terms in ways that made a lot of people read the fine print.
Godot is the open-source alternative: MIT licensed, about 100MB, no account, no seat cost, no revenue share, ever. Godot 4 has OpenXR support built into the engine, which means it talks to Quest, SteamVR, Varjo and anything else OpenXR-compliant without a vendor plugin.
For creative and installation work — where budgets are small, headsets are whatever was available, and a project might need to still run in five years — that combination is worth the smaller tutorial ecosystem.
What you need
- Godot 4.3 or later — the standard build, not the .NET/C# build unless you specifically want C#. GDScript is fine and better documented for XR.
- A headset. Quest 2/3/Pro for standalone, or any PC VR headset for tethered.
- For Quest export: Android SDK, OpenJDK 17, and a debug keystore — Godot’s Editor Settings will point you at these.
Step 1: turn OpenXR on
This is where people get stuck, because nothing tells you it’s off.
- Project → Project Settings → XR → OpenXR → set Enabled to on.
- In the same settings, Shaders → Enable Debanding is worth turning on for VR.
- Restart the editor. It will not work until you do, and it will not tell you that.
- Project Settings → Rendering → Renderer: use Forward+ for PC, Mobile for standalone Quest. This matters enormously for performance and is not something to fix later.
Step 2: the XR rig
A VR scene needs a specific node structure:
Main (Node3D)
└── XROrigin3D ← the play space origin
├── XRCamera3D ← the headset
├── XRController3D ← tracker = "left_hand"
└── XRController3D ← tracker = "right_hand"
XROrigin3D is the floor of your play space; XRCamera3D is driven by the headset and must not be moved directly — move the origin instead. On each XRController3D, set the Tracker property to left_hand or right_hand in the inspector.
Then a script on your main node to actually start the XR session:
extends Node3D
func _ready():
var xr_interface: XRInterface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
print("OpenXR initialised")
get_viewport().use_xr = true
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
else:
push_error("OpenXR failed to initialise")
use_xr = true is the line that sends rendering to the headset. Disabling vsync is correct here — the headset’s compositor handles timing, and leaving vsync on fights it.
Step 3: hands, grabbing and movement
Writing locomotion and grab interaction from scratch is a waste of an evening. Godot XR Tools is the community asset library that provides it: animated hands, grab/snap zones, teleport and smooth locomotion, climbing, pointer UI.
Install it through AssetLib inside the editor, then drop its function nodes as children of your XRController3D nodes. Function_Pickup plus XRToolsPickable on an object gets you grabbable objects in about two minutes.
Build your own only once you know what the defaults do wrong for your piece.
Step 4: export to Quest
- Install the Android Build Template: Project → Install Android Build Template.
- Project → Export → Add → Android.
- Tick Use Gradle Build.
- Under XR Features, set XR Mode: OpenXR.
- Enable the Meta vendor plugin in the export settings if targeting Quest natively.
- Connect the headset over USB with developer mode on, and use Remote Deploy (the little Android icon in the toolbar) to push and run in one step.
Remote Deploy is the workflow that makes this tolerable — iteration goes from minutes to seconds.
Performance rules for standalone
A Quest is a phone. Treat it like one:
- 72–90fps is not negotiable. Dropping frames in VR makes people ill; this is a comfort and safety constraint, not a polish item.
- Use the Mobile renderer, not Forward+.
- Baked lighting wherever possible. Real-time lights and shadows are expensive.
- Watch draw calls hard — merge meshes, share materials, use MultiMesh for repeated geometry.
- No screen-space effects. SSAO, SSR and heavy post-processing will wreck your budget.
- Profile on the headset, never in the editor. Desktop performance tells you nothing about mobile.
The honest trade-off
Godot’s XR ecosystem is smaller than Unity’s. Fewer tutorials, fewer plugins, fewer Stack Overflow answers when something breaks at midnight. When you hit an unusual problem you are more likely to be reading engine source than finding an answer.
What you get in exchange: a 100MB engine you can archive alongside the project, no licence that can change under you, full source access, and an export that just works on any OpenXR runtime. For gallery pieces, festival installations and anything that has to be maintainable by whoever inherits it, that’s usually the better trade.
Related Reading
- VR For Dummies: Getting Started in Godot XR — Muddy Wolf (YouTube)
- XR documentation — Godot Engine docs
- GodotVR/godot-xr-tools — GitHub
- Getting Started With XR in Godot 4.3 Tutorial! — VirtualRook (YouTube)
- Setting up the XR Origin — Build a VR Game in Godot #1 (YouTube)
- Deploying to Android — Godot Engine docs