Search Unity

[Solved] Problem with Awake/Start when using SceneManager to load Additively

Discussion in 'Scripting' started by Trexug, Feb 17, 2016.

  1. Trexug

    Trexug

    Joined:
    Dec 2, 2013
    Posts:
    88
    I have a problem with Awake/Start when loading a scene Additively. I want the scene which I load additively to be the active scene when Awake/Start is invoked on the objects of that scene. Like this:

    1. LevelOne is loaded at the beginning with some objects belonging to LevelOne.
    2. LevelTwo is loaded additively with some objects belonging to LevelTwo.
    3. Some of the objects belonging to LevelTwo spawns new gameobjects on Awake/Start belonging to LevelTwo
    4. LevelOne is unloaded at some point, destroying only the objects belonging to LevelOne.

    However, it seems like I cannot do this - or at least I don't know how. When Start/Awake is called (step 3) on the objects of LevelTwo, the Active Scene of the SceneManager is still set to LevelOne, making the objects belong to LevelOne. I can change the Active Scene of the SceneManager but not before Start/Awake is called. The result is that when LevelOne is unloaded (step 4), the objects created on Awake/Start is removed as well.

    I have created a simple project which loads another level additively after a second. An object in the newly loaded level spawns a cylinder. After 2 seconds, the first level is then unloaded and unfortunately also destroys the cylinder.

    How can I solve this issue? I am not keen on having to keep track of all the objects spawned and moving them to the new scene. Nor would I be especially pleased by inventing my own "Awake" and calling that after I can call SceneManager.SetActiveScene.

    The project is made with Unity 5.3.2p2 - run the scene called LevelOne.
     

    Attached Files:

  2. Trexug

    Trexug

    Joined:
    Dec 2, 2013
    Posts:
    88
    Anyone? Is there no solution or am I explaining this poorly?
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    It appears you wrapped this up in a nice small project for example. Let me check it out.
     
    ThermalFusion likes this.
  4. Trexug

    Trexug

    Joined:
    Dec 2, 2013
    Posts:
    88
    I have taken a look at my options and setting the active scene before creating any object seems to be one of them. For my sample project that would mean changing
    Code (csharp):
    1.  
    2. private void Awake()
    3. {
    4.     Debug.Log("The current scene is " + SceneManager.GetActiveScene().name);
    5.     GameObject.Instantiate(prefab);
    6. }
    7.  
    to

    Code (csharp):
    1.  
    2. private void Start()
    3. {
    4.     Scene scene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
    5.     Debug.Log("Setting scene " + scene.name);
    6.     SceneManager.SetActiveScene(scene);
    7.     Debug.Log("The current scene is " + SceneManager.GetActiveScene().name);
    8.     GameObject.Instantiate(prefab);
    9. }
    10.  
    I have to change Awake to Start as setting the active scene on Awake seems to have no effect at all. For my real project, this is going to require a decent amount of rewriting, but perhaps I shouldn't have been spawning objects on Awake in the first place :\ I guess I will have to check that the active scene has been set correctly in all scripts which need to spawn objects on Awake (which I will have to replace with Start).

    When using LoadSceneMode.Single instead of LoadSceneMode.Additive, Unity sets the active scene to the new scene, even for Awake calls. It seems like it would be useful to have an additional parameter for SceneManager.LoadScene* calls, which would specify whether the new scene should become the active scene as soon as it is loaded.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    There is SceneManager.MoveGameObjectToScene(GameObject go, Scene scene). It says that "It is required that the GameObject is at the root of its current scene", but that's not a problem. In your case, this should suffice:

    Code (csharp):
    1. private void Awake()
    2. {
    3.     Debug.Log("The current scene is " + SceneManager.GetActiveScene().name);
    4.     var go = GameObject.Instantiate(prefab);
    5.     SceneManager.MoveGameObjectToScene(go, gameObject.scene);
    6. }
    7.  
    Note that I haven't tried this, so there might be problems - but those problems would be bugs.
     
    Ash-Blue, Trexug and lordofduct like this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    So, sorry I didn't get back to you earlier. Got wrapped up with lunch and some code I was writing for other stuff.

    Anyways, it appears you resolved your issue to an extent. And yeah, I was going to mention the Awake vs Start. Really Awake shouldn't be considered as a method to really perform anything beyond setting up internal references in that script.

    The documentation is a bit vague since it predates SceneManager:

    http://docs.unity3d.com/ScriptReference/MonoBehaviour.Awake.html

    But really, you can take this to mean that you shouldn't really use Awake for game logic. But rather as just setting up references. Like a Constructor.

    Use the 'Start' message for actual game logic.



    As for your project, I noticed you never actually even set the active scene, this means the active scene won't change until after LevelOne has been unloaded (which is delayed). I also noticed your 'wait' delays for the loading and unloading depended on update.

    I went and modified the project with coroutines, set the active scene, and moved the logic to Start for adding things to the new scene. Check it in linked source zip.
     

    Attached Files:

    Last edited: Feb 18, 2016
    Trexug likes this.
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Yes, that does work as well. Just tested it.
     
  8. Trexug

    Trexug

    Joined:
    Dec 2, 2013
    Posts:
    88
    @Baste Yeah that seems like it would work while also allowing me to keep my object creation in Awake, as long as I check which scene the new object belongs to and move it if its not the correct one.

    @lordofduct I looked at your solution and stumbled on this:
    Code (csharp):
    1.  
    2. yield return SceneManager.LoadSceneAsync(level, LoadSceneMode.Additive);
    3.  
    I did not know that you can yield an AsyncOperation in a coroutine. I looked at unity's execution order Script Lifecycle Flowchart and I couldn't see when yield AsyncOperation is actually being run, but from your example I can see that it is between Awake and Start (I setup SpawnObject to execute before LoadLevelAsyncAdditiveAfterAWhile in the MonoManager/Script Execution Order to make absolutely sure that it wasn't at the same time as Start).

    That means that my rewriting efforts would be limited to moving object creation from Awake to Start and moving SceneManager.LoadSceneAsync to a coroutine which also sets the active scene - avoiding any per-object checks.

    For my project I think that solution will be the easiest and cause me the least head aches in the future.

    Big thank you to the both of you, your responses have been super helpful :)
     
    lordofduct likes this.