unity3d
Сопрограммы
Поиск…
Синтаксис
- общедоступный 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, которые можно сделать сопрограммами.
- Начните()
- OnBecameVisible ()
- 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 для получения последней информации, если это необходимо.)