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

Resolved How to get the scene loaded by SceneManager.LoadSceneAsync

Discussion in 'Scripting' started by moose0847, Jan 24, 2021.

  1. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    I'm currently setting up a system to handle tracking which game state loaded in a given scene, and have hit a little bit of a speedbump. The issue seems to be the following:
    • SceneManager.LoadSceneAsync returns an AsyncOperation, which doesn't appear to know if it is a scene load, or some other operation
    • SceneManager.sceneLoaded/sceneUnloaded events don't seem to know if they were happening as the result of an async operation, or which async operation produced the event.
    At the moment, my plan is to work around this by only allowing each scene in build settings to be loaded only once as I don't think this will be a problem with my game's design. Before moving ahead with that design, does anyone know of a way to determine which async operation loaded a given scene?
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,315
    You can wrap your AsyncOperation in a struct that also contains some metadata about the scene you're loading. Here's an example:

    Code (CSharp):
    1. public struct SceneLoader {
    2.     public delegate void SceneLoadedHandler ( string sceneId );
    3.  
    4.     public string sceneId;
    5.     public SceneLoadedHandler onSceneLoaded;
    6.  
    7.     public SceneLoader ( string sceneId ) {
    8.         this.sceneId = sceneId;
    9.         var asyncOp = SceneManager.LoadSceneAsync( sceneId ); // or however
    10.         asyncOp.completed = OnSceneLoaded;
    11.     }
    12.  
    13.     private void OnSceneLoaded ( AsyncOperation op ) {
    14.         if( onSceneLoaded != null ) onSceneLoaded( sceneId );
    15.     }
    16. }
    17.  
    18. // elsewhere
    19. void LoadMyScene () {
    20.    var loader = new SceneLoader( "my_next_scene" );
    21.    loader.onSceneLoaded += MySceneLoadHandler;
    22. }
    23.  
    24. void MySceneLoadHandler ( string sceneId ) {
    25.    // do something on scene load with the sceneId
    26.    // or any other metadata you want
    27. }
     
    Kurt-Dekker likes this.
  3. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    Thanks for the example! I have a similar solution working right now, but it's one that I'm not sure holds up when loading multiple copies of the same scene. Take the following example:

    Code (CSharp):
    1. var loader1 = new SceneLoader("scene_01");
    2. loader1.onSceneLoaded += MySceneLoadHandler;
    3.  
    4. var loader2 = new SceneLoader("scene_02");
    5. loader2.onSceneLoaded += MySceneLoadHandler;
    6.  
    7. var loader3 = new SceneLoader("scene_01");
    8. loader3.onSceneLoaded += MySceneLoadHandler;
    9.  
    10. void MySceneLoadHandler( string sceneID){}
    In this case, it's easy to see the difference between loader1 vs. loader2, and loader2 vs. loader3 due to the differing scene ID. How would I know the difference between the scenes produced by loader1 and loader3? I could assume that since the call was made to loader1 first that the first callback of MySceneLoadHandler corresponds to loader1, while the second corresponds to loader 3, but this seems like it would need some additional marshalling to resolve potential cross-script race conditions. Does unity guarantee the callback order of async load scene operations in this way?
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    I don't think C# does, but I might be wrong. Maybe just hook up your own "call everybody" mechanism so you can explicitly control it. You would only attach once to the unity callback, and every wanting to load scenes would receive calls from you.
     
  5. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,315
    You will need to be more specific about what "knowing who called what" means in your context. Why do you need to know who is loading what scene, and what information should be used to convey it?

    SceneLoader is data that you have control over -- you can pass whatever information you need into it. You can add another parameter that more uniquely identifies the loader, for example:

    Code (CSharp):
    1. var loader1 = new SceneLoader("loader1", "scene_01");
    2. loader1.onSceneLoaded += MySceneLoadHandler;
    3.  
    4. var loader2 = new SceneLoader("loader2", "scene_02");
    5. loader2.onSceneLoaded += MySceneLoadHandler;
    6.  
    7. var loader3 = new SceneLoader("loader3", "scene_01");
    8. loader3.onSceneLoaded += MySceneLoadHandler;
    9. void MySceneLoadHandler( string loaderId, string sceneID){}
     
  6. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    This was more of a bit of diligence in trying to build a robust system initially rather than a specific need. To descibre the problem more in detail, take the following example:
    1. MonoBehaviorA calls SceneManager.LoadSceneAsync("scene_01") on frame 1
    2. MonoBehaviorB calls SceneManager.LoadSceneAsync("scene_01") on frame 1
    3. SceneManager.sceneLoaded event is raised some time later
    Now I can store a reference to the AsyncOperation returned by SceneManager.LoadSceneAsync, and I can detect when this operation ends via it's .completed event. However, I don't have a means of knowing if the SceneManager.sceneLoaded event is raised as a result of the operation from MonoBehaviorA or MonoBehaviorB. I could infer the answer if I could guarantee the execution order of unity's async operation events, but at present I can't think of a 100% reliable way to solve this problem without that information. Alternatively, I could have the async operation completes fire an intermediary event, but that won't be able to handle the case where both async operation.completed events fire before SceneManager.sceneLoaded.

    In the end, my solution was to restrict my scenes to only to be able to be loaded once, and I think sidestepping the problem in this way will be alright for my purposes.