- Home /
Transform.Find(string) no longer finds grandchild?
I have the following hierarchy:
Manager
- Player(clone)
- Ship(clone)
Q_Quad
- Ship(clone)
This hierarchy is created at run time by a manager script on the 'Manager' GameObject, like so:
GameObject player, ship;
player = (GameObject) Instantiate(playerPrefab, vector, quat);
ship = (GameObject) Instantiate(shipPrefab, vector, quat);
ship.transform.parent = player.transform;
The 'Q_Quad' GameObject is part of the 'shipPrefab'. The 'Q_Quad' GameObject is inactive initially.
I have a 'PlayerController' script on the 'playerPrefab' which tries to find 'Q_Quad', like so:
transform.Find("Q_Quad").gameObject.SetActive(true);
This system was working fine the other day, but now the call to 'Find("Q_Quad")' is returning null. As a test, after I ran the scene, I manually moved the 'Q_Quad' GameObject to be under 'Player(clone)', and then executed the code that finds 'Q_Quad', and it works. But when it's under 'Ship(clone)' it fails to work.
I don't think I changed anything, and I know this worked before (I have video proof of it). Does anyone know what is going on here?
For some reason, Unity Answers doesn't like my use of underscore, but I assure you all that I have the correct names in my code, which is: Q_Quad
From a script on the player, I believe it "should" be transform.Find("Ship/Q_Quad");
When it was working previously, was it perhaps from a script on your shipPrefab? Or the hierarchy was different?
Ship prefab IS subject to change, but no, the script wasn't on the ship prefab. $$anonymous$$ainly the only difference in hierarchy was the name of the ship prefab. The old prefab I had was called "TestCommandShip" while the new one is "Firestorm". Also, there is another child under ship that I didn't think was relevant, called "Engine Glow" which contains part emitters and trail renderers for the engines of the ships (which do vary because the ships have different engines). Still, I wouldn't have thought this would affect anything.
Is there any reason that you can't reference the gameobject manually? Or, if there's a lot of them or they are dynamically generated, add a component to them (or the prefab used to to make them if applicable) that registers that gameobject into a static list on your manager in void Awake()
. While I'm sure there must be a use case somewhere, I've never personally seen any real use for either GameObject.Find or Transform.Find. They're slow, clunky, hard to modify, and there's almost always a more elegant and useful solution. Frankly with all the stuff that Unity's been deprecating and changing lately I'm surprised those methods even still exist.
Answer by Bunny83 · Sep 29, 2014 at 01:51 AM
I think you confused the static GameObject.Find with Transform.Find. Transform.Find always starts searching on the direct children while GameObject.Find can do different kinds of searching.
You can implement an extension method which searchs for a deep child:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public static class TransformDeepChildExtension
{
//Breadth-first search
public static Transform FindDeepChild(this Transform aParent, string aName)
{
Queue<Transform> queue = new Queue<Transform>();
queue.Enqueue(aParent);
while (queue.Count > 0)
{
var c = queue.Dequeue();
if (c.name == aName)
return c;
foreach(Transform t in c)
queue.Enqueue(t);
}
return null;
}
/*
//Depth-first search
public static Transform FindDeepChild(this Transform aParent, string aName)
{
foreach(Transform child in aParent)
{
if(child.name == aName )
return child;
var result = child.FindDeepChild(aName);
if (result != null)
return result;
}
return null;
}
*/
}
The first implementation uses a Breadth-first search while the second one (which is commented out) uses a Depth-first search. Usually the first one is the preferred one. The first implementation uses Transform.Find for each level, so you can still use relative path within deepchildren.
edit
Just in case you don't know what extension methods are: all you have to do is copy this class above somewhere into your project. It doesn't matter where. On any Transform reference you can simply call:
Transform someChild = transform.FindDeepChild("someChild");
instead of transform.Find
I am not interested in using GameObject.Find because it doesn't find deactivated GameObjects, which is all I am interested in, in this case. At least this used to be the case. Either way, I tried using GameObject. Find and it returned null as well.
I was not aware that Transform.Find is not recursive. Apparently the only explanation is I made a mistake somewhere with my hierarchy.
I will accept your answer to be the best, but I don't know if I will implement it due to the performance cost (despite the fact that my scene is not complex). I think I will just do this:
ship.name = "ship";
transform.Find("ship/Q_Quad");
FYI, you can add strings together to dynamically Find the 'Q_Quad' regardless of the ship name:
ship.name = "Firestorm";
transform.Find(ship.name + "/Q_Quad");
The first method does not do a breadth-first search. It does a depth-first search, just like the second method. It calls itself recursively on child transforms before finishing traversing the current level. A real breadth-first algorithm is more complex.
Yes, it's not a true breadth first search, though it's not a depth-first search either. It's acutally a mix of both as at each depth level all direct children are searched before it goes further down.
A true breadth first search isn't that much more complex. Though ins$$anonymous$$d of using recursion you would use a queue.
using System.Collections.Generic;
//True Breadth-first search
public static Transform FindDeepChild(this Transform aParent, string aName)
{
Queue<Transform> queue = new Queue<Transform>();
queue.Enqueue(aParent);
while (queue.Count > 0)
{
var c = queue.Dequeue();
if (c.name == aName)
return c;
foreach(Transform t in c)
queue.Enqueue(t);
}
return null;
}
edit
ps: I've replaced the breadth first method in my answer with the one i just posted since it's "more correct" ^^. Thanks for the notice.
Answer by cameronpennerVR · Nov 28, 2017 at 06:48 PM
I used GetComponentsInChildren. it's not super effiecient so it wouldn't be a great idea to use during your game loop, but I'm only running it at load time. Seems a bit simpler than writing your own recursive search.
Transform[] children = transform.GetComponentsInChildren<Transform> ();
foreach (var child in children) {
if (child.name == "object name") {
//do something with child
}
}
Answer by Zoltan-Rus · Sep 18, 2020 at 12:26 PM
You can find deep child using LINQ:
var childObject = parentObject.GetComponentsInChildren<Transform>()
.FirstOrDefault(c => c.gameObject.name == desiredName)?.gameObject;
Answer by rlalancette · Jun 25, 2018 at 11:27 PM
Unbelievable that Unity doesn't have something like this built-in!
GetComponentInDescendants please! And also, please add GetComponentInDescendantsByName(string name)
R.
Answer by christoph_r · Sep 28, 2014 at 11:48 PM
The issue is actually that it's disabled. I'm pretty sure it will find it when it's active.
No. Transform.Find will also find deactivated GameObjects, but it doesn't search for deep children. You can use a path-like syntax to navigate the hierarchy, but you have to start at your current level.