Search Unity

Important function deprecated. What is the new alternative?

Discussion in 'Scripting' started by MrDude, Aug 8, 2016.

?

Unity's replacing a rock solid system with an error prone delegate version "because it's better"

  1. Adding & removing delegates and requiring multiple functions to replace 1 function "is better"

    2.9%
  2. I disagree. Having a 1 function system that can't break is a much better idea

    37.7%
  3. I prefer completely revamped systems that come without any documentation. More please

    5.8%
  4. Just add detailed documentation and explain the nuances of this system and I'll be happy

    53.6%
Thread Status:
Not open for further replies.
  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Perhaps post your code for getting the scene change notification? Maybe a fresh pair of eyes will spot something important.
     
  2. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    I am getting the scene change notification, that bit is fine. The problem happens after this when I try to act upon an object that should be there - a serialized game object of the class - and it's considered to be null as it isn't initialised yet (the actual error says it's been destroyed and I'm still trying to access it - which is a bit bizarre considering the scene only just loaded). I have now coded around the problem by null checking the objects in question and taking remedial action. So my project now works fine but the 'bug' if you will is still there, I've just hidden it.

    So from my perspective the issue no longer affects me but clearly there is still something not right here.
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I wanted to see the code because I suspect you're getting the notification in a weird way, or for some other reason, the reality of what you're doing doesn't match your mental model of what you're doing.

    The error that you're accessing a destroyed object makes me believe this even more strongly. Unity really can tell the difference between a null-because-it's-uninitialized reference, and a reference to an object that's been destroyed. If they say you're accessing a destroyed object, I believe them. So, something in your full game is destroying this object you're accessing, before the end of the scene loading process (i.e. probably due to some Awake method). Maybe you then create another one just like it, so the rest of your game doesn't notice. Who knows? But I think it's pretty likely at this point that what you're seeing is not Unity's bug.
     
  4. ricardo_arango

    ricardo_arango

    Unity Technologies

    Joined:
    Jun 18, 2009
    Posts:
    64
    I just did a quick update to the script sample in the previous post to make sure the method is also called for the first scene, by adding the RuntimeInitializeLoadType.BeforeSceneLoad type as parameter to the RuntimeInitializeOnLoad method attribute

    https://docs.unity3d.com/ScriptReference/RuntimeInitializeLoadType.html

    The reason why the OnLevelWasLoaded was deprecated is that it is better to have explicit delegates than magic methods in the MonoBehaviour class. There is more control over the event, you can add callback methods and remove them.

    The RuntimeInitializeOnLoadMethod will only fire once, on startup. You can also use the Awake method to register the callback:

    Code (CSharp):
    1. void SceneLoaded (Scene scene, LoadSceneMode loadSceneMode){
    2.         Debug.Log(System.String.Format("Scene{0} has been loaded ({1})", scene.name, loadSceneMode.ToString()));
    3.         SceneManager.sceneLoaded -= SceneLoaded;
    4. }
    5. void Awake () {
    6.         SceneManager.sceneLoaded += SceneLoaded;
    7. }
    You can remove the callback when you no longer need it.
     
    VarroPipes likes this.
  5. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    Here is the code in question

    Code (CSharp):
    1.     [SerializeField]
    2.     Transform GateContainer;
    3.     [SerializeField]
    4.     Transform BarrierContainer;
    The GameObject is part of the scene and is not created or destroyed in script (I have no reason to do so, I'm only interested in child objects on this gameobject).

    upload_2016-10-12_17-3-23.png

    And you can see those happily attached to my class in the scene ....

    upload_2016-10-12_17-4-35.png

    The RaceManager class starts and the first thing it does in the start routine is clear down any child gameobjects under these two transforms ......

    Code (CSharp):
    1.     void Awake(){
    2.         racePhotonView = PhotonView.Get(this);
    3.         bootstrap = bootstrap.btstrp;
    4.         prefabs = bootstrap.getDatabaseManager().GetTrackPrefabs("");
    5.         ///dronesConnected = new List<DronePosition>();
    6.         //allocatedColors = new List<Color>();
    7.         SceneManager.sceneLoaded += OnSceneLoaded;
    8.     }
    Code (CSharp):
    1.     void OnSceneLoaded(Scene loadedScene, LoadSceneMode mode)
    2.     {
    3.         Debug.Log("Running in OnSceneLoaded");
    4.         if (PhotonNetwork.isMasterClient)
    5.         {
    6.             if(StateManager._singletonClass.getGameMode() != GameConstants.GAME_MODE.TRACK_EDITOR)
    7.             {
    8.                 string track_id = bootstrap.getSceneriesManager().getSelectedTrack();
    9.                 string trackData =  bootstrap.getDatabaseManager().getTableValue("userdb.tracks", track_id, "value" );
    10.                 loadTrack(trackData);
    11.             }
    12.            
    13.         }
    14.         else
    15.         {
    16.             sessionLoadNewTrack();          
    17.         }
    18.     }
    At this point loadTrack(trackData) is called.

    Code (CSharp):
    1.     public void loadTrack(string trackData)
    2.     {
    3.         clearAll ();
    4.         Vector3 pos;
    5.         Vector3 scale;
    6.         Quaternion rot;
    it calls clearAll()

    Code (CSharp):
    1.     public void clearAll()
    2.     {
    3.         //Debug.Log("clearAll: ");
    4.  
    5.         CheckPoint.HighestCheckPointNumber = 0;
    6.        
    7.         GameObject[] allCheckPoints = GameObject.FindGameObjectsWithTag ("checkpoint");
    8.        
    9.         foreach (GameObject clearCP in allCheckPoints)
    10.         {
    11.             clearCP.tag = "DefunctGameObject";
    12.         }
    13.        
    14.         if(GateContainer !=null)
    15.         {
    16.             int count = GateContainer.childCount;
    17.             for (int i = 0; i < count; i++)
    18.             {
    19.                 GameObject temp = GateContainer.GetChild(0).gameObject;
    20.                 temp.transform.parent = deletedObjects.transform;
    21.                 temp.SetActive(false);
    22.                 //Destroy(temp);
    23.             }
    24.         }
    25.        
    26.         if(BarrierContainer != null)
    27.         {
    28.             count = BarrierContainer.childCount;
    29.             for (int i = 0; i < count; i++)
    30.             {
    31.                 GameObject temp = BarrierContainer.GetChild(0).gameObject;
    32.                 temp.transform.parent = deletedObjects.transform;
    33.                 temp.SetActive(false);
    34.                 //Destroy(temp);
    35.             }
    36.         }
    37.  
    clearAll() now has my fix of null checks on those serialized transforms (GateContainer and BarrierContainer) as they don't exist when I try to access them. However if check the scene after the null exception has occurred those two transforms are there serialized quite happily and valid.

    Nowhere in the code is there a delete of either those serialized gameobjects. Additionally if I swap in the following instead of the SceneLoad code it all works fine (currently commented out as I'm not using now) .....

    Code (CSharp):
    1.     //void OnLevelWasLoaded(int level)
    2.     //{
    3.     //    if (PhotonNetwork.isMasterClient)
    4.     //    {
    5.     //        if(StateManager._singletonClass.getGameMode() != GameConstants.GAME_MODE.TRACK_EDITOR)
    6.     //        {
    7.     //            string track_id = bootstrap.getSceneriesManager().getSelectedTrack();
    8.     //            string trackData =  bootstrap.getDatabaseManager().getTableValue("userdb.tracks", track_id, "value" );
    9.     //            loadTrack(trackData);
    10.     //        }
    11.  
    12.     //    }
    13.     //    else
    14.     //    {
    15.     //        sessionLoadNewTrack();          
    16.     //    }
    17.     //}
    18.  
    As you can see the call is identical, it will run through EXACTLY the same code paths, yet one works and one fails.

    Furthermore here is a search through my entire project for that particular gameObject and every code reference to it.
    As you can see there is never an instruction to destroy it within RaceManager where it is serialized. RTEditorMgr does some things with it but that is in a different class and the two don't exchange references but it also does not destroy it.

    Searching for: GateContainer
    Scripts\OnOffObjects.cs(9): Transform GateContainer;
    Scripts\OnOffObjects.cs(31): public Transform GetGateContainer()
    Scripts\OnOffObjects.cs(33): return GateContainer;
    Scripts\RTEditorMgr.cs(17): Transform GateContainer;
    Scripts\RTEditorMgr.cs(187): GateContainer = transform.root.gameObject.GetComponent<OnOffObjects>().GetGateContainer();
    Scripts\RTEditorMgr.cs(236): if (go.transform.parent != GateContainer && go.transform.parent != BarrierContainer)
    Scripts\RTEditorMgr.cs(338): gameobj.transform.parent = gateTags.Contains (gameobj.tag) ? GateContainer : BarrierContainer;
    Scripts\RTEditorMgr.cs(544): foreach (Transform gate in GateContainer)
    Scripts\RTEditorMgr.cs(614): int count = GateContainer.childCount;
    Scripts\RTEditorMgr.cs(617): GameObject temp = GateContainer.GetChild(i).gameObject;
    Scripts\RTEditorMgr.cs(664): foreach (Transform child in GateContainer)
    Scripts\RTEditorMgr.cs(808): foreach (Transform gate in GateContainer)
    Scripts\RTEditorMgr.cs(1024): gameobj.transform.parent = GateContainer;
    Scripts\RTEditorMgr.cs(1084): int childCount = GateContainer.transform.childCount;
    Scripts\RTEditorMgr.cs(1088): foreach (Transform gate in GateContainer)
    Scripts\RTEditorMgr.cs(1385): gameobj.transform.parent = GateContainer;
    Scripts\GameManagers\RaceManager.cs(56): Transform GateContainer;
    Scripts\GameManagers\RaceManager.cs(930): gameobj.transform.parent = GateContainer;
    Scripts\GameManagers\RaceManager.cs(993): // if(GateContainer !=null)
    Scripts\GameManagers\RaceManager.cs(995): int count = GateContainer.childCount;
    Scripts\GameManagers\RaceManager.cs(998): GameObject temp = GateContainer.GetChild(0).gameObject;
    Found 21 occurrence(s) in 3 file(s)
     

    Attached Files:

  6. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    Hold the phone, might have an idea as to why it's doing what it is doing.
     
  7. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    OK, solved it and it was documenting it above that led me to the problem ..... don't cut and paste code from the web.

    The code I cut and paste was missing the de-register of the sceneload function.

    Code (CSharp):
    1. SceneManager.sceneLoaded -= OnSceneLoaded;
    So what was happening was actually a call of the previous RaceManager in the previous scene which had been destroyed when changing scenes. The annoying thing is I read that this was required and then went code blind as to it not being there, it was only when documented that giant post above that I suddenly thought 'hang on a minute, where's the de-register gone?'.

    So that's both annoyed me and cheered me up at the same time as I can use this now without worrying something funky is going on. Although I think some better documentation in the API reference wouldn't go amiss.
     
    Last edited: Oct 12, 2016
    JoeStrout likes this.
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Agreed. Really, the safe & standard pattern (and most similar to the old standard callback) ought to be:
    1. Register your delegate in Awake.
    2. De-register your delegate in OnDestroy.
    This should work in all cases, I believe.

    Note that from a strictly API design standpoint (which I have quite a bit of experience at, if it matters), I think Unity is making a big mistake here. Yes, the new delegate function is more flexible, but as we've demonstrated here, it is also more work to use and way too easy to screw up.

    If you fail to deregister your callback before your object is destroyed, you'll get an "accessing destroyed object" error (as you just saw). If you deregister your callback but then don't destroy your object when switching scenes, it won't get called on the next scene (another error we've seen in this thread). Feh.

    Sure, it's quite possible to use it right, but the old method was literally impossible to screw up. You declare the magic method, and it gets called at the appropriate time. I fail to see how this is worse than any of the other magic methods... and I really, really hope Unity isn't planning to change all the other magic methods into fiddly, laborious, fragile delegates like they did with this one!
     
  9. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    Yes, I have to say I agree with the sentiment here..... right off the back of royally screwing it up myself as a case in point. Yes I'm kicking myself but equally as you say the old method was just impossible to screw up.
     
  10. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    You see this all time in programming design culture, suitability vs capability. The thing is that suitability lead to limitation and capability lead to confusion. There is this endless war that push into one or the other direction, and while unity started as a suitability engine (democratization and all of that) they have been recently push toward capability in some domain, I guess that's what happen when you grow and absorb different people, the fragmentation start to sip in if the culture wasn't firmly an explicitly established before end. Still it was funny when goto (capability) was replaced by loop and branch (suitability) and people where saying "you can't make software like that, you don't have enough power". Suitability always win in the end, while capability allow sandbox of new practice to appear and be eligible for suitability :p
     
    Shorely, Eluem and JoeStrout like this.
  11. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So is the plan to push this across all of the API? Put in a virtual Awake method and then have the user manually subscribe to every single message they want. And then have them manually unsubscribe? Otherwise is pretty inconvenient to have inconsistencies.

    I understand it might be 'better' from a strict programming point of view. But it's not the way that Unity has been working for years. It might be better to keep this stuff magic and in the background. Subscribing and unsubscribing to an event was essentially done automatically under the old system.

    As long as you can spell the method name correctly. ;)
     
    Deleted User and JoeStrout like this.
  12. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    For those late to the party... The original question can be read in the thread title. The final answer is as follows:

    This was the old way:
    Code (csharp):
    1. void OnLevelWasLoaded(int level)
    2. {
    3.         Debug.LogWarning("Ready!");
    4. }
    This is the new way:

    Code (csharp):
    1. using UnityEngine.sceneManagement;
    2.  
    3. void Awake(){
    4.         SceneManager.sceneLoaded += OnSceneLoaded;
    5.     }
    6.  
    7. void OnSceneLoaded(Scene loadedScene, LoadSceneMode mode)
    8. {
    9.         SceneManager.sceneLoaded -= OnSceneLoaded;
    10.         DoMyStuff();
    11. }
    12.  
    13. void DoMyStuff()
    14. {
    15.         Debug.LogWarning("Ready!");
    16. }
    NOTE OF CAUTION:
    While the old way was solid as a mountain, the new way can cause errors if not used correctly.

    Reason for the change:
    The error prone way is better.

    That is all.
    Thank you
     
    Last edited: Oct 23, 2016
    PutridEx, Meri, Harinezumi and 4 others like this.
  13. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    What is this wall of text?
    ***ignoring
     
  14. AbandonedCart

    AbandonedCart

    Joined:
    Mar 4, 2014
    Posts:
    72
    If I follow, Unity removed a "global" call that reliably fired once the scene was finished loading (similar to the android onResume) that could be overridden to handle things that should happen every time when a scene loads and replaced it with delegates that were intended to handle multiple scene functions but do not provide a direct method for the most widely used one?
    So the intention is to replace a function by implementing static (see ANY talk on static versus private) calls to something that may or may not register the moment I intend, is explained using a method that requires per-scene scripts to launch, adds nearly 6 lines of unnecessary configuration code, and no longer directly handles the step?
    I thought the entire Unity versus Unreal debate was ease of use versus graphics, but why am I sacrificing the quality of the application for so many workarounds and native implementations? Isn't the purpose NOT to need that?
     
  15. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Remember, Unity is now giving us 2 ways to do this...

    1. Register everything on game start, whether needed or not at that point
    2. Register during Awake on objects that need it at that point

    But yeah, removed a simple, global, 1 line of code method and replaced it with a multiple function dependency to replicate the same behaviour.

    They went and added complexity purely for complexity's sake alone. Absolutely 0 other benefit to this. Look how many of us are unhappy about the removal of this function then let's flip the coin around and ask everyone: "Hey, Unity says this way is better. Who would miss it if they didn't add it?" and let's see how many people give a s...!!!
     
  16. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    In fact, I just added a poll to this thread. Cast your vote and let's see how welcome this change is...
     
    Harinezumi likes this.
  17. Trexug

    Trexug

    Joined:
    Dec 2, 2013
    Posts:
    88
    We have also had issues with the deprecation of OnLevelWasLoaded, but adding a passive aggressive poll to this thread only gives Unity a reason to assume that you are not serious.
     
    pinkhair likes this.
  18. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Facts are facts. That is why they are called facts. If someone decides "This guy is not serious because he is stating the facts as they are", well, what can I say?

    Or rather, what SHOULD I say? Should I change the facts? Cause, ummm, yeah... that is not the way facts work, you see? :p lol

    Besides, if this topic can inspire 66 responses from multiple participants and they think "nah, these people are not serious", do you really think the geniuses who gave us the Unity engine are THAT stupid?

    LoungeKatt took issue with the overly complex nature of this update. My question is rather simple: Unity says this added layer of complexity is better but who agrees with that statement? How many people will take up arms and fight for the existing function to remain and how many people welcome this new method?

    Sincerely and without any form of sarcasm or cynicism or passive aggressiveness or anything along those lines, I honestly believe that the only people who are happy with this change are people who get their payslips from Unity every month. I can't see anyone else thinking this is a good thing.

    Passive aggressive? No. Seriously curious? Definitely!

    Even WITH the examples Unity provided in this thread there is STILL confusion over this when speaking of having multiple scenes loaded at the same time. With no answers from them and nothing written in the docs about how to use this in that scenario, for them to say "This system is better", that is just asinine. It borders on "This is the better way if you can figure out how to use it. Just use trial and error until you figure it out then you will see".

    The examples above were good enough for me and how I use the function so I've already embraced the new method and moved past my desire to keep the old in with the new... but others are embracing the new features of multi scene loading and in that respect the docs are non existent in terms of how to use this. Until the docs are in place this mew system really does require trial and error to figure out so there is nothing passive aggressive about my poll. Just the facts as they stand at the moment.

    If the day comes that this method is no longer supported and on that day the docs have been updated to explain the nuances of the new system, then my poll will be meaningless and way off base... but for now the facts are the facts and that is all there is to it...
     
    Shorely likes this.
  19. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    @Lysander the above post begs your response ;) :p
     
  20. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    You know, I have this feeling that people are going to see this thread at some point in the future and just decide that I'm an absolutely massive asshole for that comment, lol. Since the joke was lost in edits, I guess it'll just be our little secret ^_~.

    Anyways, I'm a huge fan of events, and have quite a distaste for magic methods and reflection (if I could have them remove all of the rest like Awake and Start and replace them with events, I'd be a really happy camper), so I'm not at all annoyed about the change intrinsically. I do (to a lesser degree, perhaps) share your annoyance that it isn't documented properly, but that's also fairly common here, especially given that they don't even transcript their tutorial videos for text searches. This is just one instance though, and not one that has any real impact on my own projects.

    The poll is a bit passive aggressive, I feel- I have legitimate concerns about changes being introduced without supportive documentation, but all of the responses being sarcastic? This is an almost violent level of disgust that I feel is at least a little disproportionate to the problem. *shrugs*
     
  21. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    1. I love that post you made! :D You made it just after I deleted it so yeah, it's our little secret but I love it! :D

    2. I too love events and am busy redesigning one of my kits that had a lot of them to run entirely on them

    3. ...but remove ALL magic functions? Update, Start and Awake included? I think these should be limits set somewhere so watch me raise my arms and yell "Death to @Lysander 's dream"

    4. Seriously? My poll REALLY comes off as aggressive? More than 1 person thinks so? Shoot. Let me go see how else I can phrase it then... :/ challenging. Facts are facts, after all...

    Edit: Oops... can't edit or delete options so hopefully the last one will lessen the aggressive perception
     
    Last edited: Oct 31, 2016
  22. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Sorry, I can't agree with this summary. Your "old way" example would fire every time a level was loaded (in the case where you called DontDestroyOnLoad on this object). Your "new way" example, only gets notified once. Bad mojo.

    The correct "new way" is to either:
    1. Register the delegate in Awake (as you did) and then deregister it in OnDestroy (this is the method I use); OR,
    2. Register the delegate in OnEnable, and deregister it in OnDisable.
    The difference, I guess, is in the behavior of deactivated objects. Method 1 will go ahead and invoke your code even if your object is deactivated (or so I presume, though I haven't tested it). Method 2 should avoid that, invoking your code only on objects that are active when a level finishes loading.

    So I guess method 2 is probably a more accurate match for old behavior. I've never been in a situation where I have deactivated objects with OnLevelWasLoaded lying around, but if you really want to replicate the old behavior, I guess that's how you'd do it.

    But definitely don't deregister the delegate within the delegate, unless you have some perverse need to only find out about the first scene change in an object that sticks around between scenes.
     
  23. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    See, therein lies the confusion. You are speaking of "scene changes", I am speaking of "scene loadings"... Consider this example:

    I add the delegate to the OnEnable function and then I spawn the object midway through playing the game but the object is set to do not destroy on load and is NOT, I repeat NOT active. All the scripts are disabled during spawning! During the Awake of something else when the scene changes, you activate that existing object. Voila. Instant error.

    Why? Because when you spawn an object with their scripts disabled, the OnEnabled script still fires and thus registers to the sceneLoaded. Then when the next scene loads and you enable the script the same method is added again and now when the event triggers your object is going to run it's code twice. When you eventually remove the function in OnDestroy you will only be removing one copy of your function from the delegate meaning the next time a scene loads your destroyed object's function will be run and if you are doing anything that involves the game object the script was on, you get a null reference exception.

    OnEnabled is a dangerous place to register to events unless it is created and destroyed in the same scene.

    But why do you say the Awake function is only called once? If you have an Awake function on an object and you load the scene 1000 times then Awake is going to get called 1000 times. What makes you believe otherwise?

    Now, think about this logically:

    Before we didn't have the ability for objects to stick around between scenes without using DontDestroyOnLoad and in those days we didn't NEED to register to any events and in those days the OnLevelDidLoad would have been called on that object every single time a scene finished loading. There was no need to involve the Awake function on these objects

    Now that we HAVE the option of stuff persisting via DontDestroyOnLoad AND we have multiple scenes loaded at the same time, thus also persisting objects, NOW we need to worry about the Awake method and when and where to subscribe to the event. This is where the confusion comes in... Both the guy from Unity and I have said to register in Awake and unregister within the delegate and this will work perfectly if you use
    Code (csharp):
    1. SceneManager.LoadScene("NextLevel");
    ...i.e. when LOADING a scene. You are speaking of switching scenes so I am assuming you are using the "load all scenes at once and switch the active one as and when needed" approach.

    Well you, my friend, are exactly who I was referring to in my previous post when I said
    . You are why I said this needs documentation and without it it is trial and error till you find something that works. In your instance your method might work. The way I handle objects and their Awake methods, my way will work... But in the days before this added level of complexity, it would have just worked and we wouldn't have had to make over 70 posts about it and still debate how to do what used to just work out of the box before they made it better... :(
     
  24. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    p.s. Just for the sake of clarity:

    1. Register in Awake and deregister in OnDestroy... I can see that working.

    2. OnEnable is a scary thing, as I mentioned before... Not sure if OnDisable is called when you spawn an object so I would not trust this. Besides, who knows when this object is enabled or disabled? It needs to be registered before the scene has finished loading but enable and disable can happen at any time. I don't trust this method

    Just do this little experiment:
    Write a script that has an OnEnabled function, make it print something to the log, add it to a new game object, disable it, make it a prefab and then spawn that prefab whenever you press the space bar. Notice how the script is disabled in the prefab and notice how it is still disabled in the scene. Now go read the manual and see that this is intended behaviour.

    Have it in the scene and have it disabled and OnEnabled will not fire when you load the scene. Load the prefab at runtime and OnEnable will be called on all scripts on the prefab, enabled or not. So using this as a place to register to this particular delegate is not something I would consider doing... Unless the code that gets triggered by the event calls gameObject.SetActive(true). Then it might be useful. This SHOULD then call Awake and what not the frame AFTER the delegate fired... wouldn't it? Guessing here.

    This is turning into a hack-fest. Experiment away. Share your findings. I'm just gonna use what I know works until it doesn't work any more and trial and error it up till it does. You guys enjoy the experimentation. Just don't forget to nag them for documentation so we can eventually find out the right way...
     
  25. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I don't believe this. I've spawned disabled objects before, and observed OnEnabled (and Awake, for that matter) not firing until the object is later enabled.

    Because the object is only coming Awake once. Why would Awake fire again on the same object? I don't believe this either. :)

    What? Of course we still needed DontDestroyOnLoad (if we didn't want the object to be destroyed on scene load). I've been using it for years.

    But yeah, in that case, OnLevelWasLoaded got invoked on every scene load (even though Awake only fired once).

    Objects persisting across a scene change has nothing to do with multiple scenes loaded at once (a feature I have never used).

    I don't believe you or the guy from Unity. ;) Please test it, I think you'll find I am correct. Once you deregister the delegate, then (of course!) the delegate will not fire again (unless you later register it again somehow).

    I think the confusion comes because you're not using DontDestroyOnLoad. When you talk about "an Awake function on an object" I think you are actually talking about the Awake functions on different objects, as the old one gets destroyed and a new one gets created for the next scene (which may happen to look exactly like the old one, but it's still not the next object).

    And sure, if you do that, then it doesn't matter that you deregister your delegate and it only gets called once. But that's a weird case. The normal use for OnLevelWasLoaded, in my experience, is to provide notification to persistent objects (ones with DontDestroyOnLoad) that the scene has changed. Because the non-persistent objects can usually use Awake or Start or OnEnabled instead. But persistent ones don't get any of these calls when a new scene is loaded, because they already awoke, started, and got enabled long ago. So you need OnLevelWasLoaded to let these objects do whatever they need to do when the next scene comes in.

    No. I'm talking about SceneManager.LoadScene.

    Yes, in this I fully agree. :)

    But let's actually probe this confusion and see if we can figure out where you're wrong. ;) So then we can, at least, provide a better recommendation for how to update your old scripts in a way that will behave the same in all cases.
     
    Fajlworks likes this.
  26. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Let's not be superstitious. OnEnable is invoked between Awake and Start, just as documented.

    Yes, I just did this. It spawns disabled, and importantly, none of Awake, OnEnable, or Start fire when spawned. These fire only later, when I manually activate the object. (And of course if you later disable and re-enable this object again, Awake and Start do not fire again, but OnEnable does.)

    True.

    Rubbish! It most certainly does not. It fires when the object is enabled, period, end of story.

    OK, fine, it's still a free country and the sand can be a comfy place for one's head. But you should refrain from posting summaries along the lines of "here's how to deal with this," because you're not dealing with it properly. Even if it happens to work in your projects, it would cause nasty bugs for most people.
     
    Harinezumi and Fajlworks like this.
  27. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Sorry, my bad... I said OnEnable... it is the Awake function that gets called even when disabled. My bad, sorry for that. Just uploading the test project now. Press space bar to spawn the prefab with the script disabled and the Awake is still called. Press return to load the next scene, there I have the object in the scene already, still with the script disabled and Awake is still called.

    Hit enter a couple of times to toggle between scene 1 and 2 and every time you get to scene two it will print the Awake, as expected.

    http://guild.site/Downloads/OnEnabledTest.zip

    Edit:
    Notice how in all my text I said the SCRIPT is disabled and you keep saying the OBJECT is disabled....
    The GameObject != a component that is on the GameObject :p

    Again, sorry for confusing OnEnabled and awake earlier in my other posts. As it says in the docs, Awake is called after instantiation unless the GAMEOBJECT is DISABLED. Read another way, it is called even if the SCRIPT is DISABLED.
     
    Last edited: Oct 31, 2016
  28. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    I will forward your opinion to Unity and ask them to remove the sample posted by them because clearly they have no clue. I will make sure to credit you for the expert critique... Notice I merely used a cleaned up version of what they said we should do... It was a summary post, not an opinion.

    And to prove I am not being hostile, I will end this post with a joke:
    ...We are in different countries (Don't leave me hanging. Someone give me a "ha ha") :p
     
  29. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Ah, OK, if you're talking about the script disabled (rather than the object itself), yes, Awake still gets called. Disabling the script only disables calls to Update, FixedUpdate, and OnGUI (I'm sure this is documented somewhere, but I can't seem to find it now).

    Come to think of it, I bet OnLevelWasLoaded still gets called in this case too. So, that suggests that the best way to replicate the previous behavior really is to add the delegate in Awake, and remove it in OnDestroy. (It may not be exactly the same — I suspect that OnLevelWasLoaded didn't get invoked for deactivated objects, and with this method, it would — but it'll be pretty close, and correctly handle the case of a disabled script.)

    Well yeah, except that Unity guy really does (or did) have no clue, and we had already talked about it. Don't summarize an old post that's already been debunked; summarize the newer posts, where we've worked through the issues and come up with a more sensible recommendation.

    I do get your argument from authority, that somebody who works at Unity ought to know better than some random guy on the forums. But Unity is a big company composed of humans, and they really do make mistakes sometimes. I won't hold it against them. :)

    Ha ha! :) No hostility here either. I love coding, and I appreciate you hanging in there with me for it.
     
  30. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Ditto. I wasn't going to take the chance of quoting from memory again :p
     
  31. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    I'm non programmer, I know enough to know this has been confusing me endlessly. I don't even fully understand delegate, so they made something that is cutting out people like me!
     
  32. AbandonedCart

    AbandonedCart

    Joined:
    Mar 4, 2014
    Posts:
    72
    Having done some work on a project that converted to events and going through the struggle as a developer of the API in conversion, I can honestly say that there is a time and place for events and a time and place when using the term "magic methods" is just misdirection.
    If the method was magical and did not actually refer to a specific event or trigger, then sign me up for Hogwarts because it has to be the single most reliable trick I have ever seen. It was able to fool even the most skeptical audience into believing it would always work.
    Some events are nothing more than sleight of hand, though. They make something look professional, but in reality they are a hoax. They add unnecessary requirements, cripple functionality, but they look so good on paper. It's like putting a better college on your resume. It will get the job, as long as nobody actually expects you to do what that college taught.
    In the end, our adventure into events broke most of the application using the API, caused an obnoxious amount of code changes both for ourselves and the end users, never provided the same capability, but it made the API look snazzy even if most users had to enable Multidex just to fit the added methods. Ironically, most users stopped updating the API and reverted to the older version, if they continued to use it at all. The one thing we had going was it being the only API to provide such functionality at the time, which is something Unity often neglects to consider.
     
    Kurt-Dekker, JoeStrout and Trexug like this.
  33. Malice983

    Malice983

    Joined:
    May 21, 2016
    Posts:
    13
    I think the idea that the old still would work and the new is more flexible might really put it at a stage where you could use both. The deprecated version is for people who know it and don't need the extended use of the sceneManagement tools. I too am having some problems with my project and have been reading your posts here. At the end of the day I see an advantage to keep both.
     
  34. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    "detest, despise, condemn, denounce, protest against, bad mouth, rubbish" ... the antonym of praise. I don't think there is any confusion over what it means... I just didn't know how strongly deprecation could be expressed as. Wow.

    I used to think "deprecate" meant "marked as outdated". I was so wrong... :p

    "This function is despised and will be removed in a later version of Unity". Ouch. I deprecate Unity's deprecation of this function. :p

    Screen Shot 2016-11-07 at 08.43.06.jpg
     
  35. Deleted User

    Deleted User

    Guest

    Could you not, y'know override OnLevelWasLoaded to have more control over events and then have it both ways? I mean by this logic we should all really be coding in C++ doing our own garbage collection / memory allocations because a) C# is high level "magic" with automagic stuff like dynamic GC and memory allocation b) let's face it, it'd actually work better..

    But firstly that's not the point of Unity, secondly if it ain't broke why fix it? Did you get specific requests to deprecate this method or did someone just believe it was a "good" idea?
     
    neoshaman and Kiwasi like this.
  36. dhogan

    dhogan

    Joined:
    Jun 2, 2008
    Posts:
    55
    Preface : To be clear, I do understand the discussion and sentiment - because there's a bunch of them resulting from Unity's various improvements to code and decisions about everything from licensing to the asset store.

    I logged in just to say this - What I think would be most useful to the silent numbers of people that land here is a clear answer to "How do I correct this error message about OnLevelWasLoaded being deprecated, so that it doesn't eventually crater one of my projects when I go to re-use a previously useful script?"

    The delicious irony of me being here is I'm trying to correct and use a script and assets from Unity expressly created and shared to make things easier for developers making a simple game. Good times. (Game Jam Menu template, just for those wondering.)

    In the end I fixed it, and discovered that the asset had actually been updated, so I was able to check my work. Ha! As a takeaway, though, I don't understand the flow of things as well after the 'fix', so time to update my knowledge.
     
    Last edited: Dec 18, 2016
  37. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    I love changes like this. They bring Unity more toward working in a controllable, expected manner (programmatically). The only problem I'm seeing here is that a large amount of people have learned to "program" in Unity, and don't have any understanding of events and delegates. Don't blame Unity for moving away from a "magic method" based model; it's a model that encourages the kind of lazy thinking that leads to people getting upset when a magic method is replaced with a more suitable event/registration system.
     
  38. MaDDoX

    MaDDoX

    Joined:
    Nov 10, 2009
    Posts:
    764
    So next we'll deprecate "Start" and "Update", 'coz they're too magical. Ok, better start redoing all my Unity classes slides, this will take some time.
     
    neoshaman likes this.
  39. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    I doubt Start and Update will be deprecated any time soon...but I definitely do my best to rely on them as little as possible, and would recommend that as general practice. People run into issues all the time by placing half their code into those methods. Stick all your init code into Start, nothing wrong with that. Until you're attempting to place 5,000 objects into the scene in a short time and your init code has ballooned into 500 external calls and you have absolutely no control over WHEN that init code is called, so you can't smooth it out.

    Sure, have all your objects working in their own Update method, great, until you realize you've got 5,000 objects running a method with however many calls just to poll for state changes because you never bothered to learn about events and delegates, and the only way to stop it is by disabling the MonoBehaviour and disabling a bunch of other unrelated behaviour that you may or may not need. Because, you know, having an event you can subscribe to/unsubscribe from is just too confusing.

    Apparently, it's important we keep these magic methods. What harm could they do? Except the entire generation of programmers that thinks delegates are too hard, and would rather just poll for states and have no control over behaviour.
     
    Stardog likes this.
  40. MaDDoX

    MaDDoX

    Joined:
    Nov 10, 2009
    Posts:
    764
    Definitely not the point Dameon. I'm all for delegates, timed coroutines, DI, IoC, Rx and any programming architectures that make things saner and more controllable. In the other hand, there are those facilitators (Monobehaviour callbacks/messages) for newcomers, since that's one of the things Unity has always excelled in.

    I don't see good reasoning in removing the second to "force" the usage of the best practices, it'll only help to allienate new Unity developers and un-democratize game development.

    Not their motto.
     
    JoeStrout and Kiwasi like this.
  41. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    It's not so much to "force" the usage of best practices, I imagine. Leaving an old architecture in and then programming a whole new architecture on top of it is not just a matter of "leaving" it there. You wind up having to work around it; changes to underlying structures that might be compatible with the new architecture might break the old. So then you wind up either changing those structures to support an old framework, or maintaining an old framework. Next thing you know, you're maintaining 6 frameworks built around each other to support lazy programming behaviour from 3 versions ago, for fear of "alienating" "programmers" who can't take the time to learn some relatively basic modern programming concepts.
     
  42. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    As a non programmer, It's not alienating programmer, it's alienating graphist, sound designer, actor, game designer, people who have massive skills just not a programmer.

    And let's not kid ourself, programming is a frakking cacophony of people who can't agree about the best way to do something, when you just want to get stuff done, that's a huge diversion.

    Also have you ever work with a programmer? They only understand programming, when you ask something to do a tools about art, it takes age for them not only to understand but just accept the "wacky" stuff we come with that don't make sense in their little world and they think is a massive waste of time, programmer art is a reality, and we end up with compromise.

    Magic method is basically a way to communicate with programmer, it allows us to sketch something something that works and let the programmer do what he does best, to make it cleaner and more efficient, apply his basic modern programming concept they always bicker about and never get agreement on!
     
    TaleOf4Gamers and Kiwasi like this.
  43. catfink

    catfink

    Joined:
    May 23, 2015
    Posts:
    176
    The issue here was not that a magic method got deprecated it was that the replacement that we were being herded towards using was inadequately documented. Personally i have no issue with the removal of something providing the replacement is documented such that when replacing the functionality you aren't left having to muddle your own way through it. The attitude of 'learn to program properly' is quite inappropriate in this instance. This whole thread could easily have been avoided had someone documented the replacement functionality comprehensively.
     
  44. MaDDoX

    MaDDoX

    Joined:
    Nov 10, 2009
    Posts:
    764
    That's exactly why I wrote "Developer" and not "Programmer" in my post, neoshaman. If after Construct, Scratch, and all visual development tools Unity and other third parties have integrated in the engine people still don't see the difference, they haven't been paying attention.

    But still, that's just my $0.02. Let's hope Unity knows better.
     
  45. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Given a choice, I would stay with the magic methods. Given how GameObjects and Components work, it makes sense for Unity to take care of the event subscribing and unsubscribing.

    The next option would be an interface that takes care of the events automatically. Much the same way that the EventSystem works. Unity is still responsible to search out and subscribe and unsubscribe as GameObjects activate and deactivate.

    Having events to manually subscribe to would be my least preferred option. I really don't want a thirty line boiler plate OnEnable and OnDisable just so that I can subscribe and unsubscribe to every event I might need.
     
    JoeStrout likes this.
  46. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    This one caught me off guard when I updated recently (haven't updated in some time).

    I'm fine with it being an event, event's don't bother me.

    Though, what I find annoying is the inconsistency of Unity in regards to these things.

    I'm with @BoredMormon, the interface approach would be best in my book. Less garbage, similar to the existing 'magic methods' so people won't be so confused by it, and is a strong standard in the dev world (considering it's how events have been done in Java since the 90's).
     
  47. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Above all this.

    We now have three different systems running concurrently. We use magic methods to get Awake and Start and stuff. We use interfaces for UI. And now we use Events for scene load.

    What makes it worse is the systems don't play nice together. The magic methods for Input don't work with the interfaces for UI.

    Setting up a single "Unity Way" for the engine to call my code would be super nice.
     
  48. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    The one I quote do have programmer in full letters :p

    But yeah, it seems that they are trying to move "the accessibility responsibility" to the asset store community. Basically focusing on having an efficient engine™ and let the community fix the emerging quirks ... In fact I only make 400€ a month, and yet I use to spend half of that on asset store, enough that unity tried to contact me for "special discount" that I couldn't afford. But they make things more complex in practice if you are not making a skin to something that exist, the moment you deviate their genericity kills you in the eggs, and their are not always responsive when you need is too niche like "afro hair with tight curl" (no waviness is not a curl). Also it does not solve hassle I have with basic stuff like collision and input precision ... It solve generic problem for people with enough money.

    The problem, with all the scratch and the playmaker of the world, is that they confuse approachability and accessibility, they are psychological tricks to make you comfortable with the logic, but they are slow and add step to the result, and anything complex become unwieldy after a while, it keeps you dumb and you feel dumb when you can 't achieve complex stuff, they solve the wrong problem and pretend they did a good job. Try making a planetary terrain generation on any of them and see what I mean ... It might be doable, but it will be more extra pain. SO they are used by programmer to occupy the "other" by making him do rot kindergarten exercises he don't want to do. Sure you can make whole successive product on it, but it won't be the next no man sky.

    Magic method should be a graduation ritual, you use them when you start to get result, get confident, then learn how to do it properly moving away from them, that's how people do it in gamemaker, they use drag and drop then move to complex gml coding (before jumping ship to more "proper" language), it all happen organically as they need more features and power.

    I did solve the problem though, I move back to the puny blitz3D for prototyping, and I'm teaching it to a graphical artist, who couldn't stand any of unity's complicated quirk (he was scared, there is so many exception to teach prior doing anything), to his delight. I'm teaching him the basic of procedural mesh modeling, addvertex, addtriangle, press f5, done! It's accessible and approachable, it's not verbose and you get result really fast, it's design with game in mind (unlike unity) and have the right limitation that KISS and KILL, and it's free now.
     
  49. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    And artist programming is a reality, too. There's a reason specializations exist, and it's because each specialization is its own little world. I don't go around demanding artist tools conform to my programming desires, or expecting them to. Instead, I learn how to use the tools I'm presented with properly, even though it's difficult to grasp the concepts at times.

    Definitely with y'all on the consistency thing. If I want to see if something was clicked, I can currently use a static class, a magic method, an interface, or an event, and they all essentially do the same thing. I also like the interfaces approach because of the lack of garbage, although I personally like a subscription model where I have actual control over subscription/unsubscription.

    In the end, it doesn't matter too much, because I just worked around it all, but as a programmer, I found Unity's magic methods approach to be MORE confusing than normal programming flows. Rather than a single, controlled entry point, you suddenly have a variety of entry points which you can only debug one side of, with the other side a completely invisible untraceable void. Why is OnCollisionEnter2D not working? Who knows! Oh, it's because its method signature is inexplicably completely different from OnCollisionEnter, not that you get any compiler warning. Or, whoops, you named a method the same as one of the giant variety of Unity3D methods, and nothing warned you at all, and you have no way to debug it.

    You know I don't need to tell you about the many ways inheritance and/or composition could reduce that thirty line boilerplate down to a zero line no boilerplate.

    Even if that wasn't a possibility, I'd still rather have the thirty line boilerplate just to have more than a single property that turns ALL events on or off, when there's dozens of events, and no individual access to subscription to events. The two aren't even mutually exclusive; SetActive could still turn on or off all associated events, with individual subscription/unsubscription handlers for fine control. Anything is better than "Here are 70 or so events you can subscribe to. You get one single control to enable or disable all of them."
     
  50. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Key difference is I would have to write that inheritance/composition structure stuff.

    I'd probably end up doing it via reflection. Examine each class when it was first created. Figure out which events it is interested in. Automatically subscribe it to those events. And then create a destructor for each component that runs when the component is removed, automatically unsubscribing it.

    Oh wait...

    I do firmly believe this should be an engine responsibility. I use Unity to develop games. Not to build architecture.
     
    Harinezumi likes this.
Thread Status:
Not open for further replies.