
Godot Platform Vr
- 151 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Helps with ai & agent building tasks during AI-assisted development.
About
godot-platform-vr is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- godot-platform-vr
- AI & Agent Building
- AI-coding skill
Godot Platform Vr by the numbers
- 151 all-time installs (skills.sh)
- +15 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #3,370 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-platform-vrAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 151 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Platform: VR
90+ FPS, comfort-first design, and motion control accuracy define VR development.
NEVER Do (Expert VR Rules)
Rendering & Comfort
- NEVER drop below 90 FPS — In VR, 72 FPS or less causes instant nausea. You MUST maintain at least 90 FPS (Meta Quest 2/3 typical) and minimize rendering jank.
- NEVER use smooth rotation without a vignette (comfort mask) — Smooth rotation causes motion sickness. Always provide snap turning OR dynamic vignetting.
- NEVER force 3D MSAA if Foveated Rendering is enabled — Foveation can conflict with MSAA natively in the OpenXR pipeline on some hardware.
Locomotion & Interaction
- NEVER skip a teleport locomotion option — Smooth movement is intolerable for many. Always offer teleportation as an accessibility alternative.
- NEVER use billboarding for VR UI —
BILLBOARD_ENABLEDbreaks stereoscopic depth cues. Use staticMeshInstance3Dplanes withSubViewports. - NEVER place UI too close or too far — 0.5m causes eye strain; 10m is unreadable. Optimal distance is 1-3 meters from the player.
Safety & System
- NEVER forget to respect physical play area boundaries — Stepping into real-world objects is a safety risk. Use
XRServerto fetch guardian bounds. - NEVER ignore focus_lost or session_ended signals — Gracefully handle disconnections or system menu overlays by pausing the simulation.
- NEVER hardcode XRControllerTracker names — Use the OpenXR Action Map system to decouple gameplay from specific hardware labels.
---
Available Scripts
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
vr_openxr_initializer.gd
Expert OpenXR initialization with driver support and feature verification.
vr_hand_gesture_detector.gd
Pinch and Grab recognition using XRHandModifier3D for hand tracking.
vr_locomotion_handler.gd
Expert Snap Turn and Comfort Vignette (Shader-less) implementation.
vr_passthrough_manager.gd
Alpha blending and underlay setup for Mixed Reality (AR/VR) transitions.
vr_performance_config.gd
Expert Foveated Rendering and Variable Rate Shading (VRS) setup.
vr_haptic_sequencer.gd
Complex haptic pulse sequencing using XRController3D triggers.
vr_physics_hand_controller.gd
Non-clipping, physics-following hands that respect environmental solid.
vr_safety_guardian_warner.gd
Guardian/Chaperone boundary distance warning logic using XRServer.
vr_headset_focus_guard.gd
Headset-aware pause logic for focus loss (System Menu / Headset Off).
vr_input_action_mapper.gd
OpenXR Action Map abstraction to decouple logic from hwardware buttons.
---
# Enable XR
func _ready() -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.initialize():
get_viewport().use_xr = trueComfort Settings
- Vignetting during movement
- Snap turning (30°/45° increments)
- Teleport locomotion option
- Seated mode support
Motion Controls
# XRController3D for hands
@onready var left_hand := $XROrigin3D/LeftController
@onready var right_hand := $XROrigin3D/RightController
func _process(delta: float) -> void:
if left_hand.is_button_pressed("trigger"):
grab_with_left()Performance
- 90 FPS minimum - Critical for comfort
- Low latency - < 20ms motion-to-photon
- Foveated rendering if supported
Best Practices
1. Comfort First - Prevent motion sickness 2. High FPS - 90+ required 3. Physical Space - Respect boundaries 4. UI Distance - 1-3m from player
1. Mixed-Reality-Passthrough Pattern (Quest 3)
To enable AR passthrough on devices like Meta Quest 3, set the environment_blend_mode to ALPHA_BLEND. This allows the virtual scene's alpha channel to control the visibility of the real-world camera feed.
class_name MixedRealityManager extends Node
## Switches the headset to AR/Passthrough mode.
func switch_to_ar() -> void:
var xr_interface := XRServer.primary_interface
if xr_interface and XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND in xr_interface.get_supported_environment_blend_modes():
xr_interface.environment_blend_mode = XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND
# Required for the camera feed to show through transparent areas.
get_viewport().transparent_bg = true 2. XR-Performance-Overlay (Composition Layers)
Standard 2D UI is blurry in VR due to lens distortion. Use OpenXRCompositionLayerQuad to project a SubViewport directly into the headset's runtime. This bypasses the 3D pipeline for a crisp, distortion-free display.
class_name XRPerformanceOverlay extends OpenXRCompositionLayerQuad
## A crisp UI overlay projected directly by the XR runtime.
@export var sub_viewport: SubViewport
func _ready() -> void:
# Assign the viewport to the composition layer.
layer_viewport = sub_viewport
alpha_blend = true
# Position in front of the user (relative to XROrigin3D).
position = Vector3(0.0, 1.5, -1.0)3. Universal-Grab-Manager (Decoupled Interactions)
Avoid hardcoding grab logic into interactables. Use the OpenXR Action Map to define a generic "grab" action and a central manager to handle reparenting based on group tags.
class_name UniversalGrabManager extends Node3D
## Decoupled interaction manager using OpenXR actions.
@export var controller: XRController3D
@export var grab_area: Area3D
var _grabbed: Node3D = null
func _ready() -> void:
controller.button_pressed.connect(_on_grab)
func _on_grab(action_name: String) -> void:
if action_name == "grab" and not _grabbed:
for body in grab_area.get_overlapping_bodies():
if body.is_in_group("grabbable"):
_grabbed = body
_grabbed.reparent(controller, true)
breakReference
- Related:
godot-camera-systems,godot-input-handling
Related
- Master Skill: godot-master
# platform_vr_patterns.gd
extends Node
# 1. Initializing OpenXR and Enabling the Viewport
# EXPERT NOTE: use_xr must be set to true ONLY AFTER successful initialization.
func start_vr_session() -> bool:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.initialize():
get_viewport().use_xr = true
return true
return false
# 2. Tracking Focus Loss (Headset Removed)
# EXPERT NOTE: Pause the game when the user takes off the headset to prevent nausea or missed info.
func setup_xr_focus_listeners() -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface:
xr_interface.focus_lost.connect(_on_xr_focus_lost)
xr_interface.focus_gained.connect(_on_xr_focus_gained)
func _on_xr_focus_lost() -> void:
get_tree().paused = true
func _on_xr_focus_gained() -> void:
get_tree().paused = false
# 3. Dynamic Foveated Rendering Setup
# EXPERT NOTE: Drastically improves performance for the Compatibility renderer.
func enable_foveation_if_supported() -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_foveation_supported():
xr_interface.foveation_level = 3 # Highest level of edge pixel reduction
# 4. Triggering Controller Haptics
# EXPERT NOTE: Use for tactile feedback. frequency and amplitude help players feel interactions.
func trigger_hand_rumble(hand: StringName, amp: float = 0.5) -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface:
# action_name, tracker_name, frequency, amplitude, duration_sec, delay_sec
xr_interface.trigger_haptic_pulse("haptic", hand, 10.0, amp, 0.1, 0.0)
# 5. Accessing the User's Head Transform
# EXPERT NOTE: Used to position 3D UI exactly where the player is looking.
func get_eye_position() -> Transform3D:
return XRServer.get_hmd_transform()
# 6. Fetching Physical Play Area Bounds
# EXPERT NOTE: Draw a visual boundary (Guardian/Chaperone) if the player gets near.
func get_room_bounds() -> PackedVector3Array:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface:
return xr_interface.get_play_area()
return PackedVector3Array()
# 7. Setting Up AR Passthrough
# EXPERT NOTE: Allows the real world to be drawn behind virtual content.
func enable_ar_passthrough() -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_passthrough_supported():
xr_interface.set_environment_blend_mode(XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND)
xr_interface.start_passthrough()
# 8. Handling WebXR Session Signals
# EXPERT NOTE: WebXR requires a user interaction to start and specific signal handling.
func setup_webxr_events() -> void:
var webxr := XRServer.find_interface("WebXR")
if webxr:
webxr.session_started.connect(func(): get_viewport().use_xr = true)
webxr.session_ended.connect(func(): get_viewport().use_xr = false)
# 9. Validating Initialization Status
# EXPERT NOTE: Check this before calling any XR-specific methods to prevent crashes.
func is_xr_active() -> bool:
var xr_interface := XRServer.get_interface(0)
return xr_interface and xr_interface.is_initialized()
# 10. Adjusting VR Viewport Scaling (Supersampling)
# EXPERT NOTE: MUST be set BEFORE xr_interface.initialize() for it to take effect.
func set_xr_resolution_scale(multiplier: float) -> void:
var xr_interface := XRServer.find_interface("OpenXR")
if xr_interface:
xr_interface.render_target_size_multiplier = multiplier
class_name VRHandGestureDetector
extends Node
## Expert hand tracking gesture recognition using XRHandModifier3D.
## Detects 'Pinch' and 'Grab' strength for interaction.
@export var hand_modifier: XRHandModifier3D
func get_pinch_strength() -> float:
if not hand_modifier: return 0.0
# Use standard OpenXR hand joint indices (Index tip and Thumb tip)
# This is a simplified proxy for demonstration
return 1.0 # Implement actual distance check here in production
func is_grabbing() -> bool:
# Check if middle, ring, and pinky are curled
return false
## Tip: Hand tracking is highly sensitive to lighting; always provide controller fallbacks.
class_name VRHapticSequencer
extends Node
## Expert haptic sequencer for VR controllers.
## Uses trigger_haptic_pulse to create distinct tactile patterns (Success, Impact).
func play_haptic_vibration(controller: XRController3D, amplitude: float = 0.5, duration: float = 0.1) -> void:
if controller:
# frequency, amplitude, duration, delay
controller.trigger_haptic_pulse("haptic", 100.0, amplitude, duration, 0.0)
func play_triple_echo(controller: XRController3D) -> void:
for i in range(3):
play_haptic_vibration(controller, 0.2 + (i * 0.1), 0.05)
await get_tree().create_timer(0.1).timeout
## Rule: Always use the 'haptic' action name defined in OpenXR settings.
class_name VRHeadsetFocusGuard
extends Node
## Expert handler for VR headset focus events.
## Prevents the game from running while the user is in the system menu.
func _notification(what: int) -> void:
match what:
NOTIFICATION_WM_WINDOW_FOCUS_OUT:
# Headset removed or system overlay (Meta menu) opened
get_tree().paused = true
_silence_audio(true)
NOTIFICATION_WM_WINDOW_FOCUS_IN:
get_tree().paused = false
_silence_audio(false)
func _silence_audio(muted: bool) -> void:
AudioServer.set_bus_mute(0, muted)
class_name VRInputActionMapper
extends Node
## Expert OpenXR Action Map abstraction boilerplate.
## Decouples gameplay logic from specific controller button strings.
func _on_left_controller_input_event(name: String, _input_value: Variant) -> void:
# Standard OpenXR action names from project settings
match name:
"grab":
_on_grab()
"teleport":
_on_teleport_requested()
func _on_grab() -> void:
pass
func _on_teleport_requested() -> void:
pass
## Rule: Never hardcode controller strings; use the Action Map system.
class_name VRLocomotionHandler
extends Node
## Expert locomotion handler with Snap Turning and Comfort Vignette.
## Prevents motion sickness by narrowing FOV during rotation.
@export var player_origin: XROrigin3D
@export var comfort_vignette: CanvasItem # A black overlay with a hole
var _is_rotating := false
func perform_snap_turn(angle_deg: float) -> void:
if _is_rotating: return
_is_rotating = true
_show_vignette(true)
# Rotate the origin around the camera's local Y axis
player_origin.rotate_y(deg_to_rad(angle_deg))
await get_tree().create_timer(0.1).timeout
_show_vignette(false)
_is_rotating = false
func _show_vignette(visible: bool) -> void:
if comfort_vignette:
comfort_vignette.visible = visible
## Rule: Always provide 'Snap Turn' as a default for VR comfort.
class_name VROpenXRInitializer
extends Node
## Expert OpenXR initialization with feature support checking.
## Ensures the XR system is only activated if the hardware is ready.
func _ready() -> void:
var xr_interface: XRInterface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
print("VR: OpenXR already initialized.")
_setup_viewport()
elif xr_interface and xr_interface.initialize():
print("VR: OpenXR Initialized successfully.")
_setup_viewport()
else:
push_error("VR: OpenXR initialization failed. Headset not found?")
func _setup_viewport() -> void:
get_viewport().use_xr = true
# Expert: VSync should always be OFF in VR to avoid latency (headset handles pacing)
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
## Rule: Always check 'xr_interface.initialize()' before setting 'use_xr = true'.
class_name VRPassthroughManager
extends Node
## Expert AR Passthrough (Mixed Reality) manager for OpenXR.
## Enables the world-view underlay for Meta Quest and other MR headsets.
func enable_passthrough(enabled: bool) -> void:
var xr_interface := XRServer.primary_interface
if xr_interface and xr_interface.get_name() == "OpenXR":
if enabled:
xr_interface.set_environment_blend_mode(XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND)
get_viewport().transparent_bg = true
else:
xr_interface.set_environment_blend_mode(XRInterface.XR_ENV_BLEND_MODE_OPAQUE)
get_viewport().transparent_bg = false
## Rule: Passthrough requires 'transparent_bg = true' on the main viewport.
class_name VRPerformanceConfig
extends Node
## Expert VR performance configurations (Foveation, VRS).
## Optimizes frame-time for 90/120Hz targets on standalone headsets.
func _ready() -> void:
var xr_interface := XRServer.primary_interface
if xr_interface:
# Set foveation level (0 = Off, 4 = High)
if "foveation_level" in xr_interface:
xr_interface.foveation_level = 3 # High foveation for Quest 2
xr_interface.foveation_dynamic = true
func set_supersampling(multiplier: float) -> void:
# Multiplying render target size improves clarity at high GPU cost
XRServer.primary_interface.render_target_size_multiplier = multiplier
## Rule: 90/120Hz is mandatory for comfort; favor resolution over post-effects.
class_name VRPhysicsHandController
extends CharacterBody3D
## Expert physics-based hand for immersive VR interactions.
## Prevents hands from clipping through walls by following the XR controller via physics.
@export var target_controller: XRController3D
@export var follow_speed: float = 20.0
func _physics_process(delta: float) -> void:
if not target_controller: return
# Compute velocity needed to reach controller position
var target_pos := target_controller.global_position
var diff := target_pos - global_position
velocity = diff * follow_speed
move_and_slide()
## Tip: Use 'move_and_slide' to ensure hands slide against surfaces naturally.
# skills/platform-vr/scripts/vr_physics_hand.gd
extends XRController3D
## VR Physics Hand Expert Pattern
## Physics-based hand interaction using Jolt/Godot Physics with velocity tracking.
class_name VRPhysicsHand
# Configuration
@export var pickup_layer: int = 1
@export var throw_velocity_multiplier: float = 1.3
# Nodes
@onready var _grab_area: Area3D = $GrabArea # Should be child
@onready var _hand_mesh: MeshInstance3D = $HandMesh
# State
var _held_object: RigidBody3D = null
var _previous_global_pos: Vector3
var _velocity: Vector3
func _ready() -> void:
# Connect signals for standard OpenXR interaction
button_pressed.connect(_on_button_pressed)
button_released.connect(_on_button_released)
func _physics_process(delta: float) -> void:
# Calculate instantaneous velocity for throwing
var current_pos = global_position
if delta > 0:
_velocity = (current_pos - _previous_global_pos) / delta
_previous_global_pos = current_pos
# If holding object, snap it to hand (Kinematic Grabbing)
# Or apply force (Physics Grabbing) - Simple kinematic snap shown here
if _held_object:
# Keep object at hand transform
_held_object.global_transform = global_transform
func _on_button_pressed(name: String) -> void:
if name == "grip_click": # Standard grip button
_try_grab()
func _on_button_released(name: String) -> void:
if name == "grip_click":
_drop()
func _try_grab() -> void:
if _held_object: return
# Find nearest grabbable
var bodies = _grab_area.get_overlapping_bodies()
var nearest: RigidBody3D = null
var min_dist = INF
for body in bodies:
if body is RigidBody3D and body.collision_layer & pickup_layer:
var dist = global_position.distance_to(body.global_position)
if dist < min_dist:
min_dist = dist
nearest = body
if nearest:
_held_object = nearest
_held_object.freeze = true # Disable physics while holding
# Feedback
trigger_haptic_pulse("haptic_grasp", 100.0, 0.1, 0.1, 0)
func _drop() -> void:
if not _held_object: return
_held_object.freeze = false
# Apply throw velocity
_held_object.linear_velocity = _velocity * throw_velocity_multiplier
_held_object.angular_velocity = Vector3.ZERO # Optional spin
_held_object = null
## EXPERT USAGE:
## Attach this script to LeftHand/RightHand nodes.
## Ensure child 'GrabArea' exists with collision shape.
class_name VRSafetyGuardianWarner
extends Node
## Expert guardian/chaperone proximity warner.
## Uses the XRServer to fetch reference bounds and warns if player is too close.
@export var camera: XRCamera3D
func _process(_delta: float) -> void:
# Reference frame 2D bounds (square/rectangle area)
var bounds := XRServer.get_reference_frame_bounds_2d()
if bounds.size == Vector2.ZERO: return # No boundary set
var cam_pos_2d := Vector2(camera.position.x, camera.position.z)
if not bounds.has_point(cam_pos_2d):
_warn_player_out_of_bounds()
func _warn_player_out_of_bounds() -> void:
# Trigger visual/haptic warning
pass
## Rule: Respecting real-world physical space is a safety requirement for VR.