Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Understanding Delays in Unity

Discussion in 'Scripting' started by keystagefun, Jun 18, 2020.

  1. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Hi,

    I'm new to Unity, having worked for the last 15 years or so in first Actionscript / Flash and then mobile development both natively and on various cross-platform SDKs.

    I'm loving the learning curve, but something is confusing me and I'm hoping someone can shed some light on it.

    There seem to be small delays to certain code blocks being run at various stages of my application, that are making some aspects of the flow of my program hard to manage. Example:
    • I initialise a canvas object and an event system programmatically as the first things I do.
    • I add various UI elements.
    • But because the canvas object presumably hasn't finished the setup process it needs to go through, the UI elements are positioned incorrectly.
    • If I add short delay to the function that adds the UI elements, they are positioned correctly, presumably because my canvas is now "fully" set up... at least that's my assumption. I can go really small with the delay - a 0.00001 second delay works with no issue, but if I set the function to run without any delay, the positioning is wrong.
    I run both bits of code from my Start() function of the script that is attached to my main game object, in the correct order - ie canvas created and added first, then UI elements.

    This sort of thing has happened elsewhere too, when focusing on a particular UI element programmatically for example, it seems to take around a quarter of a second to "process" the request to activate that UI element, so an Update() function that checks the status of that element doesn't return a positive check immediately, which messes with what I want to happen.

    All this seems weird to me and doesn't match any experience I've had previously with any other platform, where things happen pretty much instantaneously, unless there is a good reason.

    If anyone can shed some light on whether my understanding of how things should work is missing something key to Unity (which is very possible!) or whether it is just one of those things and I'll have to find workarounds by ensuring each piece of code has definitely finished before calling the next piece, that would be really helpful.

    Thanks in advance.
     
    Last edited: Jun 18, 2020
  2. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,133
    Try running setup code in the Awake() function. it gets ran before the Start() function is called.
     
    Vryken likes this.
  3. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    What @johne5 said.
    I also think this is a bit of an oversight on Unity's beginner tutorials and with how default MonoBehaviour scripts are created, because this is a common issue new users run into and it's not really explained why or how.

    The way it works is that all MonoBehaviour scripts in a scene are initialized in an undefined order. So say if you have something like this, where one script is adding a component onto it's GameObject in the Start method, and another script wants to reference the component in its Start method:
    Code (CSharp):
    1. public class ScriptA : MonoBehaviour {
    2.    public Rigidbody body;
    3.  
    4.    void Start() {
    5.       body = gameObject.AddComponent<RigidBody>();
    6.    }
    7. }
    8.  
    9. public class ScriptB : MonoBehaviour {
    10.    public ScriptA scriptA;
    11.  
    12.    void Start() {
    13.       scriptA.body.velocity = Vector3.zero;
    14.    }
    15. }
    There's the (likely) possibility that ScriptB's Start method can run before ScriptA's Start method, so the Rigidbody reference will be null at the time.

    The way to fix this is by using the Awake method, which runs before the Start method.
    All Awake methods that exist in MonoBehaviours in the scene will run before the first Start method.

    The general rule of thumb is to make all MonoBehaviours initialize their own variables/references in Awake, and reference values from other MonoBehaviours in Start, if needed.
    So if we change our previous example to this:
    Code (CSharp):
    1. public class ScriptA : MonoBehaviour {
    2.    public Rigidbody body;
    3.  
    4.    void Awake() {
    5.       body = gameObject.AddComponent<Rigidbody>();
    6.    }
    7. }
    8.  
    9. public class ScriptB : MonoBehaviour {
    10.    public ScriptA scriptA;
    11.  
    12.    void Start() {
    13.       scriptA.body.velocity = Vector3.zero;
    14.    }
    15. }
    Then the Rigidbody component that ScriptB wants to reference is guaranteed to exist at the time.
     
    Last edited: Jun 19, 2020
    Jorhoto likes this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    If you're gonna do stuff procedurally, this sorta thing can happen. Fortunately as other folks have pointed out above, there are some things you can do to mitigate against it.

    However, the broad pattern I like to use when going procedural is the factory pattern. Any given component will have a static function used to Create it (or add it to an existing GameObject), in order to ensure it has what it needs without question.

    Here is a trivial example of the pattern I use EVERYWHERE in my games to ensure order of initialization:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MyScript : MonoBehaviour
    6. {
    7.     // Always call this to create MyScript objects so we know they are complete.
    8.     public    static    MyScript    Create(
    9.         string resourcesPath,
    10.         string additionalArgument1,
    11.         int otherArgument)
    12.     {
    13.         // NOTE: only have ONE of the following two blocks of code, not both:
    14.  
    15.         // Option 1:
    16.         // code for if this is a pre-defined prefab that you're going
    17.         // to load, instantiate, and then customize a bit
    18.         GameObject go = Resources.Load<GameObject>( resourcesPath);
    19.         go = Instantiate<GameObject>( go);
    20.         MyScript myScript = go.GetComponent<MyScript>();
    21.  
    22.         // Option 2:
    23.         // code for making a new GameObject from scratch
    24.         //MyScript myScript = new GameObject( "MyScript.Create();").
    25.         //    AddComponent<MyScript>();
    26.  
    27.         // jam in the arguments we need
    28.         myScript.additionalArgument1 = additionalArgument1;
    29.         myScript.otherArgument = otherArgument;
    30.  
    31.         return myScript;
    32.     }
    33.  
    34.     string additionalArgument1;
    35.     int otherArgument;
    36.  
    37.     void Start()
    38.     {
    39.         /// etc...
    40.     }
    41.  
    42.     void Update()
    43.     {
    44.         /// etc...
    45.     }
    46. }
    This means outside of such a function you would NEVER use .AddComponent<>. And the above sample factory can be changed to accept an existing GameObject that it "installs" itself into, and correctly ensuring it has everything it needs to open up for business.

    In the above case you would then call:

    Code (csharp):
    1. MyScript.Create( "Prefabs/MyPrefabForMyScript", "Arg1", 1234);
    It returns the
    MyScript
    instance it created, so if you want, keep a reference to that. Meanwhile you can be confident that MyScript has everything it needs.
     
  5. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Thanks everyone - really appreciate the replies.
    Hadn't realised that - and everything makes a lot more sense now!!
     
  6. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,000
    with UI theres also issues where certain things need to be forced to update (go get proper values back right away)
    example https://forum.unity.com/threads/force-immediate-layout-update.372630/

    also there is chart of the general execution order
    https://docs.unity3d.com/Manual/ExecutionOrder.html

    for individual scripts you can force some to run first
    https://docs.unity3d.com/Manual/class-MonoManager.html
    *also from the script file itself with this special attribute https://forum.unity.com/threads/undocumented-but-public-defaultexecutionorder-attribute.530618/
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,201
    In addition to what everyone else is saying - you can't really get a 0.00001 second delay unless your game is running real fast. Untiy works in frame intervals, so if you WaitForSeconds anything less than a single frame, you'll still wait a full frame.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    Adding to what Baste posted, this is how it works timing-wise:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  9. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Thanks everyone... appreciate all this. A huge learning curve, but enjoying it!