Coroutines
Cómo crear corrutinas y utilizarlas para lograr comportamientos complejos.
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.
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 targetRecuerda 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.
Apoya el contenido de mi blog a través de:
Fuente: