Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question what is the behavior of scene.isLoaded?

Discussion in 'Scripting' started by laurentlavigne, Apr 13, 2021.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,225
    I thought that a scene loaded additively would show isLoaded=true even if another scene is active but that's not the case.

    What is isLoaded for? It only works with Active scene?

    upload_2021-4-13_15-7-54.png
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    The Scene object is mildly annoying: it's actually a value type, NOT a reference type:

    https://docs.unity3d.com/ScriptReference/SceneManagement.Scene.html

    Specifically,

    Scene: struct in UnityEngine.SceneManagement


    and not a class.

    This means if you get a Scene to your local variable, you just made a full copy of the data.

    If you then talk with the Scene Manager to do some stuff to that Scene, your old copy is now potentially out of date.
     
  3. There is a loadingState which says it is still loading. I think you have checked too early.

    I tested with this code:
    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3. using UnityEngine.SceneManagement;
    4.  
    5. public  class Test: MonoBehaviour
    6. {
    7.     private IEnumerator  Start()
    8.     {
    9.         var op = SceneManager.LoadSceneAsync(sceneBuildIndex: 1, LoadSceneMode.Additive);
    10.    
    11.         while (!op.isDone) yield return null;
    12.  
    13.         var scene = SceneManager.GetSceneByBuildIndex(buildIndex: 1);
    14.         Debug.Log($"First additive scene loaded: {scene.isLoaded}");
    15.     }
    16. }
    screenshot1.png
     
    laurentlavigne likes this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    This is also possible. I think scenes load for some amount of time and then they are loaded but grayed out if you pause the inspector then.

    They don't truly activate until after the end of the current frame, so that can be another wiggly bit.
     
    laurentlavigne likes this.
  5. Oh yes, that too. Scene loading is done in the next frame.

    https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html
     
    laurentlavigne and Kurt-Dekker like this.
  6. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,225
    what if the scene is drag and dropped in the hierarchy window, then i press play, does it need one frame to register as loaded?

    what do you guys use in your project to control scene loading and availability? loadasync acts super weird some times, downright broken when a timeline is playing
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Pretty sure that's a copy. :)

    I use lots of LoadScenes, all of them additive after the first one, then just get down to business.

    I write all my inter-scene scripts to tolerate waiting for stuff to appear, e.g., the player tries to find a spawnpoint and if there isn't one, he yields one frame and tries again.
     
    laurentlavigne likes this.
  8. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,225
    i know what a copy of prefab is, it's an instance, asset = copy of the file but what's a copy of a scene?
    talking about this by the way
    upload_2021-4-13_16-5-23.png

    not async?

    how do you find stuff across scene? i have singletonite on this game :D
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    It's a copy of that Scene struct.

    If you assign an int to another int, you made a copy (int is a struct)

    If you assign a Vector3 to another Vector3, you made a copy.

    If you assign a GameObject to another GameObject, now they are both pointing at the same one.

    It's just basic C# value vs reference types.

    Rarely.

    Usually by tagging it with something, most often an otherwise empty MonoBehavior. I don't like using Unity tags because they are inflexible and don't show up in the code very well.

    The otherwise empty MonoBehavior lets me also implement OnDrawGizmos() to make it more-visible in editor.
     
  10. When you add more scenes for editing, it is VERY important, you only add them in the editor. They will be loaded when you hit the play button instantly. If they are loaded in the hierarchy. If you unload them (right-click/unload scene) or add them by holding the ALT while dropping them, they will be unloaded.

    But, in build, it doesn't matter. Only your main scene will be loaded (index: 0), nothing else. You're responsible to load them by code.

    I use a manager scene where all my game manager stuff live. It is the zero scene and it is responsible for loading and unloading everything else. I use roughly the same code (obviously I have a bunch of static public methods to handle it from anywhere) I posted above. Always additive, always async.
     
    Last edited by a moderator: Apr 14, 2021
  11. BourbonBristles

    BourbonBristles

    Joined:
    Jan 23, 2014
    Posts:
    7
    For anyone else, when checking the current scene remember to check
    scene.isLoaded
    in
    Start
    or later as during
    Awake
    that value will still be false.
     
    Nit_Ram likes this.
  12. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
    While trying to debug an issue i found that this was the root. This is really unintuitive and i'm sure it's a misbehaviour/bug. A gameobject inside a scene cannot have awake called outside of a scene, so the scene must be loaded for objects within it to receive their engine lifetime callbacks.
     
  13. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Awake is called as components are loaded into memory, before the scene is fully loaded. It's normal behaviour.
     
  14. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
    The docs state that Awake is called after all gameobjects in the scene are initialized and that GameObject.FindObjectsWithTag/OfType etc can already be used, which means they already exist inside a loaded scene.
     
  15. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    upload_2023-12-17_10-46-3.png

    No idea what you're reading then. What the docs says is consistent with what I said and the current behaviour.
     
  16. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
  17. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Just because the game objects are initialised does not mean the scene is fully loaded. Scenes are only introduced into the main thread over a number of frames. Only after that point is the scene completely loaded.
     
  18. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,364
    @spiney199 , @Flavelius is correct. Furthermore, the documentation there is really using "enabled script instance" poorly.

    If you have a MonoBehaviour script instance attached on an active object, whether the MonoBehaviour has a .enabled checkmark or not, even if you have a .enabled checkmark and it is turned off (meaning it's not enabled), then your script's Awake() will be called when the scene loading is finished. You only avoid Awake() if your GameObject is not active through inactive ancestor or self.

    All objects are loaded and initialized from the serialized data. The only thing the async is slowing down is that loading/deserializing/initializing process. Then all applicable active objects and their components are given their Awake messages via the SendMessage mechanism sorted in the prescribed execution order. Then they all get OnEnable the same way. Then in the update phase, any enabled component that has not gotten Start will get that too. And so on.

    I think Awake is called before scene.isDone can possibly be observed as true, but it all happens in the same frame at the end of the async portion of the loading process.
     
    Last edited: Dec 17, 2023
  19. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    I kinda disagree. Awake, from my understanding and experience has always been "as something is being loaded". With scriptable objects it's just after the SO has been deserialised, after being loaded into memory. With game object components, it's after all game objects in the scene have been loaded into memory, but before the actual scene has completely loaded.

    That's always been my experience across multiple versions.

    And it goes without saying Awake is really only best used for self initialisation. Start should be used for initialisation dependant on other things.
     
  20. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,364
    Time to test it. Whip up a large scene full of heavy objects that have a component on it, where the component prints
    Time.frameCount
    and
    gameObject.scene.isLoaded
    in its own Awake().
     
  21. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Well I didn't have any heavy game objects lying around so I threw 10,000 empties into a scene with this component on it:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class LoadTimingTestComponent : MonoBehaviour
    4. {      
    5.     private void Awake()
    6.     {
    7.         bool sceneIsLoaded = gameObject.scene.isLoaded;
    8.         int frameCount = Time.frameCount;
    9.         Debug.Log($"Loading Game Object ({gameObject.name}) at frame {frameCount} where scene is loaded: {sceneIsLoaded}");
    10.     }
    11. }
    And I get 10,000 logs of this when entering play mode:
    Using another component to reload the scene when hitting Space gets the same result, just at another frame. This is on the latest 2021.3 LTS.

    So seems consistent with what I'm saying. Once all the game objects are loaded, they are all Awoken at frame 0 before the scene is completely introduced into the main thread (ergo, loaded).

    Doing the same test in Start shows it's Frame 1, and
    Scene.isLoaded
    is true.
     
  22. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,364
    I took your statement to mean that various objects will get Awake piecemeal whenever they are loaded in whatever order encountered, defying the DefaultExecutionOrder in favor of immediacy. I contend that they will always get all their Awakes on the same frame. If everything's in frame0, then your test can't prove either way. Add some heavy objects, LoadSceneAsync this scene, and even print out something when asyncop.isDone goes true, just so we can settle it.
     
  23. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Sorry I meant "As the scene is being loaded, after all game objects are ready", like I said before.

    That is consistent with both the docs, and the behaviour experienced.

    Don't feel like taking any more time out on my weekend to prove this. If you feel the way you do, you can prove me wrong.
     
  24. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
    To my understanding, unity doesn't communicate any concept of not-in-scene-tree/hierarchy and not-in-assets for UnityObject instances that are accessible via scripting API, even when you new() a gameobject it is immediately placed into an existing scene, when you Resource.Find() it searches assets and scene objects etc. .
    Unity doesn't even support having no scene loaded.

    Following this, it would be inconsistent if GameObject.Find would now find objects that exist in neither of those locations.
    The thing that i acknowledge is that there has to be a time where objects are constructed out of scene-tree/hierarchy and asset collection where they are not yet fully integrated, but it don't see why unity would call awake (/lifetime callbacks) on them while they exist outside the scene-tree/hierarchy (constructors - yes) and have a special case for GameObject.Find there that treats all objects it scans over as being inside the scene-tree/hierarchy.
     
  25. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Why do you assume they're not in the heirarchy?

    They probably and most likely are. Just the scene they reside it hasn't been completely integrated yet. Like my test showed, Awake got called at Frame 0, and Start at Frame 1. Most likely the scene transform heirarchy has been established on that first frame, but since we haven't hit the end of frame, the scene is not considered loaded.

    Not sure why that's so hard to imagine.
     
  26. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
    The 'considered loaded' is what i'm mainly stumbling over, not so much the other parts, they may behave as necessary.
    There is more than just the transform hierarchy already established for awake to be called in the first place, the script instances are already associated with their respective gameobjects, enabled etc. this is the reason why GetComponent etc. already fully work.
    But when looking at the SceneManager documentation it's clear that even unity aknowledges this as confusing:

    "When using SceneManager.LoadScene, the scene loads in the next frame, that is it does not load immediately. This semi-asynchronous behavior can cause frame stuttering and can be confusing because load does not complete immediately."

    My root-issue is that i need to figure out if references are asssets/prefabs or scene objects when initializing, and i was doing that via [reference.]scene.isValid() && scene.isLoaded. For some reason it used to work in many situations over a long time, but it currently doesn't anymore, but i guess i have to change my approach anyway (as that was probably based on a brittle behaviour that only worked in a certain case).
     
  27. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    Well like was mentioned above, Start happens after the scene is completely integrated on the next frame. So that may work.

    Curious why you need to know if a reference is a scene object or not. A reference to a game object/component shouldn't make a difference either way.
     
  28. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    943
    I have a generic dynamic ui list base class that handles instantiating the entry template and allows using prefabs or scene instances as template. It is part of a custom upm package that also contains styled prefabs for some subclasses of it. Allowing the use of scene instances as template is useful when there are many instances that just need slight variations of the entry template where those would just clutter folders unecessarily. And this template instance needs to be deactivated/hidden/reset on awake (as early as possible, before other logic runs) and the scene test was a switch for me to not modify the prefab accidentally.