Coroutines Корутины

Когда вы вызываете функцию, она должна полностью выполниться, прежде чем вернуть какое-то значение. Фактически, это означает, что любые действия, происходящие в функции, должны выполниться в течение одного кадра; вызовы функций не пригодны для, скажем, процедурной анимации или любой другой временной последовательности. В качестве примера мы рассмотрим задачу по уменьшению прозрачности объекта до его полного исчезновения.

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

Как можно заметить, функция Fade не имеет визуального эффекта, который мы хотели получить. Для скрытия объекта нам нужно было постепенно уменьшить прозрачность, а значит иметь некоторую задержку для того, чтобы были отображены промежуточные значения параметра. Однако функция выполняется в полном объеме, за одно обновление кадра. Промежуточные значения, увы, не будут видны и объект исчезнет мгновенно.

С подобными ситуациями можно справиться, добавив в функцию Update код, изменяющий прозрачность кадр за кадром. Однако для подобных задач зачастую удобнее использовать корутины.

Корутин похож на функцию, которая может приостановить выполнение и вернуть управление Unity, а затем продолжить работу с того места, на котором остановилась, в следующем кадре. В C# сопрограмма объявляется так:

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;
    }
}

По сути, это функция, объявленная с возвращаемым типом IEnumerator и с оператором yield return, включенным где-то в теле. Нулевая строка yield return — это точка, в которой выполнение будет приостановлено и возобновлено в следующем кадре. Чтобы запустить сопрограмму, вам нужно использовать функцию StartCoroutine:

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

Можно заметить, что счетчик цикла в функции Fade сохраняет правильное значение во время работы корутины. Фактически, любая переменная или параметр будут корректно сохранены между вызовами оператора yield.

По умолчанию сопрограмма возобновляется в кадре после того, как она уступает, но также можно ввести временную задержку, используя 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);
    }
}

Это можно использовать как способ распространения эффекта на определенный период времени, но это также полезная оптимизация. Многие задания в игре нужно выполнять периодически, и самый очевидный способ сделать это — включить их в функцию «Обновление». Однако эта функция обычно вызывается много раз в секунду. Когда задачу не нужно повторять так часто, вы можете поместить ее в сопрограмму, чтобы получать обновления регулярно, но не каждый кадр. Примером этого может быть сигнал тревоги, который предупреждает игрока о приближении врага. Код может выглядеть примерно так:

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

Если врагов много, то вызов этой функции в каждом кадре может привести к значительным накладным расходам. Однако вы можете использовать сопрограмму, чтобы вызывать ее каждую десятую долю секунды:

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

Это значительно уменьшит число проверок, но не окажет заметного влияния на игровой процесс.

Примечание. Вы можете остановить сопрограмму с помощью StopCoroutine и StopAllCoroutines. Корутины также останавливаются, когда GameObject, к которому они присоединены, отключается с помощью SetActive(false). Вызов Destroy(example) (где example является экземпляром MonoBehaviour) немедленно запускает OnDisable, и сопрограмма обрабатывается, фактически останавливая ее. Наконец, OnDestroy вызывается в конце кадра.

Корутины не останавливаются при отключении MonoBehaviour путем установки значения false для экземпляра MonoBehaviour.

Coroutine — Это такие же функции, которые вы уже наверняка используете, но они выполняются параллельно основному потоку.

Тут стоит уточнить, параллельно не значит асинхронно. То есть, если вы запихнете свой «тяжелый» код в кучу корутин которые будут работать параллельно, программа работать быстрее не будет.

как работают корутины
Наглядный пример

Зачем же тогда нужны корутины?

Они помогут нам упростить свой код. К примеру, необходимо плавно менять цвет спрайта.

Можно, конечно, делать это в Update, но корутину можно написать так что-бы она, что-то делала раз в пол секунды или в секунду. И для этого не нужно будет проверять каждый раз сколько времени прошло с последнего вызова Update.

Вот пример, который выведет сообщение спустя пол секунды после его запуска.

IEnumerator YourCoroutine() {
     yield return new WaitForSeconds(0.5f);
     Debug.Log("Прошло пол секунды");
}

А если обернуть в цикл? Он будет выполняться каждые пол секунды.

IEnumerator YourCoroutine() {
    while(true){
         yield return new WaitForSeconds(0.5f);
         Debug.Log("Прошло пол секунды");
    }
}

Замените 0.5f на нужное значение для получения нужной задержки.

Кстати для запуска такой функции-корутины в нужном месте необходимо написать:

StartCoroutine("YourCoroutine");

или

StartCoroutine(YourCoroutine());

Yield, что это?

Эта команда, отдает процессорное время основному потоку и продолжает выполнение корутины с этого места когда произойдет необходимое событие, а событий есть достаточное количество.

1.Продолжить после следующего FixedUpdate

yield return new WaitForFixedUpdate();

2. Продолжить через некоторое время

yield return new WaitForSeconds(0.1f);

3. Продолжить после следующего LateUpdate и рендеринга сцены

yield return new WaitForEndOfFrame();

4. Продолжить после другой корутины:

yield return StartCoroutine(AnotherCoroutine());

5. После текущего Update

yield return null;

6. По завершении скачивания

yield return new WWW(someLink);

7. Завершить корутину

yield return break;

Как завершить корутину?

Иногда корутину нужно завершить преждевременно. Для это есть несколько путей.

  • StopAllCoroutines()
  • StopCoroutine(«YourCoroutine»)
  • Уничтожить родительский GameObject

Делаем выводы

Корутины удобны в некоторых случаях и иногда без них совсем туго, поэтому советую вникнуть в их суть. Часто они помогают облегчить код и сэкономить время. И помните, корутины это не потоки!)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *