jueves, 3 de noviembre de 2011

[Culebrilla] Empezado con el juego: bucle principal

En la última entrada vimos qué pinta va a tener nuestra versión del snake. Perfecto, ¿Pero ahora que? ¿Cómo se hace para que la culebra vaya por ahí, responda al teclado, detecte que ha conseguido la comida o que se ha estrellado contra una pared? En definitiva, ¿Cómo se implementa esto? Responder a este tipo de preguntas es el objetivo de este blog, así que vamos a empezar a ver cómo podemos montar un juego con nuestros conocimientos de programación. Por ahora explicaremos los conceptos en general, sin ceñirnos a nuestro juego para así tener una mejor comprensión de lo que estamos tratando y su porque. Más tarde ya veremos cómo traducimos este conocimiento al código del juego.

A grandes rasgos un videojuego es un programa que está en un bucle constante, en contraposición a una aplicación normal (como puede ser un editor de texto por ejemplo) que suele estar a la espera de algún evento: clics del usuarios, entrada del teclado, etc. Un videojuego normalmente no espera a estos eventos sino que consulta (lo que en inglés se llama polling) este tipo de cosas desde su bucle principal. La estructura de la ejecución de un videojuego sería la siguiente:
Inicializar
Mientras la cosa sigue:
  Comprobar entrada del usuario (teclado, ratón, joystick, etc)
  Actualizar el estado (enemigos, disparos, reproducir sonidos, etc etc)
  Pintar los gráficos
Desinicializar
En el paso "Inicializar" creamos las estructuras que necesitamos para nuestro juego: cargar ficheros, fijar la resolución de la pantalla, etc. Después de esto entramos en el bucle principal, y la primera acción que hacemos es el "polling" de los dispositivos de entrada.

El siguiente paso es donde por norma general suele estar el meollo de un videojuego, el paso de "Actualizar estado". Aquí, en función de lo que hemos obtenido en el paso de comprobar la entrada, del estado anterior y en definitiva en base a todos los factores significativos para el juego se decide qué hacer: crear la explosión de la nave, añadir puntos, quitar una vida, mover el fondo, comprobar colisiones, etc.

Después hacemos el paso "Pintar los gráficos". Cada vez que pasamos por aquí decimos que hemos pintado un "frame"  (fotograma), por lo que normalmente a cada paso del bucle se le llama frame. Efectivamente, en los videojuegos la ilusión del movimiento se consigue como en las películas o en la televisión: mostrando fotogramas ligeramente diferentes uno detrás de otro a gran frecuencia (normalmente mayor que los alrededor de 24 fotogramas por segundo de las películas, pero esto depende mucho de la carga del juego y el hardware).

Una vez hecho este paso ya tenemos (entre otras cosas) las posiciones de los elementos visuales del juego, por lo que sólo nos falta dibujarlos. Cuando esto se acaba volvemos otra vez al principio del bucle, obtenemos la entrada del usuario, hacemos todos los cálculos pertinentes y dibujamos otro frame y vuelta a empezar otra vez. En algún momento saldremos de este bucle, liberamos todo lo que hemos obtenido a lo largo del programa (normalmente memoria del sistema) y nuestro juego ya termina su ejecución.

Ya sabemos que un juego debe estar en un bucle casi todo el tiempo. ¿Cómo podemos implementarlo en C++ desde cero, por así decirlo? Es decir, ¿Sin usar un entorno ya orientado a crear videojuegos? La primera cosa que se nos ocurriría sería usar un for o un while, pero no podemos hacer esto sin más en un sistema operativo (SO) moderno. El SO asigna tiempo de CPU a cada proceso según su criterio. Si nuestro programa estuviese todo el rato en ese while no respondería a los eventos del SO, por lo que éste asumiría que se ha quedado bloqueado (el típico mensaje de "este programa ha dejado de responder" de Windows por ejemplo). Si fuese una consola tipo Mega Drive o un SO como MS-DOS donde un proceso se hace dueño de todo el sistema sí podríamos hacerlo, pero en un SO moderno debemos hacerlo de otra forma. ¿Cómo? Simplemente atendiendo a los eventos que nos pasa el SO. El bucle quedaría así:

Mientras la cosa sigue:
  Atender a eventos del SO
  Comprobar entrada del usuario (teclado, ratón, joystick, etc)
  Actualizar el estado (enemigos, disparos, reproducir sonidos, etc etc)
  Pintar los gráficos

En cada vuelta del bucle miraremos si hay eventos del SO (esto lo hacemos mediante la librería SFML) y los atenderemos si los hay. Los eventos del SO son cosas como que el usuario ha pulsado una tecla, ha movido el ratón, ha pulsado el botón de cerrar de la ventana, etc. Una vez cumplido con nuestro anfitrión, seguimos a lo nuestro.

Seguimos en la próxima entrada, ¡Hasta pronto!

No hay comentarios:

Publicar un comentario