I’ve been waiting for a good way to split up large Unity codebases into assemblies for a while now. Both for keeping compile times low (for Modbox I was waiting 20+ seconds every recompile), and for better code modularity. It was possible before to do this outside of Unity and just import as a plugin – but that would mean manually compiling the library on every code change. For code modularity I mostly wanted this so that I could set code as Internal to a certain namespace – which is especially important when making a SDK that other devs will use (like the ModboxSDK).
Unity’s new Assembly Definition File system sounded perfect – just drop a file into a folder and have that folder be it’s own assembly. My Modbox compile times went from 20+ seconds to under 3 – and I was able to split up my codebase into 11 projects.
The problem is how Assembly Definition Files ignores Unity’s old ‘magic folders’ system – where anything in a folder called ‘Editor’ will be compiled into the Editor assembly. It’s definitely good for Unity to start moving away from magic folders – but the problem is when switching over a large project (with a lot of Asset Store content) that means a massive refactor to move all the editor scripts to a single folder.
For developing you could just use include Editor folders in assemblies with game code – but then errors will come up when actually trying to build the game (since the built game can’t include the UnityEditor namespace). Or you could make a Editor only assembly for each Editor folder – but for Modbox that would result in making 60+ Editor assemblies. Which both sucks to set up and also massively increases Visual Studio’s start up time.
My way around this at first was to add a editor script to turn all assembly definition files on/off (so only when building the game would they be turned off, and developing can still have super low compile times / code modularity). Only after a few weeks did I notice all the problems with this with Unity’s UI event system (it references a script’s method based on the type, so if the Assembly changes the reference is then missing).
So my new solution was to make a script that would auto generate the Editor assembly definition files and then delete them after the game is built (so I wouldn’t have 60+ editor projects in my VS solution while developing).
In some ways it’s a hacky solution, but it actually behaves similar to how Unity handles the Editor namespace: easily use it during development, but exclude it for actually building.
To generate the Editor assemblies it looks for a ‘parent assembly’, and builds it’s references based on that (also uses it to set the right name). It was able to generate all 60+ Modbox Editor assemblies with the correct references (mostly third party asset store code).
All the code is here (add it as a Editor script):
class AssemblyDefinitionType // type used to load the .asmdef as json
{
public string name;
public List references;
public List includePlatforms;
public List excludePlatforms;
}
public class AssemblyDefinitionSwitch
{
[MenuItem("Tools/Create EditorAssemblyDefFiles")]
public static void TurnOnAssembly()
{
CreateEditorFiles(new DirectoryInfo(Application.dataPath), null, false);
AssetDatabase.Refresh();
}
[MenuItem("Tools/Delete EditorAssemblyDefFiles")]
public static void TurnOffAssembly()
{
CreateEditorFiles(new DirectoryInfo(Application.dataPath), null, true);
AssetDatabase.Refresh();
}
static List EditorAssemblySibilings;
static int duplicateNum = 0;
static void CreateEditorFiles(DirectoryInfo Dir, FileInfo ParentAssemb, bool Remove)
{
FileInfo[] files = Dir.GetFiles();
foreach (FileInfo file in files)
{
if (file.Extension == ".asmdef")
{
ParentAssemb = file;
EditorAssemblySibilings = new List();
duplicateNum = 1;
}
}
if (ParentAssemb != null)
{
DirectoryInfo[] dirs = Dir.GetDirectories();
foreach (DirectoryInfo subdir in dirs)
{
if (subdir.Name == "Editor")
{
FileInfo[] editorfiles = subdir.GetFiles();
foreach (FileInfo file in editorfiles)
{
// Delete old asmdef editor folder files
if (file.Extension == ".asmdef")
{
File.Delete(file.FullName);
}
}
if (!Remove)
{
// create a assemb file based on the Parent one, only with includePlatforms changed to Editor
// assembly name is the parent assembly name + directory that contains the editor folder + "Editor"
AssemblyDefinitionType EditorAssemb = JsonUtility.FromJson(File.ReadAllText(ParentAssemb.FullName));
EditorAssemb.references.Add(EditorAssemb.name); // add parent assembly as a reference
EditorAssemb.references.AddRange(EditorAssemblySibilings); // for subeditor folders to reference the above editor assemblies
EditorAssemb.name = "z_" + EditorAssemb.name + Dir.Name + "Editor"; // added z_ to be at bottom of solution explorer
if (EditorAssemblySibilings.Contains(EditorAssemb.name))
{
EditorAssemb.name += duplicateNum;
duplicateNum += 1;
}
EditorAssemblySibilings.Add(EditorAssemb.name);
EditorAssemb.includePlatforms = new List();
EditorAssemb.includePlatforms.Add("Editor");
File.WriteAllText(Path.Combine(subdir.FullName, EditorAssemb.name + ".asmdef"), JsonUtility.ToJson(EditorAssemb));
}
}
}
}
DirectoryInfo[] dirs2 = Dir.GetDirectories();
foreach (DirectoryInfo subdir in dirs2)
{
if (subdir.Name != "Editor")
CreateEditorFiles(subdir, ParentAssemb, Remove);
}
}
}

