4
\$\begingroup\$

I'm trying to get descendant game object, not a child. Let's assume that I have a GameObject and it's hierachy looks like this:

Weapon
- Hands
- metarig
  - upper_arm.L
  - upper_arm.R
    - forearm.L.001
      - hand.L.001
        - weapon.L.001
          - AttackDetection(!)
- Sword

I want to access "AttackDetection" from Weapon(Root) in script. But when I try this:

void Start() {
    attackDetectionGObject = this.transform.Find("AttackDetection").gameObject;
}

It fails with this:

NullReferenceException: Object reference not set to an instance of an object

Looks like transform.Find just looking up it's own child, not descendant. I try to find way to solve this problem, but there's nothing I found.

Any advice will be very very appreciate it.

\$\endgroup\$
8
\$\begingroup\$

Yes, transform.Find(name) will only look in the direct children of the current transform. But if you want to get a child deeper in the hierarchy, you can use slashes to describe the complete path. So transform.Find("Weapon/metarig/upper_arm.R/forearm.L.001/hand.L.001/weapon.L.001/AttackDetection") should work.

An alternative might be to use getComponentInChildren to target a component unique to the object you need (or getComponentsInChildren to get all matching components from child-objects). These methods will iterate the whole object-hierarchy of the object you call them on. The advantage of this solution is that it is more robust in case you change your object setup and that it works when you only know the exact setup at runtime. The drawback is that it is also slower, especially when you run it on a very large branch of your object-tree. But if you do this only once in your Start method of a long-living object, performance shouldn't matter that much.

\$\endgroup\$
2
\$\begingroup\$

If you want to stick to the search via string, you can use a simple recursive method like this:

using UnityEngine;

public static class FindInChildren {

    public static Transform Find(this Transform parent, string name) {

        var searchResult = parent.Find(name);

        if (searchResult != null)
            return searchResult;

        foreach (Transform child in parent) {
            searchResult = child.Find(name);
            if (searchResult != null)
                return searchResult;
        }

        return null;
    }
}

You can use the FindInChildren.Find(nameString) any time to search the full children hierarchy of a game object.

But beware that using Transform.Find or GameObject.Find should be avoided as much as possible, because it's quite slow (and GameObject.FindWithTag is faster, even though it searches the whole hierarchy) and searching items by string arguments is a bad practice anyway.

The best way, as suggested by Philipp, is to use GetComponentInChildren<T>, and attaching to the game objects you want to be found a script (it can be empty) with name T.

Remember, as a final note, that Find returns any game object, active or inactive, whereas GetComponentInChildren<T> by default doesn't: you need to use GetComponentInChildren<T>(true) in order to search for components in both active and inactive game objects.

\$\endgroup\$
  • 1
    \$\begingroup\$ This is, as far as I'm concerned, the way to go. I use this for exactly your given example, @modernator. Well, I'm typically looking for a component attached to an enabled weapon in a hand (can be either) starting from the root of my player GO. It works well and quickly. \$\endgroup\$ – Jesse Williams Jun 16 '17 at 18:36
0
\$\begingroup\$

You can also use Transform#GetChild(int) to get the Nth child of the transform.

So getting your AttackDetection object in this case would be:

Transform ad;
ad = transform.GetChild(1).GetChild(1).GetChild(0).GetChild(0).GetChild(0).GetChild(0);
attackDetectionGObject = ad.gameObject;

(Splitting things across lines so it fits within the display width here on SE).

But this only works for statically arranged hierarchies, that is, Hands will always come before metarig, etc. But for something like a humanoid player where you as the developer know that you're not going to be rearranging things, it's a nice shortcut. You can even combine it with Transform.Find(string) when the layout goes from static to dynamic. I use a mix myself in my own projects, though I typically use a .Find for the last step so that the code remains readable (that is, answering my future self's question: "which object is that again?").

The advantage is that Transform hierarchies are stored in memory like arrays, so accessing the Nth element has complexity O(1) and is much, much faster than a .Find lookup.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.