Monday, December 8, 2014

Unity: Traversing GameObjects hierarchy






 Other Unity articles
Simple system for moving platforms (not only!)


 Traversing gameobjects in scene one by one is very common task when working with Unity. It usually includes two things: you want to traverse scene hierarchy and you want to do something with each hierarchy node.

 In this article I will build a class that solves this once and for all. You can use this class for any processing on the particular gameobjects as the traversing is divided from processing.

 Lets name our class HierarchyUtils. It will hold some static methods and it also defines delegate. We will also put it into separate namespace, so we can add other useful utility classes into it in future:

1
2
3
4
5
6
7
using UnityEngine;

namespace SBC.Unity.Utils
{
    public class HierarchyUtils
    {
        public delegate void GameObjectHandler(GameObject aGameObject, int aDepthLevel);

 This delegate method is key in splitting traversing and processing part. The traversing part will call any method that meets delegate definition - any method returning void and taking two parameters: GameObject and int.

 So, let's say we have some GameObject and we want to traverse all its child gameobjects. We will add these two methods to our class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        // -------------------------------------------------------------------------
        public static void IterateGameObject(GameObject aGameObject,
                                   GameObjectHandler aHandler, bool aRecursive)
        {
            DoIterate(aGameObject, aHandler, aRecursive, 0);
        }

        // -------------------------------------------------------------------------
        public static void DoIterate(GameObject aGameObject,
                                   GameObjectHandler aHandler, bool aRecursive, int aDepthLevel)
        {
            aHandler(aGameObject, aDepthLevel);

            foreach (Transform child in aGameObject.transform)
            {
                if (aRecursive)
                {
                    DoIterate(child.gameObject, aHandler, aRecursive, aDepthLevel + 1);
                }
            }
        }

 Now, we can use our class. Put the following methods into any class that needs to traverse hierarchy (these are not part of HierarchyUtils):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    // -------------------------------------------------------------------------
    public void Traverse(GameObject aGameObject)
    {
        if (mGameObject == null)
            return;

        handler = new HierarchyUtils.GameObjectHandler(ProcessNode);

        HierarchyUtils.IterateGameObject(mGameObject, handler, true);
    }

    // -------------------------------------------------------------------------
    private void ProcessNode(GameObject aGameObject, int aDepthLevel)
    {
        Debug.Log("GameObject: " + aGameObject.name + ", depth level: " + aDepthLevel);
    }

 Call the first method with root gameobject you want to start from. It creates handler, which is ProcessNode method. This method complies delegate definition, so we can handle it to IterateGameObject. The handler here is only simple one - it prints name of each gameobject and also its depth within hierarchy. But you can do here whatever you want.

 Our solution is working. It is up to you which handler you pass to IterateGameObject method, but you do not have to write traversing every time.

 Now, we will add one more method to HierarchyUtils. We can call this method instead of IterateGameObject. The signature will be:

public static void IterateScene(GameObjectHandler aHandler, string[] aFilterNames)

 This method will traverse whole scene. And more, it will dive only into gameobjects whose names begin with names in aFilterNames. In Unity you do not have easy access to root gameobjects. You have to find gameobjects that do not have parent. Let's say your hierarchy is like this:


 There is 5 root objects. You want to traverse scene, but only for root objects that are in foreground or background tree. Your aFilterNames parameter will then look like {"fore", "back"}; (remember, that names in our filter says: "stars with").

 Here is whole listing for the method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        // -------------------------------------------------------------------------
        public static void IterateScene(GameObjectHandler aHandler, string[] aFilterNames)
        {
            GameObject[] gameObjects = GameObject.FindObjectsOfType<GameObject>();

            foreach (GameObject gameObject in gameObjects)
            {
                // not top node
                if (gameObject.transform.parent != null)
                    continue;

                // is current top node in filterNames
                bool isNameInFilter = false;
                foreach(string filter in aFilterNames)
                {
                    if (gameObject.name.StartsWith(filter))
                    {
                        isNameInFilter = true;
                        break;
                    }
                }

                //Debug.Log("Name " + gameObject.name + " in filter " + isNameInFilter);
                                
                // if in filternames
                if (isNameInFilter)
                {
                    IterateGameObject(gameObject, aHandler, true);
                }
            }
        }

 We first take all gameobjects in scene. Then we check one by one. If has parent then it is not root gameobject. If root node, but its name does not start like any filter in passed filters list then we do not iterate on this gameobject. If in list we again call our IterateGameObject method with root gameobject.

 Here is example of usage somewhere in your code, which will print indented gameobject names:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    // -------------------------------------------------------------------------
    private void TraverseScene()
    {
        // method to be called on every single gameobject traversed
        HierarchyUtils.GameObjectHandler handler = new HierarchyUtils.GameObjectHandler(PrintName);
        // filter top nodes in hierarchy
        string[] filterNames = {"back", "fore"};

        // traverse
        HierarchyUtils.IterateScene(handler, filterNames);
    }

    // -------------------------------------------------------------------------
    public void PrintName(GameObject aGameObject, int aDepthLevel)
    {
        string indent = new string(' ', aDepthLevel * 2);
        Debug.Log(indent + aGameObject.name);
    }

 With this simple class you do not have to spend time on traversing again and again. You just write method for processing gameobjects and handle it to HierarchyUtils methods.






No comments:

Post a Comment