The importance of Re-use
Here’s a story that may sound familiar:
You downloaded Unity, watched some tutorials on YouTube, and had a physics “game” running the same day. Excited, you started writing the game you’ve always wanted to make. Possibly – bitten before when trying this with GameMaker etc – you thought to organize your source code: folders, subfolders, logically-named scripts, etc.
Fast-forward a few months, and you have 50 scripts in 40 sub-folders, some of it re-usable, some of it hardcoded for your game. Hoooo-Kayyyy… Well, it’s not too bad. And then a new opportunity comes along – a paid contract, a game-jam, a collaboration with a friend – and you want to re-use some code from your main project. You look at the folder structure, you look at how intertwined it’s all become … and you weep.
Without re-use, what seemed like an “easy” project becomes just a little too much work, and you never finish it. Back to square one :(.
Code re-use in Unity
There are five major aspects to code re-use when making Unity apps. Most of these are shared with general programming, but these are the ones I’ve found *especially* important when doing Unity development.
Good names
The best way to get good at naming things in a program is to focus on “what makes a bad name”, and “Don’t do that”. e.g.
- If you have to open a Script to remember what it does … it has the wrong name.
- If you keep typo’ing the script when referencing it in other scripts … it’s the wrong name
- If your script is more than a thousand lines long … it’s the wrong name AND you’ve put too much junk in there; split it!
- If you can’t find a script, because you can’t remember which folder it’s in … the folder name is wrong, AND the script name is wrong (should have been a name that forced you to put it in the right place first time!)
- …etc
Renaming scripts in Unity is a minor pain: you rename it, then you have to close the file in MonoDevelop, re-open it, and (assuming it was a C# class) rename the class in source-code too. (I keep hoping latest Unity will do this automatically, but I’m a version behind at the moment).
We need Sub-folders. Lots of Sub-Folders
… you can never have too many. Give them good names, though!
De-couple your code
Ah, now it gets tricky. The art of decoupling takes much practice to master.
Decoupling is why C# (and Java) has the “interface” keyword. The concept is much easier to understand after you cocked it up, written code you can’t re-use, because everything’s too inter-dependent.
“I can’t just copy/paste that script to my new project, because it references 3 other scripts. Each of which … references another 12. ARGH!”
Unity itself doesn’t use Interfaces (though it really ought to!) – and you have to use a little ingenuity to use them yourself (Google it, it’s only a few small caveats). But C#-interfaces simply give you a compiler-compatible way of decoupling: you have to design your classes as decoupled to start with. Again, google this and ask around – it’s too big a topic for me to do justice to here!
Package your code, so you can re-use it
Libraries. This is why The God Of Programming invented Libraries. So, how do you do a Library in Unity?
Libraries in Unity: the easy DLL
There are two kinds of DLL’s:
- Real DLL’s, as used by Real Men, who write all their code in C++
- Fake DLL’s, as used by the rest of us, who just want an easy life so we can focus on making our game
A DLL is a great way of packaging code – it’s a very widely-used standard. So, Unity uses DLL’s for this (good move) – but if you google “Unity make DLL” you’ll get distracted by the huge complexity and depth of C++/Unity integration, and you don’t need any of it. Instead, you’ll be doing “DLL light”, which is easy.
But, as with most Unity features, it’s almost entirely undocumented. And, out of the box, it will fail. For bonus points, Unity will give you completely the wrong error message when it goes wrong. Yay!
Step 1: write a Unity script in Unity
Do something simple. I wrote a class that makes random numbers, following XKCD’s advice:
[csharp]
using UnityEngine;
/** In Unity 3, you cannot have namespaces. So comment out this next line. Unity 4 is fine */
namespace MyTestDLL
{
public class DLLClass
{
public static int GetRandom()
{
return 4;
}
}
/** In Unity 3, you cannot have namespaces. So comment out this next line. Unity 4 is fine */
}
[/csharp]
Make a second script that uses that first one, e.g.
[csharp]
using UnityEngine;
public class TestScript : MonoBehaviour
{
void Start()
{
print( DLLClass.GetRandom() );
}
}
[/csharp]
…attach it to a GameObject, check it works.
Step 2: follow Unity’s docs on creating and using a DLL
You’d think this would be enough, but it isn’t. However, the docs are simple, direct, and I found them easy to follow.
So http://docs.unity3d.com/Documentation/Manual/UsingDLL.html = read this and do what it says
NB: they write a slightly more complex script to use than mine, with but whatever. The idea is the same.
Step 3: Use the namespace, Luke. And Interfaces. And …
Now that your DLL is compiling/building in MonoDevelop … you are no longer stuck with Unity’s arbitrary rules and restrictions! The world is yours!
(so, even in Unity 3, you can now use the namespace. Which makes it MUCH easier to keep your code well-organized ;))
Step 4: What about the [square brackets]?
These Just Work ™ exactly as they did in Unity scripts. e.g.
[csharp]
using UnityEngine;
namespace MyTestDLL
{
/** Hey! Look! Unity editor-features … coded outside of Unity */
[ExecuteInEditMode]
[System.Serializable]
public class DLLClass
{
…
[/csharp]
Step 5: Editor extensions, editor scripts, editor GUIs, etc
This is where it breaks. If you had any Editor scripts (which, in Unity, MUST be stored in the “Editor” root folder, or one of its subfolders), you can include them in your DLL – but they won’t work.
Worse, when you trigger them (e.g. by selecting a GameObject that did custom editor rendering), you’ll get:
Error: multi-object editing not supported !
…at least, you do in Unity 3.x. Fixed in 4.x, I suspect? But I haven’t gone back to try it since I fixed the bug :).
There is some mumbo-jumbo on the internet (and Unity forums) about complicated workarounds, notably courtesy of AngryAnt (with a Jan 2014 post here, with some extra info worth reading but NOT required!). But these are long-since outdated and unnecessary. The fix is very simple: we must create TWO DLL’s!
- DLL #1: the one we already had
- DLL #2: will contain ONLY the editor-scripts, and we’ll drag/drop it into Unity’s “Editor” folder.
This is extremely logical, simple, and easy to remember. And it works beautifully! But .. how?
Step 5a: Upgrade MonoDevelop project to output not one, but two, DLL’s
First, right-click on the top-most item in MonoDevelop’s “Solution” panel, and select “Add New Project…”:
As you did earlier, choose “Library (C#)”. I recommend naming this “SomethingSomethingEditorScripts” (where “SomethingSomething” is your library name, which you used for the first DLL).
Second, you need to edit the References (as you did with first DLL), and this time repeat the steps and add ALL of:
- UnityEngine.dll
- UnityEditor.dll
- …
- AND: instead of the “.Net Assembly” tab, use the “Projects” tab and select your main DLL/library. It should be the only option
That gives you something like this:
…see how I have two “projects” in MonoDevelop, one which just uses normal Unity stuff, the other which additionally uses Unity Editor stuff? And the second one “references” the first, so it can see/use/create the classes and methods from the first one.
Step 5b: Put the DLL’s in different places
Build, and find the output files. The first project/DLL will still only be making one DLL, but the folder for the SECOND project/DLL will conveniently build BOTH projects and contain BOTH DLL’s.
Make sure you put one DLL “anywhere except Editor” and the other one “in Editor, or a subfolder of Editor”.
Finally, create some GameObject’s, open up the DLL’s in Unity’s Project view (expand the triangle) and drag/drop the Scripts onto your objects as desired. Click on them in the Scene view, and all your OnGUI, OnGizmos etc methods should run exactly as normal.
Step 6: Rejoice!
Now you can share your code with other Unity projects simply by drag/dropping the 2 x DLL’s into a new Unity project. BOOM!
Beautiful! Easy, simple, impossible-to-screw-up ;). That’s how I like my code re-use…