From meta-vr
Reviews Unity C# code targeting Meta Quest and Horizon OS for performance issues, rendering best practices, and VR pitfalls. Use during code reviews or Quest performance diagnostics.
npx claudepluginhub meta-quest/agentic-tools --plugin meta-vrThis skill is limited to using the following tools:
Use this skill when reviewing Unity C# code or project settings that target Meta Quest headsets. This includes:
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.
Debugs Meta Quest and Horizon OS VR/MR apps using hzdb CLI: view logs, capture screenshots, diagnose crashes, errors, and issues on connected devices.
Analyzes Unity scenes for performance bottlenecks in draw calls, batching, textures, GameObjects, lighting, physics. Delivers platform-specific optimization recommendations and workflows.
Share bugs, ideas, or general feedback.
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 hzdb tool to validate builds and check device-side behavior. Invoke via npx -y @meta-quest/hzdb <args> — no install required.
# Check connected Quest device
hzdb device list
# Install and run a build
hzdb app install path/to/build.apk
hzdb app launch com.company.app
# Check device logs for errors
hzdb adb logcat --tag Unity
# Monitor GPU performance
hzdb 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: