Unity debug profiler
Skill tea-x-random/unity-game-skills/skills/unity-debug-profiler
Claude Agent Skills for building casual iOS games in Unity 6 — orchestration, MCP Editor control, generative 2D/3D/audio assets, graphics, UI, monetization, QA & release.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
13.0 KB, 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.