Search Unity

Trying to preload a scene, failing horribly.

Discussion in 'Scripting' started by kikendo, Sep 21, 2017.

  1. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    I am trying to have a scene preload so I can smooth out transitions between levels/screen, but so far the information I found online is confusing to me, and the Unity scripting reference is no help.

    I guess my main issue is that I have no idea what an IEnumerator is or how to use it, and every example I have seen of a preload has this.

    The info I found online is this:
    http://t-machine.org/index.php/2017/06/05/changing-scenelevel-smoothly-in-unity-5-5-5-unitytips/
    Even if heavily commented, I have a terrible time trying to follow this.
    Then I also found this approach:
    http://answers.unity3d.com/questions/1153534/unity-53-and-scene-transitions-how-to-preload-a-sc.html

    I tried just copypasting the code posted in that Answers post, but it doesn't work.
    When I call the LoadLevel function I made, nothing happens. I have added Debug features to what I pasted so it would show me if teh method was called, but it isn't.

    Here is what I have:
    Code (CSharp):
    1. public class LoadScene : MonoBehaviour {
    2.  
    3.     public static AsyncOperation sceneToLoad;
    4.  
    5.     public static IEnumerator LoadLevel (int level) {
    6.         Debug.Log ("method called");
    7.         sceneToLoad = SceneManager.LoadSceneAsync (level , LoadSceneMode.Single);
    8.         sceneToLoad.allowSceneActivation = false;
    9.         Debug.Log(sceneToLoad);
    10.         // do loading animation here
    11.         yield return new WaitForSeconds (1.5f);
    12.         Debug.Log ("Loads Now");
    13.         sceneToLoad.allowSceneActivation = true;
    14.     }
    15. }
    16.  
    I am sure I am doing this wrong.
    Also I don't understand how to control this more. My idea is to call for the level load, then show an animation (loading anim), and when the loading is done (how do I check?), enable the new scene.

    Any help appreciated!
     
  2. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    When using IEnumerators you must make sure you use a StartCoroutine Function, Assuming this is done on a button press you can do something like

    Code (CSharp):
    1.     public void ButtonPressed()
    2.     {
    3.         StartCoroutine(LoadLevel());
    4.     }
     
    Last edited: Sep 21, 2017
    kikendo likes this.
  3. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    Oh, so the problem is in how I am calling it? Let me try...
    YES! That worked, thanks so much for your help!!
    I wish the Unity documentation was a bit better explaining all this.

    So what do you think of that first link I posted? Seems like this method called as-is has issues. I notice that after I click, the game freezes for a second, then unfreezes, then it loads.

    What about this line?
    Code (CSharp):
    1. yield return new WaitForSeconds (1.5f);
    I don't really understand what it is doing or why it is waiting. Shouldn't it somehow wait until ".isDone" is set? Is this the freezing I notice?
     
  4. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    This Line
    Code (CSharp):
    1. yield return new WaitForSeconds (1.5f);
    2.  
    is simply waiting 1.5 seconds before running any code thats underneath it. , you can change it to any float number you want
    Im not 100% sure why it freezes just before loading you could try waiting for the ASync Operation to load to full (when the scenes ready) before loading the scene

    Code (CSharp):
    1.         AsyncOperation async = SceneManager.LoadSceneAsync(sceneToLoad);
    2.  
    3.         if(sceneToLoad.isDone)
    4.         {
    5.                sceneToLoad.allowSceneActivation = true;
    6.         }
     
    kikendo likes this.
  5. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    I will try that, thanks so much!
    I knew there was an "isDone" parameter, just unsure how to use it.
     
  6. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Let me know if it works!
     
  7. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    So I changed it to this:
    Code (CSharp):
    1.  
    2. public static IEnumerator ALevelLoader (int level) {
    3.         sceneToLoad = SceneManager.LoadSceneAsync (level , LoadSceneMode.Single);
    4.         sceneToLoad.allowSceneActivation = false;
    5.         if(sceneToLoad.isDone) { sceneToLoad.allowSceneActivation = true; }
    6.         return null;
    7.     }
    8.  
    9.  
    And added:
    Code (CSharp):
    1.  
    2.     public static void LoadLevel (int level) {
    3.         StartCoroutine(ALevelLoader (level));
    4.     }
    5.  
    on the same script.

    The reason I added this call is because I wanted to be able to trigger this also from an Animation Event, but tit seems calling it via the StartCoroutine method isn't supported. So now I ran into this problem:

    error CS0120: An object reference is required to access non-static member `UnityEngine.MonoBehaviour.StartCoroutine(System.Collections.IEnumerator)'

    I tried changing "public static IEnumerator ALevelLoader (int level) " to "IEnumerator ALevelLoader (int level) " but that gave even more problems. This made sense at first because now I don't need the IEnumerator to be public anymore, but the method that calls it. Yet it did not work.
     
  8. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309

    Unity doesnt work well with static members, remove it also from the LoadLevel void
     
  9. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    OK, but then, how do I access it from other scripts? I have never been able to, it says I need an object reference.

    Do I have to create a reference to it using class LoadLevel?
     
  10. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Just have it as a public void to access it from other scripts no need have static at all,

    To access the function from other scripts you need to use GetComponent to achieve this, something like

    Code (CSharp):
    1. public scriptname name;
    2.  
    3. void Start ()
    4. {
    5.   name = GetComponent <scriptname>();
    6. name.LoadLevel (1);
    7. }
     
  11. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    Thanks for holding my hand through this. That worked great, I learned a valuable lesson on how to call stuff from other scripts.

    Now I had this error:
    NullReferenceException: (null)
    UnityEngine.MonoBehaviour.StartCoroutine (IEnumerator routine) (at C:/buildslave/unity/build/artifacts/generated/common/runtime/MonoBehaviourBindings.gen.cs:62)
    LoadScene.LoadLevel (Int32 level) (at Assets/Scripts/LoadScxene.cs:26)

    From my debug code I can see it fails before or during this line:
    Code (CSharp):
    1. if(sceneToLoad.isDone) {
    2. sceneToLoad.allowSceneActivation = true;
    3. }
    Using my old code:
    Code (CSharp):
    1.  
    2. yield return new WaitForSeconds (1.5f);      
    3. sceneToLoad.allowSceneActivation = true
    it loads fine.
     
  12. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Code (CSharp):
    1.     public IEnumerator ALevelLoader(int level)
    2.     {
    3.         sceneToLoad = SceneManager.LoadSceneAsync(level, LoadSceneMode.Single);
    4.         sceneToLoad.allowSceneActivation = false;
    5.         while (!sceneToLoad.isDone)
    6.         {
    7.             if (sceneToLoad.progress >= .9f)
    8.                 sceneToLoad.allowSceneActivation = true;
    9.             yield return null;
    10.         }
    11.     }
    lets try run a while loop while it's not done, then when the progress of loading the scene is over or equal to 90% we will allow the scene to be activated.

    EDIT: just tested this and it works, but because im using empty scenes it runs instantly.
     
  13. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    I start to realize the issues posted in the first link of my first post.
    I managed to lock up Unity by pressing the button more than once accidentally to load the scene. It creates a "new" scene every time and it just freezes.

    Adding the loop works, though, but I still see the game freezing on the editor right after it starts async loading. I will export a build and see what happens, but I might have to do a version of the script on the first link if I want to avoid issues with locking up.
    Level loading seems a far greater problem than it should be!

    Thanks so much for your help, really appreciate it. I learned a lot today thanks to your help.
     
  14. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    To avoid loading more than once, you can add a boolean like isLoading and set it to true on ur first press so it cant run again
     
  15. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    Yeah I have a bunch of work to do, to avoid that.
    I'm still annoyed at the little bit where the game freezes though. I was trying to do this to avoid a freeze, but I feel like this is unavoidable, then? It cannot be, I've seen games that change really smoothly between scenes.

    Must investigate further, but I had enough of this problem for the day. Thanks again for your help!
    For the time being I will make it so each scene, at the time it must load, remains in a state that makes the freeze unnoticeable.

    I just have the feeling that a simple SceneManager.LoadScene was working better than this.
     
  16. Rob21894

    Rob21894

    Joined:
    Nov 21, 2013
    Posts:
    309
    Ive looked up why it freezes and a recent solution says try putting loadscenemode as additive http://answers.unity3d.com/questions/1323527/game-freezes-after-button-is-clicked-to-switch-sce.html
     
  17. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    You can yield an async load operation, which returns control once it's finished. So instead of your 1.5 seconds, do:

    Code (csharp):
    1. public void LoadSceneAsync(string scene) {
    2.     StartCoroutine(Load(scene));
    3. }
    4.  
    5. private IEnumerator Load(string scene) {
    6.     yield return SceneManager.LoadSceneAsync(scene , LoadSceneMode.Single);
    7. }
    That'll load the scene immediately when it's done, but let the current scene play while that's going on.

    If you want to have a minimum time before you switch scenes (say wait until fade to black), you would do:

    Code (csharp):
    1. private IEnumerator Load(string scene) {
    2.     var asyncOp = SceneManager.LoadSceneAsync(scene , LoadSceneMode.Single);
    3.     asyncOp.allowSceneActivation = false;
    4.  
    5.     //assuming you've made a Fader that's available here.
    6.     Fader.FadeScreenToBlack(),
    7.  
    8.     while(!Fader.screenFadedOut || asyncOp.progress < 1.5f) {
    9.         yield return null; //wait one frame
    10.     }
    11.  
    12.     asyncOp.allowSceneActivation = true;
    13. }
    '

    You should look into coroutines, they're a really important tool when working with Unity. Check for some tutorials. Here's a starting off point.

    A note about async loading from the docs (from the deprecated Application.LoadLevelAsync method, but probably still relevant):

    You'll probably never get a completely smooth transition (there's a natural hitch from all scripts in the next scene calling Awake and Start and OnEnable at once), but if you hide it behind an animation, it'll be fine.
     
  18. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    Thanks for that code Baste!

    Yeah, I built this to see if I would notice the hiccups, and although it's slightly better than on the Editor, it's still bad.

    The problem I encounter is that on calling the async operation, that's when the freezing happens, for like a second, then the scene keeps doing what it was doing, and then after loading is done, it switches. It looks really bad and I wonder if I can hide it well with an anim or something.

    Without loading asynchronously, the same ~1 second freeze happens, but after the freeze ends, the new scene jumps up immediately, so it looks a bit better in my opinion.

    Basically what I have to do is while I async load, it seems, is to have a static screen, to let that freeze happen as unnoticeable as possible :/ music still plays while doing the loading, so that is not a problem, it's the video stopping.

    I will try the Additive tip too, Rob. Thanks for your help guys.
     
  19. kikendo

    kikendo

    Joined:
    Jul 19, 2017
    Posts:
    61
    So I am still battling with this.

    How do games that transition smoothly from one scene to the other do it? Is it all one huge scene? I'm confused as I see many Unity-made games that have no apparent hiccups between loading different parts of the game.