Search Unity

AsyncOperation always returns 0 or 0.9 (coroutine only resumes at 0.9)

Discussion in 'Scripting' started by UnresolvedExternal, Jun 28, 2018.

  1. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    Hi! I'm trying to make a loading bar for my game. I have a coroutine which loads all the scenes additive. Inside the coroutine I have a for-loop which loops through and loads all the scenes from a given start index and to the total amount of scenes in Build Settings. In the for-loop I load the next scene and after that I have a while-loop which checks if the current scene is fully loaded. If it's not fully loaded I update a slider in the UI, this is done through the method UpdateSceneLoadingSlider pasted below.

    But, this isn't working as expected since the coroutine only fires at first when async progress is 0 and only again when the async progress is 0.9. In between, the coroutine never resumes. Thus, if I have a large scene the loading bar will sit on 0% until it is fully loaded.

    I've also tried building the game but the result is the same, what am I doing wrong?

    (I'm dividing the progress with 0.9, since the last 0.1 of the operation is only for activation of the scene)
    (The code is under construction and might be a bit messy, the important part is the method and the while-loop in the coroutine)
    (Also, I have two sliders: one for total scenes loaded, and one for the loading percentage of the current scene to be loaded. Only the latter is of importance here)

    Code (CSharp):
    1.  
    2. private void UpdateSceneLoadingSlider(float progress, int index)
    3. {
    4.     float realProgress = Mathf.Clamp01(progress / 0.9f);
    5.     sceneLoadingSlider.value = realProgress;
    6.     sceneProgressText.text = string.Format("{0}{1}", (int)(realProgress * 100), "%");
    7.     Debug.Log(string.Format("{0}: {1}{2}", index, (int)(realProgress * 100), "%"));
    8. }
    9.  
    Code (CSharp):
    1.  
    2. IEnumerator LoadNewScene()
    3. {
    4.     _persistentAsyncOperation =
    5.         SceneManager.LoadSceneAsync(sceneToLoadName, LoadSceneMode.Additive);
    6.  
    7.     // Load Persistent Scene
    8.     while (!_persistentAsyncOperation.isDone)
    9.     {
    10.         yield return null;
    11.     }
    12.  
    13.     float numberScenes = SceneManager.sceneCountInBuildSettings -
    14.         additiveScenesStartIndex + 1;
    15.     float loadedScenes = 0;
    16.  
    17.     UpdateTotalLoadingSlider(++loadedScenes, numberScenes);
    18.  
    19.     // Load all subscenes additive
    20.     if (!Application.isEditor || tryInEditor)
    21.     {
    22.         for (int i = additiveScenesStartIndex;
    23.             i < SceneManager.sceneCountInBuildSettings; i++)
    24.         {
    25.             _subsceneAsyncOperation =
    26.                 SceneManager.LoadSceneAsync(i, LoadSceneMode.Additive);
    27.              
    28.             while (!_subsceneAsyncOperation.isDone)
    29.             {
    30.                 // Should write % loaded for the current scene but does not...
    31.                 UpdateSceneLoadingSlider(_subsceneAsyncOperation.progress, i);
    32.                 yield return null;
    33.             }
    34.  
    35.             UpdateTotalLoadingSlider(++loadedScenes, numberScenes);
    36.             yield return null;
    37.         }
    38.     }
    39.  
    40.     cameraMainMenu.IsStarted = true;      
    41.     mainMenu.gameObject.SetActive(true);
    42.     AudioManager.Instance.MusicIntroTheme();
    43.     MainMenu.Instance.ShowPressStart();
    44.     Destroy(gameObject);
    45.     yield return null;
    46. }
    47.  
     
    mfakkaya likes this.
  2. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
  3. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    This problem is all over the Internet. Sadly, I cannot find a conclusive answer. Maybe there is someone who works at Unity lurking near this thread who can do that?

    Without really knowing, I'd guess it's something about how Unity decides what an atomic part of a load process is. Maybe you have one gigantic asset and it doesn't know how to tell where it is?

    You could test that hypothesis by using the same code on a dummy scene that has a lot of small-to-medium assets. Like, auto-generate a couple thousand images into an assets folder, and use this code to load that scene.
     
  4. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    Thanks for the reply Max! I should clarify that I don't have any problem loading the scenes that works as expected. It is only showing the loading percentage visually by retrieving "progress" that isn't working. In that case I don't think it is the same issue, unless I'm missing something in that thread?
     
  5. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    Thanks again Max! Let's hope someone from Unity lurks here :)

    In the meantime I shall try that hypothesis.
     
  6. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    I didn't think it was the same issue, per se, but I thought they might be related. For instance, they might share a root cause and just express differently under different circumstances.
     
  7. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    Anyone else out there who have any idea why this isn't working?
     
  8. Pagefile

    Pagefile

    Joined:
    Feb 10, 2013
    Posts:
    51
    Maybe the logs just aren't showing what you expect? Is this line supposed to be like this?
    Code (CSharp):
    1. float realProgress = Mathf.Clamp01(progress / 0.9f);
    if that's how that line of code is right now, I'd expect you're getting compile errors. Even then,
    progress / 0.9f
    is going to result in a number greater than progress. Is that what you're looking for? It seems like dividing by 9 would be more what you want.
     
  9. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    I divide it with 0.9 as is done in this tutorial. Now I've tried disabled that part and just doing like this:

    Code (CSharp):
    1. private void UpdateSceneLoadingSlider(float progress, int index)
    2. {
    3.     sceneLoadingSlider.value = progress;
    4.     sceneProgressText.text = progress.ToString();
    5.  
    6.     Debug.Log(string.Format("{0}: {1}", index, progress));
    7. }
    The console will now output the same except with different values (the first numbers are the current index, i.e. the current scene being loaded):

    Code (CSharp):
    1.  
    2. ...
    3. 10: 0.9
    4. 11: 0
    5. 11: 0.9
    6. 12: 0
    7. 12: 0.9
    8.  
    I've tried it both in editor and build, and it is the same. Maybe I get this wrong, maybe this is how it is supposed to work? But according to Unity's own example I don't think that is the case.
     
  10. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    We see the same behaviour in our game, I think its a limitation in how Unity load scenes



    Our code
    Code (CSharp):
    1. public class ProgressBar : MonoBehaviour
    2.     {
    3.         private AsyncOperation operation;
    4.         public RectTransform Bar;
    5.  
    6.         public void SetAsyncOperation(AsyncOperation operation)
    7.         {
    8.             this.operation = operation;
    9.  
    10.             StartCoroutine(UpdateProgessBar(operation));
    11.         }
    12.  
    13.         private IEnumerator UpdateProgessBar(AsyncOperation operation)
    14.         {
    15.             while (!operation.isDone)
    16.             {
    17.                 Bar.anchorMax = new Vector2(operation.progress, Bar.anchorMax.y);
    18.                 yield return new WaitForEndOfFrame();
    19.             }
    20.         }
    21.     }
     
  11. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    That is because the loading phase is only between 0.0 - 0.9, try dividing the operation.progress with 0.9 and you will get a value between 0 and 1 instead. The remaining 0.1 is for activation of the scene.
     
  12. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    For some reason it's working for me now, maybe it always did... o_O I noticed know that the scene that takes a long time to load actually doesn't take a long time to load, it is the activation phase that takes long (0.9 - 1.0). As far as I have gathered information there is no way of getting progress of that returned? (Except doing some manual increment in that phase...)
     
  13. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Doesn't matter then it will stick on 1 instead of 0.9 the problem is it does not report anything between 0.9 and 1 and the most of the time is spent between this two
     
  14. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    There's probably some off-the-shelf progress thing you can use for tracking activation, assuming it can be displayed (like the current UI doesn't freeze during activation of a new scene or something).

    You'd probably have to instrument your game objects with some kind of script or allow that to dynamically be done right at the beginning of the activation process.
     
  15. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    16
    If anyone is interested this is how I "solved" it. Instead of using StartCoroutine I manually start the coroutine and store a reference to it in an IEnumerator field. Then, for each frame i use the method "MoveNext" on the IEnumerator, this way I will get a "hit" every frame when the scene is in the activation phase. So I check first if it's below 0.9, if so, I update the loading bar. If it's above 0.9 I know it's in the activation phase and I reset the bar to 0 and for each frame I just increase it slightly. As far as I know there is still no way to determine how long the activation phase will take. It's not the most elegant solution but it works in my case, to give some visual feedback to the user that the loading is making progress.


    Code (CSharp):
    1.  
    2. ...
    3. private AsyncOperation _subsceneAsyncOperation;
    4. private IEnumerator _loadSceneEnumerator;
    5. ...
    6.  
    7. void Update()
    8. {
    9.       if (_currentSceneIndex < SceneManager.sceneCountInBuildSettings)
    10.       {
    11.           // Start coroutine to load next scene
    12.           if (_loadSceneEnumerator == null)
    13.           {                  
    14.               _loadSceneEnumerator = LoadSceneCoroutine();
    15.               _activationPercentage = 0;
    16.           }
    17.  
    18.           if (_loadSceneEnumerator != null)
    19.           {
    20.               // Continue coroutine
    21.               if (_loadSceneEnumerator.MoveNext())
    22.               {
    23.                   // Loading phase
    24.                   if (_subsceneAsyncOperation.progress < 0.9f)
    25.                   {
    26.                       _loadingPercentage = _subsceneAsyncOperation.progress / 0.9f;
    27.  
    28.                       UpdateSceneLoadingSlider(_loadingPercentage);
    29.                   }
    30.                   // Activation phase
    31.                   else if (_subsceneAsyncOperation.progress < 1.0f)
    32.                   {
    33.                       _activationPercentage += activatePhaseIncrementPerFrame * Time.deltaTime;
    34.                       _activationPercentage = Mathf.Min(_activationPercentage, 1);
    35.  
    36.                       UpdateSceneLoadingSlider(_activationPercentage);
    37.                   }
    38.               }
    39.               // Scene fully loaded
    40.               else
    41.               {
    42.                   _loadSceneEnumerator = null;
    43.                   _currentSceneIndex++;
    44.                   UpdateTotalLoadingSlider(++_loadedScenes, _numberScenes);
    45.  
    46.                   _yieldedCount = 0;
    47.                   _activationPercentage = 0;
    48.               }
    49.           }
    50.       }
    51.       // All scenes loaded
    52.       else
    53.       {
    54.           // Do stuff!!
    55.  
    56.           Destroy(gameObject);
    57.       }
    58. }
    59.  
    60. #endregion
    61. #region Methods
    62.  
    63. private void UpdateSceneLoadingSlider(float progress)
    64. {
    65.     sceneLoadingSlider.value = progress;
    66.     sceneProgressText.text = string.Format("{0}{1}", ((int)(progress * 100)), "%");
    67. }
    68.  
    69. private void UpdateTotalLoadingSlider(float loadedScenes, float numberScenes)
    70. {
    71.     var percentageLoaded = loadedScenes / numberScenes;
    72.     totalLoadingSlider.value = percentageLoaded;
    73.     totalProgressText.text = string.Format("{0}{1}", (int)(percentageLoaded * 100), "%");
    74. }
    75.  
    76. #endregion
    77. #region Coroutines
    78.  
    79. private IEnumerator LoadSceneCoroutine()
    80. {
    81.     _subsceneAsyncOperation = SceneManager.LoadSceneAsync(_currentSceneIndex, LoadSceneMode.Additive);
    82.     _subsceneAsyncOperation.allowSceneActivation = false;
    83.  
    84.     while (_subsceneAsyncOperation.progress < 0.9f)
    85.     {
    86.         yield return null;
    87.     }
    88.  
    89.     _subsceneAsyncOperation.allowSceneActivation = true;
    90.  
    91.     while (_subsceneAsyncOperation.progress < 1.0f)
    92.     {
    93.         yield return null;
    94.     }
    95.  
    96.     yield return null;
    97. }
     
  16. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    I'm guessing you're a few extract moves away from having a pretty popular open source library...
     
  17. spicyroboto

    spicyroboto

    Joined:
    Nov 14, 2019
    Posts:
    1

    Sorry to bring back an old thread, but in your above solution, what did you set activatePhaseIncrementPerFrame as?
     
  18. sokolovdenys

    sokolovdenys

    Joined:
    Feb 9, 2018
    Posts:
    1
    Global Function in Tools:

    Code (CSharp):
    1.  
    2.     public static async void LoadScene(int _index, Action<float> _progress, bool _activate = false)
    3.     {
    4.         UnityEngine.AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(_index);
    5.         asyncOperation.allowSceneActivation = _activate;
    6.         while (asyncOperation.progress < ((!_activate) ? 0.9f : 1f))
    7.         {
    8.             Debug.Log($"[scene]:{_index} [load progress]: {asyncOperation.progress} {asyncOperation.isDone}");
    9.             _progress(asyncOperation.progress);
    10.             await Task.Yield();
    11.         }
    12.         if (!_activate) _progress(1f);
    13.     }
    14.  
     
    Vansoul likes this.