- Home /
this.transform.FindChild(x) and GameObject.Find(x) returning (apparently?) different results
Hi everyone,
So I'm a long time lurker (my first post!), and these forums are a great resource, but this problem is puzzling me. Let me put you in context:
I have 2 scenes (Game and Upgrade)
Between these 2 scenes, the Player gameObject travels as a singleton, carrying with him a bunch of children, among which weapons. It goes like: Player/WeaponFamily/WeaponName
In the Upgrade scene, I have a button that sets a Weapon to an "equipped" status. Upon re-entering the Game scene, I check which weapon has the "equipped" status true and assign it to the Player's weapon variable.
Here is the code in the Game scene that checks which weapon is equipped and assigns it to the player:
// Find which weapon is equipped and actually equip it
Weapon[] weapons = transform.GetComponentsInChildren<Weapon> ();
foreach (Weapon w in weapons)
{
if (w.IsEquipped()) {weapon = w; break;}
}
This finds that all weapons have the "equipped" flag set to False. However, the editor clearly shows me that I do have a weapon with an "equipped" status to true:
This is what I see in the editor upon exit of the Upgrade scene: it means my changes there were saved to that specific Weapon's script.
However, and this is what I don't get, if I use this code:
Weapon[] weapons = GameObject.Find ("Gun").transform.GetComponentsInChildren<Weapon> ();
foreach (Weapon w in weapons)
{
if (w.IsEquipped()) {weapon = w; break;}
}
It works!
I have thought that maybe I wasn't removing properly the Children for the singleton Player gameObject, but I tried to specifically remove them all and still the same error happens (that "weapon" is null).
Obviously I could go on using the working code, but I can't see the difference between the 2 and I don't get why one works and the other no, so if anyone knows what I'm doing wrong, he'd be very welcome :)
Thanks for reading and sorry for the long post!
Edit: for list formatting and title
You could try to debug this further by printing out the `InstanceID` of the components you are referencing to verify which objects are found with which calls. If you Log the InstanceIDs also in the Awake() of the gun scripts, you might get a better idea of what's happening.
To make this easier, you can make the instanceID visible in the inspector by going into debug mode (top right options above inspector tab)
Once or twice I've had similar(ish) problems where Find methods are returning objects that seem to linger in some limbo after scene change/Destroy() call because I'm still referencing them from somewhere or I'm calling Find too quickly after Destroy() (it's not instant).
Whatever the case, since transform.Find() only loops the children of a certain object ins$$anonymous$$d of everything in the whole scene, it's definitely the better, more performant method to use.
Thanks for your answer, I didn't know about InstanceID! Going to give it a go and keep you posted.
Is the script you posted attached to the Player object or the Gun object?
If it's on the Gun object, you can do this sanity check:
Transform myTransform = transform;
Transform gunTransform = GameObject.Find ("Gun").transform;
Debug.Log("GameObject.Find finds me: " + (myTransform == gunTransform));
Since you're using GetComponentInChildren in both instances, and you're getting different results, I'm assu$$anonymous$$g that the above check will print false. If I were to guess, I'd assume that there's a bug in the singleton-code, which results in the script not being on the object you assume it's on.
Actually, search for all the instances of the script you posted in the game. Write "t:ScriptName" in the search bar above your hireachy, where ScriptName is the name of your script. You might end up finding two instances, in which case that's probably your issue.
Hi Baste, thanks for dropping by.
The script I posted is attached to the Player object. "Gun" is an empty gameObject serving as a container for the Gun-type weapons (with the weird names), but I think I get what you were trying to tell me. I ran some tests using the InstanceID, this would achieve the same result as your method to check the multiple GOs, am I correct?
So the tests I ran confirmed I have two sets of GOs for my weapons:
One set that is the correct one, returned by the general GameObject.Find(), the one that appears in the hierarchy
The other "ghost" set
Here are the results with debug mode on and searching for t:Weapon in the hierarchy (red one is the correct one, yellow one is the "ghost" one):
I am afraid both of you might be right and my Player singleton/Destroy might have a problem, since I'm not too familiar using it. Here's a snippet in case you see something blatantly wrong (in Awake()):
if (!hasBeenCreated)
{
Debug.Log("Creating player...");
DontDestroyOnLoad(this.gameObject);
hasBeenCreated = true;
if (playthrough == 0) currentPlaythrough = "Gun";
else if (playthrough == 1) currentPlaythrough = "Grenade";
else if (playthrough == 2) currentPlaythrough = "$$anonymous$$elee";
weapon = transform.FindChild(currentPlaythrough+"/I-1").GetComponent<Weapon>();
weapon.SetEquipped(true);
}
else // this (supposedly) destroys the new Player object that is created when re-entering game scene
{
GameObject.Destroy (this.gameObject);
}
Please note that the previous code snippets are also called in the Awake() function. I tried moving them in another function to see if this would give more time to Destroy() to complete but the problem remained the same.
I am not really sure on where to go now.
might it be that hasBeenCreated isn't a static variable? In that case, each singleton would have it's own hasBeenCreated variable, and that wouldn't do you much good.
Also note that this.gameObject is redundant - unless you're created another variable in the Start() method that you have named "gameObject".
Answer by Baste · Apr 15, 2015 at 10:33 AM
It should be fine, then. And the variable should be private - no other class should ever need to access it.
Aaaand, I think I found the issue. You're saying that you're assigning the weapon as equipped when you enter the scene, right? Are you doing this in Start()?
So, your singleton is set to DontDestroyOnLoad. This means that it stays alive between scenes. In particular, this also means that Start isn't called again when you change scenes.
So, what's happening is that the Start method being called is the Start method on your other Player object (the one being destroyed). That is where the equipment behaviour is being started. When you do this:
if (!hasBeenCreated)
{
...
}
else
{
GameObject.Destroy (this.gameObject);
}
//More Start Stuff!
GameObject.Destroy doesn't stop the control flow - so everything later in your Start() method ("More Start Stuff") is being executed by the to-be-destroyed script. This is why GameObject.Find makes your code work, as it finds the singleton instead of the local, to-be-destroyed object.
your singleton's Start should look like this:
else {
GameObject.Destroy(this.gameObject); // or just Destroy(gameObject)
return; //STOP execution of Start!
}
After you've fixed that, you'll have to take all the code that's supposed to be called every time a level is loaded, and move it to a method. This method needs to be called both on Start (AFTER destroying other singletons), AND in the special OnLevelWasLoaded method, which is called whenever a new level is loaded after that. The OnLevelWasLoaded method takes an int argument that specifies the level that was loaded, so you can avoid calling the level start stuff on the upgrade level.