The Usage

Imagine you’re playing a survival game and you unlocked stone buildings, now you can finally upgrade from a wooden cabin to a stone house. After you risk life and limb to gather enough stone you start the upgrade and boom your house transforms from wood to stone.

The developers could have completely replaced one model for another, or they could have swapped materials for the structure, allowing it to progress through various stages without instantiating new models – an expensive process when overused. I’m interested in the later concept, how to effectively swap out materials.

The Concept

All Unity GameObjects include a Transform component which implements the IEnumerable interface (but not IEnumerable<T>), thereby providing a enumerator so you can loop through the transform’s children. You may ask “why not just use transform.GetComponentsInChildren()?” Great question, unfortunately, that method includes the parent as well as the child in the result. I can’t think of many situations where I wanted the parent and children, it’s usually one or the other.

In the picture below you can see the class definition for Transform and it derives from IEnumerable (in the red box).

Verifying Unity’s Transform Implements IEnumerable

This means you can easily write a foreach and loop through the transforms child.

foreach (Transform child in transform)
{
  Debug.Log($"Child {child.name} is at position {child.position}");
}

As I said before, Transform does not support IEnumerable<T>, however, you can cast it with the help of LINQ. Language-Integrated Query or LINQ adds query capabilities directly into the C# language. You can do lots of powerful things with LINQ. Be warned that it can have a performance impact, so always benchmark your code and optimize as needed.

So what can you do with LINQ? Say you need to pass the children off to a method that takes an IEnumerable<T> as one of its inputs. In this scenario you need to cast it to the desired type. Note, you can also cast to things like arrays and lists. In the next snippet you need to add using System.Linq; to the using directives at the top of the file.

IEnumerable<Transform> children = transform.Cast<Transform>();

Transform[] childrenArray = transform.Cast<Transform>().ToArray();

List<Transform> childrenList = transform.Cast<Transform>().ToList();

While casting it to IEnumerable<T> can be useful, it can do so much more once you’ve cast it. Then you can use all the Linq methods and do some interesting things. For example, do you need to sort the objects without changing their position in the hierarchy? You can do that with the OrderBy method on IEnumerable<T>.

foreach (Transform child in transform.Cast<Transform>().OrderBy(x => x.Name))
{
  Debug.Log($"Child {child.name} is at position {child.position}");
}

Armed with this knowledge, I can now code the feature described above – changing the materials of a group of gameObjects.

The Code

I put together an example scene in my Sandbox 2020 project on GitHub. The project uses Unity 2020.1.5f1. First off I created a wall using quads. Then I created two new materials, one for the wood and one for the bricks. I added the wood material to the quad’s mesh renderer. This gave me a nice looking wooden wall.

From here I created a script that could get the current material and change it to the next one. I added the script to the button and setup the OnClick event.

Script and OnClick Event

I copied the code below and highlighted line 20, which is only possible because Unity’s Transform supports IEnumerable.

using UnityEngine;

public class ChangeWall : MonoBehaviour
{
    #region Members
    [SerializeField] private Transform _wallParent = default;
    [SerializeField] private Material _brickWall = default;
    [SerializeField] private Material _logWall = default;
    #endregion Members

    #region Properties
    private Material CurrentWallMaterial => _wallParent.GetChild(0).GetComponent<MeshRenderer>().material;
    #endregion Properties

    #region Methods
    public void UpdateWall()
    {
        Material newWall = GetNextMaterial();

        foreach (Transform item in _wallParent)
        {
            item.GetComponent<MeshRenderer>().material = newWall;
        }
    }
    #endregion Methods

    #region Helpers
    private Material GetNextMaterial()
    {
        if (CurrentWallMaterial.name.Contains("Brick"))
        {
            print("Switch wall to logs...");
            return _logWall;
        }
        else
        {
            print("Switch wall to bricks...");
            return _brickWall;
        }
    }
    #endregion Helpers
}

I hope you enjoyed my example of using transform’s IEnumerable capabilities. Please leave me a comment and let me know how you use the important functionality.

Life’s an adventure, enjoy your quest!

Send a Missive

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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