Search code examples
c#unity-game-enginedelay

Trouble understanding Delays and Coroutines


void start()
StartCoroutine(Text());

IEnumerator Text()
{
    Debug.Log("Hello")
    yield return new WaitForSeconds(3)
    Debug.Log("ByeBye")
}

I understand the basic concept that this does but I don't get what anything means such as yield return new WaitforSeconds(3) and what StartCoroutine is and What an IEnumerator is. Can anyone explain to me what they mean?


Solution

  • When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen within a single frame update; a function call can’t be used to contain a procedural animation or a sequence of events over time. As an example, consider the task of gradually reducing an object’s alpha (opacity) value until it becomes completely invisible.

    void Fade() 
    {
        for (float ft = 1f; ft >= 0; ft -= 0.1f) 
        {
            Color c = renderer.material.color;
            c.a = ft;
            renderer.material.color = c;
        }
    }
    

    As it stands, the Fade function will not have the effect you might expect. In order for the fading to be visible, the alpha must be reduced over a sequence of frames to show the intermediate values being rendered. However, the function will execute in its entirety within a single frame update. The intermediate values will never be seen and the object will disappear instantly. It is possible to handle situations like this by adding code to the Update function that executes the fade on a frame-by-frame basis. However, it is often more convenient to use a coroutine for this kind of task. A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. In C#, a coroutine is declared like this:

    IEnumerator Fade() 
    {
        for (float ft = 1f; ft >= 0; ft -= 0.1f) 
        {
            Color c = renderer.material.color;
            c.a = ft;
            renderer.material.color = c;
            yield return null;
        }
    }
    

    It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return null line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function:

    void Update()
    {
        if (Input.GetKeyDown("f")) 
        {
            StartCoroutine("Fade");
        }
    }
    

    You will notice that the loop counter in the Fade function maintains its correct value over the lifetime of the coroutine. In fact any variable or parameter will be correctly preserved between yields. By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using WaitForSeconds:

    IEnumerator Fade() 
    {
        for (float ft = 1f; ft >= 0; ft -= 0.1f) 
        {
            Color c = renderer.material.color;
            c.a = ft;
            renderer.material.color = c;
            yield return new WaitForSeconds(.1f);
        }
    }
    

    This can be used as a way to spread an effect over a period of time, but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:

    bool ProximityCheck() 
    {
        for (int i = 0; i < enemies.Length; i++)
        {
            if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
                    return true;
            }
        }
        
        return false;
    }
    

    If there are a lot of enemies then calling this function every frame might introduce a significant overhead. However, you could use a coroutine to call it every tenth of a second:

    IEnumerator DoCheck() 
    {
        for(;;) 
        {
            ProximityCheck();
            yield return new WaitForSeconds(.1f);
        }
    }
    

    This would greatly reduce the number of checks carried out without any noticeable effect on gameplay. Note: You can stop a Coroutine with StopCoroutine and StopAllCoroutines. A coroutines also stops when the GameObject it is attached to is disabled with SetActive(false). Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and the coroutine is processed, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame. Coroutines are not stopped when disabling a MonoBehaviour by setting enabled to false on a MonoBehaviour instance.

    Reference: https://docs.unity3d.com/Manual/Coroutines.html