Profiler da Unity: Achar o Que Está Travando o Jogo

Unity Profiler na prática: CPU ou GPU, Hierarchy e Timeline, GC Alloc, por que o EditorLoop engana no Play Mode e como medir numa Development Build no celular.
Profiler da Unity: Achar o Que Está Travando o Jogo
O unity profiler mostra quanto tempo cada parte do jogo gasta em cada frame, e isso é o que separa otimizar com certeza de otimizar no chute. O uso que resolve a maioria dos casos: abrir em Window > Analysis > Profiler, achar o frame com pico, descobrir se o tempo está na CPU ou na GPU, e descer na view Hierarchy até a função culpada, olhando também a coluna GC Alloc.
O problema é que o Profiler no Play Mode mente um pouco: soma o custo do editor, que não existe no jogo final. Este post mostra a leitura correta, o caso clássico do EditorLoop enganando a medição, como medir numa build de desenvolvimento e no celular, e como marcar seu próprio código com Profile Markers no Unity 6.3 LTS.
Quando abrir o Profiler
Abra quando há um sintoma concreto:
- FPS abaixo do alvo de forma constante.
- Engasgos: o jogo roda bem e trava por um instante, às vezes em intervalos regulares.
- Uma cena específica pesa e outras não.
- Carregamento de fase demora.
- O jogo esquenta o celular ou drena bateria.
Não abra para "otimizar por garantia" com o jogo rodando liso. Cada hora gasta nisso é uma hora a menos fazendo o jogo. A teoria geral (CPU bound, GPU bound, orçamento de frame) está no post sobre otimização de performance para 60 FPS; aqui é a ferramenta.
Unity Profiler: janela e módulos
Abra com Window > Analysis > Profiler (atalho Ctrl+7). Com o Profiler aberto, dê Play: ele grava os últimos frames num gráfico.
A janela tem duas metades:
- Em cima, os módulos: gráficos por categoria. Os mais usados são CPU Usage (tempo de scripts, física, renderização), GPU Usage (precisa ser ativado e não funciona em todas as plataformas), Rendering (batches, triângulos), Memory e Audio. Clique em Profiler Modules para ligar ou desligar.
- Embaixo, o detalhe do frame selecionado no módulo ativo.
Na barra superior, o seletor de alvo escolhe o que medir: Play Mode, Edit Mode ou um jogo rodando em outro lugar (build local, celular). Para picos raros, pause o jogo e arraste no gráfico até o frame alto; clicar no gráfico seleciona o frame.
CPU Usage: Timeline vs Hierarchy
No módulo CPU Usage, o painel de baixo tem um seletor de view.
Timeline mostra cada thread como uma faixa, com blocos na ordem em que aconteceram. Serve para ver quando as coisas rodam e se a thread principal está esperando outra. Se você vê blocos longos de Gfx.WaitForPresentOnGfxThread ou parecidos na thread principal, a CPU está esperando a GPU terminar: o gargalo é GPU, e mexer em script não ajuda.
Hierarchy agrupa as chamadas em árvore e soma os tempos. É onde você acha o quê custa. Colunas importantes:
- Total e Self: porcentagem do frame incluindo ou não os filhos.
- Time ms e Self ms: o mesmo em milissegundos.
- Calls: quantas vezes a função rodou no frame. 400 chamadas de um
Updateque você esperava rodar 1 vez já é uma pista. - GC Alloc: memória alocada no heap gerenciado naquele frame.
O caminho típico: ordene por Time ms, expanda PlayerLoop, desça até Update.ScriptRunBehaviourUpdate e veja qual script aparece no topo.
Raw Hierarchy mostra cada chamada separada, sem agrupar, útil quando você precisa saber qual instância específica foi a lenta.
GC Alloc e picos de travada
A coleta de lixo do C# para o jogo por alguns milissegundos para liberar memória que não é mais usada. Se o seu código aloca um pouco todo frame, a coleta vem em intervalos e cada uma vira um engasgo visível.
Como achar:
- No gráfico do CPU Usage, procure picos regulares. Selecione um e procure
GC.Collectna Hierarchy. - Volte para um frame comum e ordene a Hierarchy por GC Alloc. Tudo que aloca toda vez aparece ali.
- Para saber a linha exata, ative Call Stacks na barra do Profiler e selecione as amostras
GC.Alloc.
Um exemplo de alocação escondida. Arquivo EnemyScanner.cs, antes:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class EnemyScanner : MonoBehaviour
{
[SerializeField] private float radius = 10f;
private void Update()
{
Collider[] hits = Physics.OverlapSphere(transform.position, radius);
List<Collider> enemies = hits.Where(h => h.CompareTag("Enemy")).ToList();
if (enemies.Count > 0)
{
Debug.Log("Inimigos: " + enemies.Count);
}
}
}
Três alocações por frame: o array do OverlapSphere, a lista do LINQ e a string do log. A versão sem alocação, mesmo arquivo:
using UnityEngine;
public class EnemyScanner : MonoBehaviour
{
[SerializeField] private float radius = 10f;
[SerializeField] private LayerMask enemyLayer;
private readonly Collider[] hits = new Collider[32];
public int EnemyCount { get; private set; }
private void Update()
{
EnemyCount = Physics.OverlapSphereNonAlloc(transform.position, radius, hits, enemyLayer);
}
}
O buffer é criado uma vez, a layer filtra os inimigos sem LINQ e o log sai do Update. A coluna GC Alloc desse script vai a zero.
EditorLoop: por que medir em build
Segundo o manual do Unity 6.3 (página "Play mode and Editor profile samples"), o Profiler separa as amostras em dois grupos: PlayerLoop, que é o seu jogo, e EditorLoop, que é o editor (Inspector, Scene View, janelas). No Play Mode, o tempo do EditorLoop vai para a categoria Others dos gráficos e não aparece detalhado.
O caso clássico: o gráfico mostra o frame a 25 ms, a maior faixa é Others, e a pessoa passa uma tarde caçando um problema que não existe. É o editor desenhando a Scene View aberta ao lado, ou um Inspector atualizando um objeto selecionado.
Para tirar a dúvida:
- Feche a Scene View e desmarque objetos no Hierarchy antes de medir.
- Olhe o tempo do PlayerLoop, não o total do frame.
- Ou, melhor, meça uma build.
Saber ler o Profiler é uma habilidade isolada. O que trava de verdade é aplicar isso num projeto inteiro: decidir quais sistemas reescrever, como estruturar spawns, UI e física para não precisar remendar no fim, e fazer isso sem quebrar o que já funciona. Essa visão de arquitetura vem de construir jogos completos com alguém revisando o caminho.
Profiling em build de desenvolvimento e no celular
- File > Build Profiles, selecione a plataforma.
- Marque Development Build e Autoconnect Profiler. Deep Profiling fica desligado.
- Build And Run. O jogo abre e conecta sozinho ao Profiler.
- Se não conectar, use o seletor de alvo da janela do Profiler e escolha a build ou o aparelho na lista.
No Android, com o celular no USB e depuração ativa, o alvo aparece na lista. No iOS, pelo projeto Xcode rodando no aparelho. Em rede local, o Profiler encontra builds na mesma rede.
A build de desenvolvimento tem um pequeno custo extra, mas é próxima o bastante do jogo final. É a medição que vale para decidir.
Duas ferramentas oficiais complementam:
- Memory Profiler (pacote
com.unity.memoryprofiler, versão 1.1.12 no Unity 6.3): tira fotos da memória e mostra quais texturas, malhas e objetos ocupam mais. Indispensável para celular. - Profile Analyzer (
com.unity.performance.profile-analyzer, versão 1.4.2 no Unity 6.3): compara capturas, por exemplo antes e depois de uma otimização, com média e mediana por marcador.
Também existe o Standalone Profiler, que roda o Profiler em processo separado do editor e evita que a própria janela pese na medição.
Profile Markers no seu código
Quando a Hierarchy mostra só "MeuScript.Update" e você quer saber qual parte do método custa, marque trechos com ProfilerMarker. Arquivo PathfindingSystem.cs:
using Unity.Profiling;
using UnityEngine;
public class PathfindingSystem : MonoBehaviour
{
private static readonly ProfilerMarker RebuildGridMarker = new ProfilerMarker("Pathfinding.RebuildGrid");
private static readonly ProfilerMarker FindPathsMarker = new ProfilerMarker("Pathfinding.FindPaths");
private void Update()
{
using (RebuildGridMarker.Auto())
{
RebuildGrid();
}
using (FindPathsMarker.Auto())
{
FindPaths();
}
}
private void RebuildGrid()
{
// atualiza a grade de navegação
}
private void FindPaths()
{
// calcula caminhos dos agentes
}
}
Os marcadores aparecem com esse nome na Hierarchy e na Timeline, em builds de desenvolvimento também. O using com .Auto() garante que o marcador fecha mesmo se o método sair no meio. Criar o marcador como static readonly evita custo de criação a cada frame. Em build de release, o custo é desprezível.
Erros comuns: otimizar pelo editor
- Confiar no FPS do Play Mode. O editor soma EditorLoop e roda numa GPU de PC. Meça build.
- Deep Profiling ligado o tempo todo. Ele instrumenta todas as chamadas C# e distorce os tempos. Use só para investigar um trecho e prefira markers.
- Olhar a média e ignorar picos. 16 ms de média com picos de 80 ms parece ok no número e trava na tela. Selecione os picos.
- Otimizar CPU quando a espera é pela GPU. Se a thread principal espera o Present, reduza resolução, overdraw e efeitos.
- Medir uma vez e mudar dez coisas. Mude uma, meça de novo. Sem isso você não sabe qual mudança ajudou.
- Esquecer o Debug.Log. Log em
Updatealoca string e escreve em disco. Em build, isso aparece no Profiler como custo real.
Próximo passo
Pegue a cena mais pesada do seu jogo, gere uma Development Build com Autoconnect Profiler e anote: tempo médio de frame, o pior pico, os três scripts com mais Time ms e os três com mais GC Alloc. Corrija o primeiro da lista de GC Alloc, meça de novo e compare no Profile Analyzer. Se o problema for muitos projéteis criados e destruídos, o post sobre object pooling mostra o padrão; se o gráfico apontar renderização, leia sobre draw calls e batching na Unity.
Perguntas frequentes
O que é EditorLoop no Profiler da Unity?
É o grupo de amostras do trabalho do próprio editor: Inspector, Scene View, janelas abertas. No Play Mode, o Profiler joga esse tempo na categoria Others e não mostra detalhe. Ele não existe na build do jogo, então um EditorLoop alto não significa que o jogo está pesado.
Como abrir o Profiler da Unity?
Pelo menu Window > Analysis > Profiler, ou com o atalho Ctrl+7 no Windows (Cmd+7 no Mac). Para medir uma build, gere com Development Build e Autoconnect Profiler marcados e escolha o aparelho no seletor de alvo no topo da janela.
Por que o jogo trava de tempos em tempos sem motivo?
Engasgo periódico costuma ser o coletor de lixo. Procure picos com GC.Collect no Profiler e, na view Hierarchy, ordene pela coluna GC Alloc para achar quem aloca memória todo frame. Corrigir essas alocações elimina a travada regular.
Deep Profiling vale a pena?
Só para investigar um trecho específico. Ele instrumenta todas as chamadas de método C#, o que deixa o jogo bem mais lento e distorce o tempo absoluto. Use Profile Markers no seu código para medir com precisão sem esse custo.


