Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Why does gameobject.Find/GetComponent not work when a new scene is loaded?

Discussion in 'Scripting' started by Reverend-Speed, Aug 29, 2016.

  1. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    I'm transitioning from from LandingPage scene to ItemFocusTest scene with a singleton script that uses Scenemanager.Loadscene called from a button.

    When Unity loads up the ItemFocusTest scene, certain scripts/gameobjects seem to fail to get references to other objects and components in the scene, preventing the scene from working properly.

    It's important to note that ItemFocusTest works correctly when loaded manually in the Unity Editor - meshRenderers are found, their material.colors set to color.clear and a reference is cached to the TileClicker script on the Main Camera. (Look for all-caps below)

    Code (CSharp):
    1. void Start () {
    2.          tapTiles                            = tileParent.GetComponentsInChildren<TapTile>();            // Get an array of all the tiles
    3.          oneOfTheTileRenderers               = tapTiles[0].GetComponent<MeshRenderer>();                 // Get the meshrenderer of one of the tiles
    4.          tileMat                             = oneOfTheTileRenderers.sharedMaterial;                     // Find the shared material of all the tiles - so when we fade the material, it fades on all the tiles
    5.          tileMat.color                       = opague;                                                   // Make sure the material is fully visible when the scene loads
    6.          touchIcon                           = GameObject.Find("TouchIcon");
    7.  
    8.          imageFaderMat                       = GameObject.Find("ImageFader").GetComponent<MeshRenderer>().sharedMaterial; // **THIS REFERENCE ISN'T BEING CACHED!**
    9.          imageFaderMat.color                 = Color.clear;
    10.  
    11.          tileClicker                         = GameObject.Find("Main Camera").GetComponent<TileClicker>(); // **NEITHER IS THIS ONE!**
    12.      }
    On loading ItemFocusTest from LandingPage, I get the following error message:

    At a certain point in itemFocusTest, using enough items SHOULD trigger a set of fades and turn off clicking interaction. Instead, I get the following error...

    Again, all this works when the scene is loaded in isolation in the Unity Editor.

    What's going on? What's stopping the script from finding the correct gameObjects and/or components? Is there a later event than Start that I can use for initialization? I'm baffled.

    --Rev
     
    Last edited: Aug 30, 2016
  2. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Please use Code tags when posting code in the forum, as per the sticky at the top: http://forum.unity3d.com/threads/using-code-tags-properly.143875/

    It might be a good idea to break up your referencing into chunks to make debugging easier and increase readability. For instance:
    Code (csharp):
    1. imageFaderMat = GameObject.Find("ImageFader").GetComponent<MeshRenderer>().sharedMaterial;
    should really be something like:
    Code (csharp):
    1. GameObject imageFader = GameObject.Find("ImageFader");
    2. MeshRenderer imageFaderRenderer = imageFader.GetComponent<MeshRenderer>();
    3. imageFaderMat = imageFaderRenderer.sharedMaterial;
    You don't even need to throw in null-checks at each step, but just separating them out this way means that the warning and errors thrown will have better context.

    I'm a bit confused about where this script is that you've copied here. If it's on an object that's being loaded in the new scene (created when you change scenes), and the object it's trying to reference (ImageFader) is also being created in the new scene, then you should just make a public reference and drag it in rather than using GameObject.Find at all in my opinion.

    If, as seems to be the case, the ImageFader object is being carried over from the old scene, make sure that the object is set to DontDestroyOnLoad or that the scene is Additive, or else it'll just get wiped out with everything else when the scene changes. What's the scene hierarchy look like in the editor when the error happens? Is there only one copy of the ImageFader object, and if there is, does it have the correct components attached or is it just an empty GameObject?
     
    Last edited: Aug 30, 2016
    Xeronin and Reverend-Speed like this.
  3. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    Goddamnit, apologies Lysander, I knew something was wrong but I couldn't find the menu option and I got lazy. Thanks for the heads up.

    Good point on breaking up the reference line, I hadn't thought about it in terms of getting error reports. Appreciate the advice.

    --Rev
     
    Last edited: Aug 30, 2016
  4. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I also wonder if GameObject.Find isn't working because it's trying to locate a gameobject that hasn't had it's Start routine called yet. Maybe when you're loading the scene, that other object hasn't yet been loaded which is why it can't be located. Things may work a bit differently in the editor.
     
    Reverend-Speed and DonLoquacious like this.
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    As a quick note, because it just occurred to me, but you should do all of your initialization in Awake, or at least the stuff that you CAN properly initialize with only the data within this script. Start should, as a good practice, be reserved for things that require other scripts in order to initialize properly. This way, if you're referencing something in ScriptB from ScriptA in Start, you can be absolutely sure that the Awake function has run in ScriptB- if you initialize everything for both scripts in Start, then you can't be sure that the Start function in ScriptB has run before ScriptA tries to reference it. Does that make sense?

    Every Awake function is called at the scene start, then when those are all done, every Start function is called, but one component's Awake function is not normally guaranteed to be called before any other other component's Awake function (the order is not quite random, but it's not reliable either), and the same for the Start function.

    In situations where you need more precise control and ScriptB's Start function really does have to run before ScriptA's Start function does (or something in ScriptA's Update function is relying on ScriptA's Update function already having been run this frame, etc...) then look into Script Execution Order.

    I'm only bringing this up because it has a small chance of being relevant here, if ImageFader's Start function is placing the MeshRenderer on the GameObject, and this script is trying to grab a reference to the MeshRenderer before ImageFader has had a chance to place it, this kind of problem can happen. It's also just good stuff to know :).

    Edit: Jinx ;)
     
    Last edited: Aug 30, 2016
    Reverend-Speed and Dustin-Horne like this.
  6. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,797
    @Reverend-Speed I've always had issues with GameObject.Find in Start().

    They most likely haven't been initialised yet, and thus cannot be found. Any time I'm looking for GameObjects in a scene startup, I use a co-routine.

    So move your code into a co-routine, and use WaitForEndOfFrame().

    Something like this...
    Code (CSharp):
    1.  
    2.  
    3. void Start()
    4. {
    5.   StartCoroutine(InitCoroutine());
    6. }
    7.  
    8. IEnumerator InitCoroutine()
    9. {
    10.    yield return new WaitForEndOfFrame();
    11.  
    12.    // Do your code here to assign game objects
    13. }
     
    Reverend-Speed and Dustin-Horne like this.
  7. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Excellent solution!
     
    Reverend-Speed likes this.
  8. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    @Dustin-Horne - Yeah, that was sorta my initial thought, but as you'll see further down this post, the GetComponent is attempted again well into the runtime of the scene. Many thanks for your input, though!

    @Lysander - Thank you so much for that thoughtful response! This might as well be printed on the inside of my eyelids (I just about have it memorised nowadays), but it's so damned easy to miss one thing and slip up. Appreciate your eyes and input on the problem.

    The Meshrenderer in question is set as a component on the ImageFader object in-editor, and so doesn't rely on a script to add the component on the gameobject. Maybe I should look into somehow getting a reference to the tilecounter from the ImageFader and... then... use that to set a reference from the tileCounter to the ImageFader...? Nah, surely that's crazy...!

    @Meltdown - Just. Golf. Clap. That is an EXCELLENT solution and I'll be sure to use it in future. Unfortunately, it doesn't appear to be helping with this issue (which is baffling). Currently, this is what I get shortly after switching to the second scene...


    The TileCounter.cs code currently is as follows...

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class TileCounter : MonoBehaviour {
    5.  
    6.     private int         tileClickOnCount    = 0;                // The number of tiles clicked on thus far
    7.     public Transform    tileParent;                             // The game object which contains the (currently hex-shaped) tiles
    8.     private TapTile[]   tapTiles;                               // An array of TapTile scripts constructed from the children of tileParent
    9.     public int          tileClickOnMax      = 0;                // The number of tiles to click before all visible tiles begin to fade out
    10.  
    11.     private Renderer    oneOfTheTileRenderers;                  // The first tile renderer of the tileParent children, used to get the material shared between tiles.
    12.     private Material    tileMat;                                // The material used on all the tiles - located through oneOfTheTileRenderers.
    13.     private bool        startFading         = false;            // Flag/bool which is prevent the Update method from fading the tiles until tileCount exceeds tileClickOnMax
    14.     private Color       opague              = Color.white;      // Making a single name for a fully white, fully visible color
    15.     public  float       tileFadeSpeed       = 1.0f;             // The speed at which the tile material fades to transparent
    16.     // public  float       tileFadeDuration    = 1.0f;             //
    17.     // private float       tileFadeTimer       = 0.0f;
    18.     private GameObject  touchIcon;
    19.  
    20.     private AudioSource audMovieTrack;
    21.     public  float       fadeSpeed = 4.0f;
    22.  
    23.     private Material        imageFaderMat;
    24.  
    25.     public TileClicker     tileClicker;
    26.  
    27.     void Awake ()
    28.     {
    29.         audMovieTrack = GetComponent<AudioSource>();
    30.         audMovieTrack.volume = 1.0f;
    31.     }
    32.  
    33.     // Use this for initialization
    34.     void Start () {
    35.         StartCoroutine(InitCoroutine());
    36.     }
    37.  
    38.     // Update is called once per frame
    39.     void Update () {
    40.  
    41.         if (startFading)
    42.         {
    43.             imageFaderMat.color = Color.Lerp(imageFaderMat.color, Color.white, fadeSpeed * Time.deltaTime);
    44.         }
    45.  
    46.         //if (startFading)//&& tileFadeTimer < tileFadeDuration) // If startFading is true...
    47.         //{
    48.         //    // tileMat.SetFloat("_Mode", 3.0f);
    49.         //    FadeTileMaterial();
    50.         //    // tileFadeTimer += Time.deltaTime;
    51.         //}
    52.     }
    53.  
    54.     IEnumerator InitCoroutine()
    55.     {
    56.         yield return new WaitForEndOfFrame();
    57.  
    58.         tapTiles = tileParent.GetComponentsInChildren<TapTile>();               // Get an array of all the tiles
    59.         oneOfTheTileRenderers = tapTiles[0].GetComponent<MeshRenderer>();       // Get the meshrenderer of one of the tiles
    60.         tileMat = oneOfTheTileRenderers.sharedMaterial;                         // Find the shared material of all the tiles - so when we fade the material, it fades on all the tiles
    61.         tileMat.color = opague;                                                 // Make sure the material is fully visible when the scene loads
    62.         touchIcon = GameObject.Find("TouchIcon");
    63.  
    64.         GameObject imgFader = GameObject.Find("ImageFader");
    65.         MeshRenderer imgRend = imgFader.GetComponent<MeshRenderer>();
    66.         imageFaderMat = imgRend.sharedMaterial;
    67.         imageFaderMat.color = Color.clear;
    68.  
    69.         tileClicker = GameObject.Find("Main Camera").GetComponent<TileClicker>();
    70.     }
    71.  
    72.     public void AddOneToCount()                 // Called from a tile when it's clicked on, increments the tileClickOnCount and checks to see if it's more than tileClickOnMax
    73.     {
    74.         if(tileClickOnCount < 1)
    75.         {
    76.             Debug.Log("Turning off icon");
    77.             touchIcon.SetActive(false);
    78.         }
    79.         tileClickOnCount++;
    80.         if(tileClickOnCount >= tileClickOnMax)
    81.         {
    82.             tileClicker = GameObject.Find("Main Camera").GetComponent<TileClicker>();
    83.             tileClicker.TurnOffTapOnTile();
    84.             StopMovieTrack();
    85.         }
    86.     }
    87.  
    88.     //void AllFallDown()
    89.     //{
    90.     //    startFading = true;                     // Starts the fade in the main Update loop
    91.     //    // tileMat.SetFloat("_Mode", 3.0f);
    92.     //    //for (int i = 0; i < tapTiles.Length; i++)
    93.     //    //{
    94.     //    //    if (tapTiles[i] != null)
    95.     //    //    {
    96.     //    //        tapTiles[i].DropTile();
    97.     //    //    }
    98.     //    //}
    99.     //}
    100.  
    101.     public void PlayMovieTrack()
    102.     {
    103.         audMovieTrack.Play();
    104.     }
    105.  
    106.     void StopMovieTrack()
    107.     {
    108.         StartCoroutine(FadeVolumeDown());
    109.         imageFaderMat = GameObject.Find("ImageFader").GetComponent<MeshRenderer>().sharedMaterial;
    110.         startFading = true;
    111.     }
    112.  
    113.     IEnumerator FadeVolumeDown()
    114.     {
    115.         while (audMovieTrack.volume > 0.0f)
    116.         {
    117.             audMovieTrack.volume = Mathf.MoveTowards(audMovieTrack.volume, 0.0f, fadeSpeed * Time.deltaTime);
    118.          
    119.             yield return null;
    120.         }
    121.     }
    122. }
    123.  
    Note the additional attempt to get the reference in StopMovieTrack() - This is called wayyy after the scene initialises, but it just gives me the same error...

    I'm sure this has to be a small thing that I'm just not seeing - I appreciate the time you folks are giving to this... Hope you're more observant than I am!

    --Rev
     
    Last edited: Aug 30, 2016
  9. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,797
    Replace

    Code (CSharp):
    1. yield return new WaitForEndOfFrame();
    with

    Code (CSharp):
    1. yield return new WaitForSeconds(5);
    If everything works, then we know its an issue with stuff not being initialised on time.

    Is it possible instead of using GetComponent to rather assign the component to your script in the editor? I try to assign as many references as I can in the editor, instead of using GetComponent or GameObject.Find etc.
     
  10. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    @Meltdown: Great minds think alike, it seems. Quite late on Weds night, I remembered that I could just assign the MeshRenderer as a public variable and presto, everything worked perfectly for the next morning.

    The issue is still annoying, though. Unlike yourself, I've been lucky enough to have been treated fairly well by Awake/Start/GetComponent/GameObject.Find in the past, so this seems like an sudden and emotionally scarring betrayal. I've always thought that getting references to private vars was the proper way of doing things - will have to think about this, now.

    For yucks, I ran WaitForSeconds (5) on the script and it still crashed and burned (Meshrenderer not found). I'm starting to suspect my menu singleton is interrupting stuff, somehow... but for the moment, the scripting problem is fixed*!

    Thanks so much for helping out, everybody!

    --Rev

    * by not using scripting at all.
     
  11. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Are you using a GameObject singleton pattern but not using DontDestroyOnLoad? It's possible that destroy is getting called but you have a game object that isn't really going away because you have a reference to it elsewhere.
     
    Last edited: Sep 7, 2016
  12. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    Hey, just saw your response. I have to admit ignorance here, as I don't know what a 'cameo next' singleton pattern is - however, I am using DontDestroyOnLoad. Here's the code...

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.SceneManagement;
    3. using System.Collections;
    4.  
    5. public class DocManager : MonoBehaviour {
    6.  
    7.     private Animator anim;
    8.     public  float   fadeTime = 0.33f;
    9.  
    10.     private AudioSource aud;
    11.     private float       startingVolume;
    12.     public  float       audFadeSpeed = 1.0f;
    13.     private bool        audIsPlaying = true;
    14.  
    15.     private const string TWITTER_ADDRESS = "http://twitter.com/intent/tweet";
    16.     private const string TWEET_LANGUAGE = "en";
    17.  
    18.     private static DocManager _instance;
    19.     public static DocManager Instance { get { return _instance; } }
    20.  
    21.     void Awake () {
    22.         if(_instance != null && _instance != this)
    23.         {
    24.             Destroy(gameObject);
    25.         }
    26.         else
    27.         {
    28.             _instance = this;
    29.             DontDestroyOnLoad(gameObject);
    30.         }
    31.  
    32.         SceneManager.sceneLoaded += OnLevelChange;
    33.  
    34.         aud = GetComponent<AudioSource>();
    35.         startingVolume = aud.volume;
    36.     }
    37.  
    38.     void Start ()
    39.     {
    40.         anim = GetComponentInChildren<Animator>();
    41.     }
    42.  
    43.     public void LoadScene (string sceneName)
    44.     {
    45.         anim.SetTrigger("ClearToBlack");
    46.         Debug.Log("Loading scene...");
    47.         StartCoroutine(FadeAndLoad(sceneName));
    48.     }
    49.  
    50.     IEnumerator FadeAndLoad(string sceneName)
    51.     {
    52.  
    53.         yield return new WaitForSeconds(fadeTime);
    54.         SceneManager.LoadScene(sceneName);
    55.     }
    56.  
    57.     public void OnLevelChange(Scene level, LoadSceneMode mode)
    58.     {
    59.         Debug.Log("Calling BlackToClear");
    60.         anim.SetTrigger("BlackToClear");
    61.     }
    62.  
    63.     public void ToggleSound ()
    64.     {
    65.         StopCoroutine("ChangeVolume");
    66.         if (audIsPlaying)
    67.         {
    68.             StartCoroutine(ChangeVolume(0.0f));
    69.             audIsPlaying = false;
    70.         } else
    71.         {
    72.             StartCoroutine(ChangeVolume(startingVolume));
    73.             audIsPlaying = true;
    74.         }
    75.     }
    76.  
    77.     IEnumerator ChangeVolume (float target)
    78.     {
    79.         while(aud.volume != target)
    80.         {
    81.             aud.volume = Mathf.MoveTowards(aud.volume, target, audFadeSpeed * Time.deltaTime);
    82.             yield return null;
    83.         }
    84.         yield return null;
    85.     }
    86.  
    87.     public void ShareToTwitter (string textToDisplay)
    88.     {
    89.         Application.OpenURL(TWITTER_ADDRESS + "?text=" + WWW.EscapeURL(textToDisplay) + "&amp;lang=" + WWW.EscapeURL(TWEET_LANGUAGE));
    90.     }
    91. }
    92.  
    Gah. I'm a combination of baffled but also relieved that I've got a workaround for this. Might just drop the issue, tbh. But thank you so much for your time on this!

    All the best,

    --Rev
     
    Last edited: Sep 6, 2016
  13. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I'm sorry, I corrected it. I meant GameObject singleton pattern... autocorrect got me as I was replying from my phone. In your Awake you're checking _instance but calling Destroy(gameObject). What is the value of gameObject? Shouldn't you be destroying _instance and then resetting it's value?
     
  14. Reverend-Speed

    Reverend-Speed

    Joined:
    Mar 28, 2011
    Posts:
    284
    Hah! Autocorrect, amirite? =)

    Uh... hang on. Let me walk through the logic of this...

    Code (CSharp):
    1.  
    2. if(_instance != null && _instance != this) // IF the private static variable of the class is NOT empty AND it ISN'T this game object...
    3.         {
    4.             Destroy(gameObject); // ...THEN this must be a new instance of the class (w/gameobject) (probably a new version or prefab in the new scene that's just loaded), which we DON'T want - get rid of it. (There should only ever be one version of a Singleton, the first instance of it to be loaded when the project runs)
    5.         }
    6.         else // OTHERWISE - if there's no gameObject running DocManager currently occupying the _instance variable...
    7.         {
    8.             _instance = this; // Make the static variable refer to 'this' (ie. the DocManager running on THIS gameObject)
    9.             DontDestroyOnLoad(gameObject); // ...and mark the gameObject 'DontDestroyOnLoad' so when the next scene is loaded, this gameObject and the data on it is retained, thus preserving some runtime data throughout the course of the project.
    10.         }
    11.  
    That still seems to make sense, I think? I'm about ready to throw my hands up at this thing and mark it down as the opposite of a learning experience. =) At least the project works!

    --Rev