Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find inactive objects using GameObject.Find(" ") in Unity3D?

Tags:

c#

unity3d

I needed to find inactive objects in Unity3D using C#.

I have 64 objects, and whenever I click a button then it activates / inactivates objects for the corresponding button at runtime. How can I find inactive objects at this time?

like image 756
user2914179 Avatar asked Oct 25 '13 07:10

user2914179


3 Answers

Since Unity 2020

In the years since this question was asked, Unity put in the exact thing you need. At least, the exact thing I needed. Posting here for future peoples.

To find an object of a certain type whether it's on an active or inactive GameObject, you can use FindObjectsOfType<T>(true)

Objects attached to inactive GameObjects are only included if inactiveObjects is set to true.

Therefore, just use it like you regularly would, but also pass in true.

The following code requires System.Linq:

SpriteRenderer[] onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>();

SpriteRenderer[] activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true);

// requires "using System.Linq;"
SpriteRenderer[] onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).Where(sr => !sr.gameObject.activeInHierarchy).ToArray();

The first array includes only SpriteRenderers on active GameObjects, the second includes both those on active and inactive GameObjects, and the third uses System.Linq to only include those on inactive GameObjects.

like image 83
shieldgenerator7 Avatar answered Sep 30 '22 07:09

shieldgenerator7


If you have parent object (just empty object that plays role of a folder) you can find active and inactive objects like this:

this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;

MainMenuItems - is your parent object.

Please note that Find() is slow method, so consider using references to objects or organize Dictionary collections with gameobjects you need access very often

Good luck!

like image 24
Dzianis Yafimau Avatar answered Sep 30 '22 09:09

Dzianis Yafimau


For newer Unity versions this answer provides probably a better solution!


First of all

In general any usage of Find or it's variants should be avoided.

Actually they are never really required but only a "hot-fix" used to cover an implementation "laziness".

Usually from the beginning storing and passing on required references is always the better approach.

Especially in your case you seem to have a fix amount of objects so you could probably already reference them all in a certain "manager" component and store them in a list or array (them you can get a reference by index) or even a Dictionary<string, GameObject> (then you can also get the according reference by name - you can find an example below).


Workarounds

There are alternative solutions (FindObjectsWithTag, FindObjectsOfType) but it will always be quite expensive (though most of the Find variants are expensive anyway).

You could e.g. also "manually" iterate through all objects in the scene using Scene.GetRootGameObjects

Returns all the root game objects in the Scene.

And then search through them until you find your object. This way you get also inactive GameObject.

public static GameObject Find(string search)
{
    var scene = SceneManager.GetActiveScene();
    var sceneRoots = scene.GetRootGameObjects();

    GameObject result = null;
    foreach(var root in sceneRoots)
    {
        if(root.name.Equals(search)) return root;

        result = FindRecursive(root, search);

        if(result) break;
    }

    return result;
}

private static GameObject FindRecursive(GameObject obj, string search)
{
    GameObject result = null;
    foreach(Transform child in obj.transform)
    {
        if(child.name.Equals(search)) return child.gameObject;

        result = FindRecursive (child.gameObject, search);

        if(result) break;
    }

    return result;
}

But ofcourse this should be strongly avoided and the usage of such deep searches reduced to a minimum!


What I would do

Another way - in my eyes the best approach here - could be to have a certain component attached to all your objects and actually store all the references once as said before in a dictionary like e.g.

public class FindAble : MonoBehaviour
{
    private static readonly Dictionary<string, GameObject> _findAbles = new Dictionary<string, GameObject>();

    public static GameObject Find(string search)
    {
        if(!_findAbles.ContainsKey(search)) return null;

        return _findAbles[search];
    }

    private IEnumerator Start()
    {
        // Wait one frame
        // This makes it possible to spawn this object and 
        // assign it a different name before it registers
        // itself in the dictionary
        yield return null;

        if(_findAbles.ContainsKey(name))
        {
            Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
            yield break;
        }

        _findAbles.Add(name, gameObject);
    }

    private void OnDestroy ()
    {
        if(_findAbles.ContainsKey(name))
        {
            _findAbles.Remove(name);
        }

        // Optionally clean up and remove entries that are invalid
        _findAbles = _findAbles.Where(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
    }
}

and then use it like

var obj = FindAble.Find("SomeName");
if(obj)
{
    // ...
}

Also for this the component would need to be enabled at least once so Start is called.

Again an alternative would be to have instead a

public void Initialize(string newName)
{
    if(_findAbles.ContainsKey(name))
    {
        Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
        return;
    }

    name = newName;

    _findAbles.Add(name, gameObject);
}

which you could call also after e.g. spawning an inactive object.

like image 39
derHugo Avatar answered Sep 30 '22 07:09

derHugo