Unity Performance skill for Codex

Name the bottleneck before touching the code. Codex profiles a development build on the target device, classifies the frame as CPU, render thread, GPU or GC bound, and only then changes code. It removes per-frame allocations, pools spawns, splits dirty canvases, checks SRP Batcher and GPU Resident Drawer compatibility in the Frame Debugger, and reports median and p95 frame times before and after so wins are proven rather than assumed.

  • Game Development
  • $unity-performance
  • 180 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- unity-performance

Installs to ~/.agents/skills/unity-performance/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $unity-performance, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Profiles and fixes Unity frame-time problems on PC, console and mobile - CPU vs GPU bound diagnosis with the Profiler, Frame Debugger and Memory Profiler, GC allocation spikes, draw calls and SRP Batcher compatibility, GPU instancing, physics and UI rebuild costs, texture and mesh budgets, and device builds. Use when a Unity game stutters, drops below target FPS, hitches on spawn or scene load, overheats phones, runs out of memory, or when asked to optimize Update loops, pooling or batching.

Use it when

  • A Unity build misses its frame budget or hitches periodically
  • The Profiler shows GC.Collect spikes or high GC Alloc per frame
  • Draw calls, overdraw or texture memory are too high on mobile
  • Spawning or scene loading causes visible freezes

Use something else when

  • You are writing new gameplay features rather than optimizing: use unity-csharp
  • The slowdown is shader math you are authoring yourself: use glsl-shaders

How to check the result

Capture the same scene on the same device before and after; median and p95 frame time, GC Alloc per frame and draw calls must improve beyond run-to-run noise.

The mistake it prevents

Optimizing based on editor Play mode numbers, which include editor overhead and a desktop GPU, instead of a development build on the target device.

~/.agents/skills/unity-performance/SKILL.md

--- name: unity-performance description: Profiles and fixes Unity frame-time problems on PC, console and mobile - CPU vs GPU bound diagnosis with the Profiler, Frame Debugger and Memory… license: MIT ---

Done well means: every change is justified by a capture from a development build on the target device, the frame budget is stated in milliseconds, the bottleneck (CPU main thread, render thread, GPU, memory) is named before anything is changed, and the after-capture proves the gain.

Budgets

TargetFrame budgetPractical headroom
30 FPS mobile33.3 msAim for ~22 ms to avoid thermal throttling
60 FPS16.6 ms~13 ms
90 FPS VR11.1 ms~9 ms, hard requirement
120 FPS8.3 ms~7 ms

Steady-state GC allocation per frame should be 0 B in gameplay. Mobile phones throttle under sustained load, so a 10-minute soak test matters more than a 10-second peak.

Workflow

- [ ] 1. Read AGENTS.md, ProjectVersion.txt, render pipeline asset (URP/HDRP/Built-in)
- [ ] 2. Build a Development Build with Autoconnect Profiler on the target device
- [ ] 3. Capture 300+ frames of the bad case; note the frame-time spike pattern
- [ ] 4. Classify: CPU main, CPU render thread, GPU, or GC/memory (table below)
- [ ] 5. Drill into the top 3 markers only; form one hypothesis per change
- [ ] 6. Make one change, recapture under the same conditions, compare
- [ ] 7. Record before/after numbers in the hand-off

Editor profiling lies: the editor adds its own overhead, runs with domain checks, and uses desktop GPUs. Use it for allocation hunting and hierarchy shape, never for final numbers.

Classify the bottleneck

Profiler showsBound byFirst tools
PlayerLoop large, scripts dominateCPU main thread (scripts)Profiler Hierarchy, Deep Profile on a small repro only
Gfx.WaitForPresentOnGfxThread large on main threadGPU or render threadGPU profiler module, Frame Debugger, platform GPU tools
Physics.Simulate / FixedUpdate.PhysicsFixedUpdate largePhysicsPhysics Debugger, collider counts, fixed timestep
GC.Collect spikes, GC Alloc column nonzeroManaged allocationsHierarchy sorted by GC Alloc, Memory Profiler
Canvas.BuildBatch, Canvas.SendWillRenderCanvasesuGUI rebuildsSplit canvases
Loading.ReadObject, Instantiate spikesAsset loading / spawningPooling, async loading, Addressables

Check the frame interval too: if WaitForTargetFPS is large, the game is already fast and capped by Application.targetFrameRate or VSync.

Measure your own code

using Unity.Profiling;

public sealed class EnemyDirector : MonoBehaviour
{
    static readonly ProfilerMarker s_Think = new("EnemyDirector.Think");

    void Update()
    {
        using (s_Think.Auto())
        {
            ThinkAll();
        }
    }
}

Markers show up in the Profiler and in builds with the Profiler attached. For on-device overlays use ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count") or "Main Thread" and read LastValue.

CPU: scripts and GC

  • Replace per-object Update on hundreds of objects with one manager that iterates an array. Each Update call crosses from native to managed code.
  • Allocation sources to eliminate in hot paths: new List<T>() per frame, LINQ, string concatenation and ToString() for UI, boxing via object parameters or enum keys in Dictionary without a comparer, closures captured in lambdas, GetComponents<T>() without a list argument, foreach over non-struct enumerators, params arrays.
  • Physics queries: use the non-allocating overloads (Physics.RaycastNonAlloc, Physics.OverlapSphereNonAlloc) with a preallocated buffer, and filter with a LayerMask.
  • Pool anything spawned more than a few times a second:
using UnityEngine.Pool;

ObjectPool<Projectile> pool;

void Awake() => pool = new ObjectPool<Projectile>(
    createFunc: () => Instantiate(prefab),
    actionOnGet: p => p.gameObject.SetActive(true),
    actionOnRelease: p => p.gameObject.SetActive(false),
    actionOnDestroy: p => Destroy(p.gameObject),
    collectionCheck: false, defaultCapacity: 64, maxSize: 256);
  • Incremental GC (Player Settings, on by default) spreads collections over frames; it does not remove the cost. Fix allocations first.
  • Heavy data loops (thousands of agents, pathfinding batches) are candidates for the Job System plus Burst. Move only after profiling shows the loop dominates.

Rendering: draw calls and GPU

SituationApproach
URP/HDRP, many objects sharing shadersSRP Batcher (on by default); keep shaders compatible (Frame Debugger shows why a batch broke)
Unity 6 URP/HDRP with many static/repeated meshesGPU Resident Drawer (requires Forward+ in URP)
Thousands of identical meshesGPU instancing or Graphics.RenderMeshInstanced
Many small static propsStatic batching (memory cost) or merging meshes offline
Overdraw from particles and transparent UIReduce layered transparency; check with Scene view Overdraw mode
Mobile GPU boundLower render scale in the URP asset, cut post effects, fewer real-time lights and shadow cascades
  • MaterialPropertyBlock breaks SRP Batcher compatibility; in SRP projects prefer separate materials or per-instance data through instancing.
  • Accessing renderer.material creates a material copy per object; use sharedMaterial when you mean the asset, and destroy copies you create.
  • Use LOD Groups and occlusion culling for 3D scenes with depth; verify with the Frame Debugger that culled objects are not drawn.

Memory and assets (mobile especially)

  • Textures: ASTC on iOS/Android, max size set per platform, mipmaps off for UI, Read/Write off unless code reads pixels (it doubles memory).
  • Meshes: Read/Write off; enable mesh compression where artefacts are acceptable.
  • Audio: long music as Streaming, short SFX as Decompress On Load, everything else Compressed In Memory.
  • Take two Memory Profiler snapshots (menu, after one level) and diff them to find leaks: assets still referenced by static fields or event subscriptions.
  • Resources.UnloadUnusedAssets() is slow and frees only unreferenced assets; it does not fix a leak.

UI (uGUI)

  • Any change to a Graphic marks its whole Canvas dirty. Put frequently changing elements (timers, health bars) on their own nested Canvas.
  • Disable Raycast Target on images and text that are not interactive.
  • Hiding with CanvasGroup.alpha = 0 still costs; disable the Canvas component instead of the GameObject to avoid a rebuild on re-enable.

Physics

  • Keep Fixed Timestep as large as feel allows (0.02 s default). Halving it doubles cost.
  • Set Maximum Allowed Timestep to bound catch-up work after a hitch.
  • Use the Layer Collision Matrix to remove pairs that never interact.
  • Prefer primitive colliders; avoid non-convex MeshCollider on moving bodies.

Gotchas

  • Deep Profile multiplies overhead and distorts the picture; use it only on a tiny repro.
  • Debug.Log in builds allocates and writes to disk; strip with a conditional wrapper ([System.Diagnostics.Conditional("ENABLE_LOGS")]).
  • VSync and targetFrameRate interact: on mobile targetFrameRate defaults to 30. Set it explicitly and check QualitySettings.vSyncCount.
  • Shader variant compilation hitches the first time an effect appears. Warm up with a ShaderVariantCollection (or the Unity 6 PSO tracing tools) in the loading screen.
  • A fix that wins 2 ms in the editor can be noise on device. Compare medians across 300 frames, not single frames.

Verification

  • Same scene, same device, same camera path, same build type, before and after.
  • Report median and 95th percentile frame time, GC Alloc per frame, draw calls / SetPass calls, and memory after the soak test.
  • If the change did not move the target metric by more than run-to-run noise, revert it.

Hand-off template

**Device:** Pixel 7, development build, URP, 60 FPS target (16.6 ms)
**Bottleneck:** CPU main thread, EnemyDirector.Think 6.1 ms + GC.Collect spikes every ~2 s
**Changes:** pooled projectiles (ObjectPool, max 256); RaycastNonAlloc in targeting; HUD timer on own Canvas
**Result:** median 19.4 -> 12.8 ms, p95 31 -> 15.2 ms, GC Alloc 14.2 KB -> 0 B per frame
**Not addressed:** shader warm-up hitch on first explosion (needs ShaderVariantCollection)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Unity Performance"
  short_description: "Name the bottleneck before touching the code"
  default_prompt: "$unity-performance Our Unity game stutters every few seconds on Android, find out why"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem