A Unity package for running video-based behavioral trials on Meta Quest 3, collecting high-frequency hand kinematics, head/camera pose, eye-gaze, and phone pose, detecting touch/click interactions, and sending BCI2000 event triggers, including online reach-onset detection (ReachInit) based on hand movement distance and thresholding.
This package implements a complete experimental pipeline:
- Welcome & instruction flow
- Ready period (baseline for movement)
- Stimulus trials (video or blank)
- Click / reach interaction window
- Rest periods
- End screen
During trials, the system logs per-frame tracking data to CSV and emits BCI2000 events aligned with trial structure, reach initiation, and touch responses.
- Deterministic trial playlist (seeded shuffle)
- Video-based stimuli (
U,I,S,Bgroups) - XR Hands joint tracking (left & right, full skeleton)
- Eye-gaze via
CenterEye(when available) - Phone-based touch detection
- Online reach-onset detection (
ReachInit) - BCI2000 integration with safe event pulsing
- High-performance CSV logging (minimal allocations)
- Unity with XR Plug-in Management enabled
- XR Hands package installed and active
- OpenXR (recommended for Quest 3)
- Meta Quest 3 with hand tracking enabled
- BCI2000 with Unity integration (
UnityBCI2000,BCI2000RemoteNET)
Create an empty GameObject (e.g. TrialManager) and attach:
Script
PhoneScreenControllerQuest
Required References
headMount– XR head anchor transformmainCamera– XR cameratvVideoPlayer–VideoPlayerfor all videosphone– phone target objectindexTipTransform– tracked index fingertip transform
UI / Optional
- Phone screen text & background
- Audio source + click sound
- Room light (for instruction dimming)
- BCI component (if used)
Defined via:
videoGroups = "U,S,B" videosPerGroup = 10
Rules:
- Videos are grouped by first character of filename
- Example:
U01.mp4→ groupU B= blank trials (no stimulus, no click required)
- Controlled by
shuffleSeed - Fully deterministic across runs
| Parameter | Description |
|---|---|
welcomeTime |
Welcome video duration |
instructionTime |
Instruction loop + light fade |
readyTime |
Baseline period before trials |
clickTimeWindow |
Max time to reach/click |
restTime |
Mean rest duration |
restRange |
Random variation (±) |
Rest duration is clamped to 13–17 s.
A click is registered when:
isClickable == trueclicked == false- Distance between:
indexTipTransform.positionphone.transform.position
- Is < 0.02 m
Effects:
- UI feedback
- CSV
Click = 1 ClickTouchBCI event pulse
ReachInit detects initial hand movement onset relative to a baseline (Ready period), using a distance-from-baseline formula and a user-defined threshold.
This runs online during the trial, independently of touch/click detection.
-
During Ready, compute baseline mean position: p0 = mean(indexTipPosition)
-
During trial, compute distance: d(t) = || p(t) − p0 ||
-
If: d(t) > reachThreshold
and ReachInit has not yet fired → trigger event.
- Fires once per trial
- Logged to CSV
- Emits BCI2000 ReachInit pulse
- Can be configured to use:
- Left or right index tip
- Custom distance threshold
| Event | Purpose |
|---|---|
TrialStart |
Trial onset |
ReachInit |
Reach movement initiation |
ClickTouch |
Phone touch |
TrialRest |
Rest period |
All events are 16-bit.
At Trial Start TrialStart = 1 ReachInit = 0 ClickTouch = 0 TrialRest = 0
On Reach Detection ClickTouch = 1
During Rest TrialStart = 0 TrialRest = 1
All pulses are sent via: SafeBCIPulse(eventName, value)
Which:
- Guards against null BCI references
- Prevents crashes if BCI is disconnected
- One CSV per run
- Timestamped filename:
TrackingData__yy-MM-dd-HH-mm-ss.csv
- Time (s)
- Current video
- Left & Right hand joints
- Position, rotation
- Velocity, acceleration
- Head pose
- Camera pose
- Gaze origin & direction
- Eye open amounts
- Phone pose
- Click flag
- ReachInit flag
- Preallocated buffers
- No per-frame allocations
- Logging occurs in
Update()viaLogTrackingData()
CalibrateOriginalPhonePosition()
- Uses index tip + offset
- Can auto-run on first click
- Optionally moves phone immediately
CalibrateRoomToHeadAndIndex()
- Aligns chair/table/TV to head & hand
- Requires optional scene references
No hand data
- Ensure XR Hands is running
- Confirm Quest hand tracking is enabled
ReachInit never fires
- Check baseline period (Ready)
- Increase
reachThreshold - Verify correct index tip is selected
Clicks not detected
- Adjust distance threshold
- Verify phone and finger are in same coordinate space
CSV not written
- Custom path may be invalid on Quest
- Falls back to
Application.persistentDataPath
- Velocity-based reach detection
- Separate ReachInit for left/right hands
- Buffered logging coroutine
- Adaptive thresholds per participant
Add your lab or project license here.
Maintainer:
Your Name / Lab
Unity + Quest 3 Experimental Framework