Поиск…


Синтаксис

  • общедоступный Coroutine StartCoroutine (процедура IEnumerator);
  • public Coroutine StartCoroutine (string methodName, значение объекта = null);
  • public void StopCoroutine (string methodName);
  • public void StopCoroutine (процедура IEnumerator);
  • public void StopAllCoroutines ();

замечания

Требования к производительности

Лучше всего использовать сопрограммы в умеренных количествах, так как гибкость приносит стоимость производительности.

  • Coroutines в большом количестве требует от CPU большего, чем стандартные методы обновления.
  • В некоторых версиях Unity существует проблема, при которой сопрограммы производят мусор каждый цикл обновления из-за того, что Unity боксирует возвращаемое значение MoveNext . Последнее наблюдалось в 5.4.0b13. ( Отчет об ошибке )

Сокращение мусора путем кэширования YieldInstructions

Общим трюком для сокращения мусора, сгенерированного в сопрограммах, является кэширование YieldInstruction .

IEnumerator TickEverySecond()
{
    var wait = new WaitForSeconds(1f); // Cache
    while(true)
    {
        yield return wait; // Reuse
    }
}

Утолщение null дает лишнего мусора.

Сопрограммы

Во-первых, важно понять, что игровые движки (такие как Unity) работают над парадигмой, основанной на «фреймах».

Код выполняется во время каждого кадра.

Это включает собственный код Unity и ваш код.

Когда мы думаем о кадрах, важно понимать, что нет абсолютно никаких гарантий того, когда будут происходить кадры. Они не происходят в обычном ритме. Промежутки между кадрами могут быть, например, 0,02632, затем 0,021167, затем 0,029738 и т. Д. В этом примере все они «около» 1/50 секунды, но все они разные. И в любое время вы можете получить фрейм, который занимает гораздо больше времени или короче; и ваш код может быть выполнен в любой момент в пределах фрейма.

Помня об этом, вы можете спросить: как вы получаете доступ к этим фреймам в своем коде, в Unity?

Достаточно просто использовать либо вызов Update (), либо использовать сопрограмму. (В самом деле - они точно такие же: они позволяют запускать код в каждом кадре).

Цель сопрограммы состоит в том, что:

вы можете запустить некоторый код, а затем «остановить и подождать» до некоторого будущего фрейма .

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

Например, вы можете подождать «около одной секунды», то есть он будет ждать около одной секунды, а затем помещать ваш код в некоторый кадр примерно через одну секунду. (И действительно, в этом фрейме код мог быть запущен в любое время, как бы то ни было.) Повторить: это будет не ровно одна секунда. Точный момент времени не имеет смысла в игровом движке.

Внутри сопрограммы:

Чтобы подождать один кадр:

// do something
yield return null;  // wait until next frame
// do something

Подождать три кадра:

// do something
yield return null;  // wait until three frames from now
yield return null;
yield return null;
// do something

Подождать примерно полсекунды:

// do something
yield return new WaitForSeconds (0.5f); // wait for a frame in about .5 seconds
// do something

Делайте что-то каждый отдельный кадр:

while (true)
{
    // do something
    yield return null;  // wait until the next frame
}

Этот пример буквально идентичен простому помещению чего-то внутри вызова «Обновить» Unity: код «делать что-то» запускается каждый кадр.

пример

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

using UnityEngine;
using System.Collections;

public class Ticker:MonoBehaviour {

    void OnEnable()
    {
        StartCoroutine(TickEverySecond());
    }

    void OnDisable()
    {
        StopAllCoroutines();
    }

    IEnumerator TickEverySecond()
    {
        var wait = new WaitForSeconds(1f); // REMEMBER: IT IS ONLY APPROXIMATE
        while(true)
        {
            Debug.Log("Tick");
            yield return wait;  // wait for a frame, about 1 second from now
        }
    }
}

Завершение сопрограммы

Часто вы разрабатываете сопрограммы для естественного завершения, когда выполняются определенные цели.

IEnumerator TickFiveSeconds()
{
    var wait = new WaitForSeconds(1f);
    int counter = 1;
    while(counter < 5)
    {
        Debug.Log("Tick");
        counter++;
        yield return wait;
    }
    Debug.Log("I am done ticking");
}

Чтобы остановить сопрограмму «внутри» сопрограммы, вы не можете просто «вернуться», как вы хотели бы уйти от обычной функции. Вместо этого вы используете yield break .

IEnumerator ShowExplosions()
{
    ... show basic explosions
    if(player.xp < 100) yield break;
    ... show fancy explosions
}

Вы также можете заставить все сопрограммы, запущенные сценарием, остановиться до завершения.

void OnDisable()
{
    // Stops all running coroutines
    StopAllCoroutines();
}

Метод остановки конкретной сопрограммы от вызывающего зависит от того, как вы ее начали.

Если вы запустили coroutine по имени строки:

StartCoroutine("YourAnimation");

то вы можете остановить его, вызвав StopCoroutine с тем же именем строки:

StopCoroutine("YourAnimation");

Кроме того , вы можете сохранить ссылку на либо в IEnumerator , возвращаемый методом сопрограммного, или Coroutine объекта , возвращенный StartCoroutine и вызвать StopCoroutine на любом из этих:

public class SomeComponent : MonoBehaviour 
{
    Coroutine routine;

    void Start () {
        routine = StartCoroutine(YourAnimation());
    }

    void Update () {
        // later, in response to some input...
        StopCoroutine(routine);
    }

    IEnumerator YourAnimation () { /* ... */ }
}

Методы MonoBehaviour, которые могут быть Coroutines

Есть три метода MonoBehaviour, которые можно сделать сопрограммами.

  1. Начните()
  2. OnBecameVisible ()
  3. OnLevelWasLoaded ()

Это можно использовать для создания, например, скриптов, которые выполняются только тогда, когда объект видим для камеры.

using UnityEngine;
using System.Collections;

public class RotateObject : MonoBehaviour
{
    IEnumerator OnBecameVisible()
    {
        var tr = GetComponent<Transform>();
        while (true)
        {
            tr.Rotate(new Vector3(0, 180f * Time.deltaTime));
            yield return null;
        }
    }
    
    void OnBecameInvisible()
    {
        StopAllCoroutines();
    }
}

Цеповые сопрограммы

Корутинцы могут выжить внутри себя и ждать других сопрограмм .

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

Это очень просто и является основной, основной, технологией в Unity.

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

IEnumerator BeginRace()
{
  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());
}

Итак, когда вы звоните в BeginRace ...

 StartCoroutine(BeginRace());

Он будет запускать рутину «подготовить гонку». (Возможно, мигание некоторых огней и запуск шума толпы, сброс баллов и т. Д.). Когда это будет завершено, оно запустит вашу последовательность обратного отсчета, где вы могли бы оживить, возможно, обратный отсчет времени в пользовательском интерфейсе. Когда это будет завершено, он запустит ваш код запуска, в котором вы, возможно, запустите звуковые эффекты, запустите некоторые AI-драйверы, поместите камеру определенным образом и так далее.

Для ясности поймите, что три вызова

  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());

сами должны быть в сопрограмме. То есть они должны быть в функции типа IEnumerator . Итак, в нашем примере это IEnumerator BeginRace . Итак, из «нормального» кода вы запускаете эту сопрограмму с вызовом StartCoroutine .

 StartCoroutine(BeginRace());

Чтобы еще больше понять цепочку, вот функция, которая направляет сопрограммы. Вы передаете массив сопрограмм. Функция выполняет столько сопрограмм, сколько вы проходите, по порядку, один за другим.

// run various routines, one after the other
IEnumerator OneAfterTheOther( params IEnumerator[] routines ) 
{
    foreach ( var item in routines ) 
    {
        while ( item.MoveNext() ) yield return item.Current;
    }

    yield break;
}

Вот как вы бы назвали это ... допустим, у вас есть три функции. Напомним, что все они должны быть IEnumerator :

IEnumerator PrepareRace() 
{
    // codesay, crowd cheering and camera pan around the stadium
    yield break;
}

IEnumerator Countdown() 
{
    // codesay, animate your countdown on UI
    yield break;
}

IEnumerator StartRace() 
{
    // codesay, camera moves and light changes and launch the AIs
    yield break;
}

Вы бы назвали это так

StartCoroutine( MultipleRoutines( PrepareRace(), Countdown(), StartRace() ) );

или, возможно, так

IEnumerator[] routines = new IEnumerator[] {
     PrepareRace(),
     Countdown(),
     StartRace() };
StartCoroutine( MultipleRoutines( routines ) );

Повторяю, одним из самых основных требований в играх является то, что определенные вещи происходят один за другим «в последовательности» с течением времени. Вы достигаете этого в Единстве очень просто, с

  yield return StartCoroutine(PrepareRace());
  yield return StartCoroutine(Countdown());
  yield return StartCoroutine(StartRace());

Способы выхода

Вы можете подождать до следующего кадра.

yield return null; // wait until sometime in the next frame

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

//wait for a few frames
yield return null;
yield return null;

Подождите примерно n секунд. Крайне важно понимать, что это всего лишь приблизительное время .

yield return new WaitForSeconds(n);

Совершенно невозможно использовать вызов «WaitForSeconds» для любой формы точного времени.

Часто вы хотите связать действия. Итак, сделайте что-нибудь, и когда это будет сделано, сделайте что-нибудь еще, и когда это будет сделано, сделайте что-нибудь еще. Для этого подожди еще одну сопрограмму:

yield return StartCoroutine(coroutine);

Поймите, что вы можете вызывать это только из сопрограммы. Так:

StartCoroutine(Test());

Вот как вы начинаете сопрограмму из «нормального» кода.

Затем внутри исполняемой сопрограммы:

Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");

Это напечатает A, запустит длительный процесс и сразу же напечатает B. Он не будет ждать завершения долгого процесса. С другой стороны:

Debug.Log("A");
yield return StartCoroutine(LongProcess());
Debug.Log("B");

Это напечатает A, запустит длительный процесс, дождитесь окончания и затем напечатает B.

Всегда стоит помнить, что сопрограммы абсолютно не связаны ни с чем, ни с нитками. С помощью этого кода:

Debug.Log("A");
StartCoroutine(LongProcess());
Debug.Log("B");

легко думать об этом как о «как», начиная с LongProcess в другом потоке в фоновом режиме. Но это абсолютно неверно. Это всего лишь сопрограмма. Игровые движки основаны на фреймах, а «сопрограммы» в Unity просто позволяют вам получить доступ к фреймам.

Очень просто дождаться завершения веб-запроса.

void Start() {
    string url = "http://google.com";
    WWW www = new WWW(url);
    StartCoroutine(WaitForRequest(www));
}

IEnumerator WaitForRequest(WWW www) {
    yield return www;
    
    if (www.error == null) {
        //use www.data);
    }
    else {
        //use www.error);
    }
}

Для полноты: в очень редких случаях вы используете фиксированное обновление в Unity; существует WaitForFixedUpdate() который обычно никогда не будет использоваться. Существует определенный вызов ( WaitForEndOfFrame() в текущей версии Unity), который используется в определенных ситуациях в связи с созданием захвата экрана во время разработки. (Точный механизм немного меняется по мере того, как Unity развивается, поэтому google для получения последней информации, если это необходимо.)



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow