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); } } }