Search Unity

Return AsyncOperation from Loading a Scene with Addressables

Discussion in 'Addressables' started by vvelasco, Jul 13, 2019.

  1. vvelasco

    vvelasco

    Joined:
    Sep 21, 2015
    Posts:
    25
    Hi.
    I'm currently making all the scenes of our game to load as Addressables as it would reduce the patch size of our game that will come to a console.

    I'm not the main programmer of the game (more like the game designer) but I'm alone this time.
    So far I see how powerful is addressables to know where is your data once you make a build.

    Anyway, we're currently using this method to load the scenes

    Code (CSharp):
    1.  
    2. static void _LoadLevelImmediateProxy(string level)
    3.     {
    4.  
    5.             Addressables.LoadSceneAsync(level, LoadSceneMode.Single);
    6.    }
    7.  
    8.    
    However, sometimes we use a Loading scene. What we're doing is returning an AsyncOperation in a IEnumerator, like this

    Code (CSharp):
    1.     IEnumerator LoadLevelAsyncWithSave(object level, bool manualActivation, bool checkWithSaving)
    2.     {
    3.         m_asop = _LoadLevelAsyncProxy(level); //this is the AsyncOperation
    4.         if (null != m_asop)
    5.         {
    6.             LoadWithLoadScene = false;
    7.             m_asop.allowSceneActivation = !manualActivation;
    8.         }
    9.     }
    But I can't see a way to return an AsyncOperation from an Addressable LoadLevel. This is what we currently have

    Code (CSharp):
    1.     static AsyncOperation _LoadLevelAsyncProxy(string level)
    2.     {
    3.  
    4.               return SceneManager.LoadSceneAsync(level);
    5.             //this is where it should return something like Addressables.LoadSceneAsync(level, LoadSceneMode.Single);
    6.    
    7.  
    8.     }
    Thanks in advance
     
  2. Favo-Yang

    Favo-Yang

    Joined:
    Apr 4, 2011
    Posts:
    464
    It seems that you already answered yourself with Addressables.LoadSceneAsync(key, loadMode, activateOnLoad) API, which returns an AsyncOperationHandle object.

    Addressables.cs
    Code (CSharp):
    1. public static AsyncOperationHandle<SceneInstance> LoadSceneAsync(object key, LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100)
    Though AsyncOperationHandle and AsyncOperation are different thing, your LoadLevelAsyncWithSave method haven't demonstrate the usage of AsyncOperation. It only set allowSceneActivation property, which can be replaced with activateOnLoad parameter of LoadSceneAsync API. AsyncOperationHandle shall cover most cases.
     
    Last edited: Jul 13, 2019
  3. vvelasco

    vvelasco

    Joined:
    Sep 21, 2015
    Posts:
    25
    Thanks for the reply. You're right the Addressables.LoadSceneAsync() returns an AsyncOperationHandle.
    However I see that we actually use AsyncOperation in many places. Like to check if is still loading. In this case m_asop is an AsyncOperation

    Code (CSharp):
    1.     public bool IsLoading
    2.     {
    3.         get
    4.         {
    5.             if (m_asop == null)
    6.                 return false;
    7.  
    8.             return !m_asop.allowSceneActivation ? m_asop.progress < 0.9f : !m_asop.isDone;
    9.         }
    10.     }
    In some other parts we use
    m_asop.allowSceneActivation = !manualActivation;

    or even
    m_asop == null
    Just found out that AsyncOperationHandle can't have null values like AsyncOperation.

    Is there any way to convert AsyncOperationHandle into AsyncOperation?
     
  4. Favo-Yang

    Favo-Yang

    Joined:
    Apr 4, 2011
    Posts:
    464
    AsyncOperation is designed to work with a group of existing LoadXXXAsync APIs: SceneManager.LoadSceneAsync, AssetBundle.LoadAssetAsync, Resources.LoadAsync.

    AsyncOperationHandle is the counterpart API of addressables system.

    To delay the scene activating,
    Code (CSharp):
    1. var handle = Addressables.LoadSceneAsync("name", activateOnLoad: false).Completed += OnCompleted;
    2.  
    3. void OnCompleted(AsyncOperationHandle<SceneInstance> handle)
    4. {
    5.     // check handle.Result != null...
    6.     var sceneInstance = handle.Result;
    7.     // Activate the scene.
    8.     // Internally called "sceneInstance.m_Operation.allowSceneActivation = true" for you.
    9.     sceneInstance.Activate();
    10. }
    To check the progress of loading,
    Code (CSharp):
    1. handle.PercentComplete
    2. handle.IsDone
    3. handle.Status == AsyncOperationStatus.Succeeded / Failed
    No way to detecting activateOnLoad during the loading. If you want that, store it in your context.

    AsyncOperationHandle is a struct, hence is not nullable. You can either convert your code to a more "push" based (you notify the changes to UI, instead of UI fetching value from you).
    Code (CSharp):
    1. var handle = Addressable.LoadSceneAsync...
    2. if (!handle.IsDone) {
    3.     // Update loading process with handle.PercentComplete
    4.     yield return null;
    5. }
    Or use C# Nullable,
    Code (CSharp):
    1. AsyncOperationHandle? handle = null;
    2.  
    3. void MyLoadSceneAsync()
    4. {
    5.     handle = Addressables.LoadSceneAsync...
    6. }
    7.  
    8. float Foo()
    9. {
    10.     if (handle.HasValue)
    11.         return handle.Value.PercentComplete;
    12.     else
    13.         return 0;
    14. }
     
    Last edited: Jul 14, 2019
  5. vvelasco

    vvelasco

    Joined:
    Sep 21, 2015
    Posts:
    25
    That makes total sense.
    Thanks for the explanation! I’ll implement it in a few.
     
  6. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I just came across this problem in the editor. Using this, in the editor, I load the loading scene before everything else. The loading scene takes care about loading the scene I try to open.

    Code (CSharp):
    1. public static Scene mainScene;
    2. public static Scene activeScene;
    3. public static AsyncOperationHandle<IResourceLocator> InitializationHandle;
    4. public static AsyncOperationHandle<SceneInstance> ActiveSceneHandle;
    5. public static SceneInstance ActiveScene;
    6.  
    7.  
    8. #if UNITY_EDITOR
    9.         private static bool initialized = false;
    10.         private static string activeSceneName;
    11.  
    12.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    13.         private static void Initialize()
    14.         {
    15.             if (!initialized)
    16.             {
    17.                 Debug.Log("App initializes");
    18.  
    19.                 InitializationHandle = Addressables.InitializeAsync();
    20.                 InitializationHandle.Completed += InitializationHandle_Completed;
    21.  
    22.                 mainScene = SceneManager.GetSceneByBuildIndex(0);
    23.                 activeScene = SceneManager.GetSceneAt(0);
    24.                 activeSceneName = activeScene.name;
    25.  
    26.                 if (activeScene != mainScene) SceneManager.LoadScene(0);
    27.  
    28.                 initialized = true;
    29.             }
    30.         }
    31.  
    32.         private static void InitializationHandle_Completed(AsyncOperationHandle<IResourceLocator> obj)
    33.         {
    34.             InitializationHandle.Completed -= InitializationHandle_Completed;
    35.  
    36.             // ToDo : Only works if the scene name is the same than its addressable key
    37.             if (activeSceneName != mainScene.name) ActiveSceneHandle = Addressables.LoadSceneAsync(activeSceneName, LoadSceneMode.Additive, true);
    38.         }
    39. #else
    Two problems with this : it only manages one scene and only works if the scene name is the same than its addressable key. While doing this for multi-scene is obvious (for (int i=0 ; i != SceneManager.sceneCount ; i++) GetSceneAt(i)...) I couldn't find how to get the active scene(s)'s key(s) or guid(s) entering in play mode.
     
    Last edited: Jul 14, 2019
  7. Favo-Yang

    Favo-Yang

    Joined:
    Apr 4, 2011
    Posts:
    464
    @Djayp, it seems it's an one-time hook in the editor scope, that BeforeSceneLoad you force load scene with build index 0(mainScene), then delays the actual scene (activeSene) loading when addressables system is ready.

    You're loading scene with
    Addressables.LoadSceneAsync(activeSceneName...
    , so you need make sure the scene is added to addressable with address(key) = activeSceneName.

    I don't quite understand the multiple scenes issue.
     
  8. vvelasco

    vvelasco

    Joined:
    Sep 21, 2015
    Posts:
    25
    I implemented the methods that @Favo-Yang mentioned and it worked like a charm.
    C# Nullable was extremely useful.
    As for allowSceneActivation I just created a boolean in the class that gets updated.
    Thanks!
     
    Djayp and Favo-Yang like this.
  9. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Yes it is. No problem with multi-scene really. The real deal was getting an AsyncOperationHandle<SceneInstance> from a Scene (I understand it isn't what the op asked). The only way I found, entering in play mode, was to use the same name for the Scene and its addressable key. "I couldn't find how to get the active scene(s)'s key(s) or guid(s) entering in play mode."
     
  10. Favo-Yang

    Favo-Yang

    Joined:
    Apr 4, 2011
    Posts:
    464
    There's no way to get address from scene object. You have to keep the address same to scene.name, then load by scene.name.