Unity Assembly Definitions: Faster Compile Times Explained

Illustration: modular code blocks arranged into separate connected modules

If every small script edit leaves you staring at the “Compiling scripts” spinner for ten or twenty seconds, your project is probably compiling everything into one giant assembly. Unity assembly definitions let you split your code into separate assemblies so Unity only recompiles the parts that changed and the parts that depend on them. This guide explains how assemblies work, how to split a real project, how references and editor-only code fit in, how test assemblies work, and how to fix the errors that appear the moment you add your first .asmdef file.

What an Assembly Definition Actually Does

An assembly is a compiled .dll. When you write C# in Unity, the compiler turns your scripts into one or more assemblies in Library/ScriptAssemblies. An Assembly Definition asset (.asmdef) placed in a folder tells Unity: “every script in this folder and its subfolders belongs to a separate assembly with this name.”

That has three effects:

  • Faster iteration. Change a script in assembly A and Unity recompiles A plus anything that references A. Assemblies that don’t depend on A are left alone.
  • Enforced boundaries. Code in one assembly can only see another assembly if it explicitly references it. Circular references are not allowed, which nudges you toward cleaner architecture.
  • Platform and define control. Each assembly can be limited to certain platforms (such as Editor only) or only compiled when a scripting define is present.

Note that a domain reload still happens after every compile, and for many projects that reload is a big chunk of the wait. Assembly definitions shrink the compile part, not the reload part. We cover the reload in the measuring section.

The Default Assemblies and Why They Get Slow

Without any .asmdef files, Unity sorts your scripts into a handful of predefined assemblies:

  • Assembly-CSharp-firstpass: scripts inside folders named Plugins (and a few other special folders).
  • Assembly-CSharp: every other runtime script in Assets.
  • Assembly-CSharp-Editor-firstpass and Assembly-CSharp-Editor: scripts in folders named Editor.

The problem is Assembly-CSharp. In a small project it is fine. Once you import a few big Asset Store packages that ship loose scripts, plus your own few hundred files, every edit recompiles all of it. Editing a single line in your player script also recompiles that 300-file dialogue system you imported last year.

Creating Your First Assembly Definition

  1. In the Project window, pick a folder that holds a self-contained chunk of code, for example Assets/_Project/Scripts/Runtime.
  2. Right-click it and choose Create > Scripting > Assembly Definition. (In older versions it is under Create > Assembly Definition.)
  3. Name the asset something unique, like Game.Runtime. The Name field in the Inspector is the actual assembly name, so keep them matching.
  4. Click Apply. Unity recompiles and every script under that folder now lives in Game.Runtime.dll.

The asset is just JSON. A simple one looks like this:

{
    "name": "Game.Runtime",
    "rootNamespace": "Game",
    "references": [
        "Unity.InputSystem",
        "Unity.TextMeshPro",
        "Unity.Cinemachine"
    ],
    "includePlatforms": [],
    "excludePlatforms": [],
    "allowUnsafeCode": false,
    "autoReferenced": true,
    "defineConstraints": [],
    "noEngineReferences": false
}

Setting Root Namespace means new scripts you create in that folder get the namespace automatically. It is optional but tidy.

The moment you click Apply, expect compiler errors. Your scripts could previously see every package automatically; now they can only see what you reference. That is normal, and the references section fixes it.

A Folder Layout That Works

The biggest mistake is making dozens of tiny assemblies. Each assembly adds overhead, and a web of references means a change near the bottom still recompiles everything above it. Aim for a handful of assemblies split along real dependency lines:

Assets/_Project/
    Scripts/
        Core/            Game.Core.asmdef      (utilities, interfaces, data types)
        Gameplay/        Game.Gameplay.asmdef  (references Game.Core)
        UI/              Game.UI.asmdef        (references Game.Core)
        Editor/          Game.Editor.asmdef    (Editor only, references the others)
    Tests/
        EditMode/        Game.Tests.EditMode.asmdef
        PlayMode/        Game.Tests.PlayMode.asmdef
ThirdParty/
    SomeBigPackage/      SomeBigPackage.asmdef

Rules of thumb:

  • Put stable code at the bottom. Core utilities rarely change, so the assemblies above them rarely rebuild because of Core.
  • Put code you edit constantly at the top. If Gameplay is where you spend your day and nothing references Gameplay, every edit there recompiles only Gameplay.
  • Wrap big third-party folders. If an Asset Store package ships loose scripts with no .asmdef, adding one around it is often the single biggest win, because it drops out of Assembly-CSharp completely.
  • Use interfaces or events to talk upward. If Core needs to notify UI, Core can’t reference UI (that would be circular). Define an event or interface in Core and let UI subscribe. Our C# events and delegates guide covers the pattern.

Remember that Assembly-CSharp references every auto-referenced assembly, so any leftover scripts outside your asmdef folders can still use your code. The reverse is not true: your asmdef code can never see scripts that live in Assembly-CSharp.

References, Packages and Plugins

Select the .asmdef and look at Assembly Definition References. Click + and drag in, or pick, the assemblies this code uses. Common ones:

  • Unity.InputSystem for the Input System package.
  • Unity.TextMeshPro for TMP text (still the assembly name, even where TMP is bundled with uGUI in Unity 6).
  • Unity.Cinemachine for Cinemachine 3.
  • Unity.RenderPipelines.Universal.Runtime and Unity.RenderPipelines.Core.Runtime if you touch URP Volume or camera data from code.
  • Unity.Addressables and Unity.ResourceManager for Addressables.

To find the right name, look inside the package folder in the Project window (under Packages) for its .asmdef and read its Name field. uGUI types like UnityEngine.UI.Image live in the UnityEngine.UI assembly, which you also need to reference.

The Use GUIDs option stores references by GUID instead of name, so renaming an assembly does not break other assemblies pointing at it. Leave it on.

Precompiled .dll plugins are handled separately. By default an asmdef references all precompiled assemblies that have Auto Reference enabled. If you tick Override References, you must list the .dlls you need under Assembly References by hand.

Auto Referenced on your own asmdef controls whether Assembly-CSharp sees it automatically. Leave it on unless you specifically want to hide a library from loose scripts.

Editor-Only Assemblies

Once your runtime code is in an asmdef, the special Editor folder rule changes. A folder named Editor inside an asmdef folder is still part of that asmdef, so its using UnityEditor; code would end up in your build and fail. You need a separate editor assembly.

  1. Create an .asmdef in the Editor folder, for example Game.Editor.
  2. Under Platforms, untick Any Platform and tick only Editor.
  3. Reference the runtime assemblies whose types your editors inspect (Game.Gameplay and so on).

Custom inspectors, property drawers and menu items all go here. If you are writing editor tools for the first time, our editor scripting 101 guide walks through them.

For small bits of editor code inside a runtime script, you can still wrap it in #if UNITY_EDITOR … #endif. That works in any assembly.

💻 Compile times still painful?

Assembly definitions help a lot, but a slow CPU and a spinning hard drive will always fight you. A fast multi-core processor and an NVMe SSD make compiles and domain reloads noticeably snappier. We compared laptops that handle Unity well without breaking the bank.

See our budget Unity laptop picks →

Test Assemblies

The Unity Test Framework requires tests to live in their own assembly. The easiest way is the Test Runner window (Window > General > Test Runner), which has buttons to create an EditMode or PlayMode test assembly folder. You can also do it manually:

  • Create an asmdef, then under Assembly Definition References add your game assemblies plus UnityEngine.TestRunner and UnityEditor.TestRunner.
  • Under Assembly References (enable Override References), add nunit.framework.dll.
  • Add UNITY_INCLUDE_TESTS to Define Constraints so the assembly is excluded from player builds.
  • For EditMode tests, set the platform to Editor only.

A tiny EditMode test:

using NUnit.Framework;
using Game;

public class DamageTests
{
    [Test]
    public void Damage_NeverGoesBelowZero()
    {
        var result = DamageMath.Apply(current: 10f, damage: 50f);
        Assert.AreEqual(0f, result);
    }
}

Here’s a big hidden benefit of assembly definitions: tests can only reference asmdef assemblies, not Assembly-CSharp. If your code isn’t in an asmdef, you can’t unit test it properly.

Measuring the Gain and Other Compile Tips

Don’t guess. Before and after splitting, time a one-line change:

  • Check the Editor log (Console window menu > Open Editor Log) after a compile. It records how long compilation and the domain reload took.
  • Run a few edits in the assembly you work in most and average them.

If compile time fell but the overall wait didn’t, the domain reload is your bottleneck. Options:

  • Enter Play Mode Options (Project Settings > Editor): disabling domain reload on entering Play Mode makes pressing Play near-instant. Be aware static fields then keep their values between runs, so reset them with [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)].
  • Fewer static constructors and [InitializeOnLoad] classes. Each one runs on every reload. Editor tools from packages are frequent offenders.
  • Remove unused packages. Every package assembly gets loaded on reload, whether you use it or not.

Also check your IDE: after big asmdef changes, regenerate project files (Preferences > External Tools > Regenerate project files). If IntelliSense breaks, see our IntelliSense fix guide.

Troubleshooting

CS0246: the type or namespace could not be found

Your asmdef doesn’t reference the assembly that contains the type. Find the package’s .asmdef name and add it to Assembly Definition References. If the type is one of your own scripts still sitting in Assembly-CSharp, move it into an asmdef, because asmdef code can’t reference Assembly-CSharp at all. More on this error in our compiler errors guide.

“Assembly has reference to non-existent assembly”

A referenced package was removed or renamed. Remove the broken entry from the reference list, or reinstall the package.

Cyclic dependency detected

Assembly A references B and B references A, directly or through a chain. Move the shared types into a lower assembly both can reference, or invert the dependency with an interface or event.

Build fails with “UnityEditor does not exist”

Editor code is in a runtime asmdef. Move it into a folder with an Editor-only asmdef, or wrap it in #if UNITY_EDITOR.

Tests don’t appear in the Test Runner

The test asmdef is missing the TestRunner references or nunit.framework.dll, or the tests are in a PlayMode assembly but you are looking at the EditMode tab.

Compile is still slow after splitting

The assembly you edit is low in the chain, so everything above it rebuilds. Or most of the time is domain reload rather than compile. Check the Editor log to see which.

FAQ

How many assembly definitions should a project have?

Usually between three and ten for an indie game. Split along dependency lines, not one per folder.

Do assembly definitions make my game run faster?

No. They affect compile time and code organisation in the editor, not runtime performance.

Can asmdef code use scripts in Assembly-CSharp?

No. Assembly-CSharp can reference asmdef assemblies, but never the other way round.

What is an Assembly Definition Reference (.asmref)?

It adds a folder’s scripts to an existing assembly defined elsewhere, which is handy when one logical assembly is spread across several folders.

Do I need assembly definitions for a small project?

Not for speed, but adding a couple early is cheap and makes unit testing possible later.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *