Search Unity

How to yield untill level is loaded in c#?

Discussion in 'Scripting' started by Jordos, Nov 13, 2009.

  1. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    Im trying to make my function call Application.LoadLevel() and wait with execution untill a level is loaded. The script is in C#, and I can't get it to work.

    The script reference says this: "OnLevelWasLoaded can be a co-routine, simply use the yield statement in the function."

    So I made this:
    Code (csharp):
    1. private IEnumerator OnLevelWasLoaded(int iLevel)
    2.     {
    3.         Debug.Log("in OnLevelLoaded");
    4.         yield return true;
    5.     }
    And the loading function:
    Code (csharp):
    1.     private IEnumerator LoadGame(string strLevel)
    2.     {
    3.         Debug.Log("Loading Level");
    4.         Application.LoadLevel(strLevel);
    5.         yield return StartCoroutine("OnLevelWasLoaded");
    6.         Debug.Log("level was Loaded");
    7.     }
    8.  
    I never really expected this to work, but I'm kinda stuck also. Can anyone tell me how to do this?
     
  2. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    No-one?
     
  3. AmazingRuss

    AmazingRuss

    Joined:
    May 25, 2008
    Posts:
    933
    I think execution stops until after the level is loaded, no matter what, so you should be able to just continue after the LoadLevel().
     
  4. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    If the script with LoadGame(...) in it isn't marked with DontDestroyOnLoad(), it will no longer exist when the level is done loading, so it won't be able to execute your code. If, on the other hand, it is marked with DontDestroyOnLoad, it could just use the OnLevelWasLoaded() call-back directly to detect when it is done.
     
  5. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    AmazingRuss, execution doesn't stop.

    tomvds, it is marked as DontDestroyOnLoad. I know I can use the OnLevelWasLoaded() method, but I still like to yield the LoadLevel method, as things need to happen after the loading of different scenes and I'd like to not use a huge switch() statement in the OnLevelwasLoaded() method.

    The docs say it can be done. I just need to find the syntax I guess.
     
  6. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    In that case, you could try something like this:
    Code (csharp):
    1.  
    2.     private bool levelWasLoaded = false;
    3.     private void OnLevelWasLoaded(int iLevel)
    4.     {
    5.         levelWasLoaded = true;
    6.     }
    7.  
    8.     private IEnumerator LoadGame(string strLevel)
    9.     {
    10.         Debug.Log("Loading Level");
    11.         Application.LoadLevel(strLevel);
    12.         while (!levelWasLoaded)
    13.             yield return 1;
    14.         levelWasLoaded = false;
    15.         Debug.Log("level was Loaded");
    16.     }
    17.  
    (Warning: untested, I don't know if it will work at all).

    However, I must add that I don't really see much advantage using this over just using the OnLevelWasLoaded function itself, but obviously I don't know the context (and perhaps I don't dislike switch() as much as I should :p).
     
  7. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    Thanks tomvds, I appreciate your suggestion. There is indeed not that much advantage over using the switch. You're right though using the switch is not a huge problem.

    It's just I wanna know how, or if, this could be done with yield :)
     
  8. Troy-Dawson

    Troy-Dawson

    Joined:
    Nov 2, 2009
    Posts:
    120
    Another approach to avoid the switch would be to use C# delegates.

    Code (csharp):
    1.     public delegate void UpdateDelegate();
    declares the delegate method signature.

    Your controller class declares either a public or private method handler "pointer":

    Code (csharp):
    1.     private UpdateDelegate updateDelegates;
    This pointer can be assigned just like C++ function pointers:

    Code (csharp):
    1.     void AButton()
    2.     {
    3.         Debug.Log("A");
    4.     }
    5.  
    6.     void BButton()
    7.     {
    8.         Debug.Log("B");
    9.     }
    10.  
    11.     void Update()
    12.     {
    13.         b = XBoxController.GetDeviceButtons(0);
    14.        
    15.         if (b.A > 0)
    16.             updateDelegates = this.AButton;
    17.  
    18.         if (b.B > 0)
    19.             updateDelegates = this.BButton;
    20.            
    21.         if (updateDelegates != null)
    22.             updateDelegates();
    23.     }
    You can also do "multicast" delegate calls via the handler by using the overloaded += operator:
    Code (csharp):
    1.         if (b.X > 0)
    2.             updateDelegates += this.XButton;
    Anyhoo, for your application you'd have an object register a method to be called via the callback delegate handler from OnLevelWasLoaded():

    Some object:
    Code (csharp):
    1. public class TestScript : MonoBehaviour {
    2.  
    3.     void Awake()
    4.     {
    5.         Loader.updateDelegates += this.LoadUpdate;
    6.     }
    7.  
    8.     void LoadUpdate()
    9.     {
    10.         Debug.Log("Loaded!");
    11.     }
    Then in your Loader class:
    Code (csharp):
    1.     public delegate void UpdateDelegate();
    2.  
    3.     public static UpdateDelegate updateDelegates;
    4.  
    5.     . . .
    6.  
    7.     void OnLevelWasLoaded(int level)
    8.     {
    9.         Debug.Log("OLL: "+ level);
    10.  
    11.         if (updateDelegates != null)
    12.             updateDelegates();
    13.     }
     
  9. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    Troy, thanks for your reply. Using a delegate would indeed be a nice workaround. If I have to use a workaround, I'll go for this one.

    However, I'm still very interested in finding out how the yield can be used here (as the docs say it can).
     
  10. Factoid

    Factoid

    Joined:
    Mar 30, 2008
    Posts:
    69
    You might be using the wrong function.

    The OnLevelWasLoaded callback is called after the level succesfully loads. If you need to watch the progress of the level loading (for a load bar or something), then you should be using LoadLevelAsync (new to the 2.6 API).

    LoadLevelAsync returns a AsyncOperation, which can be yielded on, as well as inspected and modified.

    Code (csharp):
    1.  
    2. private IEnumerator LoadGame(string strLevel)
    3. {
    4.   Debug.Log("Loading Level");
    5.   yield return Application.LoadLevelAsync(strLevel);
    6.   Debug.Log("Level Load complete");
    7. }
    8.  
    9. // Here's how OnLevelWasLoaded works
    10. void OnLevelWasLoaded( int level )
    11. {
    12.   Debug.Log( "Level : " + level + " was loaded" );
    13. }
    14.  
    15. // If you wanted to use OnLevelWasLoaded as a co-routine, it's like this
    16. IEnumerator OnLevelWasLoaded( int level )
    17. {
    18.   Debug.Log( "Level : " + level + " was loaded, waiting for 10 seconds" );
    19.   yield return WaitForSeconds( 10.0f );
    20.   Debug.Log( "Level : " + level + " was loaded 10 seconds ago" );
    21. }
    22.  
    Keep in mind that if the script that LoadGame exists in doesn't have "DontDestroyOnLoad" set, that 2nd debug message might not ever execute, and OnLevelWasLoaded most certainly won't.

    Also OnLevelWasLoaded doesn't have to be in the same script as the script that calls LevelLoad, and there can be multiple scripts with OnLevelWasLoaded, they'll all get called whenever the level changes.

    Lastly, unless you're running a streaming web player, or loading a level from an asset bundle, LoadLevelAsync should be instantaneous, and LoadLevel will either be instantaneous, or it won't work at all (a streamed level isn't loaded yet).
     
  11. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    Hi Factoid, thanks for your reply. You might be right. It looks like LoadLevelAsync is the function I need. Unfortunately I don't have the Pro version of Unity so I can't test.

    As I replied to tomvds, the object has DontDestroyOnLoad set.
     
  12. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    I think you're reading the statement on the OnLevelWasLoaded page wrong:
    It simply means OnLevelWasLoaded can be a coroutine containing yield, that could be used for something like starting an animation after level load or any other sequence that runs during that level.

    You seem to interpret the statement such that OnLevelWasLoaded can yield until a next level is loaded, which is not the case. When a next level is loaded a fresh OnLevelWasLoaded is called (i.e. with DontDestroyOnLoad).
     
  13. Jordos

    Jordos

    Joined:
    Sep 16, 2009
    Posts:
    84
    Yes I think you're right.
     
  14. jmasinteATI

    jmasinteATI

    Joined:
    Jul 14, 2015
    Posts:
    7
    I realize this is an old thread, but I looked everywhere for an answer and couldn't really find one. Here was my solution. GameResources.IsLevelLoaded is set to true in the loaded level's Start() function:
    Code (CSharp):
    1. public class MyGame : MonoBehaviour
    2. {
    3.     void Awake()
    4.     {
    5.         DontDestroyOnLoad(gameObject);
    6.     }
    7.  
    8.     void LoadLevel()
    9.     {
    10.         GameResources.IsLevelLoaded = false;
    11.         Application.LoadLevel("Level");
    12.  
    13.         InvokeRepeating("WaitForLevelToLoad", 0f, .01f);
    14.     }
    15.  
    16.     void WaitForLevelToLoad()
    17.     {
    18.         if (GameResources.IsLevelLoaded)
    19.         {
    20.             CancelInvoke("WaitForLevelToLoad");
    21.             LevelLoaded();
    22.         }
    23.     }
    24.  
    25.     void LevelLoaded()
    26.     {
    27.         //Level Code Here
    28.     }
    29. }
     
  15. Deepscorn

    Deepscorn

    Joined:
    Aug 13, 2015
    Posts:
    25
    jmasinteati, I don't see any good reason for that logic. Why don't do all you need to do inside scene (i.e. level) and do all that must be done before that - in another loading scene or even do that when editor starts (useful when debugging starting from current scene)
     
  16. vfxjex

    vfxjex

    Joined:
    Jun 3, 2013
    Posts:
    93
    Just want to update this for Unity 5 and higher

    Code (CSharp):
    1. AsyncOperation asyncLoadLevel;
    2.  
    3. IEnumerator AddLevel (){
    4. asyncLoadLevel = SceneManager.LoadSceneAsync("MyLevel", LoadSceneMode.Additive);
    5. while (!asyncLoadLevel.isDone) yield return null;
    6. }
     
    Lorrak and OscarLeif like this.