Corutinas en Unity3D

8
Corutinas en Unity 1 Corutinas en Unity3D: Update Me Please! Comúnmente cuando estamos aprendiendo a programar en Unity3D, la función Update() resulta ser la función mas cargada de código. Puesto que es invocada cada frame del juego, allí podemos manipular acciones en determinados tiempos y manejar eventos de acuerdo a los estados de juego. Sin embargo, esta no es siempre la manera optimizada de abordar estas situaciones, y casi siempre es preferible (en términos de performance, por ejemplo para mobile iOS y Android) usar las Coroutines, o en español, Corutinas. PROBLEMA Supongamos por ejemplo que en determinada escena nuestro personaje principal abre una puerta y que se desee programar que esta se cierre después de un determinado tiempo. Una manera de abordar esta tarea podría ser el uso de una zona trigger cerca de la puerta de manera que cuando el personaje salga de esta zona, invoquemos nuestra función de cierre de puerta. Infelizmente, esta solución a pesar de que puede funcionar bien en muchos casos, no es lo que queríamos exactamente: Cerrar la puerta después de un “determinado tiempo”; dado que no podemos estar seguros cuanto tiempo demorará el personaje en salir de la zona de trigger. Una manera de solucionar nuestro problema de tiempos usando la función Update() , es usar una variable de estado “puertaAbierta” que nos permite saber si la puerta esta abierta en un determinado instante de tiempo, a la vez de usar una variable “tPAbiertapara contar cuanto tiempo lleva abierta la puerta en cualquier momento del juego, de manera que si esta variable supera el tiempo de cierre que nosotros especifiquemos, la puerta se cierre: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 bool puertaAbierta = false; float tPAbierta; float tiempoDeCierre = 5f; //tiempo que queremos //que dure la puerta abierta void AbrirPuerta(){ /*... codigo de abrir puerta aqui... */ puertaAbierta = true; tPAbierta=0; //comenzamos a contar el tiempo //desde la abertura de la puerta. } void Update(){ if(puertaAbierta){ tPAbierta+= Time.deltaTime; //contamos tiempo desde abertura if(tPAbierta > tiempoDeCierre){//cerramos /*... codigo de cerrar puerta aqui ...*/ puertaAbierta = false; } } }

description

Corutinas en Unity3D

Transcript of Corutinas en Unity3D

Page 1: Corutinas en Unity3D

Corutinas en Unity

1

Corutinas en Unity3D: Update Me Please!

Comúnmente cuando estamos aprendiendo a programar en Unity3D, la función

Update() resulta ser la función mas cargada de código. Puesto que es invocada cada

frame del juego, allí podemos manipular acciones en determinados tiempos y manejar

eventos de acuerdo a los estados de juego. Sin embargo, esta no es siempre la manera

optimizada de abordar estas situaciones, y casi siempre es preferible (en términos de

performance, por ejemplo para mobile iOS y Android) usar las Coroutines, o en

español, Corutinas.

PROBLEMA

Supongamos por ejemplo que en determinada escena nuestro personaje principal abre

una puerta y que se desee programar que esta se cierre después de un determinado

tiempo. Una manera de abordar esta tarea podría ser el uso de una zona trigger cerca de

la puerta de manera que cuando el personaje salga de esta zona, invoquemos nuestra

función de cierre de puerta. Infelizmente, esta solución a pesar de que puede funcionar

bien en muchos casos, no es lo que queríamos exactamente: Cerrar la puerta después de

un “determinado tiempo”; dado que no podemos estar seguros cuanto tiempo demorará

el personaje en salir de la zona de trigger.

Una manera de solucionar nuestro problema de tiempos usando la función Update() , es

usar una variable de estado “puertaAbierta” que nos permite saber si la puerta esta

abierta en un determinado instante de tiempo, a la vez de usar una variable “tPAbierta”

para contar cuanto tiempo lleva abierta la puerta en cualquier momento del juego, de

manera que si esta variable supera el tiempo de cierre que nosotros especifiquemos, la

puerta se cierre:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

bool puertaAbierta = false; float tPAbierta; float tiempoDeCierre = 5f; //tiempo que queremos //que dure la puerta abierta void AbrirPuerta(){ /*... codigo de abrir puerta aqui... */ puertaAbierta = true; tPAbierta=0; //comenzamos a contar el tiempo //desde la abertura de la puerta. }

void Update(){ if(puertaAbierta){ tPAbierta+= Time.deltaTime; //contamos tiempo desde abertura if(tPAbierta > tiempoDeCierre){//cerramos /*... codigo de cerrar puerta aqui ...*/ puertaAbierta = false; } } }

Page 2: Corutinas en Unity3D

Corutinas en Unity

2

Esta solución funciona, pero no es la mejor. Observemos que incluso cuando estemos en

cualquier otro momento de la escena que no tenga nada que ver con la puerta, nuestro

código aun estará evaluando la veracidad de la sentencia if(puertaAbierta){…} en cada

frame.

USANDO LAS CORUTINAS

Para obtener una solucion mas limpia y optimizada debemos usar corutinas. Las

corutinas en Unity3D son funciones especiales con las siguientes funcionalidades y

características:

Permiten que creemos una función que ejecuta acciones (bloques de código) en

sucesión, y especialmente con la posibilidad de hacer pausas entre cada acción.

Permiten que creemos una función que se invoque automáticamente de

diferentes maneras: 1. cada determinada cantidad de segundos; 2. en cada

fixedTime (similar a FixedUpdate); 3. al final de cada frame de TODAS las

cámaras que estén haciendo render en la escena.

Pueden ser pausadas y reanudadas por medio de sentencias “yield”. Puesto que

la podemos interrumpir cuando queramos, es ideal para situaciones que

involucren tiempos como nuestro caso de la puerta.

Se ejecutan asincronamente en el hilo de ejecución principal (main thread).

HELLO WORLD EN CORUTINAS

El siguiente código seria el equivalente a el hello world usando corutinas:

1

2

3

4

5

6

7

8

9

10

IEnumerator helloWorldCorutina(){ /* Imprima HELLO */ Debug.Log("HELLO");

/*retorne, y reanude a partir de aqui dentro de 2 segundos */ yield return new WaitForSeconds(2f);

/* Imprima WORLD */ Debug.Log("WORLD"); }

ENTENDIENDO LAS CORUTINAS

Podemos entender las corutinas intuitivamente de la siguiente manera: Es una función

normal que contiene una o muchas sentencias yield. Cuando una de estas sentencias es

alcanzada, nuestro código se suspende hasta que esta sentencia se cumpla. Cuando se

cumple, nuestro código continua normalmente, justo donde se había interrumpido.

Actualizaciones normales de Coroutines (corrutinas) son ejecutadas después del return

que hace la función Update. Una coroutine es una función que puede suspender su

ejecución (yield) hasta que la YieldInstruction finalice. Diferentes usos de Coroutines:

Page 3: Corutinas en Unity3D

Corutinas en Unity

3

yield La coroutine va a continuar después de que todas las funciones Update hayan sido llamadas en el siguiente frame.

yield WaitForSeconds Continúa después de un retraso de un tiempo específico, después de que todas las funciones Update hayan sido llamadas para el frame

yield WaitForFixedUpdate Continua después de que todos los FixedUpdate hayan sido llamadas en todos los scripts.

yield WWW Continúa después de que una descarga WWW haya sido completada yield StartCoroutine Encadenada la coroutine, y va a esperar para que la coroutine

MyFunc haya sido completado primero.

Existen múltiples sentencias yields en Unity3D, estas son:

1

2

3

4

5

6

7

8

9

10

11

12

13

/*Interrumpa esta corutina, y reanude solo al final de render de todas las camaras presentes en la escena.*/ yield return new WaitForEndOfFrame();

/* Interrumpa esta corutina, y reanude solo en el proximo Fixed

frame.*/ yield return new WaitForFixedUpdate();

/*Interrumpa esta corutina, y reanude solo despues de "segs" cantidad de segundos.*/ yield return new WaitForSeconds(segs); /*Interrumpa y NO reanude nunca mas esta corutina*/ yield break;

En C# una corutina debe ser declarada como tipo IEnumerator, y además debe devolver

al menos una vez una sentencia yield. Consideremos la siguiente corutina:

1

2

3

4

5

6

7

8

9

IEnumerator corutinaEjemplo(){ while(true){ /*retorne, y reanude a partir de aqui dentro de 5 segundos */ yield return new WaitForSeconds(5f);

/*Codigo de algo que necesito actualizar cada 5 segundos */ /* ..... */ } }

Page 4: Corutinas en Unity3D

Corutinas en Unity

4

Como mencione anteriormente, las sentencias yield son sentencias especiales de

interrupción de corutinas, si no las usáramos (suponiendo se pudiera compilar una

corutina sin ellas) el bucle while de este ejemplo quedaría ocupando el hilo principal

de ejecucion eternamente, y obtendríamos un bucle infinito sin permitir que otras tareas

se ejecuten en este hilo. Lo especial de las corutinas es precisamente esto, mediante las

sentencias yield podemos liberar al hilo de ejecución principal para que se ocupe de

otras tareas, y después reanudar nuestra corutina de acuerdo a como lo especifique la

sentencia yield.

Es asi que se podria deducir erróneamente que las corutinas son como código que corre

en paralelo a otras tareas. Y digo erróneamente porque ESTO NO ES VERDAD y en

realidad este paralelismo no ocurre: todo se ejecuta en el hilo principal, pero en orden

diferente, alternado las tareas de las corutinas con el resto de tareas. Para verificar la

veracidad de esto ultimo, hagan el intento de correr la siguiente corutina, y se darán

cuenta que el editor de unity colapsa:

1

2

3

4

5

6

IEnumerator corutinaColapsa(){ yield return new WaitForSeconds(1f); while(true){ Debug.Log("HELLO WORLD"); } }

COMO INVOCAR UNA CORUTINA

A diferencia de las funciones normales, para que una corutina empiece su ejecución

usando C#, utilizamos el método (heredado de MonoBehaviour)

StartCoroutine(miCorutina()). Por ejemplo para invocar que nuestra corutina hello

world helloWorldCorutina() arranque, podríamos usar:

1

2

3

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

Finalmente para terminar, damos la solución del problema de cierre de puerta propuesto

anteriormente, ahora usando nuestras alabadas corutinas, con las cuales resulta ser

sorprendentemente una tarea simplificada:

1

2

3

4

5

IEnumerator AbriryCerrarPuerta(){ /*... codigo de abrir puerta ...*/ yield return new WaitForSeconds(tiempoDeCierre); /*... codigo de cerrar puerta ...*/ }

Page 5: Corutinas en Unity3D

Corutinas en Unity

5

La moraleja es: Si necesitas que algo se chequee constantemente, no en todos los

frames, sino en solo durante un periodo determinado de tiempo; o si necesitas

sincronizar tareas de acuerdo tiempos exactos: usa corutinas y no cargues de código a la

función Update()!

UNITY 3D– StartCoroutine

En este post vamos a ver cómo se utiliza la función de UNITY3D StartCoroutine.

yield

yield es una palabra reservada que indica al compilador que en la función en la que

aparece es un iterador. yield se utiliza con la palabra reservada return que devuelve un

objeto al iterador. También se utiliza con la palabra reservada break para indicar que el

iterador se ha finalizado la iteración. Para profundizar más, os recomiendo echar un

vistazo a la documentación.

Vamos a ver un ejemplo para que quede un poco más claro.

1: static void Main()

2: {

3: foreach (int value in ComputePower(2, 30))

4: {

5: Console.Write(value);

6: Console.Write(" ");

7: }

8: Console.WriteLine();

9: }

10:

11: public static IEnumerable<int> ComputePower(int number, int

exponent)

12: {

13: int exponentNum = 0;

14: int numberResult = 1;

15:

16: while (exponentNum < exponent)

17: {

18: numberResult *= number;

19: exponentNum++;

20:

21: yield return numberResult;

22: }

23: }

La función ComputePower nos devuelve todos los valores que hay de number elevado

desde 1 hasta exponent, al utilizar yield, podemos llamar a esta función desde dentro de

un bucle foreach.

¿Esto para qué nos sirve dentro de Unity3D?

En Unity3D nos puede ser util cuando usamos la función StartCoroutine, ya que esta

función lo que hace es ejecutar un método cuyo retorno es un IEnumerator.

Page 6: Corutinas en Unity3D

Corutinas en Unity

6 StartCoroutine

En primer lugar vamos a ver que es una coroutine:

Una coroutine es una función que se ejecuta parcialmente, presuponiendo que se

cumplen las condiciones adecuadas, y se continuará en el futuro hasta que esta finalice.

Hay que tener en cuenta que una coroutine no se ejecuta de forma asíncrona.

Unity procesa las coroutine en cada frame del juego, después del Update y antes del

LateUpdate, esto es así en la mayoría de los casos, salvo algunas circunstancias

especiales. Aquí os pongo un diagrama con el ciclo de vida en Unity3D, para una

explicación detallada, no dudéis en echar un ojo aquí.

Con la función StartCoroutine, lanzamos esta función. La sintaxis es

StartCoroutine(“Nombre de función”);.

Cuando en una coroutine se llega a un yield, la ejecución de dicha función se detiene

hasta el siguiente frame en la mayoría de los casos, aunque también es posible detener le

ejecución por un lapso de tiempo si usamos la función WaitForSeconds.

Esto podría usarse para mostrar un efecto a lo largo del tiempo, o si en el juego tenemos

que hacer algún tipo de validación recurrente pero que no es necesaria que se haga en

cada frame.

En la propia documentación de Unity3D puedes encontrar una muy buena explicación

sobre las coroutines.

Coroutines

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.

Page 7: Corutinas en Unity3D

Corutinas en Unity

7

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 coroutine (corrutina) es como una función que tiene la habilidad de pausar su

ejecución y retornar el control a Unity para luego continuar donde dejó en el siguiente

frame. En C#, una coroutine 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 es esencialmente una función declarada con un tipo de retorno de IEnumerator y con

una instrucción de retorno yield (yield return) incluida en algún lugar de su cuerpo. La

linea de retorno yield es el punto en el cual la ejecución se pausará y reanudará en el

siguiente frame (cuadro). Para establecer una coroutine en ejecución, usted necesita usar

la función StartCoroutine

void Update() {

if (Input.GetKeyDown("f")) {

StartCoroutine("Fade");

}

}

Por defecto, una coroutine es reanudada en el frame después de que haga 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);

}

}

Esto puede ser usado como una forma de propagar un efecto sobre 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 incluirles en la función

Update. Sin embargo, esta función usualmente será llamada muchas veces por segundo.

Cuando una tarea no requiere ser repetida tan frecuentemente, le puede poner en una

coroutine 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 verse algo como esto:-

Page 8: Corutinas en Unity3D

Corutinas en Unity

8 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 esta función cada cuadro podría introducir

una sobrecarga significativa. Sin embargo, podría usar una coroutine para llamarle 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.