Package management for Unity projects
As many of us have found; when working with even small projects you end up having many variations of reusable source code scattered across the projects. In some situations you will even find yourself having a project that has many versions of the exact same JSON library since each asset bundles it within a unique (or sometimes clashing) namespace.
Clearly this is a problem that needs to be addressed! The obvious solution is to utilize a package manager so that common dependencies can be shared. A package manager also makes it easier to update to newer versions of a package whilst at the same time allowing you to use a specific version of a package when needed.
So if you need to make an important change to a package that is common to a number of your projects, you can make that change centrally and then simply rebuild each of the projects using the latest version of that package. Another nice advantage is that this encourages you to manage each reusable unit within their own repositories.
At the time of writing, Unity doesn’t actually have its own package manager; and to make matters worse, none of the existing package managers are ideal. Having evaluated a few options I found that NuGet and npm were the most viable however each with their own shortcomings.
NuGet is quite complex to setup for this use case and revolves around having compiled assemblies. One of the things that I wanted to move towards, especially for open source projects, was to switch over to source distribution rather than DLL distribution since it is far easier to debug problems.
npm on the other hand is rather straightforward to setup and provides all of the features necessary to manage packages and their dependencies. In fact npm5 can even install npm packages directly from a git repository using semver encoded tags. This is nice because it means that we don’t have to clutter the npm registry with packages that don’t really fit into the regular node.js ecosystem. However, npm puts all packages into a hard-coded “node_modules” directory.
At first I raised a question in the npm issue tracker which asked about a feature whereby a custom directory name could be used for installed packages. I did some searching and found that shadowmint had been using npm for Unity focused packages (see unity-package-template) where each package effectively installs itself into the Unity project. Whilst this works I felt uncomfortable with potentially having quite inconsistent package syncing logic across many packages. Not to mention that the packages become exclusive to Unity projects (when UE4 has C# support it’s highly plausible that such packages could work with both).
So I did some experimentation and found a couple of different ways to sync npm packages into the Unity project. I found that it was fairly straightforward to create a package whose soul purpose is to sync such packages unity3d-package-syncer. This can then be run any time packages are installed, updated or uninstalled.
I soon discovered that actually I didn’t want to synchronize the entire contents of the package folder into the Unity project since the folder would often contain javascript source files that Unity would misinterpret as UnityScript. In addition packages could contain various things that are only relevant to the development of the package such as makefiles, unit tests, etc. This rendered the issue of npm forcing you to use the “node_modules” directory a moot point since you actually don’t want to install directly into the Unity project’s “Assets” folder.
I chose to synchronize packages into the “Plugins” directory of the Unity project because this avoids the frequent recompilation of package scripts each time user scripts are modified since Unity places them into different assemblies. The “Plugins” directory also allows packages to declare internals without exposing them to project specific scripts.
This doesn’t prevent you from constructing packages where DLLs are compiled from sources that are then synchronized into the Unity projects. It is straightforward to setup such a workflow.