Search Unity

SceneManager LoadScene Additive AND Set Active

Discussion in 'Scripting' started by NioFox, Jan 20, 2016.

  1. geroppo

    geroppo

    Joined:
    Dec 24, 2012
    Posts:
    140
    Any workaround for this?
     
  2. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    @MrDude
    There is no difference between a Manager scene and regular scenes, it is just a concept.

    If your bootstrap scene contains the managers you need, then you can consider this your Manager scene and keep it in memory then load other scenes additively and unload them again.

    Cheers
     
  3. tanoshimi

    tanoshimi

    Joined:
    May 21, 2013
    Posts:
    297
    @MrDude - Can you provide a link to the docs in question? I'm currently using a setup similar to yours so just trying to understand what my options are.
     
  4. duencil

    duencil

    Joined:
    Dec 17, 2012
    Posts:
    91
    tanoshimi likes this.
  5. duencil

    duencil

    Joined:
    Dec 17, 2012
    Posts:
    91
    Have you tried setting the scene as the active one (SceneManager.SetActiveScene) in the sceneLoaded callback? I cant upgrade to 5.6 to test this but I understood that callback is supposed to occur prior to the gameobjects' Start(), so as long as you make sure any spawning is done in Start rather than Awake you would be okay.
     
  6. tanoshimi

    tanoshimi

    Joined:
    May 21, 2013
    Posts:
    297
    Thanks! Wish I'd seen this a few weeks ago before figuring out much of the same stuff manually through trial and error! Never mind - it's still nice to have my findings verified.

    @SteenLund The docs page on best practices for multi-scenes is slightly out-of-date since it refers to the deprecated UnloadScene rather than UnloadSceneAsync - where does one go to file documentation bugs these days?
     
  7. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    524
    tanoshimi likes this.
  8. Deleted User

    Deleted User

    Guest

    I think that's pretty stupid. When the SceneManager.sceneLoaded event is fired is when one would expect the scene to be loaded. Maybe you should rename it to SceneManager.sceneWillLoadSoon.
     
    breakmt and IgorAherne like this.
  9. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
  10. Deleted User

    Deleted User

    Guest

    The timing that of the firing of the sceneLoaded event. If the scene isn't actually loaded until the end of the frame, then the event should be fired at the end of the frame as well. If that's not possible, then at the beginning of the next frame. Or be renamed to sceneWillLoadOnNextFrame.

    Anyway that's just my .02.
     
    kfielding likes this.
  11. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    @supremegrandruler It fires after the scene is loaded and awake, no point in renaming is to something the indicates it would fire before loading happens.
     
  12. Deleted User

    Deleted User

    Guest

    @SteenLund yes but the scene from the event is not loaded at that point. If you invoke SceneManager.SetActiveScene() it will throw an exception saying the scene is not loaded or something.
     
    kfielding likes this.
  13. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    @supremegrandruler I believe this was fixed in 5.6. Basically the event was called before the flag was set to loaded, but the scene was actually completely loaded and awake
     
    Deleted User likes this.
  14. Deleted User

    Deleted User

    Guest

    Oh that's good to know. I'm still on 5.4 and that's basically the thing that was annoying me. Thanks! :)
     
  15. mm234345

    mm234345

    Joined:
    Oct 15, 2014
    Posts:
    5
    Is it possible to get the root gameobject of a scene that is still held in "inactive" state at 90% progress (as you state, the gameobjects are loaded at that point, only the "Awake" remains for the last 10%)?
    Example:
    Code (CSharp):
    1.  
    2. AsyncOperation async = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    3. async.allowSceneActivation = false;
    4. //... elsewhere in coroutine or something
    5. if(async.progress >= 0.9f) {
    6.     Scene scene = SceneManager.GetSceneByName(sceneName);
    7.     GameObject root = scene.GetRootGameObjects()[0]; // is this possible?
    8. }
    9.  
    I tried a basic test, in Unity 5.3, and the answer is No. Is it possible in 5.6? Or via a different strategy?
    This is described in the excellent Playdead GDC talk with their "PreAwake" concept (~10 minute mark of the video)
    But they had access to the Unity source and could modify it to make this possible if needed.
     
    Last edited: Apr 10, 2017
    PuzzledBoy likes this.
  16. Ziplock9000

    Ziplock9000

    Joined:
    Jan 26, 2016
    Posts:
    360
    I'm having this same issue. I load two scenes. Scene1 is async+single, Scene2 async+additive. I want to make the second scene active. Checking isLoaded either in the same thread or in a co-routine makes no difference, SetActiveScene does nothing.

    However if I create a script and attach it to an object in scene2's Start() it works there. But I don't want to have to replicate lots of code and objects in every scene.

    5.6.0f3 - Editor Mode
     
  17. nameymcblahnarf

    nameymcblahnarf

    Joined:
    Apr 25, 2017
    Posts:
    1
    Has anyone built super small example projects showing best strategies yet? This thread is old and very up to date with people still reporting odd bugs or weird work arounds. Seems like a simple case that Unity Dev could make some sample scenes for this. Especially for those that are dealing with Oculus back button behaviors.
     
    FuriantMedia likes this.
  18. Trentity

    Trentity

    Joined:
    May 31, 2013
    Posts:
    1
    @SteenLund "It [AsyncOperation.completed] fires after the scene is loaded and awake"

    If that is the case, then how does one set the loaded scene to be active BEFORE Awake() is called on objects on the new scene. Because all objects instantiated upon Awake in the newly loaded scene, are created in the previously loaded scene.

    Can it not be so simple as to add a bool argument to the SceneManager.LoadLeveAsync() that will make the new scene the "active" one immediately upon load, before Awake()?

    If it helps, I'm running 2017.2.0f3
     
  19. NioFox

    NioFox

    Joined:
    Dec 28, 2012
    Posts:
    65
    I installed the new 2017.2.0f3 (was on 2017.1.0p5) and my scene loading code broke.
    I used SceneManager.LoadScene, followed by WaitForEndOfFrame and finally SceneManager.SetActiveScene, but I got the error saying the scene cannot be set to active because it's not loaded yet.

    I have switched to using the SceneManager.loadedScene event, but thought I'd mention this here in case it was not an intentional change.
     
  20. loadexfa

    loadexfa

    Joined:
    Sep 2, 2008
    Posts:
    214
    I'm also running into this error after upgrading Unity.
     
  21. adamgryu

    adamgryu

    Joined:
    Mar 1, 2014
    Posts:
    187
    Hey, I've been struggling with this same problem.

    I want to load an additive scene asynchronously, and on the frame that the scene becomes available, I want to set the active scene to the new scene, before any Awake / Start / OnEnable events run on the new objects.

    After reading this thread, I tried putting the "SetActiveScene" call inside SceneManager.sceneLoaded, and it *did* seem to do the trick. However, this thread has been conflicted on whether this actually works.

    Has anyone else noticed this working for them, and can also confirm this? Could we get the documentation updated to clarify this? The documentation on asynchronous / additive loading seems to leave out some useful information about execution order. Perhaps the fact that this order is important to my code indicates my design isn't great, but it would still be nice to know.
     
  22. duencil

    duencil

    Joined:
    Dec 17, 2012
    Posts:
    91
    AFAIK the Start methods on gameobject's monobehaviours in the newly loaded scene will be called after the SceneManager.sceneLoaded event, so you need to move any startup logic that relies on the new scene having been set active to there. Awake and OnEnable happen earlier during loading, when the monobehaviours are instantiated.
     
  23. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    1) F*cking Awake() and Start() is called for scene A, then Awake() and Start() is called for Scene B, even though they are activated at the same time. Should have been Awake Awake, Start Start.

    2) Component.FindObjectOfType<MyMonobehavior>() doesn't work when called from SceneA Start(), because MyMonbehavior is in SceneB

    Unity 2017.2.0f3

    Edit:
    Work-around is in one of the bullet-points of this post
     
    Last edited: Mar 25, 2019
    Gobey likes this.
  24. SteenPetersen

    SteenPetersen

    Joined:
    Mar 13, 2016
    Posts:
    103
    Hi I have read through this thread and tried a few of the solutions for my project. But it still not working as intended.

    I have made a Scene called "Loading" when you arrive on that scene it basically starts a loading animation very simple one. Then runs this code:

    Code (CSharp):
    1.  // Start an asynchronous operation to load the scene that was passed to the LoadNewScene coroutine.
    2.         AsyncOperation async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Single);
    3.         async.allowSceneActivation = false;
    4.  
    5.         while (async.progress < 0.89f)
    6.         {
    7.             Debug.Log(async.progress);
    8.             yield return null;
    9.         }
    10.  
    11.  
    12.         async.allowSceneActivation = true;
    However, the scene that it is being asked to load is quite heavy as it is generated a dungeon. Now loading it in "Single" makes the sceneActivation happen, the player appears and stands in oblivion, then the couroutine that does all the heavy lifting simply freezes the game until it is done and then everything works fine and I can play in my dungeon.

    Is there anyway that I can make the courotine run in the backround before allowing the scene to activate fully? So I can keep playing my loading animation until th dungeon is done being created?

    thanks.
     
  25. SteenPetersen

    SteenPetersen

    Joined:
    Mar 13, 2016
    Posts:
    103
    Thanks for the reply. But surely it cannot be impossible to make a loading screen in unity when you are generating randomized content. Has anyone found a solution for this. This would mean unity can't create loading screens for anything that requires async Awake, Start or at least a Coroutine to run for the next scene. I simply cant beleive that this is the case.

    any help would be appreciated.
     
  26. PuzzledBoy

    PuzzledBoy

    Joined:
    Sep 9, 2014
    Posts:
    23
    Hi,I'm also follow the Playdead GDC talk to optimize my project.
    After some work, I find a way to implement "PreAwake" system without access the Unity source code.

    First I create a manager script to an empty scene gameobject,the manager script's Script Execution Order was ahead of Default Time(you can modify it here : Editor->ProjectSettings->Script Execution Order).

    When a scene was active,it first call the Awake() of the manager script.

    Then in the Awake(),I disable all root gameobjects on the scene(So they can't trigger Awake() unless you active it again),and start to call PreAwake() within 6 frames, after that, I enable all root gameobjects that has been disable.

    So you can get the execute order : manager script Awake()-> PreAwake() -> Awake() -> Start()
     
  27. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    So if I understand correctly, the current preferred way to do this is to use the SceneManager callback? Or can we set the scene active whilst it is still loading?

    Thanks
     
  28. DragonRaja

    DragonRaja

    Joined:
    Apr 14, 2017
    Posts:
    3
    Code (CSharp):
    1. AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Splash", LoadSceneMode.Additive);
    2. asyncOperation += OnLoadOperationComplete;
    in OnLoadOperationComplete:
    Code (CSharp):
    1. SceneManager.SetActiveScene(SceneManager.GetSceneByName("Splash"));
    try this if I understand correctly
     
    NinjaDuck24 likes this.
  29. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    Is there a way to load a scene with activaton set to false and get access to its resources (gameobjects, etc) beforehand (.i.e before it calls awake and fills the rest of 10%)?
     
  30. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    That's interesting... I can kinda see why you'd be interested in that, but the closest thing I've done is to load a scene full of UI stuff, then grab one GameObject and reparent it to my operating Canvas.

    What exactly is your use case idea? There might be a better way to get it done.

    In other news, you probably ought to start a fresh thread for a new idea or question, rather than hijack a multi-year-old thread. :)
     
  31. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,325
    And I think you need to wait a frame or two before GetSceneByPath returns the loaded scene... one of those forever bugs
     
  32. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    If it just an UI stuff that is not an issue but U have full-fledged scene, so.
    Just needed access to specific resource.
     
  33. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    If this is the intended solution, then in 2020.3 this seems broken again.
    I just made a quick test project containing a few scenes that get loaded additively with each new addition meant to become the new active scene. Each scene has a component that creates a primitive in Awake. Expected behaviour is that SceneLoaded is called, in which I set the loaded scene as active, and after that the scene component's awake gets called and instantiates their primitives within themselves.
    Instead, the primitives are added to the previously active scene.
    I am writing up a bug report.

    Edit: So it turns out that in loaded scenes Awake calls will go before SceneManager.levelLoaded, but Start calls will go after.
    Good enough, I guess, but consistency might be nice?
     
    Last edited: Sep 9, 2021
  34. dyamanoha_

    dyamanoha_

    Joined:
    Mar 17, 2013
    Posts:
    87
    Looking through this thread, it doesn't look like there's actually a solution to the problem? Just to make sure I'm dealing with the same problem, I'll restate:

    If I load an additive scene... with say an 'EnemySpawner' GameObject... which spawns some enemies on Awake or Start, then all the spawned objects will end up in the *previously* active scene. Not the scene that's loading, which owns the spawner (note that's unintuitive).

    To my understanding, the only way to get my game objects into the correct scene is to track all created objects from a spawner, wait for the additive scene that owns the spawner to finish loading and initializing game objects and then transfer them all to the new scene via https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html

    Is that right?

    I feel like there could be an option to specify the desired instantiation target of the additive. Either 'self' or 'active'. Each scene would have an instantiation target member with the default being 'active' to preserve the current default behavior.
     
  35. dyamanoha_

    dyamanoha_

    Joined:
    Mar 17, 2013
    Posts:
    87
    And... 5 minutes later I figured out a workaround. I guess a simpler solution is to create a holder object like a 'BattleField' in the additive scene and parent any spawned enemies to it. That also gets them into the additive scene as children of an object already there.
     
    Kurt-Dekker likes this.
  36. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Always best to be explicit if you're going to care where stuff is for future unloading purposes.

    Your post above made me think about this experiment: two scenes as you describe, but listen to when they load and set active as soon as they are loaded.

    It's interesting where the created objects end up, but they make sense given the canonical execution order chart.

    (To be even more clear, ALL "Cube created" objects are fabricated by SceneB, additively loaded from SceneA.

    Screen Shot 2021-12-07 at 5.18.55 PM.png

    The code:

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.SceneManagement;
    3.  
    4. // @kurtdekker
    5. public class SceneA : MonoBehaviour
    6. {
    7.     void SceneChanged( Scene scene, LoadSceneMode mode)
    8.     {
    9.         SceneManager.sceneLoaded -= SceneChanged;
    10.         SceneManager.SetActiveScene( scene);
    11.     }
    12.  
    13.     void Start ()
    14.     {
    15.         SceneManager.sceneLoaded += SceneChanged;
    16.         SceneManager.LoadScene( "SceneB", LoadSceneMode.Additive);
    17.     }
    18. }
    and

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. // @kurtdekker
    4. public class SceneB : MonoBehaviour
    5. {
    6.     void OnEnable()
    7.     {
    8.         GameObject.CreatePrimitive( PrimitiveType.Cube).name = "Cube created in OnEnable()";
    9.     }
    10.     void Awake()
    11.     {
    12.         GameObject.CreatePrimitive( PrimitiveType.Cube).name = "Cube created in Awake()";
    13.     }
    14.     void Start ()
    15.     {
    16.         GameObject.CreatePrimitive( PrimitiveType.Cube).name = "Cube created in Start()";
    17.  
    18.     }
    19. }
     
    dyamanoha_ likes this.
  37. dyamanoha_

    dyamanoha_

    Joined:
    Mar 17, 2013
    Posts:
    87
    > Always best to be explicit if you're going to care where stuff is for future unloading purposes.

    Yeah, I think I agree with that. Even from the perspective of just keeping scene objects organized.

    Good experiment... so `Start()` is invoked after the `sceneLoaded` callback. In that case, warning to future readers...

    What I did was poll for the async load operation bool to flip to done in a loading screen script. After which I'd activate the additive scene and turn off the loading screen. Because I was looking for the load to complete on the main thread update, that ensured that I wouldn't see the load complete until after all awakes, enables, and starts ran on the additive game objects.

    Code (CSharp):
    1. private void Update()
    2. {
    3.     ...
    4.     if (this.loadingOper != null && this.loadingOper.isDone)
    5.     {
    6.         SceneManager.SetActiveScene(SceneManager.GetSceneByName(this.loadingScene));
    7.         ...
    So as a pattern going forward, generate objects on Start() and activate additive scenes via the SceneManager.sceneLoaded callback.

    Thanks Kurt!
     
    Kurt-Dekker likes this.
  38. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Yes, but as I noted, it DOES make sense given the execution order chart:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    Here is my reasoning why it makes sense:

    Callbacks like OnEnable() and Awake() are ULTRA tightly bound to the lifecycle of a MonoBehaviour.

    For instance if you call
    .AddComponent<T>()
    at runtime (on an active GameObject!!!), both OnEnable() and Awake() will have already executed before AddComponent<T>() returns the new class reference to you.

    That's really early.

    Now, for the ".sceneLoaded()" callback to make sense, one would want it:

    a) at the earliest place in time that...
    b) features a fully-loaded scene

    (I assert this just as a matter of general API principle!)

    It stands to reason that .AddComponent<T>() would already have been executed for everything in the scene, otherwise, well, the scene isn't really ready is it?

    And thus all OnEnable() and Awake() calls will fire BEFORE the scene loads.

    And then we come to good old Start()... Start() isn't anywhere near as tight-bound as that, so having the object spawned in Start() create itself in the newly-selected scene makes 100% sense.
     
    dyamanoha_ likes this.
  39. dyamanoha_

    dyamanoha_

    Joined:
    Mar 17, 2013
    Posts:
    87
    > Yes, but as I noted, it DOES make sense given the execution order chart:

    Yeah, none of this stuff is explicitly called out in technical documentation, at least not that I've found. The docs just tend to talk about the fact that Awake/Start are executed once in a lifecycle and that if you depend on another component to be initialized, do it in start etc...

    But I think the architecture/mental model is a good addendum that will probably pay dividends as I work with other Unity systems. Awake() appears to serve a similar role as a class/struct constructor. A scene needs to instantiate all objects, and it does that by 'allocating memory and invoking constructors'. Once that's done the Scene is considered loaded and then the Starts can be called.
     
    Kurt-Dekker likes this.