Loading...
Loading...
Reviews Unity code targeting Meta Quest and Horizon OS for performance issues, rendering best practices, and common VR pitfalls. Use during code review or when diagnosing Quest performance problems in Unity projects.
npx skill4agent add meta-quest/agentic-tools hz-unity-code-review| 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 |
// 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);
}OVRInputOVRHandOVRSkeletonOVREyeGaze| 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>();
}metavrmetavr <args>metavrmetavrnpx -y metavr <args># 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