Home » Extending Unity API

Extending Unity API

Extending Unity APII’m sure we’ve all felt the frustration with Unity API, when we can’t find the method we really need. A classic case is traversing the transform hierarchy. For instance, why on Earth we don’t have methods for iterating the children transforms? One reason might be that there are many ways to do it, suggesting that it’s left for us to implement them. But how is that possible? Quite simply – with extension methods.

How to Use Extension Methods

Extension methods were first introduced in .NET 3.5,  so they are available in Unity by default. The purpose of extension methods is to allow the user of a code base to extend it without having to change the original code. With extension methods, you can implement custom methods and use them just like normal instance methods.

Let’s have an example.

You are probably familiar with List<T> and it’s handy ForEach() method that allows us to replace short foreach loops with one-liners. Curiously enough, ForEach() was not included in System.Linq (on this, see Eric Lippert’s and Kirill Osenkov’s blog posts). Fortunately, it’s very easy to implement our own version of it as follows:

public static class IEnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }
}

This simple example illustrates everything we need for implementing an extension method.

  • A static, non-generic, non-nested “sponsor” class to hold the method.
  •  this keyword to explicitly mark the instance that is using the method.

That’s it! Note that we can’t override or hide instance methods with the same names. So if we call list.ForEach() , we are still using the instance method defined in List<T> class. But now we can use this method on any other class that implements IEnumerable<T> interface.

Besides one-liners, another advantage of iterating with ForEach() is that we can now use the loop as the end of a function chain like this: collection.Where(predicate).ForEach(action).

Avoiding Hidden Side Effects

When using extension methods, keep in mind that they are not object-oriented by design. Instead, expression methods are meant to be used within functional paradigm, so it’s maybe worth knowing a bit about the main principles of it. There are books written about functional programming, and it’s impossible to cover the topic in depth here. But the relevant points to mention here are:

  • Avoid state mutation
  • Isolate side effects

Functional programming aims for stateless data flows, where methods can be chained together without a risk of the data being changed behind the scenes. If some of the methods manipulates the data, it will have side effects. Side effects are bad, because they cannot be seen without knowing (and remembering) how exactly the methods are implemented. They also mutate the state of the object. From state mutation follows that the object is not necessarily in the state we suppose it is when we write the code.

One way to avoid state mutation is to use new instances instead of manipulating the old instances. System.Linq methods do this behind the scenes. The drawback of this is that it generates extra memory allocations, which are left for the Garbage Collector to take care of. Hence, you don’t want’ to follow this approach in methods that are used too frequently (like in the Update loop).

In practice, side effects cannot be entirely avoided. Therefore we should at least isolate them to make it clear where there are some. The above-mentioned ForEach() is exactly that: an encapsulation of side effects. That’s why ForEach() should not yield anything! Otherwise it could be used in the middle of a function chain, which would effectively cause side effects in the middle of a function chain.

Extending IEnumerable<T>

Before discussing Unity API, let’s have another example on how we can extend IEnumerable<T>interface. This underlines the main reasons why we have extension methods in the first place.

Let’s suppose we want to get a random sword from a mixed collection of sword. Yet, we don’t want the sword to be blunt, do we? We could get the sword like this:

swords.Where(s => !s.isBlunt).ElementAt(Random.Range(0, source.Count());

But what if we also wanted to get a random rusty armor? We don’t want to write duplicate code. The pattern is the same, so why don’t create an extension method for it? In order to be as generic as possible, we should break the operations into two methods. Let’s start by implementing a method for getting a random item from a collection:

public static T GetRandom<T>(this IEnumerable<T> source)
{
    int count = source.Count();
    return count == 0 ? default(T) : source.ElementAt(Random.Range(0, count));
}

This method simply returns a random item from the collection. Let’s also add a method that takes a predicate to filter the collection:

public static T GetRandom<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    return source.Where(predicate).GetRandom();
}

Now, we can get a sharp sword and a rusty armor using exactly the same methods, where the only things we have to change are the arguments:

var sharpSword = swords.GetRandom(s => !s.isBlunt);
var rustyArmor = armors.GetRandom(a => a.isRusty);

Traversing Transform Hierarchies

OK, it’s time to return to the beginning of this post and continue by applying extension methods on Unity API.

As mentioned before, there is no built-in method for getting only the children components of a GameObject. GetComponentsInChildren<T>includes also the components found on the root GameObject. So we need to filter out these. This operation makes an excellent candidate for an utility extension method:

public static IEnumerable<T> GetComponentsOnlyInChildren<T>(this Component c, bool includeInactive = false)
{
    var components = c.GetComponents<T>();
    return c.GetComponentsInChildren<T>(includeInactive).Where(obj => !components.Contains(obj));
}

Now we can get all the components from the children, excluding the components of the root object itself. Finding the child transforms of an object can be packed into another extension method of more specific nature:

public static IEnumerable<Transform> GetAllChildren(this Component c)
{
    return c.GetComponentsOnlyInChildren<Transform>(true);
}

By using this extension method, we can easily get the children of a game object like this:

var children = component.GetAllChildren();

Getting an Instance Reference without Null Checks

In functional languages, like F# or Haskell, they don’t use null to check if a reference value is found or not. Instead they wrap the value in a type that might contain the value (like a collection). If we wanted to do this in C#, we would need to implement a custom generic type, like Option<T>. The point of this is to ensure that we get some value, so that we can chain up the functions without the possible side effects like exceptions. Effectively this would enable us to treat everything as collections similar to IEnumerable<T>.

Regarding game development, there are many reasons to not to do that – one of them being that Unity is not a functional game engine. Moreover, wrapping everything into classes would in many cases hit the performance.

However, wouldn’t it be cool to at least to reduce the null checks? Using extension methods, we can get rid of some checks that are frequent. For instance, it’s quite common pattern to use lazy evaluation in property accessors like this:

private MyComponent _component;
public MyComponent Component
{
    get
    {
        if (_component == null)
        {
            _component = gameObject.GetComponent<MyComponent>();
        }
        return _component;
    }
}

We can wrap this null check into an extension method as follows:

public static T GetReferenceTo<T>(this GameObject go, T instance)
{
    if (instance == null)
    {
        instance = go.GetComponent<T>();
    }
    return instance;
}

As such, this method allows us to omit only one null check like so: component = gameObject.GetReferenceTo(component). But the advantage of this approach is that if we want to complement the method with more conditions, it’s quite easy to do:

public static T GetReferenceTo<T>(this GameObject go, T instance, bool includeInactive = true, bool seekChildren = false, bool seekParents = false)
{
    if (instance == null)
    {
        instance = seekChildren ? go.GetComponentInChildren<T>(includeInactive) : go.GetComponent<T>();
        if (instance == null && seekParents)
        {
            instance = go.GetComponentInParent<T>();
        }
    }
    return instance;
}

This way, we don’t have to refactor all the cases with similar checks. Instead, we can simply call the same method with different arguments.

Vector Math

Frequently used vector calculations are also a good candidate for extension methods. For instance, if we wanted to check whether an object is facing another object, we could implement the following function:

public bool IsFacingTarget(Transform t, Transform target, float angle)
{
    var dir = target.position - t.position;
    return Vector3.Angle(t.forward, dir) <= angle;
}

This is a simple function that compares the angle between a forward vector and a direction. We’d want to define the angle that we compare to, because it can be used as the object’s field of view. And even if we are using the function simply to check if the object is facing directly at the target, it’s often better to use a small error margin as the accepted deviation.

The function defined above works, but it suits only very specific needs. We might also want to check if the target is facing another position that’s not a position of a Transform object. Now we would need an overload function that takes Vector3 instead of Transform as the second parameter. In addition to positions, vectors can also represent directions. So, that’s a third function.

We could implement these methods as static helpers, but let’s use extension methods instead. So the core function would be an extension method for Vector3:

public static bool IsFacing(this Vector3 forward, Vector3 dir, float angle)
{
    return Vector3.Angle(forward, dir) <= angle;
}

It turns out that this is the core function, on which the other methods rely on. We could leave it here. But to make it easier to use, let’s implement three simple extensions for Transform component:

public static bool IsFacingDir(this Transform t, Vector3 dir, float angle)
{
    return t.forward.IsFacing(dir, angle);
}

public static bool IsFacingPos(this Transform t, Vector3 target, float angle)
{
    var dir = target - t.position;
    return t.IsFacingDir(dir, angle);
}

public static bool IsFacing(this Transform t, Transform target, float angle)
{
    return IsFacingPos(t, target.position, angle);
}

Depending on the requirements and the context, we can now use the calculations from the same code base for both Vector3 and Transform:

bool isFacing = transform.IsFacing(targetTransform, angle: 1);
bool isFacing = transform.IsFacing(targetPos, angle: 1);
bool isFacing = transform.IsFacing(targetDir, angle: 1);
bool isFacing = direction.IsFacing(targetDir, angle: 1);

This kind of function hierarchy is not restricted to extension methods, but it really shines when used with them. If you now want to change the core method to use Vector3.Dot() instead of Vector3.Angle(), it’s very easy to do. And it doesn’t require refactoring the client code! In my own code base, I also have optional parameters to inverting the facing direction and for locking the y-axis. Also these can be added to the above-defined methods without any refactoring.

Conclusion

Extension methods are a powerful tool in your arsenal. With them, you can extend the API’s you are using, without touching the original code. This is handy when it comes to Unity API, but also regarding third-party plugins. Be aware though that misusing extension methods may hinder you code design.

You should not try to do object-oriented programming with extension methods, because that’s not what they are meant (or good) for. When creating extension methods, try to think functionally instead: aim for helper functions that receive data as function parameters and return data without manipulating the source data.

Also think abstract. Create generic methods and split functions in parts. For example, instead of implementing specific functions like SetLayerForAllChildren(layer), create more general level pieces like GetAllChildren()and ForEach(). Then use these as the building blocks for the more specific methods: transform.GetAllChildren().ForEach(c => c.gameObject.layer = layer). This way you can keep using the same code in various contexts.

Further reading:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/extension-methods

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.