Aprende C# con Unity - Corrutinas

Una corrutina es una forma especial de hacer que la lógica suceda con el tiempo. Debo admitir que nunca usé corrutinas hasta Unity, había estado usando programación basada en eventos en cualquier otro escenario comparable. Sin embargo, corrutinas son una alternativa rápida y fácil que definitivamente vale la pena ver. En esta lección, mostraré cómo funciona Unity con corrutinas, incluidas varias formas de ceder el control e incluso vincular corrutinas para tener un control total sobre la lógica basada en el tiempo. Al final, también mostraré cómo trabajar con corrutinas de forma nativa para cualquiera que tenga curiosidad sobre cómo funcionan.

GitHub Gitlab

Corrutinas de Unity

Una corrutina es realmente solo un método con un tipo de devolución de “IEnumerator”. Una diferencia clave es que no simplemente “devuelve” el tipo de datos como lo haría en un método normal. En cambio, “cede” un valor que hace que la ejecución de la lógica se detenga en su lugar; puede reanudarse más tarde. Echemos un vistazo a una muestra rápida:


#region Librerias
using System.Collections;
using UnityEngine;
#endregion

namespace MoonAntonio
{
	public class UnityCorrutinas : MonoBehaviour 
	{
		void OnEnable()
		{
			StartCoroutine("DoStuff");
		}

		void OnDisable()
		{
			StopCoroutine("DoStuff");
		}

		IEnumerator DoStuff()
		{
			int value = 0;
			while (true)
			{
				yield return new WaitForSeconds(1);
				value++;
				Debug.Log("Valor:" + value);
			}
		}
	}
}

Tenga en cuenta que debemos asegurarnos de que se utiliza el espacio de nombres “System.Collections”, o se obtendrá un error: “No se pudo encontrar el tipo o el nombre del espacio de nombres ‘IEnumerator’. ¿Echas de menos una directiva de uso o una referencia de ensamblado?

Para usar una corrutina con Unity, usa un método llamado “StartCoroutine”. Hacemos esto dentro del método OnEnable (línea 8). StartCoroutine está sobrecargado para permitirle pasar un “IEnumerator” o una cadena que representa el nombre de la corrutina para comenzar. En el ejemplo utilicé la versión posterior, porque solo esa versión se puede “detener” manualmente usando StopCoroutine. Muestro StopCoroutine en el método OnDisable (línea 13) aunque su uso en este ejemplo es innecesario porque todas las corrutinas administradas por unity se detendrían cuando el script se desactivara independientemente de cómo se inicien. También debe tener en cuenta que puede “Comenzar la rutina” varias veces, incluso para el mismo método, que a menudo puede ser un comportamiento involuntario. Es posible que desee establecer un indicador que le indique cuándo está activa una corrutina, de modo que no la inicie varias veces,

Aquí hay algunas variaciones de llamar a StartCoroutine:


// Versión 1, iniciada por una cadena (nombre de coroutine): esta versión es compatible con StopCoroutine
StartCoroutine("DoStuff");
// Versión 2, Igual que la Versión 1 pero con un parámetro - nota, el método de destino debe ser modificado para aceptar un parámetro
StartCoroutine("DoStuff", 5);
// Versión 3, esta versión se inicia al pasar el IEnumerator, usted NO PUEDE utilizar StopCoroutine
StartCoroutine(DoStuff());
// Versión 4, Igual que la Versión 3 pero con parámetro - nota, el método de destino debe ser modificado para aceptar un parámetro
StartCoroutine(DoStuff(5));

El método “DoStuff” es nuestra “corrutina”. La primera instrucción crea una variable local llamada “valor” y la inicializa a cero. Entonces comenzamos un “bucle infinito” (un bucle “while” que itera mientras sea “verdadero”, que es siempre). Dentro del ciclo vemos nuestra primera declaración de “WaitForSeconds” (línea 21) que indica a Unity que deseamos esperar un segundo. En este punto, la ejecución de este método se suspende y no continuará hasta que se cumpla nuestra condición de espera. Después de esperar un segundo, Unity reanuda la corrutina justo donde lo dejó, incluso manteniendo en memoria los valores de las variables locales (en este caso, “value”). Incrementamos el valor en uno, e imprimimos en la ventana de la consola.

Si adjunta este script a algo en su escena, verá un nuevo valor impreso en la consola una vez por segundo. Pruébalo.

Unity ha proporcionado varias opciones para obtener su corrutina, que incluye:

  • WaitForEndOfFrame
  • WaitForFixedUpdate
  • WaitForSeconds
  • WWW

Tenga en cuenta que también puede usar “yield return null;” para simplemente esperar un frame o “yield break;” para cancelar una corrutina.

Una muestra más divertida

Nuestro primer ejemplo fue funcional pero bastante aburrido. Hagamos otra versión donde hagamos que un objeto se mueva a través de una serie de ubicaciones (puntos de referencia). Podría imaginarme esto como una pieza en un tablero de juego que se mueve de una casilla a otra a lo largo de un camino específico.


#region Librerias
using UnityEngine;
using System.Collections;
#endregion

namespace MoonAntonio
{
	public class Corrutinas : MonoBehaviour 
	{
		public Vector3[] waypoints;
		public float speed;

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

		IEnumerator DoStuff()
		{
			for (int i = 0; i < waypoints.Length; ++i)
			{
				while (transform.position != waypoints[i])
				{
					yield return null;
					transform.position = Vector3.MoveTowards(transform.position, waypoints[i], speed * Time.deltaTime);
				}
			}
			Debug.Log("Completado!");
		}
	}
}

En esta versión del script, he declarado una matriz pública de Vector3 que representa ubicaciones en el espacio mundial por las que quiero mover el objeto. También especifiqué una variable de velocidad que determina qué tan rápido el objeto cubrirá esas distancias.

Comienzo la corrutina pasando el IEnumerator directamente. Tenga en cuenta que esta es la forma preferida de comenzar una coroutine a menos que quiera poder detener la coroutine usando el método StopCoroutine de Unity (podría insertar lógica en el método para abortar como alternativa).

La corrutina tiene dos bucles que están anidados juntos. El bucle “for” externo itera sobre la matriz de puntos de referencia Vector3, y el bucle “while” interno itera durante el tiempo que tarde hasta que el objeto llegue a su ubicación deseada. Tenga en cuenta que espero un frame antes de actualizar la posición de los objetos. Si no hubiera ninguna instrucción de rendimiento dentro del ciclo while, el objeto completaría su ruta antes de mostrar cualquiera de los “pasos” de su progreso al usuario. Una vez que se ha seguido la ruta hasta su punto final, se imprime un mensaje en la consola que indica que nuestro trabajo está completo.

Crea una nueva escena y agrega un Cubo. Adjunte este script de demostración y asegúrese de asignar valores a cada una de nuestras propiedades públicas a través del inspector. Por ejemplo, sus waypoints podrían ser:

  • 5,0,0
  • 5,1,0
  • 5,1,3
  • 3,0,0
  • 0,0,0

y tu velocidad podría ser 1. Por supuesto, puedes usar cualquier valor que desees, pero tu velocidad debería ser al menos mayor que cero.

Corrutinas anidadas

A veces puede ser conveniente anidar corrutinas para que pueda reutilizar bits de lógica. Aquí hay una muestra que hace eso:


#region Librerias
using UnityEngine;
using System.Collections;
#endregion

namespace MoonAntonio
{
	public class CorrutinasAnidadas : MonoBehaviour 
	{
		Vector3 m1 = new Vector3(-1, 0, 0);
		Vector3 m2 = new Vector3(1, 0, 0);
		Vector3 s1 = new Vector3(1, 1, 1);
		Vector3 s2 = new Vector3(0.5f, 0.5f, 0.5f);

		void Start()
		{
			StartCoroutine(DoStuff());
		}

		IEnumerator DoStuff()
		{
			while (true)
			{
				switch (UnityEngine.Random.Range(0, 2))
				{
					case 0:
						yield return StartCoroutine(Move());
						break;
					case 1:
						yield return StartCoroutine(Scale());
						break;
				}
			}
		}

		IEnumerator Move()
		{
			Vector3 target = transform.position == m1 ? m2 : m1;
			while (transform.position != target)
			{
				yield return null;
				transform.position = Vector3.MoveTowards(transform.position, target, Time.deltaTime);
			}
		}

		IEnumerator Scale()
		{
			Vector3 target = transform.localScale == s1 ? s2 : s1;
			while (transform.localScale != target)
			{
				yield return null;
				transform.localScale = Vector3.MoveTowards(transform.localScale, target, Time.deltaTime);
			}
		}
	}
}

Creó tres corrutinas. La corrutina DoStuff es la corrutina “principal” que se activa con nuestro método de Start. Las corrutinas Move y Scale se activan aleatoriamente dentro del ciclo y se reproducirán hasta que se completen antes de que continúe la corrutina principal original.

Si utilizó la escena de la demostración anterior y simplemente actualizó la secuencia de comandos, ahora verá que su cubo se mueve al azar hacia adelante y hacia atrás, así como hacia arriba y hacia abajo.

Corrutinas nativas

Puede tener curiosidad acerca de cómo usar corrutinas fuera de la implementación de Unity. Si es así, echa un vistazo al siguiente ejemplo:


#region Librerias
using UnityEngine;
using System.Collections;
#endregion

namespace MoonAntonio
{
	public class CorrutinaNativa : MonoBehaviour 
	{
		IEnumerator trend;

		void Start()
		{
			trend = TrendLine();
		}

		void Update()
		{
			if (trend.MoveNext())
				Debug.Log((int)trend.Current);
		}

		IEnumerator TrendLine()
		{
			int value = 0;
			while (true)
			{
				value += UnityEngine.Random.Range(-1, 2);
				yield return value;
				if (Mathf.Abs(value) >= 10)
					yield break;
			}
		}
	}
}

En la línea 6 creé una variable para mantener una referencia a un IEnumerator llamado trend. Lo asigno a nuestro método en Start (línea 10).

Utilizo el bucle del Update de Unity para manejar la reanudación de la corrutina desde sus puntos de ejecución cedidos, aunque podría haber usado cualquier tipo de evento para hacerlo. La reanudación de la corrutina se produce con el método “MoveNext” que devuelve un verdadero o falso en función de si la ejecución se completó o no. Si la corrutina no estaba completa, imprimo el valor de retorno de la corrutina a la que se accede mediante la propiedad “Current” (línea 16).

En esta corrutina, genero el valor de una línea de tendencia imaginaria y observo cómo crece hasta que alcanza un valor máximo en que punto la corrutina está completa. Logré este efecto al hacer que un valor suba, baje o permanezca igual en función del resultado del número aleatorio generado por Unity.

Adjunte este script a algo en escena y ejecútelo, y verá un grupo de números generarse en la ventana de la consola. En algún punto, la línea de tendencia debe alcanzar su extensión máxima, ya sea positiva o negativa 10, y la producción se detendrá.

/img/codebackdoor/learncsharpunity/08.gif
.
Resumen
En esta lección aprendimos todo acerca de una característica del lenguaje, llamada corrutina. Aprendimos a comenzar y detener las corrutinas administradas por Unity. Cubrimos corrutinas con y sin parámetros, usamos diferentes opciones de rendimiento y corrutinas anidadas juntas. Finalmente, exploramos cómo una corrutina funciona de forma nativa y se maneja al pasar por una corrutina y recuperar sus valores manualmente.
Siguiente - Clases
/img/ref.png
.