Search Unity

Order of void Update calling

Discussion in 'Scripting' started by sampa93, Sep 17, 2020.

  1. sampa93

    sampa93

    Joined:
    Jul 18, 2020
    Posts:
    10
    Hello community,

    I am new to unity and got an question to the common "void Update()" method.

    If I got a class which calls in it's update() another class, which also does calculation in it's update() - in which order will it be called? I know update is called once per frame but anyway there must be an order for the classes?

    For example: Will the result be 0 or 1 after the first frame?

    Code (CSharp):
    1. public Class A : MonoBehaviour {
    2.     public ClassB classB;
    3.  
    4.     void Start()
    5.     {
    6.         classB = GetComponent<ClassB>();
    7.     }
    8.  
    9.     void Update()
    10.     {
    11.         int result = classB.Value;
    12.     }
    13. }
    14.  
    15.  
    16. public Class B : MonoBehaviour {
    17.     public int Value = 0;
    18.  
    19.     void Update()
    20.     {
    21.         this.Value++;
    22.     }
    23. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,744
    sampa93 likes this.
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,910
    There is no guaranteed ordering, unless you explicitly create such an ordering using this: https://docs.unity3d.com/Manual/class-MonoManager.html

    However I would recommend instead just writing your code in such a way that it works no matter what order the components are updated in. Perhaps move some of the logic from one of the Update methods into a different public method that the other Update method calls.
     
  4. OliverTheLove

    OliverTheLove

    Joined:
    Jun 26, 2016
    Posts:
    24
    There also exists [DefaultExecutionOrder(NUMBER)] that you can put over a class to set its order, without going into the editor in the link that PraetorBlue mentioned. They won't however show up in that list either, so beware!
     
    sampa93 likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,744
    Thanks, didn't know that. I think it's still better to link the calls explicitly otherwise if you drop code from one project to another, the execution order numbers might not be meaningful. If you explicitly link codepath you will get a compiler error and know you need to fix it.
     
  6. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    Another option is have a single class that has update in it, such as a manager class and have it call a method on other scripts to run an "update".

    Something along these lines. This way you control the update flow yourself and you now only have one script running Update.

    Code (CSharp):
    1. public Script1 script1;
    2. public Script2 script2;
    3.  
    4. void Update()
    5. {
    6.    script1.UpdateFromManager();
    7.    script2.UpdateFromManager();
    8. }
     
    Olipool and Kurt-Dekker like this.
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    In practice the order of Updates rarely matters. If you think A runs before B but B actually goes first, your count is off by 1, in theory. But it's not like you need an exact count most times. You probably playtest and decide "about 125" is when the cows should appear, and so on. It's also reliable. Unity keeps the Update order, even on a different platform (I think). Or suppose A dies and you expect B to notice on the same frame, but since B runs first it takes an extra frame. No problem. That may look better and you were probably going to add a random 10-15 frame delay anyway.

    The order of initialization often does matter -- if B reads values from an array created in A::Start(), A clearly needs to run first. Unity suggests you use Awake and Start for those (all Awakes run, then all Starts).
     
    sampa93 likes this.
  8. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Actually, it does matter in some cases and it can really affect the gameplay. For example, I have a playerController that can "enter" moving platforms. Instead of parenting to those platforms, I simply check how far the platform has travelled and move the player this distance as well. If in one frame the platform is updated and then the player, it works fine. But the other way around wouldn't move the player at all because the platform is still at its position from the last frame. So the player does not move, but the platform does which elevates the player a bit above the platform.
    Visuals aside this may lead to drastic different outcomes like for me:
    The player can hide in a box that acts as a moving platform. The box has a trigger inside it so the player "knows" when he is in the box and has to travel with it. If the player jumps and gets out of the trigger, he no longer is considered to be in the box.
    In my case the ending of the game depends on the fact if the player is in the box or not so the random order of Update executions triggers a false ending.
    Solutions I will try (after debugging for a long time): maybe use the platform movement in LateUpdate or use parenting to the platform (which might not work in every scenario).
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Note that @Owen-Reynolds didn't say that it never matters.

    They said:
    You demonstrating a scenario that matters only goes to demonstrate that Owen was right to say that there are some scenarios. It's just that those scenarios aren't common. And when you run into them, well that's when you'll do something about it. Hence the existence of Execution Order settings which @PraetorBlue linked:
    https://docs.unity3d.com/Manual/class-MonoManager.html

    ...

    As to the scenario you're describing. I'm not exactly following it. You say that if the player updated before the platform, then it would never move.

    How did you code this?

    Because wouldn't the platform's new position be there the next frame? Sure the player didn't move this frame, but next frame it surely would have?
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    A really basic example of what I mean...

    Code (csharp):
    1. //script moves up and down a platform
    2. public class Platform : MonoBehaviour
    3. {
    4.  
    5.     public float Speed = 1f;
    6.     public float MaxY = 10f;
    7.     public float MinY = 0f;
    8.  
    9.     [System.NonSerialized]
    10.     private float _dir = 1f;
    11.  
    12.  
    13.     void Update()
    14.     {
    15.         this.transform.position += Vector3.up * Speed * _dir * Time.deltaTime;
    16.         if (this.transform.position.y > MaxY) _dir = -1f;
    17.         else if (this.transform.position.y < MinY) _dir = 1f;
    18.     }
    19.  
    20. }
    21.  
    22. //if a Platform intersects this with a Trigger collider on it, it will move with the platform.
    23. //This is a very basic version and will only work with the most recent platform that touched it.
    24. //If more than 1 platform exists you would get strange behaviour. This script merely exists to demonstrate
    25. //the ordering concept doesn't effect things.
    26. public class RepositionPlayerOnPlatform : MonoBehaviour
    27. {
    28.  
    29.     private Platform _platform;
    30.     private Vector3 _lastPosition;
    31.  
    32.     private void OnTriggerEnter(Collider other)
    33.     {
    34.         var platform = other.GetComponent<Platform>();
    35.         if (platform == null) return;
    36.  
    37.         _platform = platform;
    38.         _lastPosition = _platform.transform.position;
    39.     }
    40.  
    41.     private void OnTriggerExit(Collider other)
    42.     {
    43.         var platform = other.GetComponent<Platform>();
    44.         if (platform == null) return;
    45.  
    46.         if (platform == _platform) _platform = null;
    47.     }
    48.  
    49.     private void OnTriggerStay(Collider other)
    50.     {
    51.         var platform = other.GetComponent<Platform>();
    52.         if (platform == null) return;
    53.  
    54.         if (_platform == platform)
    55.         {
    56.             var delta = _platform.transform.position - _lastPosition;
    57.             _lastPosition = _platform.transform.position;
    58.             this.transform.position += delta;
    59.         }
    60.     }
    61.  
    62. }
    With this you can change the order of both scripts relative to one another and it'll still work regardless. The only thing that ends is a slight tiny visual difference... if the player updates after the platform they look perfectly in sync... if the platform updates after the player the player looks like it's lagging every so slightly behind (depending the speed of the platform).

    I can think of things that could mess this up though. For example say we made 'Platform' have the job of calculating its 'delta'. And we uncovered it as a property. Maybe we do this so that way we can have multiple platforms impact the player at the same time. (the problem in the current implementation is that we only have 1 _lastPosition variable. By moving it to the Platform each Platform gets its on LastPosition and Delta each, resolving that whole issue).

    BUT, in doing so lets say we keep the update in RepositionPlayerOnPlatform inside of the OnTriggerStay (tossing everything else out).

    There will be strange behaviour here then. And it's to due with the timing of OnTriggerStay versus Update. Because OnTriggerStay runs on the physics timer as the documentation states:
    https://docs.unity3d.com/ScriptReference/Collider.OnTriggerStay.html

    What ends up happening is the "player" gets out of sync with the platform. If the framerate gets too fast it falls behind, and if it gets too slow, it speeds past it.

    But this has nothing to do with the relative execution of Update in both scripts and everything to do with how physics events are called.

    ...

    OF COURSE... all of this can be avoided by NOT having it in 2 scripts!

    The Platform could take care of everything if you wanted:
    Code (csharp):
    1. public class Platform : MonoBehaviour
    2. {
    3.  
    4.     public float Speed = 1f;
    5.     public float MaxY = 10f;
    6.     public float MinY = 0f;
    7.  
    8.     [System.NonSerialized]
    9.     private float _dir = 1f;
    10.  
    11.     [System.NonSerialized]
    12.     private Vector3 _lastPosition;
    13.  
    14.  
    15.     void Update()
    16.     {
    17.         this.transform.position += Vector3.up * Speed * _dir * Time.deltaTime;
    18.         if (this.transform.position.y > MaxY) _dir = -1f;
    19.         else if (this.transform.position.y < MinY) _dir = 1f;
    20.     }
    21.  
    22.     private void OnTriggerEnter(Collider other)
    23.     {
    24.         if (!other.CompareTag("Player")) return; //some test to confirm that the collider is the player, I'm using Tag
    25.  
    26.         _lastPosition = this.transform.position;
    27.     }
    28.  
    29.     private void OnTriggerStay(Collider other)
    30.     {
    31.         if (!other.CompareTag("Player")) return; //some test to confirm that the collider is the player, I'm using Tag
    32.  
    33.         var delta = this.transform.position - _lastPosition;
    34.         _lastPosition = this.transform.position;
    35.         other.transform.position += delta;
    36.     }
    37.  
    38. }
    (NOTE - all code in this post is naive and intended for demonstration purposes. It does not reflect my preferred way of accomplishing this specific task. There are many flaws with this approach. The 'timing' issue just isn't one of them)
     
    Last edited: Mar 10, 2022
    Olipool likes this.
  11. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Hey, thanks for the detailed thoughts. I did not mean to criticize Owen, it is just that these special cases can have bad outcomes and you won't think immediately that script execution order would be the problem. And because I was fighting with one of those cases and it took me a few hours to figure out I just wanted to point that out for everyone to keep in mind.
    I did code my version similar to yours, the platform has a trigger zone the player can detect, that gets added as variable in the player. In the movement code, I also calculate the difference to the last frame and use Move() on the characterController to move the player according to his input + the distance of the platform.
    Now my first problem was when the player gets updated first then he wil move 0 because the platform was not updated at that point. Then the platform moves and is a bit under the player and depending on the speed, the player may move out of the trigger zone and is no longer considered to be on the platform. It is especially bad because my platform is not moving up and down but is just falling down by gravity. So suddenly the player falls with a different pace than the platform.
    So I tried to move the code to LateUpdate and later just decided to parent the player to the platform and let Unity deal with movement. Which lead to even greater confusion and after some debugging I realized what was going on:
    Now the box got updated first and moved down, making a gap to the player. Before Unity could apply the parent/child effect to the player the trigger event fired that the player exited the platform. So in the end I had to make the trigger zone high enough that the difference between player and platform can't get big enough for that to happen, also it helps that the platform (which is actually a falling crate) has a lid, that keeps the player inside (although under extreme circumstances I would not count on that).
    I will attach an image so you can better imagine what I mean.
    The black thing is the box, the cyan thing the trigger zone and the capsule the player.
    upload_2022-3-11_23-26-15.png
     
  12. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    I could have explained it a little better. I meant that it's natural to assume the order always, always matters; and it's even more natural to think that if you have some previous programming experience. That's reinforced by seeing how Unity provides Awake() and Start() for the sole purpose of getting things in the correct order.

    So it's a real surprise to realize that 90% of the time the order Update()'s happen doesn't matter. Either way, your objects takes turns: A, B, A, B, A, B ... . If they're in the "wrong" order you almost never get actual errors. All that happens is some things are drawn a frame late, and in a game that often looks better. In fact, we often create an X-frame delay, with the length chosen by feel. The "wrong" order makes zero difference in that case.

    An example of "accidental lag is good": tracking Olipool's platform's movement in the wrong order would make the player a little out-of-synch with the platform. But that could look good. When the platform starts to move the player will appear to slide back a little as if friction wasn't perfect. Then they'll scoot forward for a frame when the platform stops, as if they still have momentum. Those little touches are often better than a perfectly mechanical tracking.
     
  13. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    I agree... it can look more natural. But I was totally unaware of the side effects especially because of the many things that happen per frame and which order is always a bit shrouded in mystery :D At least for me, yea there are huge flowcharts of what happens when but I can't keep that in my working memory.