I have made a public BitBucket repo for this tutorial that you can find with the link below. Head is a starting point that has a C# project and a unity project. The csproj has extended MSBuild to export all scripts to the Unity project.This project was made in Visual Studio but will also work with XBuild (Xamarin Studios). The branch “completed” is the same as the first but contains all the code used in this tutorial.
What is code generation?
Code generation is an immensely powerful tool that any developer can use. In it's super basic form it’s code that outputs text. This text can then be used for many means. Output a .cs file and have Unity compile it and add it to your project. Poll the assets that are currently in your Unity project and print them out to an xml file. The sky's the limit. Below is a very simple example of a code generator without using T4 templates. This class takes in class name, a save location, and a list of enum names. All we do is start writing the class definition by appending each line one by one.
As you can see with this simple class it’s already quite large and will only get worse as the output becomes more dynamic. This would not be practical to use for larger generators as it would be very difficult to follow, hard to maintain, and difficult to debug. This is where T4 Templates come in.
Text Transformation Template Toolkit is integrated in Monodevelop and Visual Studio. It allows you to work much faster and more efficiently when creating code generators. It abstracts away the processes of writing a class line by line. The syntax is a bit awkward at first but once you start understanding it, it becomes much easier to use. Unfortunately when starting off you will find that the documentation is a little hard to follow, and when you factor in working with Unity they get all that much harder. There is a long standing bug with Unity which they have refused to fix so we have to do a small work around, this is done by extending MSBuild/XBuild to export our scripts to Unity.
An excellent example of how T4s can make things easier can be found in my most recent tool: Jiffy Editor. Jiffy Editor uses T4 templates to write custom editors and property drawers with the click of a button. Setting up a custom editor or property drawer will sometimes take up to half an hour, with T4s it can instead be done in an instant.
The left side in the input data and the right is the output of Jiffy
Using reflection I read a System.Type and find all the fields that can be serialized for the object. This data is then sent through Jiffy Editor’s generator which transforms it into a class definition which is then written to disk for you to edit. All that is left is for you is to implement your custom functionality before you have a fully flushed out customized editor.The task of creating an editor or property drawer could take 20 minutes to set up can now be done with the click of a button.
This is just one example of something you can do with code generators. The next part of this article we will write a complete generator from the ground up.
For this example we are going to try to solve a simple problem that has happened to a lot of developers. In Unity you have access to layers, tags, and sorting layers. These unfortunately have the disadvantage of only being assigned in the Editor.
Edit/Project Settings/Tags and Layers
To work with these tags in code you have to use string comparisons based on the ID defined in the inspector. To simplify this a lot of people create a static class that holds constants which relate back to these inspector values. This is all fine and dandy if none of them ever change. But what if someone were to go in and switch the Ammo and Mana tags? Your code would still work, your game would still compile, but everything would be messed up; You would get mana when you walked over Ammo Crates and ammo when you collided with your Mana Pots. This very simple mistake is now causing no amount of headaches and worst of all is wasting time that could be spent on making your game better!
To combat this all too common problem we are going to create our first code generator to manage these statics for us. If you want to write these scripts yourself as we continue through the tutorial make sure you grab the starter files located at the top of this article
Creating our generator
Let's start off by opening Visual Studio (or Monodevelop) and opening the project file. You can find the Cs Project under proj.cs/CodeGenerationWithUnity.csproj.
The first thing we want to do is create our new template. In this example I use Visual Studio but you can also do this with MonoDevelop. Create a new Runtime Text Template called LayersGenerator.tt
There are two types of templates: Design Time Text Template (just called Text Template in VS) and Runtime Text Template (The other ones you see are custom from Tangible T4). A Design Time Text Template has everything defined up front and outputs the final result as soon as you hit save. It's output is built with the project. A Runtime Text Template creates a class that is very similar to the one we made for the enum at the beginning of this article. Instead of outputting a complete class it outputs a semi processed class with a function called Transform Template. This function returns a string which is your class definition. This type of template is reusable as you can keep changing it's data to output different class definitions.
We want to create a Runtime one so we can keep reusing our template in the Unity Editor.
By default Visual Studio does not support T4 syntax highlighting (Mono does). I use Tangible T4 Editor (free version) to fix this problem and make my template more readable.. It includes basic intellisense and syntax highlighting.
Once you’ve created your new class you should see the same code as in the image above.
The first thing you’ll notice is that all of the pre-generated code is wrapped in <# and #>. These tags are used by the T4 to define what is logic and what is written to the output file. Let's review what each of these pre-generated pieces of code represent and then we’ll move on.
<#@ template language="C#" #>
Tells the template what programming language you are going to use for the generator logic. In this case we are using C# (the other option is VB). Any libraries that are usable in your language of choice are accessible with your template.
<#@ assembly name="System.Core" #>
You are going to be using the System.Core assembly in the logic of your templates
<#@ import namespace=”” #>
Tells the generator which namespaces to import from the included assemblies.
Anything you type outside of the <# #> will be written how you see it. So you mix and match the generator logic with the direct output to get the result that you want. This is where a lot of the power comes from when you are writing templates.
https://docs.google.com/uc?id=0B_423tkEkgZiMnFYWmFUbURaRHc" style="-webkit-transform:rotate(0.00rad); border:none; height:77px; transform:rotate(0.00rad); width:307px" />
Now that we have reviewed the default T4 let's take a look at what it generates. In your solution explorer find your Template and expand the elements by clicking the arrow to the left of it. You should see a .cs file with the same name as your template. This is the output of the generator. Double-click to open this file and take a look at what we are starting with. Don’t worry if this doesn’t make much sense now, I’ll explain it in more detail later.
Once you are ready to continue jump back to your template and make the following updates.
https://docs.google.com/uc?id=0B_423tkEkgZiVFVWUmZoRnpUUEU" style="-webkit-transform:rotate(0.00rad); border:none; height:189px; transform:rotate(0.00rad); width:445px" />
Here you will see that I have cleaned up the code a bit, removing some things and adding others. We did not need the extra namespaces so they were taken out. Instead we make use of the parameter T4 function to define what our class will be named and an array to store our tags later.
By default T4 engine will print out Line Directives with the hard coded path to the source file. This makes it annoying for source control. This is intended for debugging but I don’t find they help much.
<#@ paramter #>
This is used for defining variables that your template will be using. These are the arguments that we want to fill out before running our generator. In this case we have a string for the name of the class and an array of string for all the layers that Unity has. Note you have to use the full name including the namespace when defining the type.
We have also started to define what our class will be. Since the “public static class” text and the braces are not between angle brackets they will be output the same way we’ve written them. The class name however is between angle brackets along with an equal sign. This syntax tells the generator that you want it to output the value of the provided variable as the class name; Whatever the user defines as m_ClassName will be written here.
Since we’ve made some changes we should test the template to make sure it’s still working. Again find your template and open the little arrow, double-clicking the .cs file to open it.
What you are looking at is the pre-processed template. This was generated because we picked Runtime Text Template at the start. If you look just below the TransformText function (at the bottom of the screenshot above) you will see the parameters we defined in our .tt file. The generator created m_UnityLayers and m_ClassName but only gave it get accessors We are going to have to add setters later on so we can change the data.
Now you may be wondering what happened to the class we defined. It’s there, it just probably doesn’t look the way you expected it to. As I mentioned this is the pre-processed template. The work of the template is all done in the TransformText function. This is what creates our class definition. As you can see above our current class is created with 4 lines of code. This function grows in size as the classes become more complex. The template is really just a String Builder behind the scenes. What T4 gives you is some custom syntax and a bunch of helper functions which will save you a ton of time.
Actually testing your runtime template is kind of a pain. You don’t know what it’s going to look like until it’s been used; One thing that is an eternal struggle is keeping nice formatting. Since this is designed to be processed in Unity we would have to export the template to Unity in order to test it. This creates an unnecessary testing step that I like to avoid. What we are going to do is set up a Design Time Template to test our runtime template. The only difference between the two is the Design Time Template class calls TransformText for you in your Editor and adds it as a source file. You will get the results right away.
Once again add a new template to your project and call it LayersGeneratorDemo and make sure you pick Text Template not Runtime Text Template. If you expand the .tt file like we did earlier you will notice that the output is a .txt file and it has nothing in it. Obviously there are some things we will need to change.
As with our class before I have cleaned it up and removed the unnecessary includes. With a runtime template we have a new command that you have not seen before.
<# output extension=”.cs” #>
Since a design time template is processed every time you hit save you have to tell it the extension that you want the file saved as. In this case we are making a cs file so we define that here.
Our goal with this template is to have it output the contents of our other template but with the variables filled out. The first thing we have to do is import the LayersGenerator.tt into LayersGeneratorDemo.tt. Be sure to save the Template when you are done, this is what actually causes the Design Time template to execute.
As seen in Visual Studio 2015
Visual Studio now runs the Transform Text function and explodes. We have to fix the first error to get everything working.
An expression block evaluated as Null.
If you open the output file you will notice the template just stopped writing when it went to get the m_Class name that we defined earlier. T4s don’t handle nulls very well so we have to define our values.
There are quite a few ways to set values to our variables. I am going to show you the easiest one so that we can get our template working quickly.
Every parameter we define in a T4 has a corresponding field generated . Each field has a leading underscore and a trailing ‘Field’ added to the names you defined. Knowing this we take our fields and assign their values to the names that will be generated. Hit save and you should see your newly created class… Awesome!
As you might have noticed, though we set the value for our UnityLayers we didn’t actually do anything with it. This is the next step we are going to take. Open up LayersGenerator.tt and copy the code below.
There are many ways of writing templates, this is just one example.
Here we’ve taken the layers we defined in m_UnityLayers of LayersGeneratorDemo.tt and process them for output. The first block creates a constant int value for each of our layers. The second block creates a new enum named Values and adds an entry for each layer we defined. If everything worked correctly you should have the same output as below.
You're a wizard Harry!
Congratulations you just created your first T4 code generator! With this output we know this is the format that we want. Our next goal is to hook this up to allow Unity to populate our information.
We are going to have to extend our generated class so that we can hook it into Unity. Create a new cs class in your project and call it LayersGeneratorInterface.cs. Copy the code below.
Since the LayersGenerator.cs file is automatically generated we don’t want to be adding code to it or it will get erased every time we save our template. This is where the partial keyword comes into place. The two class will be combined into one when the project gets compiled. This is where we are going to define our Unity logic.
Here is the first part of our class that we are making. If you have created any Unity extensions in the past this should start to look familiar to you.
We first ask the user to pick a save location and save the value as an output path. Then we create a new instance of our LayersGenerator; This is the one that we are going to use to make a class definition. Next we save the class name based on the output path that was picked by the user. Finally we tell the generator that we want to set it’s class name field.
When generators are run they try to find their field values in one of two ways. CallContext or using a dictionary called Session. Here is the snippet of code that runs when it tries to get the value for m_ClassName.
We are using the first option of Session which is much easier to understand.
Now that we have our class name set we need to get our list of layers. Lucky for us Unity has a way to do that. Update your GenerateLayers function with the following code, the comments will break down each step of the process for you.
This template is not quite ready to be used in Unity. We are still missing one thing: We need to be able to call it. To do that we are going to add the MenuItem attribute above our GenerateLayers function.
That is it you are ready to go. Build the project and all your scripts should be exported by MSBuild. If you look at the output log you should see something like the image below. This will only happen if you used one of my two projects.
If something happened to go wrong or you made your own project just copy the source files and paste them in your Unity project. You should see the Tools menu popup at the top of Unity.
Click the menu item and choose where your class should be saved. You are now down and have completed your first ever (maybe?) code generator!
As a closing note, this tutorial is to show developers how to use code generation in Unity. It’s not saying that you should use it for all situations.
If you are curious about how the scripts are exported to Unity; check out the script in your project Properties/UnityScriptExporter.xml. This does all the heavy lifting.
If you have any question please feel free to reach out.
Follow me on Twitter @ByMayne
Thanks to @AtticusMarkane for being the Editor