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 como1000 / targetFPS
.this.lastTime
: Momento en que se actualizó por última vez el bucle, obtenido conperformance.now()
para alta precisión.this.startTime
: Momento en que se comenzó a medir los FPS actuales, también inicializado conperformance.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 quethis.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
.
- Si
- if (currentTime – this.startTime >= 1000) {:
- Si ha pasado un segundo, se calcula los FPS actuales dividiendo
frameCount
entre el tiempo transcurrido y reiniciastartTime
yframeCount
.
- Si ha pasado un segundo, se calcula los FPS actuales dividiendo
- Resultado:
- Devuelve
true
si ha pasado el tiempo suficiente para la tasa de fotogramas objetivo (targetFPS),false
si no.
- Devuelve
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 ashouldUpdate
y reinicia las propiedadesstartTime
yframeCount
. - 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 !