Search Unity

Bug FindObjectsOfType in BeforeSceneLoad no longer finds anything in 2022.2

Discussion in 'Scripting' started by Snowdrama, Nov 8, 2022.

  1. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    I have a system that uses prefabs for loading game critical objects into my scene, but to ensure they are only loaded once, I make sure that they aren't already in the scene.

    To do this I use a static method with a BeforeSceneLoad to find all the objects using FindObjectsOfType that are in the hierarchy, and if they are not present then I Instantiate a copy from a prefab.

    This worked in a previous project that was using 2021.3.1f1, last week I started a new project in 2022.2.0b7 and this no longer works.

    I have identified that this is due to the objects not being found in the hierarchy when using FindObjectsOfType but only in the BeforeSceneLoad function.

    Here's the code, and screen shots of the setup I have. I did this test for both versions with the same exact code both times.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ObjectToFind : MonoBehaviour
    4. {
    5.     //this object is just something I want to find
    6. }
    7.  
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ObjectFinder
    4. {
    5.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    6.     public static void FindObjects()
    7.     {
    8.         //Find my objects
    9.         var foundObjects = GameObject.FindObjectsOfType<ObjectToFind>();
    10.         Debug.LogFormat("Found Objects: {0}", foundObjects.Length);
    11.         foreach (var item in foundObjects)
    12.         {
    13.             Debug.LogFormat("Found: {0}", item.name);
    14.         }
    15.     }
    16. }
    17.  
    18.  
    Here's the scene view
    upload_2022-11-7_21-47-12.png
    And each of the ObjectToFind objects have this script:
    upload_2022-11-7_21-48-15.png

    Results from the 2022.2.0b7 Version:
    upload_2022-11-7_21-51-7.png

    Results from the 2021.3.1f1 Version:
    upload_2022-11-7_22-3-34.png

    While I didn't test this in my recent testing, I've been using this same loading system since at least 2019.3 so to my knowledge this has always worked before

    I'm not sure if this change was intentional and if so for what reason it was changed, I have been looking around for a reason or something pointing to why this was changed or when it was changed but I do not see anything in the issue tracker about it and doing some searches through the "What's New" sections of 2022 stuff hasn't revealed anything about why it was changed.

    Any information would be helpful, especially if someone can point me at the patch note for when this changed so I can get context.

    I submitted a bug report as well but am posting it here to see if anyone has any possible solutions or workarounds.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,843
    Frankly this set up seems pretty fragile even at the best of times.

    There's much better ways to ensure important objects remain present. Personally, I just have an additively loaded scene(s) to ensure all the important stuff hangs around.

    It's also pretty trivial to have self instantiating singletons, or use scriptable objects to store this persistent data.
     
    MaskedMouse and Kurt-Dekker like this.
  3. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    I've been using the system I have for roughly 3 years now, and I've finished a handful of projects using this system and during that time It's worked flawlessly with no issues, so I wouldn't consider it fragile, from my perspective it's pretty battle tested.

    I'm simply mentioning that something has changed under the hood and it no longer behaves as expected. Since this has worked for so long now, I think it may be an unintended side effect of another change. If it was changed unintentionally then the bug should be fixed.
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,843
    Except there are many other methods of doing this that will never suffer this issue, because they are, you know, not fragile. Your example straddles the line between the lifetime of objects before, during and after scene load.

    Find and similar methods are generally bad practice and ill-performant.

    And if you care about the lifetime/existence of these objects, then you should hold onto references to them at the time of their creation, not search for them ad-hoc over and over.
     
    Kurt-Dekker and MaskedMouse like this.
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Has this worked in builds?

    BeforeSceneLoad implies that it happens before, uh, the scene is loaded. So I would think that the correct result for this is to not find any objects. I could see it "working" in editor by looking at the currently loaded objects rather than the ones in the scene that will be loaded.

    Why not AfterSceneLoad? That should happen after the scene is loaded, after all. It has the problem of only happening with your first scene, but your old approach has the same.
     
    Bunny83 likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    As others point out above, you don't need to go to graymarket functions like the scene manager delegates to do something this simple and fundamental to all games.

    For all such things like above, I use one of these patterns:

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    If you really insist on a barebones C# singleton, here's a highlander (there can only be one):

    https://gist.github.com/kurtdekker/b860fe6734583f8dc70eec475b1e7163

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , only while it exists.
     
    Snowdrama likes this.
  7. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    I feel like we're off topic, I'm not really asking for help solving the issue lol. I know enough to solve the problem just fine. I posted this to point out that the functionality changed and is now inconsistent with the past 3 major versions of Unity, find out when and why it was changed, and to bring light to the fact that it is possibly a bug haha

    Yes it has worked in builds! It also worked beautifully with my upgrade to Addressables when that came out! and I don't use AfterSceneLoad for consistency, all the things I do in BeforeSceneLoad are intended to be accessible on Start/Awake/OnEnable of the objects in the scene regardless of which scene I hit play from, and what additional scenes are loaded Additively. Putting them all together in BeforeSceneLoad makes sure that everything is initialized and set up without needing to hem and haw about what needs to be intialized before and what needs to be done after. Simplicity is a factor of why I did it this way, and while it worked it was clear and concise.

    I basically use everything you mentioned in your post at one point or another, so I'm not doing anything particularly interesting or novel.

    In the case of where it interacts with FindObjectsOfType use the ScriptableObject that's loaded with Resources.Load but where the only thing that's different is that the SO has a prefab reference and an Init function that is called after it's loaded from Resources that does a FindObjectsOfType and Instantiates the prefab if one isn't present. The SO singleton is stored statically and has a host of functions that can pass messages down to the instantiated GameObject, for example the scene transition SO that sends messages to a script on a canvas that manages the transition transparency and has callbacks for when the transition if fully faded in and out for hooking into Addressables for scene management.

    A bit more complex than the example given above but again, this was a bug report and I wanted to build something simple and repeatable.
     
    Last edited: Nov 8, 2022
  8. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    I mean if you intend this to just be a bug report, then you should probably send a bug report! If it's a regression in 2022, then the correct place to post about it is also in the beta forums, where devs responsible for the beta will notice.
     
    Snowdrama likes this.
  9. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    Yep! I did do that as well, that's already filed. I didn't find that forum earlier when looking through the forums for where to put it but that makes sense to put it there, RIP me
     
  10. BillHolmes

    BillHolmes

    Unity Technologies

    Joined:
    Jan 5, 2021
    Posts:
    1
  11. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
  12. MirceaI

    MirceaI

    Unity Technologies

    Joined:
    Nov 24, 2020
    Posts:
    36
    A quick workaround is to use
    Code (CSharp):
    1. var foundObjects = GameObject.FindObjectsOfType<ObjectToFind>(true);
    Because of some recent changes in how the scenes get loaded the objects are not active at the point you are calling FindObjectsOfType and they are filtered out. We are working to fix this
     
    yakivmospan and Snowdrama like this.
  13. Mads-Nyholm

    Mads-Nyholm

    Unity Technologies

    Joined:
    Aug 19, 2013
    Posts:
    217
    Hi @Snowdrama, I had a look at the issue you brought the table here. Thanks for creating the bug that helped investigating the issue.
    The
    RuntimeInitializeLoadType.BeforeSceneLoad 
    callbacks are invoked very early in the load scene operation: just after the objects are loaded in memory but before Awake and OnEnable is called. So basically you get this callback before the objects loaded have been fully setup. The change in behavior for finding active objects is here to stay since objects are not actually 'activated' before Awake and OnEnable are called. This change was need to fix various systems.
    What you need to do now when using callbacks that are invoked before Awake() is to get all inactive objects and use the state for 'activeSelf' and manually check if the object will be active after Awake() by checking all parents are also activeSelf. In other words activeInHierarchy will always return false until the GameObjects have been awakened.
    The docs for RuntimeInitializeLoadType are incorrect and will be corrected to match when these callbacks are invoked and more info about the lifetime object state.
     
    Snowdrama likes this.
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Could you perhaps also add some more documentation to the different members of RuntimeInitializeLoadType, so we know exactly what they mean, and when they happen in relation to methods? Perhaps also add them to https://docs.unity3d.com/Manual/ExecutionOrder.html ?
     
  15. Mads-Nyholm

    Mads-Nyholm

    Unity Technologies

    Joined:
    Aug 19, 2013
    Posts:
    217
    Yes that was my intention: "The docs for RuntimeInitializeLoadType are incorrect and will be corrected to match when these callbacks are invoked and more info about the lifetime object state"
     
    MirceaI and Baste like this.