Buscar..


Sintaxis

  • Coroutine StartCoroutine pública (rutina IEnumerator);
  • public Coroutine StartCoroutine (string methodName, valor del objeto = nulo);
  • public void StopCoroutine (string methodName);
  • public void StopCoroutine (rutina IEnumerator);
  • public void StopAllCoroutines ();

Observaciones

Consideraciones de rendimiento

Es mejor usar coroutines con moderación ya que la flexibilidad viene con un costo de rendimiento.

  • Coroutines en gran número exige más de la CPU que los métodos de actualización estándar.
  • Hay un problema en algunas versiones de Unity donde las rutinas producen basura en cada ciclo de actualización debido a que Unity MoveNext valor de retorno de MoveNext . Esto fue observado por última vez en 5.4.0b13. ( Informe de error )

Reducir la basura mediante el almacenamiento en caché de instrucciones de rendimiento

Un truco común para reducir la basura generada en las corrutinas es almacenar en caché la YieldInstruction .

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

El rendimiento null no produce basura adicional.

Coroutines

Primero, es esencial comprender que los motores de juego (como Unity) funcionan en un paradigma "basado en marcos".

El código se ejecuta durante cada fotograma.

Eso incluye el propio código de Unity, y tu código.

Al pensar en los marcos, es importante comprender que no hay absolutamente ninguna garantía de cuándo suceden los marcos. No suceden en un ritmo regular. Los espacios entre cuadros podrían ser, por ejemplo, 0.02632, luego 0.021167, luego 0.029778 y así sucesivamente. En el ejemplo, todos están "aproximadamente" a 1/50 de segundo, pero todos son diferentes. Y en cualquier momento, puede obtener un marco que tarda mucho más tiempo o más corto; y su código puede ejecutarse en cualquier momento dentro del marco.

Teniendo esto en cuenta, puede preguntar: ¿cómo accede a estos marcos en su código, en Unity?

Sencillamente, usa la llamada Update () o una coroutina. (De hecho, son exactamente lo mismo: permiten que el código se ejecute en cada fotograma).

El propósito de una coroutina es que:

puede ejecutar algún código, y luego, "detenerse y esperar" hasta algún marco futuro .

Puede esperar hasta el siguiente fotograma , puede esperar un número de fotogramas , o puede esperar un tiempo aproximado en segundos en el futuro.

Por ejemplo, puede esperar "aproximadamente un segundo", lo que significa que esperará aproximadamente un segundo, y luego colocar su código en algún marco aproximadamente un segundo a partir de ahora. (Y de hecho, dentro de ese marco, el código podría ejecutarse en cualquier momento, en cualquier caso). Para repetir: no será exactamente un segundo. La sincronización precisa no tiene sentido en un motor de juego.

Dentro de una coroutina:

Para esperar un fotograma:

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

Para esperar tres cuadros:

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

Para esperar aproximadamente medio segundo:

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

Haz algo en cada cuadro individual:

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

Ese ejemplo es literalmente idéntico a simplemente poner algo dentro de la llamada "Actualización" de Unity: el código en "hacer algo" se ejecuta en cada fotograma.

Ejemplo

Adjuntar Ticker a un GameObject . Mientras ese objeto del juego esté activo, el tick se ejecutará. Tenga en cuenta que la secuencia de comandos detiene cuidadosamente la rutina, cuando el objeto del juego se vuelve inactivo; Este suele ser un aspecto importante de la correcta ingeniería de uso de coroutine.

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

Acabando con una coroutine

A menudo se diseña coroutines para terminar naturalmente cuando se cumplen ciertos objetivos.

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

Para detener una coroutine desde "adentro" de la coroutine, no puede simplemente "regresar" como lo haría antes de salir de una función ordinaria. En su lugar, utiliza la yield break .

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

También puede forzar la detención de todas las coroutines lanzadas por el script antes de terminar.

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

El método para detener una llamada coroutine específica de la persona que llama varía dependiendo de cómo lo inició.

Si empezaste una coroutine por nombre de cadena:

StartCoroutine("YourAnimation");

luego puede detenerlo llamando a StopCoroutine con el mismo nombre de cadena:

StopCoroutine("YourAnimation");

Alternativamente, se puede mantener una referencia a la IEnumerator devuelto por el método de co-rutina, o la Coroutine objeto devuelto por StartCoroutine , y llamar a StopCoroutine en cualquiera de los:

public class SomeComponent : MonoBehaviour 
{
    Coroutine routine;

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

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

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

MonoBehaviour métodos que pueden ser los coroutines.

Existen tres métodos de MonoBehaviour que se pueden realizar en las coroutinas.

  1. Comienzo()
  2. OnBecameVisible ()
  3. OnLevelWasLoaded ()

Esto se puede usar para crear, por ejemplo, secuencias de comandos que se ejecutan solo cuando el objeto es visible para una cámara.

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

Encadenamiento de coroutines

Los coroutines pueden ceder dentro de sí mismos y esperar a otros coroutines .

Por lo tanto, puede encadenar secuencias - "una tras otra".

Esto es muy fácil, y es una técnica básica, básica, en Unity.

Es absolutamente natural en los juegos que ciertas cosas tengan que suceder "en orden". Casi cada "ronda" de un juego comienza con una cierta serie de eventos que suceden, en un espacio de tiempo, en algún orden. Así es como puedes comenzar un juego de carreras de autos:

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

Entonces, cuando llamas BeginRace ...

 StartCoroutine(BeginRace());

Se ejecutará su rutina de "preparar carrera". (Tal vez, encendiendo algunas luces y ejecutando algo de ruido de la multitud, reajustando las puntuaciones y así sucesivamente.) Cuando termine, ejecutará su secuencia de "cuenta atrás", donde quizás anime una cuenta atrás en la interfaz de usuario. Cuando haya terminado, ejecutará su código de inicio de carrera, donde quizás ejecute efectos de sonido, inicie algunos controladores AI, mueva la cámara de una determinada manera, y así sucesivamente.

Para mayor claridad, entendamos que las tres llamadas.

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

Deben estar ellos mismos en una coroutine. Es decir, deben estar en una función del tipo IEnumerator . Así que en nuestro ejemplo es IEnumerator BeginRace . Por lo tanto, desde el código "normal", inicie esa rutina con la llamada StartCoroutine .

 StartCoroutine(BeginRace());

Para entender mejor el encadenamiento, aquí hay una función que encadena a las coroutinas. Pasas en una serie de coroutines. La función ejecuta tantas corrutinas como pases, en orden, una tras otra.

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

Así es como llamaría a eso ... digamos que tiene tres funciones. Recordemos que todos deben ser 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;
}

Lo llamarías así

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

o tal vez así

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

Para repetir, uno de los requisitos más básicos en los juegos es que ciertas cosas suceden una tras otra "en una secuencia" a lo largo del tiempo. Lo logras en Unity de manera muy simple, con

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

Maneras de ceder

Puedes esperar hasta el siguiente fotograma.

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

Puede tener múltiples de estas llamadas en una fila, para simplemente esperar tantos cuadros como desee.

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

Espere aproximadamente n segundos. Es extremadamente importante entender que esto es solo un tiempo muy aproximado .

yield return new WaitForSeconds(n);

Es absolutamente imposible utilizar la llamada "WaitForSeconds" para cualquier forma de sincronización precisa.

A menudo quieres encadenar acciones. Entonces, haz algo, y cuando eso haya terminado, haz algo más, y cuando hayas terminado haz algo más. Para lograrlo, espera otra coroutina:

yield return StartCoroutine(coroutine);

Comprende que solo puedes llamar a eso desde una coroutine. Asi que:

StartCoroutine(Test());

Así es como se inicia una corutina a partir de un código "normal".

Luego, dentro de esa corrida corutina:

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

Eso imprimirá A, comenzará el proceso largo e inmediatamente imprimirá B. No va a esperar a que el largo proceso para terminar. Por otra parte:

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

Eso imprimirá A, iniciará el proceso largo, esperará hasta que termine y luego imprimirá B.

Siempre vale la pena recordar que los coroutines no tienen absolutamente ninguna conexión, de ninguna manera, con los hilos. Con este código:

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

es fácil pensar que es "como" iniciar LongProcess en otro subproceso en segundo plano. Pero eso es absolutamente incorrecto. Es sólo una corutina. Los motores de juego están basados ​​en marcos, y las "rutinas" en Unity simplemente te permiten acceder a los marcos.

Es muy fácil esperar a que se complete una solicitud web.

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

Para completar: En casos muy raros, utiliza la actualización fija en Unity; hay una llamada WaitForFixedUpdate() que normalmente nunca se usaría. Hay una llamada específica ( WaitForEndOfFrame() en la versión actual de Unity) que se usa en ciertas situaciones en relación con la generación de capturas de pantalla durante el desarrollo. (El mecanismo exacto cambia ligeramente a medida que Unity evoluciona, así que busque la información más reciente si es relevante).



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow