Cuando usted llama a una función, esta se ejecuta en su totalidad antes de retornar. Esto significa efectivamente que cualquier acción tomando lugar en una función debe suceder en una sola actualización de frame (cuadro); un llamado a una función no puede ser usado para contener una animación procedimental o una secuencia de eventos en el tiempo. Como un ejemplo, considere la tarea de reducir gradualmente el valor alfa (alpha) de un objeto (opacidad) hasta que se convierta completamente invisible.
void Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
}
}
Tal como está, la función Fade (Desvanecer) no tendrá el efecto que usted espera. Para que el desvanecimiento sea visible, el alfa debe ser reducido sobre una secuencia de frames (cuadros) para mostrar los valores intermedios siendo renderizados. Sin embargo, la función se ejecutará en su totalidad en una sola actualización de cuadro. Los valores intermedios nunca se verán y el objeto desaparecerá instantáneamente.
Es posible manejar situaciones como estas agregando código a la función Update que ejecuta el desvanecer en una base cuadro-a-cuadro (frame-by-frame). Sin embargo, es usualmente mas conveniente usar una coroutine (corrutina) para este tipo de tarea.
Una corrutina es una función que tiene la habilidad de pausar su ejecución y devolver el control a Unity para luego continuar donde lo dejó en el siguiente frame. En C#, una corrutina es declarada así:
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return null;
}
}
Es esencialmente una función declarada con un tipo de retorno IEnumerator y con una instrucción de retorno yield incluida en algún lugar de su cuerpo. La linea con la instrucción de retorno yield es el punto en el cual la ejecución se pausará y reanudará en el siguiente frame. Para establecer una corrutina en ejecución, necesitas usar la función StartCoroutine
void Update() {
if (Input.GetKeyDown("f")) {
StartCoroutine("Fade");
}
}
En UnityScript, las cosas son ligeramente mas simples. Cualquier función que incluya una instrucción yield se entiende que es una corrutina y el tipo de retorno IEnumerator no requiere ser declarado explicitamente:
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield;
}
}
Además, una corrutina puede ser iniciada en UnityScript llamándola como si fuese una función normal:
function Update() {
if (Input.GetKeyDown("f")) {
Fade();
}
}
Notará que el contador en el bucle de la función Fade mantiene su valor correcto durante el tiempo de vida de la corrutina. De hecho, cualquier variable o parametro será preservado correctamente entre los yields.
Por defecto, una corrutina es reanudada en el siguiente frame después de haberse interrumpido (yield), pero también es posible introducir un tiempo de retardo usando WaitForSeconds:-
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
y en UnityScript:
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield WaitForSeconds(0.1);
}
}
Esta puede usarse como una forma de propagar un efecto durante un periodo de tiempo pero también es una útil optimización. Muchas tareas en un juego necesitan ser llevadas a cabo periódicamente y la forma mas obvia de hacer esto es incluirlas en la función Update. Sin embargo, esta función normalmente será llamada muchas veces por segundo. Cuando una tarea no necesita ser repetida tan frecuentemente, puedes ponerla en una corrutina para obtener una actualización regularmente pero no en cada frame. Un ejemplo de esto puede ser una alarma que alerta al usuario si un enemigo está cerca. El código podría parecerse a esto:
function ProximityCheck() {
for (int i = 0; i < enemies.Length; i++) {
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
Si hay muchos enemigos, entonces llamar a esta función en cada frame podría introducir una sobrecarga significativa. Sin embargo, podría usar una corrutina para llamarla cada décima de segundo:
IEnumerator DoCheck() {
for(;;) {
ProximityCheck;
yield return new WaitForSeconds(.1f);
}
}
Esto reducirá en gran cantidad el número de comprobaciones llevados a cabo sin ningún efecto notable en la experiencia de juego.