03/10/26 · 6 min de lectura

Coroutines

Cómo crear corrutinas y utilizarlas para lograr comportamientos complejos.

Imagen por Aaron Douglas .

Una corrutina puede considerarse una función que se ejecuta en intervalos.

Estas funciones trabajan con sentencias especiales tipo yield que regresan el código que ejecuta la función. Luego, cuando la función continúa, la ejecución empieza nuevamente, justo en donde lo dejó. Vamos a echarle un vistazo a un ejemplo de una corrutina.

Para comenzar, vamos a echarle un vistazo a la corrutina, que tenemos que se llamada MyCouroutine().

IEnumerator MyCoroutine (Transform target)
{
    while(Vector3.Distance(transform.position, target.position) > 0.05f)
    {
        transform.position = Vector3.Lerp(
            transform.position,
            target.position,
            smoothing * Time.deltaTime
        );

        yield return null;
    }

    print("Reached the target.");

    yield return new WaitForSeconds(3f);

    print("MyCoroutine is now finished.");
}

Lo primero que debemos tomar en cuenta es su tipo de retorno, IEnumerator. Esto significa que la función puede regresar cualquier cosa que implemente la interfaz IEnumerator. Sin embargo, existe más de lo que se espera usualmente del regreso de una corrutina, como verás en un momento. Luego vemos que toma un Transform como parámetro.

IEnumerator MyCoroutine (Transform target)
{
    /*...*/
}

Estamos empezando un bucle While, el mismo que continúa hasta que la distancia entre el objeto y el objetivo sea menos de 0.05.

while(Vector3.Distance(transform.position, target.position) > 0.05f)
{
    transform.position = Vector3.Lerp(
        transform.position,
        target.position,
        smoothing * Time.deltaTime
    );

    yield return null;         
}

Dentro del loop estamos interpolando la posición del objeto entre sí mismo y la posición objetivo

transform.position = Vector3.Lerp(
    transform.position,
    target.position,
    smoothing * Time.deltaTime
);

Luego, tenemos la línea: yield return null;. Esto es lo que le permite a la corrutina funcionar de la manera que lo hace.

yield return null;         

Lo que significa es que cuando esta línea de código se ejecuta, va a pausar la ejecución de la función y regresar un IEnumerator de tipo null o nulo. La ejecución se continuará en este punto, en el momento determinado por el valor de retorno. Dado que el valor de retorno es de tipo null, eso significa que la corrutina continuará después del próximo Update.

Ya que la corrutina continuará al final del bucle, la condición del bucle será reevaluada. Si el Transform aún no ha llegado al objetivo, se interpolará más cerca del objetivo y otra vez se pausará hasta el siguiente Update.

Una vez que la condición del bucle no se evalúe como true o verdadera, el bucle saldrá y la corrutina va a mostrar, Reached the target (Llegó al objetivo), en la consola.

print("Reached the target.");     

A esas alturas, la corrutina se va a pausar nuevamente, pero esta vez, va a regresar una instancia de la clase WaitForSeconds. El constructor se invoca usando la palabra reservada new y en este caso, estamos pasando 3 como un parámetro.

yield return new WaitForSeconds(3f);

Como es de esperarse, el resultado de retornar una instancia de la clase WaitForSeconds es que el código continuará ejecutándose después del número determinado de segundos.

Después de tres segundos, las palabras MyCoroutine is now finished (Mi corrutina ha acabado) se mostrarán en la consola.

print("MyCoroutine is now finished."); 

En este momento hemos llegado al final de la corrutina y la ejecución terminará.

Ahora vamos a echarle un vistazo a cómo invocar la corrutina. Para hacerlo simplemente usamos la función StartCouroutine().

StartCoroutine(MyCoroutine(target));

Toma un parámetro de una invocación a una corrutina o una string o cadena del nombre de la corrutina. La primera invocación es la que hemos mostrado aquí y es la más aconsejable usar. Sin embargo, si usas una invocación con un nombre string o de cadena, luego, también puedes invocar StopCouroutine() para que pare prematuramente.

Puedes anexar el script de la corrutina a un personaje, crea un elemento target (objetivo). Cuando inicies la escena, el personaje se moveá hacia su target y cuando el personaje llega a su target, la consola mostrará el mensaje. Luego, después de tres segundo la corrutina termina con el segundo mensaje. Ten en cuenta que hemos creado movimiento sin usar Update() y sin crear algún timer (cronómetro).

Esto es realmente excelente y hace que nuestro código sea más eficiente. Sin embargo, una de los aspectos más fuertes de las corrutinas es cuando se combinan con propiedades. Vamos a echarle un vistazo a un ejemplo de eso.

En esta escena, tenemos un robot. El robot tiene un script PropertiesAndCoroutines. También tenemos un script llamado Click Set Position anexado al entorno

En una escena, tenemos un personaje robot. El robot tiene un script PropertiesAndCoroutines. También tenemos un script llamado ClickSetPosition anexado al entorno.

gif

El script PropertiesAndCoroutines tiene una propiedad pública Vector3 llamada target.

public Vector3 Target
{
    get { return target; }
    set
    {
        target = value;

        StopCoroutine("Movement");
        StartCoroutine("Movement", target);
    }
}

que encapsula el campo target.

private Vector3 target

Recuerda que cuando asignamos la propiedad target, invocamos la función que está establecida.

El script ClickSetPosition establece el objetivo de un objeto con un script PropertiesAndCoroutines en lo que sea a lo que hagamos clic en el entorno.

public class ClickSetPosition : MonoBehaviour 
{
    public PropertiesAndCoroutines coroutineScript;

    void OnMouseDown()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        Physics.Raycast(ray, out hit);

        if(hit.collider.gameObject == gameObject)
        {
            Vector3 newTarget = hit.point + new Vector3(0, 0.5f, 0);
            coroutineScript.Target = newTarget;
        }
    }
}

De regreso en el script PropertiesAndCoroutines , vamos a ver qué ocurre cuando se establece el target.

Primero, vamos a poner el valor correcto en el campo target, luego paramos cualquier corrutina de movimiento. Es importante tener en cuenta que StopCoroutine() solo funciona con corrutinas que se han comenzado con la invocación a través de una string o cadena, como lo hacemos en la siguiente línea.

set
{
    target = value;

    StopCoroutine("Movement");
    StartCoroutine("Movement", target);
}   

Esta es la forma alternativa de invocar StartCoroutine(), pasamos el nombre de la corrutina como una string o cadena y luego los parámetros.

StartCoroutine("Movement", target);

Nuestra corrutina de movimiente es similar a MyCoroutine() del ejemplo anterior, simplemente interpola la posición del cubo hacia un objetivo determinado hasta que esté cerca.

IEnumerator Movement (Vector3 target)
{
    while(Vector3.Distance(transform.position, target) > 0.05f)
    {
        transform.position = Vector3.Lerp(
            transform.position,
            target,
            smoothing * Time.deltaTime
        );

        yield return null;
    }
}   

El efecto de tener nuestro script configurado de esta manera es que ahora tenemos un GameObject móvil sin la necesidad de una función Update() que sondee valores para cada frame. Sondear valores es algo que reduce la eficiencia del código que escribimos. Y mientras que en ciertas ocasiones es inevitable, es algo que debemos tratar de evitar.

Gist del proyecto

Apoya el contenido de mi blog a través de:

Fuente:

Copiado!