Unity debug profiler
Skill tea-x-random/unity-game-skills/skills/unity-debug-profiler
Debug and profile casual iOS games in Unity through the unity-mcp-bridge. Use for compile errors, console errors, NullReferenceException, MissingReferenceException, missing/broken references, pink/magenta materials, blank or black scenes, won't enter Play Mode, domain-reload issues, MCP connection drops, and for performance profiling: frame rate, draw calls/batches/SetPass, GC allocations, memory and texture memory, mobile thermal throttling, IL2CPP/AOT managed-code stripping, and slow builds. Evidence-driven: read the actual console and profiler, verify Unity 6 APIs via MCP, and re-run to confirm fixes.From its SKILL.md
npx -y skills add tea-x-random/unity-game-skills --skill unity-debug-profilerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.0 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it
Unity Debug Profiler
Find root causes from real evidence and optimize measured bottlenecks, for casual iOS games in Unity, driven entirely through the unity-mcp-bridge (CoplayDev unity-mcp). This is the debug/profile phase of unity-game-director.
Doctrine (do not skip)
- Evidence-driven. Read the actual console and profiler before theorizing.
read_console(types=["error"])is the first move on any "it's broken." Never guess at a cause you could have read. - Lean gates. Diagnose, fix the root cause in the owning script/component, verify. Don't stack confirmation prompts at every micro-step.
- Unity 6 APIs differ from memory. The model's built-in Unity knowledge is ~2022.3. On Unity 6 (6000.x), the Input System, URP/RenderGraph, profiler, and build APIs differ. Verify any C#/shader/API you write with MCP
unity_reflect(live reflection) orunity_docsbefore writing it. Trust order: reflection > project assets > docs > memory. - Verify fixes by re-running. Re-compile, re-read the console clean, enter Play Mode, screenshot. A fix is not done until the evidence is clean.
Step 0 — readiness
Read mcpforunity://editor/state and proceed only when ready_for_tools==true, is_compiling==false, is_domain_reload_pending==false. Read mcpforunity://project/info once to branch correctly: renderPipeline (URP vs Built-in shader names), activeInputHandler, UI stack. The render pipeline value is the single most common root cause of "pink materials."
Console-first debugging
Compile errors block everything — scene work, Play Mode, and tests all fail or run stale until C# compiles clean. Fix compile errors first.
read_console(types=["error"])— compile errors AND runtime exceptions land here. Read the full message and the stack trace, not just the first line.read_console(types=["warning"])— missing references, deprecated APIs, shader fallbacks often surface here before they become errors.- To get a clean signal: clear the console (
manage_editor/console clear), reproduce the exact action, then read. A pile of stale errors hides the live one. - Map the top stack frame to a script + line; that file is where the fix goes.
Load references/common-errors.md for the full symptom → cause → fix table.
Common Unity failure modes
- NullReferenceException — a
[SerializeField]left unassigned in the Inspector/prefab;GetComponent<T>()returning null (component absent, or called before it exists); wrong script execution order so a dependency isn't ready inAwake/OnEnable. Fix: assign the field, null-check or require the component, cache inAwakeand consume inStart, or set Script Execution Order. - MissingReferenceException — referencing a
Destroyd object, or a broken prefab/scene link (asset moved, GUID changed). Differs from NRE: the field was set but the target is gone. Fix: null-check after destroy, re-link the prefab reference, pool instead of destroy. - Pink / magenta materials — shader didn't compile for the active pipeline, almost always a Built-in shader (
Standard) in a URP project. Fix: Edit → Rendering → Materials → Convert/Upgrade to URP, or set the material to a URP shader (Universal Render Pipeline/Lit). VerifyrenderPipelinefrom project/info first. - Blank / black scene — no camera (or camera disabled / wrong culling mask / clear flags), no light (URP Lit looks black unlit), camera near/far or position wrong, or the wrong scene is loaded/active. Check via the scene-debugging checklist before touching content.
- Won't enter Play Mode — a compile error (read console first), or an exception thrown in
Awake/OnEnablethat aborts entry, or "Enter Play Mode without domain reload" leaving stale static state. Fix the exception; if domain-reload-off causes stale statics, reset them inOnEnableor re-enable reload. - Safe Mode on open / mass
CS0619from a package — almost always a package-version vs editor-version mismatch, not your code. Hand-pinned versions inPackages/manifest.json(or a stalepackages-lock.json) that predate the installed Editor break when the Editor hard-obsoletes an API (e.g. Input System ≤1.11.x using removedTreeView/TreeViewItemon a newer Unity 6). Fixes, in order: align each package to the Editor's recommended version (read<Editor>/.../PackageManager/Editor/manifest.json), deletepackages-lock.jsonto force a clean re-resolve, and drop packages you don't actually need — a tap-driven uGUI game needs no Input System package (EventSystem +StandaloneInputModulehandles touch; keepactiveInputHandler= 0/Both). Verify the fix headlessly (seeunity-qa-releasebatchmode verification) before reopening.
Load references/checklists/scene-debugging.md for a blank/broken scene.
Domain-reload / MCP recovery
Cross-reference unity-mcp-bridge/references/reliability.md. Every C# compile, package add/remove, or scripting-backend change triggers a Unity domain reload that drops the MCP connection for ~5s and invalidates in-flight calls. Recovery pattern:
- Issue the change (
create_script/script_apply_edits/apply_text_edits). Do not also callrefresh_unity— create/edit already import+compile. - Poll
mcpforunity://editor/stateuntilis_compiling==false,is_domain_reload_pending==false,ready_for_tools==true. The first poll(s) will fail/timeout during the drop — retry with backoff for ~10s. read_console(types=["error"]); only continue (e.g.AddComponentreferencing the new type) when clean.- If you held a file SHA, re-fetch with
get_shabeforeapply_text_edits— the file may have been reimported (stale_file).
A modal Editor dialog ("reload scripts?" / reimport) stalls the bridge until clicked. If a sequence hangs with no progress, ask the user to check the Editor for a dialog. On macOS the WebSocket can also close (1005 / no_unity_session) on reload/test boundaries — reconnect and re-poll, don't assume the work failed.
Profiling
Enable the group first: manage_tools(action="enable_group", group="profiling") (gives manage_profiler). Use manage_graphics for render stats (draw calls / batches / SetPass / triangles).
MCP reading gotchas (these waste time):
-
manage_graphics(action="stats_get")returnsdraw_calls/batches= 0 for a Screen-Space-Overlay uGUI game — those counters reflect the SRP camera path, not the overlay canvas. Read the Render profiler counters instead:manage_profiler(action="get_counters", category="Render")→ SetPass Calls Count (the meaningful batching metric; single digits = excellent), Triangles Count, Vertices Count. Output is large — pipe throughjqfor the specific counters. -
manage_profiler(action="get_frame_timing")reportscpu_frame_time_ms~200ms in the Editor when the Game view is unfocused — that's WaitForTargetFPS/vsync IDLE, not work. Readcpu_main_thread_frame_time_ms,cpu_render_thread_frame_time_ms, andgpu_frame_time_msfor real per-frame work (sub-1ms each = comfortably 60fps with headroom). Don't mistake the idle-inflated total for a perf problem. -
Target 60fps on mobile → 16.6ms/frame budget. Capture a baseline first: FPS/frame time, draw calls, batches, SetPass calls, triangles, GC alloc/frame, texture memory.
-
CPU vs GPU bound: if frame time tracks CPU main-thread (scripts/physics) it's CPU bound; if GPU time dominates it's fill/geometry bound. Optimize the one that's actually limiting.
-
Draw calls / batches / SetPass: high SetPass = too many materials → atlas, share materials, GPU instancing, static batching for non-moving geometry.
-
GC allocations: aim for zero allocations per frame in steady state. Per-frame
new, LINQ, boxing, string concat, and uncachedGetComponentcause GC spikes → frame hitches. Hunt them in the profiler GC Alloc column.- Per-frame UI text is a double cost — setting a
TMP_Text/.textevery frame (a timer via$"{m:00}:{s:00}"inUpdate) allocates a string and dirties the canvas, forcing a full uGUI rebuild every frame. Fix: cache the last value and only assign when it changes (gate on the integer second). This kills both the steady-state GC alloc and the per-frame rebuild. For a frequently-updating readout, put it on its own nested Canvas so its rebuild doesn't dirty the rest of the UI.
- Per-frame UI text is a double cost — setting a
-
Texture memory & overdraw: oversized/uncompressed textures and heavy transparent overdraw are the usual mobile GPU killers.
-
2D uGUI overlay games: GPU/draw cost is usually trivial (SetPass single digits, sub-ms GPU). The real perf risks are (a) per-frame canvas rebuilds from changing UI every frame and (b) per-frame string/GC allocations — audit
Update()methods for both first before chasing render cost.
Load references/profiling-guide.md for how to read stats via MCP, frame-budget math, and reduction tactics. Use references/checklists/mobile-performance.md to gate a perf pass.
Mobile-specific (casual iOS)
- Thermal throttling under sustained load — the device downclocks after minutes, so a scene that hits 60fps cold may drop hot. Profile sustained, not just the first frame.
- Memory ceilings — iOS kills apps that exceed the device budget; watch total + texture memory.
- ASTC texture sizes — iOS textures should be ASTC-compressed and sensibly sized; uncompressed/RGBA32 atlases blow the memory budget (the build pipeline has a known wrong-compression bug — verify ASTC yourself).
- Physics / Update cost —
Updateon many objects, uncached lookups, and dense physics are common CPU spikes on mobile. - GC hitches — even small per-frame allocations cause visible stutters on mobile; treat steady-state GC as a bug.
- On-device profiling — for real numbers, build a development/profiler-enabled player to the iOS target and connect the Profiler to the device; Editor numbers under-report mobile cost.
IL2CPP / AOT gotchas (iOS)
iOS requires the IL2CPP backend with AOT compilation. The class of bug to flag here:
- Managed code stripping removes types/members only reached via reflection or serialization — works in the Editor (Mono, no stripping), throws
ExecutionEngineException/ missing-type at runtime on device. Fix: preserve them via alink.xml,[Preserve]attributes, or lower the stripping level. - Generic / AOT limits — value-type generics instantiated only at runtime can fail AOT; reflection-heavy serializers (JSON.NET) and
MakeGenericTypeare typical offenders. - Long build times — IL2CPP + Xcode is far slower than Editor iteration; budget for it.
Detailed iOS build steps live in unity-qa-release — flag the stripping/AOT class of bug here, route the build there.
Debug workflow
- Reproduce the exact failing action.
- Read console —
read_console(types=["error"])then["warning"]; read stack traces. - Isolate to the owning script/component (top stack frame; or bisect the scene).
- Fix the C# root cause (verify any Unity 6 API with
unity_reflect/unity_docs). - Wait for compile — poll editor/state through the domain-reload drop.
- Read console clean — no errors before proceeding.
- Verify in Play Mode —
manage_editor(action="play")→read_console→manage_scene(action="screenshot")→manage_editor(action="stop"). For loop coverage,run_tests(mode="PlayMode")→get_test_job.
Final response
Lead with the root cause or the measured bottleneck. Report: checklists/references used, files changed (and that scene/prefab edits went through MCP), before/after metrics for perf work, console-clean + Play Mode screenshot evidence, and residual risks (e.g. on-device-only, thermal, signing).
Field notes & lessons
- per-frame
TMP_Text/.text(e.g. a timer) is a double cost — string alloc + full canvas rebuild every frame; cache the value and only assign on change, nested Canvas for hot readouts. - for 2D uGUI overlay games, audit
Update()for per-frame canvas rebuilds and string/GC allocs before chasing render cost — GPU/draw is usually trivial. manage_graphics(stats_get)reports draw_calls/batches=0 for Screen-Space-Overlay uGUI; usemanage_profiler(get_counters, category="Render")SetPass Calls Count instead (pipe through jq).get_frame_timingcpu_frame_time_ms~200ms in unfocused Editor is vsync IDLE, not work — read cpu_main_thread/cpu_render_thread/gpu_frame_time_ms for real per-frame cost.
What ships with it: 5 files
13.0 KB alongside SKILL.md
agents/
- openai.yaml204 B
references/
- checklists/mobile-performance.md1.4 KB
- checklists/scene-debugging.md1.6 KB
- common-errors.md5.3 KB
- profiling-guide.md4.5 KB