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

Bug WaitWhile Coroutine Does Not Work Properly For WEBGL Builds

Discussion in 'Editor & General Support' started by amazeinggamedesign, Sep 1, 2023.

  1. amazeinggamedesign

    amazeinggamedesign

    Joined:
    Jun 4, 2022
    Posts:
    2
    I noticed my game, which was working in the Editor, wasn't properly working in my game build for WebGL, and it was particularly strange that I was receiving a NullReferenceException error in the logs, something that occurred in neither the Editor, nor in the downloadable build.

    I rooted around in the code, and after a short amount of troubleshooting, I was able to find what exactly was was returning the error, and it was quite surprising to me what was, since my code specifically ensured this would not happen. Take a look:

    Code (CSharp):
    1. IEnumerator Setup()
    2.     {
    3.         //Gives time to unload the current level
    4.         yield return null;
    5.  
    6.         //Gives time to load the next level
    7.         yield return new WaitWhile(IsTileMapsNull);
    8.        
    9.         // Here tilemaps is null, so when we use it on the following lines,
    10.         // a null reference exception error occurs.
    11.         tileMaps = GameObject.Find("TileMaps");
    12.  
    13.  
    14.         activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;
    15.         inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
    16.         activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
    17.         inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;
    18.  
    19.         yield break;
    20.     }
    21.  
    Here is the code for IsTileMapsNull()

    Code (CSharp):
    1.  
    2.     bool IsTileMapsNull()
    3.     {
    4.         bool isNull = GameObject.Find("TileMaps") == null;
    5.  
    6.         if (isNull)
    7.         {
    8.             Debug.Log("waiting");
    9.         }
    10.  
    11.         return isNull;
    12.     }


    The WaitWhile coroutine should ensure that we only pass after once we've given the game enough time to load the next scene and confirm the TileMaps object isn't null.

    I really wasn't sure at all why this was happening, but I did some research and found this similar issue happening with async/await builds for WebGL, but, since coroutines don't use multiple threads, I still was a bit confused. Then, I also researched similar issues of features not properly working due to compression, so I turned that off, built, and still nothing changed. In the end I replaced the previous code with the following version of the code, and it fixed the issue. (The IsTileMapsNull func remained the same in both versions).

    Code (CSharp):
    1. IEnumerator Setup()
    2.     {
    3.         Debug.Log("Setup");
    4.  
    5.         while (true)
    6.         {
    7.             yield return null;
    8.  
    9.             yield return new WaitWhile(IsTileMapsNull);
    10.            
    11.             var findTilemaps = GameObject.Find("TileMaps");
    12.  
    13.             if (findTilemaps != null)
    14.                 break;
    15.         }
    16.  
    17.         tileMaps = GameObject.Find("TileMaps");
    18.  
    19.         Debug.Log($"Are tileMaps null : {tileMaps == null}");
    20.  
    21.         activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;
    22.         inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
    23.         activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
    24.         inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;
    25.  
    26.         Debug.Log("TileMaps doene");
    27.         yield break;
    28.     }
    29.     }
    So, why did my WaitWhile code did not work on the WebGL build, but did inside of the Unity Editor and in the Windows/Mac/Linux build? Does it have something to do with the WaitWhile() function? With how WebGL works? Or did it have to do with an issue inside of my code? Much appreciated for any answers!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    The answer is always the same... ALWAYS!

    How to fix a NullReferenceException error

    https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

    Three steps to success:
    - Identify what is null <-- any other action taken before this step is WASTED TIME
    - Identify why it is null
    - Fix that

    That may be true... but is that even what is null?

    As far as this:

    and the fact that you are using all kinds of findy stuff like GameObject.Find(), well, there you have it!

    Remember the first rule of GameObject.Find():

    Do not use GameObject.Find();

    More information: https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

    More information: https://forum.unity.com/threads/why-cant-i-find-the-other-objects.1360192/#post-8581066

    In general, DO NOT use Find-like or GetComponent/AddComponent-like methods unless there truly is no other way, eg, dynamic runtime discovery of arbitrary objects. These mechanisms are for extremely-advanced use ONLY.

    If something is built into your scene or prefab, make a script and drag the reference(s) in. That will let you experience the highest rate of The Unity Way(tm) success of accessing things in your game.
     
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I imagine this is breaking due to some form of non-determinism in Unity that is cropping up in WEBGL.

    I agree with Kurt
    GameObject.Find
    is really not anything you should use in any capacity. Can you just serialise a direct reference to this component?

    In any case you haven't really pointed out where the null ref occurs.
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Ugh … using GameObject.Find(„“) in a coroutine‘s while loop. That hurts!

    You should instead have an inspector reference to the Tilemap, done.
    If you can‘t think of or find a way to make this work, refactor.
    The worst solution is alway, ALWAYS ;) to try and „find“ something. You know where scene objects are, you don‘t actually need to find them if you properly structure your scene. Even unregistered objects would have some sort of parent container objects in order to foreach(Transform child in transform) every child. Or objects register themselves to well-known (singleton) manager‘s properties (usually to be avoided though if there are other options). Or manager sends events and objects register themselves to listen for events they are interested in. Etc etc.
     
  5. amazeinggamedesign

    amazeinggamedesign

    Joined:
    Jun 4, 2022
    Posts:
    2
    Yes, I apologize. I should have clarified where the null reference exception occurs, but I can confirm that the TileMaps object is null, which is the cause of the error. I made sure this was the case while I was initially testing, but I should have shared this. I recreated the code and made a new build to share:

    Code (CSharp):
    1. IEnumerator Setup()
    2.     {
    3.         //Gives time to unload the current level
    4.         yield return null;
    5.  
    6.         //Gives time to load the next level
    7.         yield return new WaitWhile(IsTileMapsNull);
    8.  
    9.         tileMaps = GameObject.Find("TileMaps");
    10.  
    11.         Debug.Log($"Are tileMaps null : {tileMaps == null}");
    12.  
    13.         activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;
    14.  
    15.         Debug.Log("Does this run?");
    16.  
    17.         inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
    18.         activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
    19.         inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;
    20.  
    21.         yield break;
    22.     }
    Here are my logs, multiple ways to view, as I'm not sure which one will work:


    Imgur
    Google Drive


    Hopefully that is sufficient to show that is where the error occurs, since the second debug doesn't run.

    With regards to using GameObject.Find, I can do some additional research to improve performance. However, I can create a script for the tilemaps object, and use an approach that looks more like this:


    Code (CSharp):
    1. tileMaps = GameObject.Find("TileMaps").GetComponent<TileMaps>();
    2.  
    3.         activeHeavenTiles = tileMaps.ActiveHeavenTiles;
    4.         inactiveHeavenTiles = tileMaps.InactiveHeavenTiles;
    5.         activeHellTiles = tileMaps.ActiveHellTiles;
    6.         inactiveHellTiles = tileMaps.InactiveHellTiles;
    I am admittedly new to managing multiple scenes, but here is an explanation of my scene management and why I use GameObject.Find("TileMaps") over serialization, so if you have a suggestion I would be very open to hearing it so I can improve:

    I have a scene called 'Game' that contains all of my GameManagers and systems that need to exist for the game to function. One of these managers is the TileController script, which contains the IEnumerator Setup() function. Whenever I need to start a new level, I load in my level scene additively, and get a reference to all the tile maps for the level.

    A simple solution I can think of off the top of my head, would be with the TileMaps script idea, with all the references serialized just like above, but instead of finding it, I can make just that script a singleton, since I would never have more than one in any given level. Would that be a sufficient enough solution?

    However, besides that, I was still curious as to the reason why my WaitWhile() function doesn't work properly? I appreciate calling out some of my poor programming practices, since it helps me to get better, however, is that the reason the code doesn't work? Is it GameObject.Find() that is causing this and if so, why does the build specifically only return this error for the WebGL build, but not for for the Downloadable Build? Was this solely due to my code using GameObject.Find()? Or was it something more?
     

    Attached Files:

  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    It does. See my post above about not using GameObject.Find().

    This is all very well-studied well-understood best practices.

    Don't use GameObject.Find(). It causes nullrefs. And here we are.