VR deprecated

Deprecated as of 1.2.8. New code should use VirtualReality — the always-loading replacement that exposes a HasVrFlag property for graceful degradation on non-VR builds. The legacy VR import is now gated behind the vr Cargo feature (import("VR") errors out on builds compiled without --features vr) and won't receive new functionality.

Head-mounted display + motion-controller integration. Two-step lifecycle: detect, then link.

local VR = import("VR")
if not VR.IsVrPresent() then return end

local cam = VR.LinkVRView()
cam.BodyCframe = CFrame.new(Vector.new(0, 1.6, 0))

cam.HeadMoved:Connect(function(cf) print(cf.Position) end)

local pads = cam:GetControllers()
pads.Right.OnInput:Connect(function(name, value, state)
    if name == "Trigger" and state == "Begin" then
        pads.Right:Vibrate(0.05)
    end
end)

VR API

VR.IsVrPresent() -> boolean

True if a VR runtime is installed on this host. Detects SteamVR / Oculus / OpenXR via env vars + Windows registry. Doesn't require a headset to be currently plugged in, it's the "should we offer VR in the menu" check.

VR.LinkVRView() -> VRCamera

Hand the engine camera over to the VR pipeline. Calling while already linked is safe; returns a fresh handle observing the same state.

Coordinate space. BodyCframe is world-relative, where the player's VR rig sits. HeadCframe is the headset pose; the engine composes Body &compose; Head onto the active 3D camera each frame, so existing BasePart / Renderable code keeps working without any VR-aware changes.

VRCamera

Properties

.BodyCframeCFrame

Player body pose. Writable, set this to teleport, snap-turn, or move the rig.

.HeadCframeCFrameread-only
.IsLinkedbooleanread-only
.RuntimeName"SteamVR" | "Oculus" | "OpenXR" | "Unknown"read-only
.RefreshRatenumber

Headset refresh rate in Hz (90 default; runtime usually reports 72/90/120/144).

.IsHeadsetWornboolean

Proximity-sensor state. Older HMDs always report true.

.IPDnumber

Interpupillary distance in metres. Default 0.063.

.HeadMovedSignal<CFrame>read-only
.ConnectedSignal<string>read-only

Fires "Connected" / "Disconnected" / "Worn" / "Removed".

Methods

cam:Unlink()method

Release the engine camera and tear down the VR session. Idempotent.

cam:Recenter()method

Re-anchor the play space so the current head pose becomes the origin facing +Z.

cam:GetEyePose("Left" | "Right")-> CFramemethod
cam:GetPlayArea()-> { Width, Depth }method

Chaperone / guardian dimensions in metres. (0, 0) when not reported.

cam:Fade(color, duration)method

Fade the headset view to/from a solid colour. Useful for teleport transitions.

cam:GetControllers() -> { Left: VRController, Right: VRController } method

Returns the same logical controllers on every call (the userdata is freshly allocated but the underlying state is shared) so signals connected on one handle still fire when the runtime reports input.

VRController

Properties

.Side"Left" | "Right"read-only
.CFrameCFrameread-only
.VelocityVectorread-only

Linear velocity in metres/second. Useful for throwing physics, multiply by mass at release.

.AngularVelocityVectorread-only

Angular velocity in degrees/second around X / Y / Z.

.IsConnectedbooleanread-only
.BatteryLevelnumber?read-only
.Triggernumberread-only

Cached 0..1 trigger pull from the most recent OnInput event.

.Gripnumberread-only
.ThumbstickVectorread-only

x = -1..1 horizontal, y = -1..1 vertical, z = 0.

.MovedSignal<CFrame>read-only
.OnInputSignal<string, number, "Begin" | "End">read-only

(name, value, state). Names include "Trigger", "Grip", "A", "B", "ThumbstickX", "ThumbstickY", "Menu", etc.

Methods

controller:Vibrate(duration, frequency?, amplitude?) -> boolean method

Fire a haptic pulse. Frequency in Hz (typical 160–320), amplitude 0..1; both default to a "click" pulse if omitted. Returns true when scheduled. Multiple overlapping calls layer additively.

controller:StopVibration()method