VR arena: room-scale movement, hand events, and where players actually stand.
VR arena and arcade-style games have a different shape than flat shooters: the locomotion is your body, the input is your hands, the play area is bounded by the guardian. Analytics has to follow that. You care less about long-range gunfights and more about which corner of the play space players retreat to, where they swing or block, and when comfort or motion-sickness telltales appear. This recipe wires the events that answer those questions, with code patterns that respect the cadence of head and hand transforms.
Event vocabulary
player_stance— Point + rotation. Fire on a slow cadence (every 2-5 seconds) using the head transform. Cheap, gives you a density map of where players physically stand. Metadata:locomotion(smooth / teleport / room-scale),height_cm.hand_strike— Oriented. Fire when a hand controller hits a hittable. Position is the impact point, rotation is the hand forward. Metadata:hand(left / right),target_kind,velocity_mps.block— Oriented. Defensive contact. Same fields ashand_strikeplusdamage_absorbed.teleport— Pair. From-position to to-position. Lines reveal preferred lanes; cluster of identical hops reveals favorite camping spots.guardian_breach— Volume. Center on the guardian violation, radius based on overshoot. Critical for safety reviews.comfort_event— Point. Optional, but useful: fire when motion-sickness telltales appear (sharp camera rotation, out-of-bounds momentum, sudden floor change). Metadata:trigger.
Sample wiring
Drive the stance ping from a coroutine rather than Update — you don't need 90Hz fidelity for a heat layer. Strike and block events are one-shot from your existing combat layer; the snippet below shows the cadence pattern for stance.
using System.Collections;
using UnityEngine;
using Traced;
using System.Collections.Generic;
public class VrTelemetry : MonoBehaviour {
public Transform Head;
public float StancePingSeconds = 3f;
void OnEnable() {
StartCoroutine(StancePing());
}
IEnumerator StancePing() {
var wait = new WaitForSeconds(StancePingSeconds);
while (enabled) {
var meta = new Dictionary<string, string> {
{ "locomotion", LocomotionPrefs.Current }, // "smooth" | "teleport" | "room"
{ "height_cm", Mathf.RoundToInt(Head.position.y * 100f).ToString() },
};
Traced.TrackEvent("player_stance", Head.position, Head.rotation, meta);
yield return wait;
}
}
}What to look for in the dashboard
Start with Spatial Density. Toggle player_stance. The cluster shape tells you whether players are using the whole arena or pinning to a corner — corner pinning is almost always a sign the locomotion or the enemy AI is too aggressive. Filter by the locomotion metadata to compare your smooth and teleport cohorts side by side; the difference is usually obvious and informs which mode to default new players into.
Switch to Path Flow for teleport pairs. Bundles of identical hops reveal the routes players have learned; outliers reveal level features players noticed that you may not have intended. A high count of short hops is a sign your teleport granularity is wrong; a few very long hops are a sign players are skipping content.
Density of hand_strike tells you where the action actually happens — and where it doesn't. An expensive set piece with no strike density is dead content. Filter by hand to verify both controllers are getting used (a strongly one-handed game means the off-hand is wasted).
Any density at all in guardian_breach is a safety review item. Open the recent-events table, filter to that layer, and inspect the originating session_id. comfort_event is the same workflow — clusters near a specific level feature are where the playtest broke.
Adjacent reading: sim-training recipe shares the head-and-hand pattern with a very different intent.