May 20 2013

Generating a class for Layers in Unity3D

In my previous article (Generating classes for Tags), I talked about creating a class for all of the Tags used inside of Unity3D. This time, I’m going to follow a similar implementation; however, for the Layers in Unity3D. If you’d like to follow along, I’ll outline some of the key points and pitfalls in creating a simple code file generator.

You can download the final source code here. Just unzip the file into an Editor folder under your project’s Assets folder.

So, let’s start by taking a look at the class in full. Some people like to look at the code all at once.

using System.IO;
using System.Text;
using UnityEditor;
using System.Text.RegularExpressions;

public static class GenerateLayersClass
{
    [MenuItem("Tools/CodeGen/Generate Layers Class")]
    public static void GenerateLayers()
    {
        string path = "Assets/Scripts/Layers.cs";
        using (var fileStream = new FileStream(path, FileMode.Create))
        {
            using (var writer = new StreamWriter(fileStream, Encoding.UTF8))
            {
                writer.WriteLine("public static class Layers {");

                for (int i = 0; i < 32; i++)
                {
                    string name = UnityEditorInternal.InternalEditorUtility.GetLayerName(i);
                    if (!string.IsNullOrEmpty(name))
                    {
                        if (Regex.IsMatch(name, "^[0-9]."))
                        {
                            name = "_" + name;
                        }
                        writer.WriteLine(string.Format("\tpublic const int {0} = {1};", name.Replace(" ", ""), i.ToString()));
                    }
                }

                writer.WriteLine("}");
            }
        }

        AssetDatabase.Refresh();
    }
}

OK. One of the first things I will mention about layers in Unity3D. Even though we can access the names of the layers though UnityEditorInternal.InternalEditorUtility.layers, that doesn’t help us when it comes to dealing with the correct layer indices. That list of strings accessible through the layers property is a condensed list. With the actual layers in Unity3D, there are gaps in their indices, so we can’t simply iterate through that list. To solve this problem, we can do a bit of a trick.

Have you ever noticed that, in the Layers list, there are only 32 slots? You should take a look, if you haven’t seen it. Here’s a screenshot of it (with some layers I’ve spaced out to show that we don’t have to be sequential in our layering).

Layers

So, what we’re going to be doing is loop from 0 to 31 and check to see which entries actually have names.

for (int i = 0; i < 32; i++)
{
    string name = UnityEditorInternal.InternalEditorUtility.GetLayerName(i);
    if (!string.IsNullOrEmpty(name))
    {

With this, we can skip the entries that are left unnamed and still have the proper indices for those layers at the same time. Isn’t that neat? Of course, Unity might decide to change the total number of layers we can have, which means we’ll need to update our loop; but, that hasn’t happened, so we don’t need to worry about that right now.

Now, if you’ve read the last article, you might recall how we dealt with spaces in the tag’s name. We’ll keep this implementation as it’s still a valid issue for the layers; but, we’re going to take it one step further and deal with an extra issue: What happens if the name starts with a number. If you haven’t read the previous article, read this next little bit.

One problem you will undoubtedly encounter, when you’re dealing with stringified names, is spaces. If we were to just use the name of the layer as-is, we would have compile errors, since some of the built-in layer names contain spaces, and that isn’t acceptable in C#. So, here’s what we do. We’ll tell the name to give us back a modified string with all of the spaces removed. We can accomplish this quite easily, with this piece of code.

name.Replace(" ", "")

This will trim the spaces out of the name variable and give us back a modified copy that we can use for our variable names. That’s neat, right? It sure does make things easier.

Hopefully, you’ve remembered that variable names, in C# can’t start with a number. This can make it slightly more difficult to handle with our simple code generation tool. But, do not fear! The .NET Framework has a class that we can make use of to easily test for this case; Regex. Now, if you’ve never worked with the Regex class, you should take some time to read this page on the MSDN. It has a lot of useful information. Anyway, back to the article!

As I was saying, we can use the Regex class to perform our test on each of the layer names. Let’s take a quick look at the relevant code.

if (!string.IsNullOrEmpty(name))
{
    if (Regex.IsMatch(name, "^[0-9]."))
    {
        name = "_" + name;
    }
    writer.WriteLine(string.Format("\tpublic const int {0} = {1};", name.Replace(" ", ""), i.ToString()));
}

The part that we’re interested in here is the Regex.IsMatch(name, “^[0-9].”) part of the inner if statement. The IsMatch method will attempt to perform a regular expression match on the name variable we’ve filled with the layer’s name. The second part of the call is the regular expression being passed as the second argument. I won’t go into how you can construct a regular expression pattern, as that’s a bit off-topic for this article; but, check out the article at the bottom for a tutorial. What I will do, though, is quickly go over its structure.

^[0-9].

Ok, so let’s break down the regular expression. The first part, the caret (^), tells the Regex class that the pattern we’re trying to match must be at the start of the string we’re running the test against. This means that if we had a layer with the name “Ghost3″, there wouldn’t be a match; but, if we had the layer named like this, “3Ghosts”, a match would be found.

The second piece of the expression is telling the Regex class that if the first character in the string was a numerical value from 0 to 9, then we have a potential match. But, that’s not enough. The final part to the expression is the point(.) at the end. This is a special character in regular expressions. It is used to tell the pattern matching algorithm that we don’t care what character comes afterwards. It’s a very simple expression test that solves a very big blocking issue.

If a match is found in the Regex test, what we want to do is prefix the name of the layer with an underscore (_). This, of course, is completely up to you as to how you want to deal with the name; but, just be sure to have the variable name start with something other than an invalid character, otherwise, our generated file won’t compile.

Here’s a sample of the generated Layers.cs file that is based on the screenshot in this article. Pay close attention to the integer values for the constants. Can you spot the gaps in the numbers?

public static class Layers {
	public const int Default = 0;
	public const int TransparentFX = 1;
	public const int IgnoreRaycast = 2;
	public const int Water = 4;
	public const int R3 = 10;
	public const int _2D = 14;
	public const int UI = 19;
}

Now, with this generated file, we can access all of our layers using their constant identifiers, safe in the knowledge that if someone were to remove a layer, the compiler will notify them of the missing or incorrect reference to a layer. The days of hard-coding magic numbers are gone.

Regular Expressions Tutorial


May 20 2013

Generating classes for Tags

I am a big fan of not having to keep track of dynamic values, whether they are numerical or strings. I find that when you have a set of these special values, people tend to change them without letting the rest of the team know that they’ve changed. Also, I personally tend to forget what all of them are. For these reasons, I’ve come up with a quick little code generator that will help you, and potentially your team mates, from having to worry about which tags are currently existing in your project.

using System.IO;
using System.Text;
using UnityEditor;

public static class GenerateTagsClass
{
    [MenuItem("Tools/CodeGen/Generate Tags Class")]
    public static void GenerateTags()
    {
        string path = "Assets/Scripts/Tags.cs";
        using (var fileStream = new FileStream(path, FileMode.Create))
        {
            using (var writer = new StreamWriter(fileStream, Encoding.UTF8))
            {
                writer.WriteLine("public static class Tags {");

                foreach (var tag in UnityEditorInternal.InternalEditorUtility.tags)
                {
                    writer.WriteLine(string.Format("\tpublic static readonly string {0} = \"{1}\";", tag.Replace(" ", ""), tag));
                }

                writer.WriteLine("}");
            }
        }

        AssetDatabase.Refresh();
    }
}

If you’re new to Unity3D, this code is a script that runs inside of the editor. Let’s go ahead and break this code down into its important parts.

The first part is the MenuItem attribute that we’ve given to our static method.

[MenuItem("Tools/CodeGen/Generate Tags Class")]
public static void GenerateTags()
{
   // ...
}

This attribute is scanned by Unity3D to help build up its list of menu items. In our case, we’re telling Unity3D that this method should be triggered when the user clicks on the Tools > CodeGen > Generate Tags Class menu item.

Next up, is the code for creating the file.

The first part, which we’ll separate out, is the path to the file. You’ll notice that we are explicitly adding the Assets portion to the path. This is necessary, since Unity3D’s editor uses the root folder of your project as the starting point for relative paths in your code.

string path = "Assets/Scripts/Tags.cs";

Now that we have the file path set up correctly, we need to create a FileStream into which we will be writing the tag constants. For our purposes, we specify a FileMode.Create so that every time this method runs, it will replace this file with new contents.

using (var fileStream = new FileStream(path, FileMode.Create))
{
     using (var writer = new StreamWriter(fileStream, Encoding.UTF8))
     {
          writer.WriteLine("public static class Tags {");

          foreach (var tag in UnityEditorInternal.InternalEditorUtility.tags)
          {
              writer.WriteLine(string.Format("\tpublic static readonly string {0} = \"{1}\";", tag.Replace(" ", ""), tag));
          }

          writer.WriteLine("}");
     }
}

The real work that we want to pay attention to is the foreach loop. This gives us the ability to inspect all of the tags in the project so we can create a static variable that we can use instead of hard-coding the tag’s name inside of a string directly in the code.

If you’ve looked carefully through the code, you may have noticed something important. In-case you missed it, it’s this little gem.

tag.Replace(" ", "")

One of the advantages with the Tags system in Unity3D is that you can have multiple words, separated by spaces. This, unfortunately, causes problems when we create a variable name. Having spaces in a variable name is illegal and causes compiler errors. So, we need to pass the result of this Replace call so it strips out all of the spaces in the tag. We could just as easily replace the spaces with underscores if we wanted to, and could make that all uppercase. This really depends on what your coding standards are, assuming you have any defined. I chose to strip out the spaces, since Unity3D contains tags that do not have spaces splitting up the multiple words, so I like to keep it consistent.

Finally, we have

AssetDatabase.Refresh();

This call will tell Unity3D that we want to have it refresh its assets. We do this because we need to have Unity3D pick up on the changes that have been made to the created/changed file. If we didn’t have this call in the code, we would have to manually refresh the assets, or have to wait for Unity3D to realize there’s been a change, which could take a while, depending on what you’re doing with your project.

“But, what does this give me,” you ask? When we go to retrieve any GameObject using the FindGameObjectWithTag method, we no long need to hard-code any strings, like this

GameObject player = GameObject.FindGameObjectWithTag("Player");

Now, we can use the newly generated Tags constants, like this

GameObject player = GameObject.FindGameObjectWithTag(Tags.Player);

So, whenever you have custom tags defined, this generated class will allow you and your team to easily know which tags exist in your project. The beauty of this is if one of your tags gets removed, or the name changes, the Unity3D compiler will notify you of any misspellings or missing references.