So this post has been delayed for quite a while… One question I got from many sources after my Advanced Editor Scripting talk with Adam Mechtley at Unite ’10 was “so how did you do that assembly building thing?”.
Well I did it before we had a port of MonoDevelop available, so my options were limited. Now that we do have that port, you’ll be able to redo this exercise in there with some GUI drag-drop nicenessness. However, just because I can (and because it is healthy learning and lovely hacky) I will provide the command-line/perl script solution, which is still my approach.
First a quick “why”: As mentioned in my Unite talk, there are three reasons I wrap my projects in .net assemblies before distributing – and no it’s got nothing to do with keeping the source secure – assemblies are easily decompiled:
- Reduce deployment clutter. If I were to release Behave without being wrapped in assembly, it would span around 150 – 170 source files and textures in various folder configurations. Now I have two folders, three assemblies and about five script files.
- Reduce funky bug reports. While .net assemblies are very easily disassembled, it does at least take some dedication to do so. More than it takes to randomly change something in a full source distribution when things don’t go your way. So assemblies help you get rid of a portion of those really long bug reports which end up with "oh, by the way, I changed this one thing in your distribution – you think that might have something to do with it?.
- They can load in at runtime. This is really cool and quite useful if applicable to your scenario – for more information, check out this other tips and tricks post: downloading the hydra.
In this post I am going to show you how to build your scripts into a .net assembly (.dll) and add it to your Unity project. And just to spice things up, lets take that in reverse: Once your assembly is built, copy that bastard into your Assets folder. Done.
Easy, right? Now, let’s take a look at the assembly building. And, before you start, yes this will run on all Unity supported platforms and no it does not require pro – run this on the base Unity license without issues.
So first off, if you are on windows, you’ll be needing a perl install. I usually got for ActivePerl. Not saying it is more awesomerer than anything else out there – just that I know it and it usually works just fine. If you are on OS X, you’re in luck, as perl comes preinstalled.
Now, let’s get perl hacking. This is in no way meant as a “my first perl script” guide. We’re just getting introduced to a means to an end – and it is not going to be pretty. So first off, create a new text file in your favorite text editor and save it as build.pl. Start the file off like so:
Now, that was a bit of a mouthful, but it should be pretty straight-forward. Lines three through eight include a lot of perl functionality we will be using later. And actually the last two ones are already used in line nine. Here we’re ensuring that the rest of the script is being executed in the context of the folder containing the file.
In the next few lines of interest, three variables are declared – pathes to the mono compiler shipped with Unity and the UnityEngine and UnityEditor assemblies. Notice that these are OS X pathes, for a windows setup (or if you installed Unity into a different folder than the default), you’ll need to correct these.
Debug command-line parameter
Next up, let’s add support for command-line parameters. “Why?” you ask – because they are going to be awesome for debugging our assembly later. The only command-line parameter we will be supporting for now is “—release”. This will basically let us toggle, when building the assembly, a bunch of logging and other stuff on or off. This is what it looks like:
We declare a variable named optionRelease, initialize it to zero and then call GetOptions, telling it to pass data on the use of the “release” parameter into our variable. Now, GetOptions has a bunch of functionality, but the way we use it here, it simply assigns one or zero to our variable – depending on whether the parameter is used or not.
Next up, we need to use this variable to affect the parameters we pass to the gmcs compiler. Like so:
The idea here is that gmcs takes a parameter -d:[name] which instructs it to, during pre-processing, run with the symbol [name] defined. Something we can work with in our code like this:
Building two assemblies
Next up, a bit of info. Since Unity 3, we’ve enforced that scripts and assembly depending on UnityEditor, even if not using that, cannot be included in builds. This means that if you have some editor scripts, you would also like built to assembly, you will need to split these out into a different assembly than the one containing your runtime scripts.
Lets’s assume that is the case for this exercise, that the runtime scripts are located in Source/ and that the editor scripts are located in Source/Editor. So we will have to build two assemblies. That could mean code duplication, which should cause anyone pain, so let’s be nice and wrap some of the building up in a perl subroutine:
Let’s take this subroutine line by line. First off we pull in the global variable “compiler”. After this, we populate the variables target, out, arguments and source from subroutine parameters. With these at hand, the fun begins. First off, we print out the name of our build target (MyAwesome.dll) and the command we’re planning to execute – marked in fashionable blue.
Second we actually do the building – colouring any compiler output red (no news is good news – output is bad). If compilation fails, we kill the script – allowing the user to take action. If all went well, we remember to reset the colouring before returning from the subroutine.
So how about some use of this subroutine, eh?
As you see, we first build the runtime assembly from Source/*.cs, with the RUNTIME and optionally the DEBUG symbol defined. We also make sure to include the UnityEngine assembly in the building, so that you can reference stuff from the UnityEngine namespace.
In the second line, we build the editor assembly – containing all our editor code from Source/Editor/*.cs. This time we define the EDITOR and optionally the DEBUG symbol during building. This assembly references not only the UnityEngine assembly, but also the UnityEditor one and the runtime assembly which was just built in the line above.
And that’s about it! As mentioned early in this post, all you need to do in order to include these assemblies in your project is to drag them into your assets folder, but if you’re very nice, you could just make that copy a part of the build script:
Notice that this uses the OS X cp command-line tool. For windows, replace with equivalent.
Limitation: Editor classes in assemblies
While MonoBehaviours can live just fine in assemblies, editor scripts (ScriptableObject derived classes) come with a limitation (currently still active, as of Unity 3.1): You can not initialize these classes via UnityEditor API calls. That would be calls like ScriptableObject.CreateInstance and EditorWindow.GetWindow.
However – you can create instances of classes via “new” just fine and you can derive scripts located outside of the assembly and instantiate those via UnityEditor calls without incident. Say for example that MyEditorWindow derives from EditorWindow and is located in an assembly. Now I cannot instantiate it via GetWindow, but if I create a .cs file in my assets folder containing a class which does nothing but inherit from MyEditorWindow, then that class can be instantiated via GetWindow just fine and will have the exact same behaviour.
Extra trick: Including resources in the assembly
For extra points, you can include resources, such as textures, in your assembly. This has the bonus of limiting the clutter of your distribution even more.
Including the resource in your build is as simple as appending the “-resource:[path]” parameter to your gmcs build command. This will add the bytes into the assembly manifest – under the name of the original file. You can fetch these bytes at runtime, using that name. For example, here’s some utility methods for fetching textures:
Given that you added “-resource:path/to/Logo.png” to your gmcs build command, you will with the above utility methods be able to fetch that into a texture like so:
Well, I hope you guys found some of this useful! I’ll see if I can’t do a follow-up on how to do this in MonoDevelop.