Assembly Definition na Unity: Compilar Mais Rápido
Assembly Definition na Unity é um arquivo .asmdef que separa uma pasta de scripts num assembly próprio, para que salvar um arquivo recompile só aquele pedaço e quem depende dele, e não o projeto inteiro. Se a barra de "compiling scripts" está comendo dez segundos a cada Ctrl+S, uma divisão mínima com unity assembly definition em Runtime, Editor e Tests é o primeiro ajuste a fazer. Este tutorial mostra como criar, como ligar as referências, como medir o ganho e como resolver o CS0246 que aparece logo depois.
Os nomes de menu e de campos são do Unity 6.3 LTS.
Por que a Unity demora para compilar
Sem nenhum .asmdef, todo script seu vai para um assembly predefinido chamado Assembly-CSharp (e os de pastas Editor vão para Assembly-CSharp-Editor). É um bloco único. Mudou uma linha no PlayerController? A Unity recompila o Assembly-CSharp inteiro, com todos os scripts do jogo, e todos os assemblies que dependem dele.
Depois da compilação vem o domain reload: a Unity descarrega e recarrega o código gerenciado no editor, o que reinicia estados estáticos e roda os inicializadores de editor. O tempo que você sente entre salvar e poder voltar a trabalhar é a soma dos dois.
Em projeto novo, isso é rápido. Com o crescimento do código, com pacotes da Asset Store que trazem centenas de scripts soltos em Assets e com ferramentas de editor, o tempo cresce. Esse é um custo que não aparece no Profiler do jogo, mas aparece em cada iteração sua. Otimizar o ciclo de trabalho é tão importante quanto otimizar a performance do jogo.
O que é Assembly Definition
Um assembly é a unidade de código compilado do .NET (uma DLL). Quando você cria um .asmdef numa pasta, todos os scripts daquela pasta e das subpastas, exceto as que tiverem o próprio .asmdef, viram um assembly com o nome que você escolher.
Três consequências práticas:
- Recompilação parcial. Mudou um script do assembly
Game.UI? A Unity recompilaGame.UIe os assemblies que referenciamGame.UI. O resto é reaproveitado. - Dependências explícitas. Um assembly só enxerga o código dos assemblies que ele referencia. Isso força uma arquitetura em camadas e impede que a UI dependa do inimigo e o inimigo dependa da UI.
- Controle de plataforma. Dá para dizer que um assembly só existe no editor ou só em certas plataformas.
Criando o primeiro .asmdef
Parto de uma estrutura de pastas por área, como a sugerida no post sobre organizar o projeto (o princípio vale para qualquer engine):
Assets/_Project/Scripts/
Core/ (utilitários, eventos, interfaces)
Gameplay/ (player, inimigos, armas)
UI/ (HUD, menus)
Editor/ (ferramentas de editor)
Tests/ (testes)
Na janela Project, entre em Scripts/Core, clique com o botão direito e escolha Create > Scripting > Assembly Definition. Dê o nome Game.Core. A Unity cria Game.Core.asmdef e recompila: agora todo script dentro de Core pertence a esse assembly.
Selecione o arquivo e veja o Inspector. Os campos que importam no começo:
- Name: o nome do assembly (pode ser diferente do nome do arquivo, mas manter igual evita confusão).
- Auto Referenced: quando ligado, os assemblies predefinidos (
Assembly-CSharp) referenciam este automaticamente. Deixe ligado enquanto houver código seu fora de asmdef que precisa usar este. - Root Namespace: namespace sugerido para scripts novos criados nesta pasta.
- Assembly Definition References: a lista de outros asmdefs que este pode usar.
- Platforms: com Any Platform marcado, compila para todas, exceto as que você desmarcar; desmarcado, só para as que você marcar.
- Define Constraints: o assembly só é compilado se todos os símbolos listados estiverem definidos.
Clique em Apply depois de mudar qualquer campo. Repita para Gameplay (Game.Gameplay) e UI (Game.UI).
Referências entre assemblies
Agora as dependências. Game.Gameplay usa interfaces e eventos de Game.Core. Game.UI mostra dados de Game.Gameplay e de Game.Core. Configure:
Game.Core: nenhuma referência a outros asmdefs seus.Game.Gameplay: referenciaGame.Core.Game.UI: referenciaGame.CoreeGame.Gameplay.
Para adicionar, selecione o .asmdef, clique no + em Assembly Definition References, arraste o outro .asmdef para o campo e clique em Apply.
Pacotes também são assemblies. Se o Game.Gameplay usa o Input System, ele precisa referenciar Unity.InputSystem. Se Game.UI usa TextMeshPro, precisa de Unity.TextMeshPro. Código no Assembly-CSharp enxerga esses pacotes automaticamente; código num asmdef precisa de cada dependência como uma linha na lista. Pode parecer burocracia, mas é exatamente essa lista que mostra, de relance, do que cada parte do jogo depende.
O desenho resultante é uma pirâmide: Core na base, sem depender de ninguém; Gameplay no meio; UI no topo. Mudar algo na UI recompila só a UI. Mudar algo no Core recompila tudo, então o Core deve ser pequeno e estável.
Separar código em camadas que não se enroscam é uma habilidade de arquitetura que só aparece quando o projeto passa do tamanho de tutorial. É nessa fase, com o jogo de verdade crescendo e o editor ficando lento, que quem estuda sozinho mais costuma travar e reescrever tudo do zero.
Assembly para Editor e para testes
Editor. Scripts de editor (inspectors customizados, janelas, menus) usam UnityEditor, que não existe na build. Se a pasta Editor está dentro de uma pasta com asmdef de runtime, os scripts dela entram no assembly de runtime e a build falha. Resolva com um asmdef próprio: em Scripts/Editor, crie Game.Editor.asmdef, em Platforms desmarque Any Platform e marque só Editor, e adicione as referências aos assemblies de jogo que as ferramentas inspecionam.
Testes. Com o pacote Test Framework instalado, a janela Window > General > Test Runner cria pastas de teste com o asmdef já configurado (referenciando o NUnit e o runner de testes). Os testes ficam num assembly separado que referencia Game.Core e Game.Gameplay, e não entram na build. Um bônus: lógica pura no Game.Core, sem depender de cena, é a mais fácil de testar.
Medindo o ganho de tempo de compilação
Medir evita a sensação enganosa de "parece mais rápido". Este script de editor registra no Console quanto tempo cada compilação levou e quais assemblies foram recompilados. Arquivo CompileTimer.cs, dentro de Scripts/Editor:
using System.Diagnostics;
using UnityEditor;
using UnityEditor.Compilation;
using Debug = UnityEngine.Debug;
[InitializeOnLoad]
public static class CompileTimer
{
private static Stopwatch stopwatch;
static CompileTimer()
{
CompilationPipeline.compilationStarted += OnCompilationStarted;
CompilationPipeline.assemblyCompilationFinished += OnAssemblyFinished;
CompilationPipeline.compilationFinished += OnCompilationFinished;
}
private static void OnCompilationStarted(object context)
{
stopwatch = Stopwatch.StartNew();
}
private static void OnAssemblyFinished(string assemblyPath, CompilerMessage[] messages)
{
Debug.Log($"Compilado: {System.IO.Path.GetFileName(assemblyPath)}");
}
private static void OnCompilationFinished(object context)
{
if (stopwatch == null)
{
return;
}
stopwatch.Stop();
Debug.Log($"Compilação total: {stopwatch.Elapsed.TotalSeconds:F2} s");
}
}
O teste: antes de criar os asmdefs, mude uma linha num script de UI, salve e anote o tempo e a lista de assemblies. Depois da divisão, repita a mesma mudança. A lista deve encolher para Game.UI (e quem depende dele), e o tempo deve cair na proporção do código que deixou de ser recompilado. Não espero um número fixo: o ganho depende de quanto código seu e de terceiros estava no Assembly-CSharp.
O domain reload não entra nessa conta e pode ser a maior parte da espera. Para cortar a espera ao apertar Play, abra Edit > Project Settings > Editor, seção Enter Play Mode Settings, e no campo When entering Play Mode escolha Reload Scene only (desliga o domain reload ao entrar no Play Mode). A contrapartida: campos static e eventos estáticos não são zerados entre uma sessão de teste e outra, e o seu código precisa reiniciar esses valores por conta própria.
Erros comuns: CS0246 após criar asmdef e referência circular
CS0246: The type or namespace name 'X' could not be found. É o erro mais comum logo depois de criar um asmdef. Causas, em ordem de frequência:
- O tipo está noutro asmdef seu que não foi adicionado em Assembly Definition References. Adicione e clique em Apply.
- O tipo está num pacote (
InputAction,TMP_Text,CinemachineCamera) e falta a referência ao assembly do pacote (Unity.InputSystem,Unity.TextMeshPro,Unity.Cinemachine). - O tipo ficou no
Assembly-CSharp, fora de qualquer asmdef. Asmdef não pode referenciar o assembly predefinido. Mova o script para um asmdef ou mova a dependência para baixo na pirâmide.
O post sobre o erro CS0246 na Unity cobre as outras origens desse erro, como using faltando e pacote não instalado.
Referência circular. Game.Gameplay referencia Game.UI, e Game.UI referencia Game.Gameplay. A Unity recusa e acusa dependência cíclica. Isso é um sinal de arquitetura: duas camadas que se conhecem mutuamente. A solução clássica é mover o que as duas compartilham (uma interface, um evento) para Game.Core e fazer a camada de baixo avisar a de cima por evento, sem referência direta.
Build quebrando por script de editor. Um script com using UnityEditor; caiu num asmdef de runtime. Mova para o asmdef de Editor ou envolva o trecho em #if UNITY_EDITOR.
Granularidade exagerada. Um asmdef por pasta pequena gera dezenas de assemblies, cada um com sua lista de referências para manter. Comece com três ou quatro e divida só onde a medição mostrar ganho.
Próximo passo
Instale o CompileTimer no seu projeto atual, meça três salvamentos típicos e anote os tempos. Depois crie só dois asmdefs (Game.Core e Game.Gameplay), corrija os CS0246 que aparecerem e meça de novo. Com os números na mão você decide se vale dividir mais.
Perguntas frequentes
O que é asmdef na Unity?
É um arquivo Assembly Definition (.asmdef) que transforma os scripts da pasta onde ele está, e das subpastas, num assembly separado (uma DLL própria). Assim a Unity recompila só os assemblies afetados por uma mudança, em vez de todo o código do projeto.
Assembly Definition deixa o jogo mais rápido?
Não o jogo em si. O ganho é no editor: menos código recompilado a cada vez que você salva um script. Em projetos pequenos a diferença é pequena; ela cresce com o volume de código e de pacotes.
Por que meus scripts pararam de achar outras classes depois do asmdef?
Porque um assembly só enxerga os assemblies que ele referencia. Se o script foi para um asmdef e a classe que ele usa ficou no Assembly-CSharp ou em outro asmdef não referenciado, aparece o erro CS0246. Adicione a referência em Assembly Definition References ou mova a classe para um assembly que possa ser referenciado.
Posso referenciar o Assembly-CSharp a partir de um asmdef?
Não. Os assemblies predefinidos (Assembly-CSharp e Assembly-CSharp-Editor) referenciam os seus asmdefs, mas não podem ser referenciados por eles. Por isso a migração costuma começar pelo código mais básico, de que os outros dependem.



