Aprende a crear juegos en HTML5 Canvas

lunes, 26 de noviembre de 2012

RequestAnimationFrame: Regulando el tiempo entre dispositivos

Debido a las diferentes capacidades en los dispositivos, requestAnimationFrame no es consistente entre ellos. Su tiempo de actualización varía en intervalos menores para dispositivos de menor poder, que hará los juegos más lento en unos dispositivos que en otros. Para evitar este problema, es necesario regular el tiempo entre estos dispositivos.

Existen varias formas de hacer esta acción. A continuación mostraré algunas de las formas mas conocidas y populares:

1. Envolver requestAnimationFrame en setTimeout.


Tan simple como se lee. Cuando busqué en Internet información de como regularizar el tiempo con requestAnimationFrame, esta fue la primer respuesta en aparecer. Se haría de la siguiente forma:
    setTimeout( function () {
        window.requestAnimationFrame(run)
    }, 50);
Esto es prácticamente hacer un setTimeout, con la ventaja de optimización de requestAnimationFrame. O al menos eso es lo que decía el sitio donde lo leí.

¿Efectivo? Lo dudo mucho. Al hacer la prueba a 50 milisegundos, el resultado me daba 15 cuadros por segundo en lugar de los 20 esperados, lo que me indica que este método es poco efectivo. Pero al menos, crea un tiempo regular entre dispositivos.

Ventajas:
  • Fácil de implementar

Desventajas:
  • No cumple el tiempo esperado en tiempos altos.
  • Poco efectivo en tiempos bajos.

Ve el ejemplo y código de este método.

2. requestAnimationFrame para paint, setTimeout para act.


Una alternativa para regularizar el tiempo, es hacer de forma asíncrona las funciones paint y act. Esto quiere decir, que cada uno se actualice a su propio rítmo, optimizando así los tiempos para ambas acciones. La desventaja, es que a veces querrás que algunos valores interactúen entre las funciones act y paint, lo que podría volverse una tarea un poco más compleja de lo que uno está acostumbrado.

La forma más sencilla de realizar un método asíncrono, es (como lo dice el título), usar un requestAnimationFrame para la función paint y un setTimeout para la función act. Para eso, se crean dos funciones distintas que se llaman a si mismas continuamente:
function repaint() {
    window.requestAnimationFrame(repaint)
    paint(ctx);
}

function run() {
    setTimeout(run, 50);  
    act();
}
Solo hay que llamar a ambas funciones (run y repaint) al final de la función init, y el juego correrá como de costumbre.

En el ejemplo, he agregado además de los cuadros por segundo, una segunda variable llamada ciclos por segundo (CPS), que demuestra que, efectivamente, ambas funciones cumplen con su objetivo.

Ventajas:
  • Fácil de implementar.
  • Efectivo en tiempos altos.

Desventajas:
  • Es asíncrono
  • Poco efectivo en tiempos bajos.
  • Sigue consumiendo CPU si el juego pasa a segundo plano, aunque en mucha menor medida que al no usar requestAnimationFrame para paint.

3. Usar la delta de tiempo.


¡El método más popular y efectivo! E implementarlo será muy sencillo, ya que la parte difícil ya la has hecho antes, cuando implementamos los cuadros por segundo.

Prácticamente es tomar el mismo código para calcular el deltaTime, enviando este valor cuando llamas a la función act. Con este valor, solo debes multiplicarlo por el desplazamiento de los objetos, y de esta forma tendrás garantizado que siempre se desplazará la misma cantidad de pixeles por segundo, independiente de los cuadros por segundo en cada dispositivo.

¿Que valor se debe usar para esta multiplicación? La cantidad de pixeles que quieras se desplace tu objeto en un segundo. Para el ejemplo actual, estamos haciendo un desplazamiento de 2 pixeles por ciclo, en un tiempo óptimo de 60 cuadros por segundo... Eso quiere decir que nos estamos desplazando 120 pixeles por segundo:
function act(deltaTime) {
    x += 120 * deltaTime;
    if (x > canvas.width) {
        x = 0;
    }
}
Para este ejemplo, este método ha sido muy sencillo de aplicar, pero cuando tenemos en cuenta que debemos hacer esta multiplicación por cada movimiento y animación en nuestro juego, para garantizar consistencia, descubriremos que este método puede ser un poco difícil de mantener. Aun así, sigue siendo el más efectivo de todas las opciones.

Ventajas:
  • Siempre fiable y consistente
  • Las transiciones siempre son tan fluidas como el CPU lo permita

Desventajas:
  • Difícil de mantener
  • Se congela el juego al pasar a segundo plano.

Ve el ejemplo y código de este método.


Conclusión: ¿Qué método usar?


Todos los métodos tienen ventajas y desventajas entre ellos. En realidad, la elección del método a utilizar, depende de las necesidades de tu juego. Solamente si no recomendaré el primero... Pero el tercero es la mejor opción, y el segundo es una forma fácil de implementar, que suele trabajar bien en la mayoría de los casos.

Se te anima a experimentar con ellos, y ver cual es el que mejor se acomoda a tus necesidades.

9 comentarios:

  1. Me parecen interesantes las 3 formas de animar el canvas. Aún tengo en mente lo que dices de dispositivos...¿te refieres que estas 3 formas de animar se emplean sobre todo en handsets? En principio uso la RequestAnimationFrame que tienes en el apéndice 2 y no me ha dado problemas. Supongo que este es sobre todo por si cargas la animacion en un navegador de móvil/tablet por ejemplo.

    Saludos!

    PD: Despues de un tiempo sin tocar Canvas, vuelvo a ello y con más ganas que nunca (y tiempo espero). He visto Karl que tienes muchas más publicaciones, que alegría, no me voy a aburrir ^_^

    ResponderEliminar
    Respuestas
    1. Sobre todo a handsets, así es, pero también aplica a algunas computadoras de años atrás con procesadores limitados (Aun muchos desarrolladores tiemblan al escuchar la palabra "netbook"). Si en algún momento puedes probar tus ejemplos en un dispositivo móvil sin usar uno de estos métodos, podrás comprobar que va un poco lento en comparación a las computadoras de escritorio.

      PD: Eso explica el hecho que me extrañaba no verte más por aquí... ¡Pero que bueno que estás de vuelta! Bienvenido de nuevo, espero encuentres igual de gratificantes los nuevos cursos.

      Eliminar
  2. Hola, voy a aplicar el tercer método que mencionas, pero me gustaría saber a que te refieres con "se congela el juego al pasar al segundo plano", ¿esto pasa en todos los dispositivos? y por ultimo y mas importante ¿Existe un modo de solucionar este problema aplicando el tercer método?

    Discúlpame si os molesto mucho solo quiero aprender :) y muchas gracias por tu BLOG esta genial.

    Bonus Question: ¿Que método usas tu en tus trabajos?

    Saludos.

    ResponderEliminar
    Respuestas
    1. Para ahorrar proceso y batería, el navegador automáticamente pausa los eventos de requestAnimationFrame cuando este pasa a segundo plano, ya que el jugador no interactúa con el juego y se ve como un tiempo conveniente para proporcionar dicho ahorro.

      Nosotros desechamos cualquier pausa por más de un segundo para prevenir efectos no deseados, por lo que todo este tiempo en pausa no afecta en forma alguna al juego.

      Si deseas que tu juego se vea afectado por el tiempo que estuvo en segundo plano, debes usar escuchas para los eventos "blur" y "focus", obtener el tiempo ocurrido entre ellos, y hacer los cálculos necesarios para que el juego continúe con su flujo natural. Este caso no es el más común, pero se que hay juegos donde se espera que ocurra esta clase de eventos.

      Sobre tu pregunta final, personalmente prefiero trabajar con delta del tiempo en la mayoría de los casos, lo veo más práctico y efectivo, aunque no siempre fácil de manejar, por ello prefiero usar el segundo caso para enseñar en el blog, porque se que es más fácil para que aprenda la gente que apenas empieza con esta clase de conocimiento.

      Eliminar
    2. Muchas gracias por tus aclaraciones, me han servido un montón pero me podrás aclarar ¿como es eso de desechar cualquier pausa?

      Eliminar
    3. Es esta línea:

      if(deltaTime > 1) deltaTime = 0;

      Esto previene que se ejecuten posibles errores que no se manejan de forma correcta, y es por ello que los escuchas de blur/focus manejan un procedimiento aparte que arregle cualquier error potencial ejecutado por dicho lapso de inactividad.

      Eliminar
  3. Hola seo Faustina .
    Soy María Salazar García como me pongo en el canva que lo quiero hacer.
    FIRMA:Maríangeles.

    ResponderEliminar
    Respuestas
    1. Hola María

      Prueba seguir las instrucciones del primer tema:

      http://juegos.canvas.ninja/2012/01/parte-1-dibujando-en-el-canvas.html

      Si aún te causa dudas, puedes preguntar. ¡Mucha suerte!

      Eliminar