Appearance
Profiler
The Profiler window (open it from the Windows menu) shows where each frame's time goes — scripts, physics, rendering, particles, animation — plus live renderer counters and a per-script cost table. When a map runs badly, it answers the two questions that matter: which frames are slow, and what inside them is slow.
It records only while the window is open, and costs effectively nothing while closed. Everything it shows is measured on your machine, in the running frame — no build step, no external tools.
Reading the window
The frame strip
The bar chart across the top is the last ~5 seconds of frames, newest on the right. Each bar is one frame, stacked by color:
| Color | Section |
|---|---|
| blue | Scripts (update) — every script's onUpdate, timers, coroutines, HUD tweens |
| sky | Scripts (fixed) — every script's onFixedUpdate |
| red | Physics — the physics world step |
| yellow | Render (main) — drawing the main view |
| peach | Render (extra) — render-texture cameras, reflection probes, minimaps, the Game-view split |
| green | Particles |
| mauve | Animation — mixers and clip playback |
| light gray | Other (our JS) — the rest of the frame callback: editor loop odds and ends, gizmos, UI work it triggers |
| dark gray | Idle / browser — time the frame spent with none of our code running |
The two dashed lines are the 60 fps (16.7ms) and 30 fps (33.3ms) budgets. A healthy map stays under the first line; bars crossing the second are visible stutter.
Click any bar to pause recording and pin that frame — the sections table switches to its exact breakdown. Press Resume to go live again.
Counters
The row under the strip is the live state of the renderer and scene:
| Counter | What it means |
|---|---|
| fps, avg, p95 | Frame rate, average frame time, and the time your worst 5% of frames exceed. p95 is the stutter number — a good average with a bad p95 feels worse than the reverse. |
| draws, tris | Draw calls and triangles in the last frame. Draw calls are usually the render bottleneck — a thousand small objects cost more than ten big ones with the same triangles. |
| geo, tex, shaders | Geometries, textures and shader programs resident on the GPU. Watch these during play: if they climb steadily while you spawn and destroy things, something is not being freed. Flat is healthy. |
| entities, bodies | Scene entities and live physics bodies. |
| MB heap | JavaScript heap (Chromium browsers only). |
Sections and top scripts
The sections table shows the pinned (or latest) frame next to the average — a frame that looks bad against its own average tells you whether a cost is constant or a spike.
The last three rows are the ones that explain a spike nothing else accounts for:
| Row | What it means |
|---|---|
| Other (our JS) | Time inside our own frame callback that no section covers. If a spike lands here, the cost is ours and the sections above narrow it down. |
| Idle / browser | Time the frame held while none of our code ran: waiting for the display's next refresh, browser style/layout/paint, garbage collection, GPU stalls. On a healthy frame this is nearly all of it — a 7ms frame of work on a 144Hz screen still reports the full refresh interval, and the wait is what fills it. A spike here was not caused by your map. |
| Total frame | The whole frame, refresh to refresh. Always the sum of every row above it. |
When the heap moves by more than half a megabyte across the shown frame, a heap Δ row appears. A large negative number is a garbage collection, and it is the usual answer when Idle / browser spikes on its own. Each entry in the Spikes list carries the same verdict as a tag — our JS, GC -14MB, or idle/browser — so you can tell at a glance whether a spike is yours to fix.
The top-scripts table ranks your script classes by time spent over the last second, with call counts. Every lifecycle call is attributed — onUpdate, onFixedUpdate, collision and trigger handlers, timers, coroutines — so the script eating the frame is at the top by name, whatever hook it burns the time in.
Spikes
Any frame slower than 2.5× the recent average (and over 25ms) is captured automatically: its full section breakdown plus the scripts running at that moment. The last 20 sit in the list at the bottom — click one to pin it. This is how you catch the hitch that happened three seconds ago while you were looking at the game instead of the profiler.
Pause, Clear, Export
Pause freezes the buffers while the game keeps running; Clear starts fresh; Export downloads everything — frames, sections, scripts, spikes — as JSON, which is the right thing to attach to a bug report.
Isolation toggles
The disable: row turns individual costs off while everything else keeps running — the fastest way to bisect a slow frame. Flip one, watch the strip: if the bars drop, you found your cost.
| Toggle | What it disables |
|---|---|
| main render | The main viewport render. The view freezes on its last frame; the sim, scripts and physics keep running underneath. If frames are still slow with this on, rendering was never the problem. |
| editor render | Only the editor's Scene viewport. In the Game-view split your game keeps rendering while the editor viewport's cost drops out — the cleanest way to see what your game itself costs. In edit mode it freezes the viewport; in fullscreen play and published games it does nothing (there is no editor render there). |
| extra renders | RTT cameras, reflection probes, minimaps, camera previews and the Game-view split — everything in Render (extra). |
| post-fx | Post-processing; the plain view draws instead. The difference is exactly what your effects cost. |
| shadows | Shadow-map re-rendering. Shadows freeze in place (they do not disappear), and their per-frame depth-render cost — hidden inside Render (main) — drops out. |
| particles | Particle simulation. Systems stay visible, frozen mid-air. |
| animation | Animation mixers. Poses hold where they are. |
Toggles are debugging aids, not settings: they reset the moment the Profiler window closes, so a frozen viewport can never outlive the panel that explains it.
Profiling a play-test
Recording continues while you play. In fullscreen play the editor windows hide, but the profiler keeps capturing — press Stop and the reopened window holds the last 5 seconds of the session, spikes included. With the Game window open (the split view), the profiler stays on screen and updates live while you drive the player.
One thing about onFixedUpdate
Scripts (fixed) accumulates once per physics step, not per frame. When a frame runs late, the physics loop runs extra catch-up steps inside it — and a heavy onFixedUpdate runs in every one of them. So the cost of a slow fixed hook multiplies exactly when the frame is already behind, which is how a "2ms" script becomes the reason a 40ms frame happened. If Scripts (fixed) grows on exactly the frames that spike, that multiplication is your problem: move display work to onUpdate and keep onFixedUpdate to forces and physics decisions.
Profiling published games
Add ?profile=1 to the URL of any published game and the same panel docks as an overlay.
In multiplayer, scripts and physics run on the server, so those rows stay near zero — the client-side rows that matter are render, particles and animation, plus a table the editor never shows: Network, the per-second rate and size of every message kind arriving from the server. A game that stutters only in multiplayer is often not slow at all — it is drowning in messages, and this table names which ones.
A standalone build runs the full simulation locally, so ?profile=1 there gives the complete editor-style breakdown — scripts, fixed, physics and all — inside the exported game.
Reading it like a pro
- Gray-dominated slow frames with quiet sections mean the cost is outside the game — usually garbage collection. Look for scripts allocating in
onUpdate(creating arrays or objects every frame). - Render (main) high with few draw calls points at fragment cost: post-processing, large transparent surfaces, or too many shadow-casting lights. High draw calls with cheap frames elsewhere points at object count — merge, instance, or cull.
- A sawtooth strip (fast-fast-slow repeating) is usually the physics catch-up pattern from the
onFixedUpdatenote above. - The geo/tex counters are your leak detector. Play for two minutes doing the thing your game does — spawning, dying, switching scenes. If the numbers ratchet up and never come back, report it with an Export attached.