FPS en videojuegos JavaScript

En este artículo, vamos a explicar cómo hacer un contador de FPS y controlar la velocidad de fotogramas en nuestros videojuegos JavaScript. Cuando desarrollamos un videojuego, los FPS (Frames Per Second o Fotogramas por Segundo) son uno de los factores más importantes para tener una experiencia de juego fluida y agradable. Por ello, mantener unos FPS estables y controlados ayuda a evitar tirones y a mejorar el rendimiento general del juego, especialmente en dispositivos con diferentes capacidades.

Tal como expliqué en el artículo sobre cómo hacer un bucle de animación de videojuegos con JavaScript, nuestros juegos intentarán ejecutarse a una velocidad constante de 60 fps, dependiendo del hardware. Esto quiere decir que, si nuestro monitor es gaming, pueden ser más (90, 120, 144, 165) o, si nuestro monitor es antiguo, menos (50, 45, 30).

¿Qué son los FPS en Videojuegos?

Los FPS en un videojuego representan la cantidad de fotogramas que se renderizan en pantalla por segundo. Para muchos videojuegos, especialmente si los desarrollamos con JavaScript, 60 FPS es el estándar ideal. Sin embargo, para juegos menos exigentes o en dispositivos más antiguos, 30 FPS puede ser suficiente. Esta disparidad en la tasa de fotogramas puede traernos problemas en el movimiento y fluidez del videojuego.

En muchos juegos, se emplea una técnica llamada movimiento basado en tiempo delta para evitar que las fluctuaciones en los FPS afecten la experiencia de juego. Con esta técnica, el movimiento y otros cálculos se ajustan automáticamente en función del tiempo que ha pasado desde el último fotograma, en lugar de depender de la cantidad de fotogramas renderizados por segundo.

Con ello, se logra un movimiento constante y fluido, al margen de los fps que esté ejecutando el videojuego. Este concepto lo explicaré detalladamente en otro artículo, pero ahora vamos a centrarnos en cómo implementar un contador de FPS para nuestros videojuegos en JavaScript.

FPS en videojuegos JavaScript

Para medir la tasa de FPS que ejecutan nuestros videojuegos JavaScript, dentro del bucle de animación, necesitamos calcular el tiempo transcurrido entre cada fotograma renderizado. Para ello, podemos crear una clase JavaScript que, añadida a nuestros videojuegos, nos calcule los FPS. Una vez instanciada esta clase, podemos llamar a sus métodos para que calcule y nos devuelva el número de fotogramas a los que se ejecuta el bucle de animación. Con estos datos, podemos mostrar los fps en la pantalla del videojuego o decidir si debe ejecutarse el juego o no, en función de la tasa de fotogramas con la que queremos renderizar el juego.

Clase FPSController

El código completo de nuestra clase JavaScript para controlar los FPS en nuestros videojuegos es el siguiente:

class FPSController {
  constructor(targetFPS) {
      this.actualFPS = 0;
      this.frameCount = 0;
      this.targetFPS = targetFPS || 60;
      this.interval = 1000 / this.targetFPS;
      this.lastTime = performance.now();
      this.startTime = this.lastTime;
  }

  getCurrentFPS() {
    return this.actualFPS;
  }  

  shouldUpdate() {
    const currentTime = performance.now();
    const elapsed = currentTime - this.lastTime;
    if (elapsed >= this.interval) {
      this.lastTime = currentTime - (elapsed % this.interval);
      this.frameCount++;
      if (currentTime - this.startTime >= 1000) {
        this.actualFPS = Math.round((this.frameCount * 1000) / (currentTime - this.startTime));     
        this.startTime = currentTime;               
        this.frameCount = 0;       
      }
      return true;
    }
    return false;
  }

  getMaxFPS() {
    const currentTime = performance.now();
    this.frameCount++;
    if (currentTime - this.startTime >= 1000) {
      this.actualFPS = Math.round((this.frameCount * 1000) / (currentTime - this.startTime));     
      this.startTime = currentTime;               
      this.frameCount = 0;       
    }
    return this.actualFPS;
  }  
}

Este código define la clase JavaScript FPSController que permite controlar y monitorear los FPS (fotogramas por segundo) de nuestros videojuegos en un bucle de animación. Vamos a explicar el funcionamiento de cada uno de sus métodos.

Constructor(targetFPS)

El constructor se usa para inicializar las propiedades necesarias. Con ellas, podemos ejecutar la clase y obtener información de los fotogramas renderizados, así como iniciar la clase con una tasa de fotogramas objetivo pasadas al contructor (targetFPS). Esto nos permite monitorizar los FPS de nuestros videojuegos con JavaScript:

  constructor(targetFPS) {
      this.actualFPS = 0;
      this.frameCount = 0;
      this.targetFPS = targetFPS || 60;
      this.interval = 1000 / this.targetFPS;
      this.lastTime = performance.now();
      this.startTime = this.lastTime;
  }
  • this.actualFPS: Almacena los FPS actuales, calculados cada segundo.
  • this.frameCount: Cuenta el número de fotogramas para calcular los FPS.
  • this.targetFPS: El FPS deseado para el juego, si no se pasa como parámetro, se establece en 60 FPS por defecto.
  • this.interval: Tiempo en milisegundos que deberá pasar entre fotogramas, para llegar a la tasa deseada, calculado como 1000 / targetFPS.
  • this.lastTime: Momento en que se actualizó por última vez el bucle, obtenido con performance.now() para alta precisión.
  • this.startTime: Momento en que se comenzó a medir los FPS actuales, también inicializado con performance.now().

getCurrentFPS()

Este método simplemente devuelve el valor de los FPS actuales (actualFPS), que se han registrado durante el último segundo. Debemos utilizar este método, junto con shouldUpdate(), cuando queremos mostrar una tasa limitada de FPS en nuestros videojuegos JavaScript. El motivo es que este método simplemente devuelve el valor de (actualFPS), mientras que los otros métodos modifican propiedades. Lo explicaremos más adelante.

  getCurrentFPS() {
    return this.actualFPS;
  }  

shouldUpdate()

Este es el método que utilizaremos cuando queremos limitar la tasa de FPS en nuestros videojuegos JavaScript y, para mostrar los FPS obtenidos, llamaremos a getCurrentFPS(). El motivo es que el método shouldUpdate() solo devuelve un valor booleano (true/false) en virtud de si ha llegado el momento de actualizar el siguiente fotograma, según la tasa de fotogramas establecida en el constructor. Veamos el proceso paso a paso:

  shouldUpdate() {
    const currentTime = performance.now();
    const elapsed = currentTime - this.lastTime;
    if (elapsed >= this.interval) {
      this.lastTime = currentTime - (elapsed % this.interval);
      this.frameCount++;
      if (currentTime - this.startTime >= 1000) {
        this.actualFPS = Math.round((this.frameCount * 1000) / (currentTime - this.startTime));     
        this.startTime = currentTime;               
        this.frameCount = 0;       
      }
      return true;
    }
    return false;
  }
  • const
    • currentTime: Almacena el tiempo actual en milisegundos.
    • elapsed: Calcula el tiempo transcurrido desde la última actualización
  • if (elapsed >= this.interval) {
    • Si elapsed es mayor o igual que this.interval, significa que ha pasado el tiempo suficiente para la tasa de fotogramas objetivo (targetFPS).
    • Actualiza lastTime para reflejar el tiempo actual, con una corrección (elapsed % this.interval) que asegura que se mantenga el ritmo constante.
    • Cada vez que se actualiza el fotograma se incrementa frameCount.
  • if (currentTime – this.startTime >= 1000) {:
    • Si ha pasado un segundo, se calcula los FPS actuales dividiendo frameCount entre el tiempo transcurrido y reinicia startTime y frameCount.
  • Resultado:
    • Devuelve true si ha pasado el tiempo suficiente para la tasa de fotogramas objetivo (targetFPS), false si no.

getMaxFPS()

Este es el método que utilizaremos cuando queremos mostrar la tasa MÁXIMA de FPS que podemos alcanzar en nuestros videojuegos JavaScript. Este método se usa para obtener los FPS máximos que puede generar el bucle de animación. Este método NO debe llamarse en el mismo ciclo que shouldUpdate(), puesto que comparten las mismas propiedades en la clase FPSController y produciría resultados inesperados.

  getMaxFPS() {
    const currentTime = performance.now();
    this.frameCount++;
    if (currentTime - this.startTime >= 1000) {
      this.actualFPS = Math.round((this.frameCount * 1000) / (currentTime - this.startTime));     
      this.startTime = currentTime;               
      this.frameCount = 0;       
    }
    return this.actualFPS;
  }  

El funcionamiento de este método es similar a shoudUpdate(), aunque su resultado es un número que corresponde a la tasa máxima de FPS que generan nuestros videojuegos JavaScript en el bucle universal de animación.

  • Almacena el tiempo actual en milisegundos en currentTime.
  • Aumenta frameCount en cada llamada.
  • Si ha pasado un segundo calcula los fps actuales en actualFPS de forma similar a shouldUpdate y reinicia las propiedades startTime y frameCount.
  • Devuelve el valor actual de los fps

Funcionamiento del controlador FPS

Para utilizar nuestro controlador de FPS, simplemente debemos añadirlo a nuestros videojuegos mediante una instancia y utilizar sus métodos. Un ejemplo de utilización, junto con algunas de las metodologías y controladores explicados en este blog y en mis libros sería:

<script>
  let fps: new FPSController(30);
  function animateScene() {	
    switch(status) {
      // Pantalla inicio (máximos fps)   
      case	'Splash'  :	
        this.page.pageSplash(); 
        Graphics.fillText({x: 5, y: 15, text: fps.getMaxFPS() + " fps",	size: "12", align: "left"});                
      break;	
      // Motor del juego (limitado a 30 fps)                                                      
      case	'Run'			: 
        if (fps.shouldUpdate()) { 
          this.level.run();          
          Graphics.fillText({x: 5, y: 86, text: fps.getCurrentFPS() + " fps",	size: "12", align: "left"});   
        }                       
      break; 
    }
    requestAnimationFrame(animateScene);
  }
</script> 

Este extracto de código no funciona por sí solo, simplemente es un ejemplo del funcionamiento de FPSController.

  • Primero, instanciamos la clase estableciendo sus FPS objetivo a 30.
  • En la bifurcación «Splash» del juego mostramos los fps máximos que puede ejecutar el bucle de animación
  • En la bifucación «Run», condicionamos la actualización del juego a los fps objetivo que hemos establecido en el constructor. Sólo en caso afirmativo se ejecutará el juego y mostramos los fps actuales.

IMPORTANTE: Observa que getMaxFPS() y shouldUpdate() son incompatibles por usar las mismas propiedades, por este motivo no deben llamarse en el mismo ciclo del bucle de animación. Y también por ello, creamos la función extra getCurrentFPS() y poder mostrar los fps limitados junto con shouldUpdate().

FPS en videojuegos JavaScript

Con la clase FPSController, puedes sincronizar el bucle de animación de tus videojuegos a una tasa de FPS específica, al mismo tiempo que monitoreas los FPS actuales para ajustar el rendimiento en videojuegos desarrollados con JavaScript.

Implementar un controlador de FPS y manejar la velocidad de fotogramas en tus videojuegos JavaScript, no solo mejora la experiencia de juego, sino que también optimiza el uso de recursos. Esto permite que el juego funcione de manera fluida en diferentes dispositivos, garantizando un rendimiento estable y agradable para los jugadores.

Aplicando la clase FPSController, podrás asegurarte que tus videojuegos en JavaScript ofrezcan una experiencia de FPS óptima.

¡ Espero que este artículo sea de vuestro interés !

Deja un comentario