Si vas a aprender a desarrollar videojuegos, o ya te dedicas a ello, habrás oido hablar del «game loop».
Pero, ¿qué es el game loop?
El Game Loop es el corazón básico de todo juego, el bucle principal que hace que todo funcione.
Piensa en cualquier tipo de videojuego, ¿qué ocurre mientras estás jugando?
- Pasan cosas en la pantalla. Se mueven los enemigos 👾 , suena la música, o incluso nuestro player hace algo.
- Si interactúas, por ejemplo moviendo el joystick 🕹 , gamepad 🎮, o pulsando alguna tecla ⌨️ , el juego reacciona a dicho estímulo, y volvemos a esperar otro estímulo.
Si traducimos esto a pseudo-código, nos quedaría un bucle tal que así:
while (true) {
processInput();
update();
render();
}
processInput: Procesa la entrada del usuario desde la última iteración.
update: Actualiza el estado del juego un paso más. Ejecutando la inteligencia artificial o la física en caso de haberlas.
render: Muestra los resultados del nuevo estado, en pantalla.
Si ejecutamos este bucle infinito, las instrucciones se ejecutarán unas tras otras, tan rápidas como la máquina nos lo permita, y así de forma indefinida, por lo que nos encontramos con 2 problemas principales:
- La sobrecarga, el procesador estará funcionando al 100%.
- La velocidad, dependiendo de la potencia de la máquina, las iteraciones del bucle se ejecutarán en mayor o menor tiempo, dando lugar a una ejecución más o menos lenta del juego.
Por lo que aunque ya tenemos nuestro game loop básico, tendremos que mejorarlo para evitar dichos problemas.
¿Y si ponemos una espera tras cada ciclo? Nos quedaría algo como:
while (true) {
processInput();
update();
render();
sleep( time );
}
Con sleep, «dormiríamos» el proceso un determinado tiempo, para una vez transcurrido, volver a iterar en nuestro bucle.
Recuerda, que comentamos que dependiendo de la potencia de nuestra máquina, tardará más o menos en procesar las instrucciones, y esto podría hacer que nuestro juego vaya a diferente velocidad, por lo que tendremos que tener en cuenta dicha velocidad.
Para ello, se suele establecer una velocidad deseada, por ejemplo 60 FPS (frames por segundo), que significa que cada frame se ejecuta cada 1/60 = 16 milisegundos. Así el tiempo que tenemos que dormir será de 16 milisegundos, menos el transcurrido en ejecutar processInput() + update() + render().
Quedando el código:
while (true) {
var start = getCurrentTime();
processInput();
update();
render( start + (1/FPS) - getCurrentTime() );
}
Con esto tendríamos un Game Loop más «estable».
Veamos ahora un ejemplo muy básico desarrollado en javascript (que me perdonen los expertos en javascript, que seguro hay mejores formas de hacerlo a nivel de código).
Partimos de un HTML muy básico, con un div que representará nuestra «paleta», que moveremos con las teclas k y l a izquierda y derecha respectivamente.
<html>
<head>
<title>Primer juego</title>
<script src="game.js"></script>
<style>
.racket {
background-color: red;
width: 5%;
height: 15px;
position: absolute;
bottom: 10px;
left: 100px;
}
</style>
</head>
<body style="background-color: #000;">
<div id="racket" class="racket"></div>
</body>
</html>
Ya tenemos nuestra raqueta colocada, Play Now !!
const KEY_NONE = false;
const KEY_LEFT = 'a';
const KEY_RIGHT = 'd';
let keyPressed = KEY_NONE;
let direction = '';
let position = 100;
window.onload = function() {
setTimeout(gameloop, 100);
};
function gameloop()
{
play();
// Llamamos a gameloop() la siguiente vez. Esto emula el while(true)
setTimeout(gameloop, 100);
}
// Loop principal del juego
function play() {
processInput();
update();
render();
}
function processInput() {
if ( keyPressed == KEY_LEFT ) {
direction = '+';
} else if ( keyPressed == KEY_RIGHT ) {
direction = '-';
}
}
function update() {
if ( direction == '+' ) {
position = position + 10;
} else if ( direction == '-' ) {
position = position - 10;
}
// Aquí actualizaríamos el estado del juego, y otros elementos como enemigos
}
function render() {
let racket = document.getElementById('racket')
racket.style.left = position + "px";
}
// -------
document.addEventListener('keydown', function(e){
if ((e.key === KEY_LEFT) || (e.key === KEY_RIGHT)) {
keyPressed = e.key;
}
});
document.addEventListener('keyup', function(e){
if ((e.key === KEY_LEFT) || (e.key === KEY_RIGHT)) {
keyPressed = KEY_NONE;
}
});
function sleep(ms) {
var unixtime_ms = new Date().getTime();
while(new Date().getTime() < unixtime_ms + ms) {}
}
Aunque no es mi fuerte javascript, he querido implementarlo en este lenguaje, para que veamos una forma diferente de implementar el while(true), y el sleep(), pero que veamos en esencia de que se trata de lo mismo, es decir, un bucle «infinito», y una espera en cada iteración.
Y ya tenemos nuestro cutre-juego interactuando con nosotros.