Skip to content

CVR Input

TL;DR: CVR.CVRInput is a static class exposing the local player’s movement, look, and button state from the VR or desktop input system, plus last-frame values and per-frame deltas. The state is double-buffered in guest memory by the host — reads are essentially free. Writes are conditional on context and let a world (or the local avatar’s wearer) drive the player’s input programmatically.

Buttons (flag-set):

// Single-frame tests
if (CVRInput.WasPressedThisFrame(CVRButton.Jump)) { /* just pressed */ }
if (CVRInput.WasReleasedThisFrame(CVRButton.GripLeft)) { /* just let go */ }
// Held state
if (CVRInput.GetButtonDown(CVRButton.Sprint)) { /* holding sprint */ }
// Last-frame equivalent (for your own edge detection)
bool wasHolding = CVRInput.WasButtonDown(CVRButton.InteractRight);

Continuous analog state:

Vector3 move = CVRInput.Movement; // full 3D movement axis
Vector2 look = CVRInput.Look; // 2D look axis
float grabR = CVRInput.InteractRight; // 0..1 grab/trigger analog
float gripL = CVRInput.GripLeft; // 0..1 grip analog

Last-frame and delta companions for every analog field:

Vector3 previousMove = CVRInput.LastMovement;
Vector3 deltaMove = CVRInput.MovementDelta; // current - last, per frame

All these are static properties. The host writes them into a fixed memory slot on every OnInputReady dispatch (once per input tick); the guest reads them directly — no host round-trip per access.

Source: CVRInputCCK.cs:52-60.

[Flags]
public enum CVRButton : ulong
{
Jump = 1ul << 0,
Sprint = 1ul << 1,
InteractRight = 1ul << 2,
InteractLeft = 1ul << 3,
GripRight = 1ul << 4,
GripLeft = 1ul << 5,
}

Combined flags are supported — pass CVRButton.Jump | CVRButton.Sprint to test either, or set both in one call via SetAllButtons.

Scripts can override the input stream:

CVRInput.SetMovement(new Vector3(0f, 0f, 1f)); // force-walk forward
CVRInput.SetLook(new Vector2(0.2f, 0f)); // gentle right yaw
CVRInput.SetInteractRight(1f); // fully-pressed grab
CVRInput.SetButton(CVRButton.Jump, true); // assert jump
CVRInput.SetAllButtons(CVRButton.Sprint | CVRButton.Jump);

The override takes effect only if the calling VM is allowed to drive input. From WasmVM.Events.cs:518-522, the host commits the write back to CVRInputModuleWasm.Instance.Input iff:

  • the VM runs a world script, OR
  • the VM runs an avatar script and the avatar is worn by the local player (OwnerContext == Self).

A remote avatar or a prop script can call the setters without throwing, but the write won’t reach the local input system.

Common use cases:

  • Cutscene locomotion — a world forces the player to walk into a scripted camera path.
  • Autopilot / assisted driving — a vehicle script on the local avatar drives the input to stabilize control.
  • Accessibility mods — remap inputs (though CVR has native input remapping; use sparingly).

Subscribe by defining the matching method:

void OnInputReady() { /* fires once when the input subsystem is initialized */ }

Fires once per VM on initial input setup — treat it as “now is a good time to initialize input-dependent state.” It is not a per-frame event; use Update / FixedUpdate to sample current input every frame.

Source: CVRInputCCK.cs:42-50.

public struct CVRInputStruct
{
public CVRButton Buttons;
public Vector3 Move;
public Vector2 Look;
public float InteractRightValue;
public float InteractLeftValue;
public float GripRightValue;
public float GripLeftValue;
}

This is the on-the-wire shape the host writes to the shared memory slot. You rarely touch it directly; the static property getters dereference it for you.

  • Reads: no gate. Any VM context. The memory is always populated for the local player.
  • Writes: implicitly gated. The setter methods compile and run from any VM, but the write only reaches CVRInputModuleWasm from a world script or an avatar-on-self script. Remote-avatar and prop writes silently no-op.

Analog reads are guest-side memory reads — the cheapest possible call. Use freely.

Button tests (GetButtonDown, WasPressedThisFrame, etc.) are also guest-side — the guest computes (current & flag) != 0, (current & flag) != 0 && (last & flag) == 0, etc., from the double-buffered struct.

Setters are the only host-crossing calls, and only world/avatar-self scripts materialize them. Cheap.

using UnityEngine;
using WasmScripting;
using CVR;
public partial class ForwardWhileSprinting : WasmBehaviour
{
void Update()
{
if (CVRInput.GetButtonDown(CVRButton.Sprint))
{
// Override movement to pure forward while sprint is held.
CVRInput.SetMovement(Vector3.forward);
}
}
}

Works on a world script; a no-op on a remote player’s client (only the local sprint input is intercepted).

  • Permissions — why writes fail silently on wrong VM context.
  • EventsOnInputReady signature.
  • Performance — why analog reads are free.