From meta-vr
Reviews Unity C# code and project settings for Meta Quest VR, focusing on rendering, batching, shaders, and memory to prevent performance issues.
How this skill is triggered — by the user, by Claude, or both
Slash command
/meta-vr:hz-unity-code-reviewThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when reviewing Unity C# code or project settings that target Meta Quest headsets. This includes:
Use this skill when reviewing Unity C# code or project settings that target Meta Quest headsets. This includes:
Quest applications must use the Universal Render Pipeline (URP) with specific settings optimized for mobile VR. The Built-in Render Pipeline is not recommended for new Quest projects.
Critical settings to verify:
Quest has draw call budgets that vary by workload complexity. Every draw call has CPU overhead that directly impacts frame timing.
| Metric | Quest 2 / Quest Pro | Quest 3 / Quest 3S |
|---|---|---|
| Draw calls (busy simulation) | 80-200 | 200-300 |
| Draw calls (medium simulation) | 200-300 | 400-600 |
| Draw calls (light simulation) | 400-600 | 700-1000 |
| Triangles per frame | 750K-1M | 1M-2M |
| SetPass calls | < 50 | < 80 |
Enable and verify:
Mobile GPUs on Quest cannot handle desktop-class shaders. Review all materials for:
GC allocations cause frame hitches and must be eliminated from hot paths.
// BAD: Allocates every frame
void Update() {
string label = "Score: " + score.ToString();
var enemies = FindObjectsOfType<Enemy>();
var filtered = enemies.Where(e => e.IsAlive).ToList();
}
// GOOD: Zero allocations in Update
private StringBuilder _sb = new StringBuilder(32);
private List<Enemy> _enemyCache = new List<Enemy>();
private Enemy[] _enemyArray;
void Start() {
_enemyArray = FindObjectsOfType<Enemy>();
}
void Update() {
_sb.Clear();
_sb.Append("Score: ");
_sb.Append(score);
}
Quest supports multiple input modalities. Code should handle:
OVRInput is maintained for legacy supportOVRHand and OVRSkeleton for hand pose dataOVREyeGaze (Quest Pro / Quest 3, requires permission)Physics simulation is expensive on mobile. Review for:
Audio is often overlooked but can impact performance:
| Area | Target | Notes |
|---|---|---|
| Draw calls | 80-200 (busy) to 400-600 (light) | Use batching, instancing, atlasing |
| Triangles | 750K-1M/frame | Use LODs, occlusion culling |
| Texture resolution | Max 2K, 4K sparingly | ASTC compression required |
| Shader | URP mobile shaders | No Standard shader, no screen-space effects |
| Rendering mode | Single-pass multiview | Must be enabled in XR settings |
| FFR | Enabled (High or HighTop) | Fixed foveated rendering reduces edge fragment cost |
| MSAA | 4x quality / 2x perf | Free on tile-based GPU when configured correctly |
| Target frame rate | 72 Hz minimum | 90 Hz recommended, 120 Hz for smooth experiences |
| GC allocations | 0 B/frame in steady state | No allocations in Update/LateUpdate/FixedUpdate |
| Audio sources | < 16 simultaneous | Use pooling for audio sources |
// Flag these patterns in code review:
Camera.main // Calls FindWithTag internally
GameObject.Find("name") // Linear search every call
GetComponent<T>() in Update // Cache the result
new List<T>() in Update // Allocates on heap
string + string in Update // Creates new string objects
foreach on non-List collections // Enumerator allocation
LINQ queries (.Where, .Select) // Multiple allocations
Boxing (int -> object) // Heap allocation
delegate/lambda in hot paths // Closure allocation
// BAD: Empty Update still has overhead
void Update() { }
// BAD: Logic that doesn't need per-frame execution
void Update() {
SavePlayerPrefs(); // Should be event-driven
}
// GOOD: Use events, coroutines, or InvokeRepeating for non-per-frame logic
void OnScoreChanged(int newScore) {
UpdateScoreUI(newScore);
}
// BAD: Camera.main uses FindWithTag internally
void Update() {
transform.LookAt(Camera.main.transform);
}
// GOOD: Cache the reference
private Transform _cameraTransform;
void Start() {
_cameraTransform = Camera.main.transform;
}
void Update() {
transform.LookAt(_cameraTransform);
}
// BAD: Expensive search every frame
void Update() {
var player = GameObject.FindWithTag("Player");
var rb = player.GetComponent<Rigidbody>();
}
// GOOD: Cache in Awake/Start or use dependency injection
private Rigidbody _playerRb;
void Awake() {
_playerRb = GameObject.FindWithTag("Player").GetComponent<Rigidbody>();
}
You can use the metavr tool to validate builds and check device-side behavior. Invoke via metavr <args> (published as the npm package metavr; if metavr is not on PATH, run npx -y metavr <args>) — no install required.
# Check connected Quest device
metavr device list
# Install and run a build
metavr app install path/to/build.apk
metavr app launch com.company.app
# Check device logs for errors
metavr adb logcat --tag Unity
# Monitor GPU performance
metavr perf capture
Use device-side profiling to validate that code review findings translate to real performance improvements.
For detailed guidance on specific topics, see the following reference documents:
npx claudepluginhub meta-quest/agentic-tools --plugin meta-vrPre-flight check that verifies Meta Quest VR code and answers against official Meta documentation via metavr CLI, preventing stale or deprecated APIs.
Profiles and optimizes Unity game performance using Profiler, Frame Debugger, Memory Profiler; covers CPU/GPU bottlenecks, GC reduction, object pooling, batching, LOD, occlusion culling, and platform tweaks.
Analyzes Unity scenes for performance bottlenecks in draw calls, batching, textures, GameObjects, lighting, physics. Delivers platform-specific optimization recommendations and workflows.