Unity tag system sucks

One of the first things that I learned when I started to work with Unity is its tag system. It is very easy to understand as it is based on a concept of categorizing game objects into groups. Each game object belongs to a single group. Unity provides a convenient mechanism for checking whether a game object belongs to a group or not. You can also find a game object, or all game objects, of a group.

But the system does have a flaw. You cannot assign two or more “tags” to a game object, and this is quite annoying. For example, your bullets already had the “Bullet” tag, but you cannot tell who owns each one of those bullets, the player or the enemy thief. In this post, I offer a solution as a replacement for Unity tag system.

Some early concepts and design decisions

My category system (I chose a different name for avoiding meaning collisions between concepts) is created to, primarily, assign game objects into categories where a game object can have more than one category as its description. It would be more natural to have a bullet to have more than one categories, such as: “Damage Object”, “Limited Lifetime” and “Explodable”. Of course, using Unity tag system, you can achieve the same by checking whether a game object has a component for controlling the bullet, and then check its stats. But this would lead to a few more member variables, more support data structures or other types of headaches related to checking the object’s group(s).

There is a very simple and efficient technique about multiple choices, that is the mask. The underlying concept here is, given that I have n categories, the categories that a game object holds would be described with an integer field of n bits. If the object has the category #1 and #3, those respective bits in the integer fields would have the value of 1, and other bits have the value of 0.

In case you haven’t understood yet, this is a simple example. The circle is the game object, which holds two categories, A and C. The mask value is 00101, with the bit #1 and #3, respective to two categories, is 1.

I cannot modify the GameObject class nor remove the current Unity tag system, the only way I can do this category system is through a MonoBehaviour. However, getting components frequently may cause some serious performance penalty. Especially, when the scene is large, and it contains some complex game objects with many components attached. So the desired solution must require no GetComponent calls.

Implementation

First of all, there must be a class to hold the mask value of the categories. Let’s call it CategoryMask.

using System;
using UnityEngine;
 
[Serializable]
public class CategoryMask
{
    [SerializeField] private int value;
 
    public int Value { get { return value; } }
}

The class only hold an int value – the mask, and it is private. I only provide a getter, so that the categories value cannot be changed by outside classes. Next, I would put this data structure to a component that can be attached to a game object.

public class ObjectCategory : MonoBehaviour
{
    [SerializeField]
    CategoryMask category;
 
    public int CategoriesValue { get { return category.Value; } }
}

While the base bone is simple as that, it would be inconvenient to fill these mask values by hand. We should provide some editor scripts so that the user can assign meaningful category names to the game object, rather than some soul-less integer values. We start with a scriptable object, which holds an array of names as category names. Unity’s scriptable objects can be serialized1 into files, and it is a handy way to store and access commonly used data.

public class CategoryChoices : ScriptableObject
{
    [SerializeField]
    string[] categories;
 
    public string[] GetCategories()
    {
        return categories;
    }
}

We then create a property drawer. A property drawer is an editor script for displaying a particular type of serializable data structure on the Inspector window. My intention is that instead of a field to enter an integer value, the Inspector window would display a dropdown list containing category names so that user can tick into the categories that they want to game object to have. Something like this:

Unity Manual’s example on Layer Mask

The custom drawer for our CategoryMask data structure above would look like this:

[CustomPropertyDrawer(typeof(CategoryMask))]
public class CategoryMaskDrawer : PropertyDrawer
{
    string[] categories;
 
    void LoadCategories()
    {
        CategoryChoices serializedCategories = AssetDatabase.LoadAssetAtPath(
            "Assets/Resources/Category/Categories.asset",
            typeof(CategoryChoices)
        ) as CategoryChoices;
        categories = serializedCategories.GetCategories();
    }
 
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        LoadCategories();
 
        EditorGUI.BeginProperty(position, label, property);
 
        // Draw label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
 
        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;
 
        // Create a dropdown mask values
        SerializedProperty maskValue = property.FindPropertyRelative("value");
        int currentMask = maskValue.intValue;
 
        int chosenMask = EditorGUI.MaskField(position, currentMask, categories);
        maskValue.intValue = chosenMask;
 
        // Set indent back to what it was
        EditorGUI.indentLevel = indent;
 
        EditorGUI.EndProperty();
    }
}

I load the serialized categories from the scriptable object above and assign those into an array of strings for quick access. The OnGUI function takes care of drawing the mask drop-down and assign the modified value to the category mask.

Finally, to feel that the category system is really a part of Unity, I would put some extra editor functions so that we can access through the menu bar. With the script below, you can view and edit current categories, or create new category scriptable object in the first time you work – unfortunately, you have to create the folder “Assets/Resources/Category” by hand with this script.

public class CategoriesEditor
{
    const string CATEGORY_PATH = "Assets/Resources/Category/Categories.asset";
    const string MENU_PATH = "GameObject/Categories/";
 
    [MenuItem(MENU_PATH + "View Current Categories")]
    public static void ViewCurrentCategories()
    {
        CategoryChoices categories = AssetDatabase.LoadAssetAtPath(CATEGORY_PATH, typeof(CategoryChoices)) as CategoryChoices;
        EditorUtility.FocusProjectWindow();
 
        Selection.activeObject = categories;
    }
 
    [MenuItem(MENU_PATH + "New Categories Set")]
    public static void CreateNewCagoriesSet()
    {
        CategoryChoices categories = ScriptableObject.CreateInstance();
 
        AssetDatabase.CreateAsset(categories, CATEGORY_PATH);
        AssetDatabase.SaveAssets();
 
 
        EditorUtility.FocusProjectWindow();
 
        Selection.activeObject = categories;
    }
}

So now when we attach the Object Category component into a game object, you will see a nice drop-down list so that you can make multiple choices without worrying about the value of the mask. Also, we jump to a more critical part now: how to check whether an object holds a category, and how to find game object(s) effectively.

Since the code is quite long and it’s the pain in the ass to edit the greater than operators (>) in the code within this WordPress editor, I would not paste the code here to explain line by line. I will just explain the concept, and you can check out the GitHub link at the end of the post to have the full solution.

Recalling the Object Category component above, you can see that I don’t allow the object’s categories to be changed at run-time, which means the category mask value of a game object is the same from the moment that it is constructed to the moment when it is destroyed. We can make use of this, by registering the game object to the categories it holds and deregistering it when it is destroyed.

To register an object to, and deregister it from, a category, there is no data structure as efficient as the dictionary. Basically, the category value – the index of it in the scriptable object – is the dictionary key, and each key is associated with a list of game object, which all hold that very same category. The dictionary makes it simple to check whether an object holds a category or not, just by a simple and effective query from the dictionary, then the game object list. The search operations would be fast enough when it doesn’t have to check every game object in the scene to return the result. If you need the first object you can find of that category, it is there almost at once, by returning the first element of the game object list acquired right after making the dictionary results. And if you need all of them, a dictionary query is enough.

Conclusion

There is a thing to note about this solution, though. As the category is registered when the object is created, it would be a safe move to put the execution order of Object Category script a bit higher. If you don’t know how to do it, check out this link: https://docs.unity3d.com/Manual/class-ScriptExecution.html

My solution worked just fine for me so far, and I am eager to show it to you for discussion. While it does its job, it is by no mean a perfect solution. If you spot something that can be improved, please don’t be hesitate to tell me, or even better, make a pull request. The full solution can be found here: https://github.com/TongTungGiang/UnityObjectCategory

Print Friendly, PDF & Email

Leave a Reply

Your email address will not be published. Required fields are marked *