jueves, 10 de noviembre de 2011

Pequeña introducción a las SFML


Bienvenido(s) de nuevo mi(s) querido(s) lector(es). En el capítulo de hoy vamos a echar un pequeño vistazo a las librerías SFML, Simple Fast Multimedia Library. En el primer post dijimos que no íbamos a ver cómo inicializar cosas como las SDL y tampoco es la intención de esta entrada, pero considero adecuado dar un ligero vistazo a cómo se hace para poner un sprite en pantalla, hacer sonar un efecto de sonido, etc con las SFML. Tampoco vamos a ver cómo se hace todo en sus múltiples variantes, para eso mejor mirar la sección de tutoriales de la página de las SFML.

Las librerías SFML son un equivalente de las SDL, sólo que están programadas en C++ (en contraposición al C de las SDL) y orientadas a objetos. Dispone de varios "bindings" para diferentes lenguajes de programación aparte de C++ y está estructurado en varios módulos (gráficos, sonido, etc). Para dibujar usa openGL en vez de una solución por software como hacen las SDL (aunque parece ser que en la siguiente versión estas librerías también usarán openGL). Esto implica que con las SFML tenemos efectos como rotaciones, escalados, semitransparencias y demás de serie, a diferencia de las SDL donde no podemos hacer ni un simple "flip" con lo que nos ofrecen las librerías. Las SFML también se pueden usar como las SDL como un sistema multiplataforma para inicializar openGL y lidiar con el teclado y demás, si lo estimamos oportuno. Gracias a su diseño modular podemos usar sólo lo que necesitemos.

Las SFML incluyen librerías para hilos, teclado, joystick, ratón, crear ventanas, pintar gráficos, reproducir música (en formato OGG), reproducir efectos de sonido, etc. Todo multiplataforma (Windows, Linux y Mac OS X). Aquí vamos a ver la última versión estable, la 1.6. La versión 2.0 se supone que debe salir en un futuro más o menos próximo con interesantes novedades en el aspecto gráfico, entre otros un mecanismo para hacer batching. La versión 1.6 no hace esto, aunque para un juego como Culebrilla no supone ninguna diferencia.

Bueno, empecemos: nuestra aplicación comienza con un simple main() corriente y moliente. Para ver algo debemos crear un RenderWindow, que es la clase que usan las SFML como punto de partida de su infraestructura. Al crearlo podemos indicar los típicos parámetros de tamaño, tipo de ventana, si es a pantalla completa o no, esperar el retrazo vertical, etc.

Después de instanciar un RenderWindow ya podemos entrar en nuestro famoso bucle principal. Si la instancia de RenderWindow se llama "renderWindow", el código podría quedar así:

while(renderWindow.IsOpened())
{
 sf::Event currentEvent;
 while(renderWindow.GetEvent(currentEvent))
 {
  // El usuario ha cerrado el programa?
  if(sf::Event::Closed == currentEvent.Type || 
  (sf::Event::KeyReleased == currentEvent.Type &&
   sf::Key::Escape == currentEvent.Key.Code))
  {
   renderWindow.Close(); // Cerramos la aplicacion
  }
  else
  {
   // Pasamos el evento a nuestro juego (teclado, etc)
   game.processEvent(currentEvent);
  }
 }

 // Actualizamos nuestro estado
 game.update(renderWindow);
 
 // Pintamos los graficos
 game.draw(renderWindow);
 
 // Mostramos lo que hemos pintado en el paso anterior
 renderWindow.Display();
}

Aquí vemos que tenemos un while que se ejecuta mientras tenemos abierto renderWindow. Después viene la parte donde somos buenos invitados del SO y atendemos a sus eventos: mientras RenderWindow::GetEvent() nos devuelva true tenemos un evento pendiente del SO, sino ya hemos cumplido y salimos del while para hacer nuestras cosas. En este while de los eventos miramos si el evento es el cerrar la ventana (ya sea pulsando el botón o con alguna combinación como ALT + F4) o ha pulsado la tecla ESC. Si es así cerramos renderWindow, lo cual hace que salgamos de la aplicación. Si no es uno de estos eventos se lo pasamos a nuestro juego (la instancia "game") para que pueda responder correctamente al teclado, joystick o lo que considere adecuado.

Una vez atendidos los eventos del SO pasamos a ejecutar código del juego: actualizamos el estado y pintamos los diferentes elementos del juego. A estos dos métodos le pasamos la instancia renderWindow, ya que el método update() lo usará para calcular el tiempo (usando RenderWindow::GetFrameTime()) y el método draw() lo usará como destino de los gráficos, que vamos a ver a continuación.

La parte gráfica se base en las clases Image y Sprite, aunque hay más (dibujado de formas geométricas, texto, efectos de postproceso, etc). La primera es la encargada de guardar la información de las imágenes (normalmente cargada de un fichero gráfico como puede ser un png). La segunda es la que usa la información de Image y lo pinta a un RenderTarget. En nuestro caso es la instancia de RenderWindow, que deriva de RenderTarget. Esto implica que podemos tener una instancia de Image que es usada por múltiples instancias de Sprite, pudiendo cada uno de estos presentarla de una forma diferente (con su propio escalado, rotación, etc).

Cargar un fichero gráfico es tan sencillo como lo siguiente:

sf::Image img;
img.LoadFromFile("graphics/head.png");

Hay que tener en cuenta que la clase Image no controla la carga de ficheros repetidos. Es decir, si yo creo 5 instancias de la clase Image y en todas cargo el fichero "background.png", este fichero se habrá cargado 5 veces, consumiendo 5 veces la memoria correspondiente. Evitar cargar más de una vez un fichero queda en manos del cliente de la clase.

Respecto a la clase Sprite su funcionamiento también es sencillo. Por ejemplo para dibujar lo que hemos cargado en el ejemplo anterior haríamos lo siguiente, usando también la instancia de RenderWindow que hemos visto antes en el bucle principal:

sf::Sprite sprite(img);
sprite.SetPosition(0, 0);
renderWindow.Draw(sprite);

Esto dibujaría lo que tengamos en la variable img en la esquina superior izquierda. El sistema de coordenadas de la parte gráfica de las SFML es como vimos en la entrada sobre la actualización del estado. El ejemplo es de una pantalla de 800x600 píxeles:


La clase Sprite dispone además de varios métodos que son muy útiles para programar juegos: flip (efecto espejo) tanto en X como en Y, fijar la posición del sprite o moverlo, rotarlo, escalarlo, cambiar el tipo de blending, definir un rectángulo para dibujar sólo una parte del Image del Sprite, etc. Todo bien explicado en la documentación en su página web.

Para reproducir música o efectos de sonido la cosa es más o menos la misma: instanciar la clase correspondiente e indicarle la ruta del fichero a cargar. Además de cargar los datos de un fichero, las SFML ofrecen también métodos para cargar de un buffer en memoria, por ejemplo si queremos hacer algún sistema de paquetes para proteger nuestro contenido. Esto se aplica a todas las clases que cargan de un fichero (gráficos, música, etc).

No creo que merezca la pena ahondar más en las SFML, ya iremos viendo su uso con más detalle a lo largo del desarrollo de Culebrilla. Como colofón de esta entrada tenéis un ejemplo muy sencillo para ver lo fácil que es usar las SFML en la documentación.

¡Nos vemos en la siguiente entrada!

domingo, 6 de noviembre de 2011

[Culebrilla] Empezando con el juego: algunas notas sobre el tiempo

En el capítulo anterior hemos visto que hay que controlar el tiempo para que el juego vaya de la misma forma independientemente del hardware, y además los dos métodos habituales para hacerlo. En el capítulo de hoy hablaremos sobre ciertos detalles relativos a esto.

En muchas librerías de ventanas u otros entornos existe la posibilidad de crear un timer (se podría decir que en casi todos). Según la librería y el lenguaje de programación hay varias formas de usarlo pero conceptualmente es el mismo mecanismo: se indica un intervalo de tiempo y la infraestructura de la librería se encarga de llamar periódicamente a una función, método de un objeto, etc.

Efectivamente, esto es lo mismo que el fixed time step que vimos en la entrada anterior pero debemos andar con cuidado al usarlo. Dependiendo del entorno y cómo está implementado es posible que no nos sirva. Puede que la resolución que ofrezca no sea la suficiente, que haya un intervalo mínimo y de esta forma no podamos aprovechar al máximo el hardware, etc. También depende de lo que necesitemos: Para Culebrilla nos serviría más o menos cualquier timer que pudiésemos encontrar pero vamos a hacerlo a mano para aprender cómo sea hace. Un juego más exigente tendría que usar el bucle que hemos estado viendo a lo largo de las últimas entradas.

Relacionado con esto está el consumo de la CPU: nuestro bucle está iterando constantemente, no espera a ningún evento sino que como hemos visto consultamos (polling) lo que necesitamos. Esto hace que se consuma el 100% de tiempo de una CPU, incluso si no calculamos nada o hacemos cálculos muy simples. No es en función de la carga de nuestros cálculos sino del bucle sin bloqueos que tenemos en nuestro juego. Esto se puede suavizar llamando a la función de bloqueo del hilo (sleep(), delay(), etc) con un tiempo muy pequeño o directamente con el valor 0. Esto hará que el SO no esté ejecutando nuestra aplicación casi todo el tiempo, bajando un poco el consumo de CPU.

En la siguiente entrada miraremos un poco la librería SFML como preludio a empezar a programar nuestro primer juego. ¡Hasta pronto!

[Culebrilla] Empezando con el juego: El problema del tiempo

En la entrada anterior vimos cómo se encarna en código (de una forma muy simple) el famoso bucle general de los videojuegos. También dijimos que era una chapuza. ¿Por qué? Muy simple: porque no tiene en cuenta el tiempo y es totalmente dependiente de la velocidad de proceso del hardware donde funciona. Recordemos el código que usamos en la última entrada:

// Inicializamos las cosas...
Img bulletImg = loadImg("bala.png");
bool done = false;
float bulletPos = 0;
while(!done)
{
 // Actualizamos el estado
 bulletPos += 1;
 if(800 <= bulletPos)
 {
  done = true;
 }
 
 // Pintamos la bala
 drawSprite(bulletImg, bulletPos, 300);
}
// Liberamos las cosas
freeImg(bulletImg);

He añadido cosas como loadImg() y freeImg(), además de declarar las variables para completar un poco el ejemplo. Estas funciones son inventadas de raíz, pero el primero se supone que cargaría el fichero indicado y el segundo liberaría lo creado por el primero. Un poco como el new/delete de C++ (o malloc/free de C).

Bien, ¿Qué problema tiene el bucle para llamarlo chapuza? Pues que la bala se moverá un píxel por frame (por cada iteración). El problema de esto es que dependiendo del hardware donde se ejecute puede tardar más o menos en ejecutar cada iteración. Esto implica que por ejemplo en una máquina pueda hacer 100 iteraciones por segundo, pero en otra menos potente puede que sólo haga 20. Si este código fuese para una consola o un hardware específico quizás podríamos dejarlo tal cual, ya que tendríamos la garantía de que todas las unidades de ese hardware tardan lo mismo en ejecutar el mismo código. Si queremos que nuestro juego vaya en PC no podemos dejarlo así ni mucho menos.

Imaginemos este caso para tener más claro el problema: tenemos un PC que puede procesar 100 veces por segundo el bucle. En este caso diríamos que el juego va a 100 FPS, fotogramas por segundo o frames per second en inglés. Tenemos otro PC que es menos potente y que sólo puede ejecutar el bucle a 50 FPS. En un segundo, en el primer caso la bala recorrería 100 píxeles, mientras que en el mismo lapso de tiempo el segundo PC lo hubiese móvido sólo 50. Es decir, en el segundo PC el juego iría a la mitad de velocidad, afectando seriamente a la jugabilidad. Lo que es más, imaginemos que se lo pasamos a un amigo con un PC mucho mas potente que mueve el juego a 1000 FPS. ¿Quien podría jugar a un juego donde todo va tan deprisa?

Con esto queda claro que no podemos usar el bucle tal cual, necesitamos que el juego vaya de la misma forma en todos los PCs. Sea cual sea el hardware (dentro de unos límites, claro) las balas del juego deben moverse a la misma velocidad, el personaje debe saltar lo mismo, etc. ¿Cómo podemos hacerlo? La clave es que el movimiento y demás cálculos sean independientes de la frecuencia de las iteraciones (framerate), de forma que en vez de expresar la velocidad de una bala en píxeles/frame lo expresemos como píxeles/segundo. Cuando en el bucle que tenemos ahí arriba hacemos bulletPos += 1 estamos indicando que la bala se mueve 1 píxel por frame, cosa que hemos visto no es aceptable. Debemos poder moverlo 1 píxel por segundo, es decir, controlar el tiempo para poder hacer los cálculos correctamente.

Hay 2 formas habituales de hacer el control del tiempo: "Variable time step" y "fixed time step". Ambos parten de la misma idea: calculamos el tiempo que nos ha llevado ejecutar el último frame y usamos este valor para actualizar el estado actual. Veamos cómo funciona cada método:

Variable time step
En cada iteración obtenemos el tiempo que ha pasado desde el último frame hasta el momento actual. Usando este valor se escalan todos los cálculos que dependan del tiempo. El bucle de nuestro ejemplo quedaría así:

float oldTime = currentTime();
while(!done)
{
 float now = currentTime();
 // Calculamos cuanto segundos han pasado desde el último frame
 float deltaTime = now - oldTime;
 oldTime = now;
 // Actualizamos el estado
 bulletPos += 1 * deltaTime;
 if(800 <= bulletPos)
 {
  done = true;
 }
 
 // Pintamos la bala
 drawSprite(bulletImg, bulletPos, 300);
}

La función currentTime() devuelve el número de segundos que han pasado desde que se inició el programa con el tipo float. Es decir, si ha pasado medio segundo por ejemplo devolverá el valor 0.5. Es una función inventada para el ejemplo como puede ser drawSprite(). Declaramos la variable oldTime y le asignamos los segundos que han pasado hasta ese momento.

En cada iteración calculamos el tiempo que ha tardado en procesarse el último frame: restamos los segundos que han pasado hasta este momento con los que obtuvimos en la iteración anterior y lo asignamos a la variable "deltaTime". Luego asignamos los segundos del momento actual (la variable "now") a la variable "oldTime" para tener este valor en la siguiente iteración.

Una vez calculado deltaTime lo usamos para escalar el desplazamiento de la bala. Como se mueve un píxel por segundo y han pasado deltaTime segundos, añadimos a bulletPos la cantidad 1 * deltaTime. Así, si ha pasado un segundo se movería 1 píxel, si han pasado 10 segundos se movería 10, y si ha pasado una décima de segundo se movería 0.1 píxeles. Como se ve la posición debe ser un valor de coma flotante, sino perdemos esta precisión de mover las cosas menos de un píxel.

Fixed time step
La idea de este método es definir un intervalo fijo para los cálculos y ejecutar tantas veces estos cálculos como sea necesario. Nuestro bucle quedaría así implementando este método:

const float LogicTime = 1.0 / 60.0; // 1/60 segundos cada paso de la lógica
float oldTime = currentTime();
float deltaTime = 0;
while(!done)
{
 float now = currentTime();
 deltaTime += now - oldTime;
 oldTime = now;

 while(LogicTime < deltaTime)
 {
  // Actualizamos el estado
  bulletPos += 1.0 / 60.0;
  if(800 <= bulletPos)
  {
   done = true;
  }

  deltaTime -= LogicTime;
 }

 // Pintamos la bala
 drawSprite(bulletImg, bulletPos, 300);
}

Primeramente definimos la frecuencia a la que queremos que se ejecute la lógica del juego. En este ejemplo lo hemos puesto a 60 veces por segundo. Después declaramos la variable oldTime como antes pero también definimos la variable deltaTime, la cual estaba dentro del while en el caso del variable time step. Esto es necesario para llevar un control preciso sobre el tiempo, como veremos ahora.

Dentro del primer while calculamos el tiempo que ha pasado del frame anterior, pero en vez de asignar este lapso de tiempo a deltaTime se lo sumamos. A continuación tenemos otro while, que es la parte fundamental del fixed time step: actualizamos el estado tantas veces como LogicTime-s contenga deltaTime. Es decir, si deltaTime es 1 segundo este while se ejecutará 60 veces. Si es 0.5 segundos se ejecutará 30 veces y si es 1/60 segundos se ejecutará 1 vez. Si deltaTime es un valor inferior a 1/60 segundos no se ejecutará nada, se pintará la bala y estaremos otra vez al principio del bucle general. Es aquí donde se ve el sentido de añadir y no asignar el lapso de tiempo a deltaTime: si el lapso de tiempo es inferior a LogicTime hay que ir acumulando los lapsos de tiempo hasta llegar a ser igual o superior a este valor para poder ejecutar la lógica del juego. De la misma forma si el framerate no es un múltiplo de LogicTime al terminar de ejecutar el segundo while deltaTime tendrá cierto tiempo residual (por decirlo de alguna forma) y este tiempo no se puede desechar, hay que tenerlo en cuenta para la siguiente iteración.

Como se puede observar a bulletPos ya no le sumamos 1 sino 1/60, que es lo que se tiene que mover en una frecuencia de 60 veces por segundo si queremos que se mueva 1 píxel por segundo.

Ya hemos visto los dos métodos típicos para controlar el tiempo. Como veis el concepto general es similar: cuanto más grande sea el lapso de tiempo más movemos la bala, ya sea haciendo más exagerado su movimiento o ejecutando más veces la lógica del juego y viceversa (a menor lapso de tiempo menos desplazamiento por frame). Además ambos métodos también sirven para hacer frente a lapsos de tiempo no constantes, ya que lo normal suele ser que varíe durante la ejecución. Es decir, en cada iteración del bucle principal podemos tener un lapso de tiempo diferente.

Dicho esto ¿Qué método escogemos? Depende un poco de las preferencia de cada uno. En mi caso prefiero el fixed time step, lo encuentro más sencillo y no hay que ir multiplicando deltaTime a cada variable dependiente del tiempo. Para el Culebrilla usaremos este método y también para los futuros juegos del blog. Los dos métodos tampoco son excluyentes: se puede usar el fixed time step para la lógica del juego y el variable time step para los gráficos, interpolando el movimiento y las animaciones con deltaTime. Por último aquí tenéis un artículo (en inglés) hablando sobre este tema, por si queréis otro enfoque: http://www.iguanademos.com/Jare/Articles.php?view=FixLoop

En la siguiente entrada hablaremos un poco más sobre el control del tiempo. ¡Hasta pronto!


viernes, 4 de noviembre de 2011

[Culebrilla] Empezado con el juego: actualización del estado

En la entrada anterior hemos visto el bucle principal que tienen todos los juegos y cómo hay un paso que es el "corazón" del juego, donde se hacen los cálculos para que todo funcione. Es decir, la parte donde se actualiza el estado del juego.

Pongamos un caso concreto muy simple para entender mejor el concepto de actualizar el estado: tenemos una bala que empieza a la izquierda del todo (X = 0) y acaba en la derecha (X = 800). El estado en este caso sería la posición 2D de la bala, o si queremos reducirlo aún más, su posición en el eje horizontal (ya que en el vertical no hay cambios). En el sistema de coordenadas del ejemplo el eje horizontal es el X y el vertical el Y. El origen (0, 0) está arriba a la izquierda y abajo a la derecha es (799, 599). Por lo tanto tenemos una pantalla de 800x600 píxeles:


Recordemos el bucle del juego:
Inicializar
Mientras la cosa sigue:
  Atender 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
Desinicializar
Vamos a ver cómo sería a grandes rasgos cada paso en nuestro ejemplo:

Inicializar
Creamos una variable llamada bulletPos y fijamos su valor a 0. Cargamos el gráfico de la bala y por último creamos la variable booleana done, que fijamos a false. Como veis uso nombres en inglés en el código.

Mientras la cosa sigue
Comprobamos si done es true. Si es así salimos del bucle, sino hacemos otra pasada. Básicamente sería esto:

while(!done)
{
// ...
}
Atender eventos del SO y Comprobar entrada del usuario
En este ejemplo obviamos estos pasos

Pintar los gráficos
Aquí con la librería de gráficos que tengamos dibujamos el gráfico que hemos cargado en el paso de inicialización en la posición (bulletPos, 300)

Desinicializar
Liberamos los recursos que hemos cargado, es decir, el gráfico de la bala

Nos falta el paso Actualizar estado, que vamos a explicar ahora: tenemos que hacer que la bala se mueva y que cuando llegue a la derecha salir del bucle. Esta condición es fácil, bastaría con algo así:

if(800 <= bulletPos)
{
 done = true;
}

Con esto el while saldría del bucle. Ahora el movimiento: tenemos la posición del eje X de la bala en la variable bulletPos, y queremos que la bala vaya de izquierda a derecha. Muy fácil: le añadimos una cantidad a bulletPos en un for y listos. Algo así:

for(int i = 0; i < 800; ++ i)
{
 bulletPos += 1;
}

Ejecutamos el programa y vemos que la bala aparece directamente en la derecha. Bueno, no lo veríamos porque el programa saldría enseguida ya que al hacer la primera pasada del while bulletPos es igual a 800, en la segunda pasada done sería true y al volver a comprobar la condición del while este ya saldría del bucle.

¿Entonces cómo se hace el movimiento? La clave es entender que lo que va dentro del bucle es un pasito del juego, un frame como dijimos en la entrada anterior. En cada frame la bala se mueve un poquito, creando así la ilusión del movimiento. El bucle correcto quedaría así:

while(!done)
{
 // Actualizar estado
 bulletPos += 1;
 if(800 <= bulletPos)
 {
  done = true;
 }
 /* Pintar la bala. bulletImg es donde tenemos guardado  la imagen de la bala y que la función para pintar es drawSprite(Img, posX, posY) */
 drawSprite(bulletImg, bulletPos, 300);
}

La función drawSprite me la he inventado para el ejemplo por lo que carece de importacia. Lo importante es lo que se hace con la variable bulletPos: En cada iteración, "movemos" la bala una unidad a la derecha, comprobamos que ha llegado o no al borde de la pantalla y luego pintamos la bala. Según el valor que le añadamos a bulletPos la bala irá más o menos rápida.

En la siguiente entrada veremos porque esto es conceptualmente correcto pero una chapuza.

¡Hasta pronto!

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!

Proyecto 1: Culebrilla

Empecemos con nuestro primero proyecto, y empecemos de una forma simple: haremos nuestra propia versión del conocido juego "snake". Uno de los juegos más jugados de toda la historia por cierto, ya que venía de serie en muchos terminales Nokia.

El objetivo del juego es controlar a la culebra para comer la comida que va apareciendo por la pantalla y aguantar el máximo posible de tiempo con ella viva. Cada vez que come esta comida (su cabeza colisiona con este elemento) se le añade una "bola" más a su cuerpo. Es decir, cuanto más come mas crece su cola. Si la cabeza colisiona con una esquina la culebra muere, de la misma forma que si colisiona con alguna parte de su cuerpo también muere.

La vista del juego es cenital, es decir, el observador está mirando el juego desde arriba hacia abajo. La culebra puede moverse arriba, abajo, izquierda o derecha. Nunca se para, siempre va para adelante. El cuerpo de la culebra es articulado, como una serie de bolas atadas entre sí con una cuerda.

Nuestra versión, llamada "Culebrilla", tendrá las siguientes características generales:
  • Resolución de 800x600
  • Control por el teclado
  • Sólo tendrá un modo de juego, tipo "survival" (aguantar todo lo posible)
  • Una única pantalla con 2 estados: esperando a que el usuario pulse la tecla espaciadora (mostrando un mensaje para ello, además del record del tiempo máximo) y jugando
Aquí un montaje muy simple de cómo se verá el juego en mitad de la partida:


Como veis en este blog no vamos a ver unos gráficos de última generación, quizás ni siquiera bonitos. De hecho el marcador no está ni centrado, pero para mostrar el concepto esto es más que suficiente.

Bien, tenemos ese fondo gris, que es donde se moverá nuestra culebrilla. Rodeando el fondo gris tenemos unas casillas más o menos marrones. Estas casillas representan los límites del área por donde puede moverse la culebrilla. Si su cabeza choca contra ellas hemos perdido el juego.

Nuestra culebrilla está formada por la cabeza y luego un número de bolas que representan su cuerpo. La bola roja es la comida, que irá apareciendo en lugares aleatorios a lo largo de la partida. Por último, arriba del todo tenemos los segundos que lleva el jugador jugando sin morir y a su derecha el tiempo máximo que alguien (desde que se ejecutó el juego) ha aguantado vivo.

En la siguiente entrada vamos a ver cómo diseñamos nuestro código para que podamos implementar el juego.

¡Hasta pronto!

Presentación del blog

Bienvenidos al blog "El taller de videojuegos". Este blog nace con la intención de enseñar a programar videojuegos. Si siempre has querido programar juegos pero no sabes cómo hacerlo o por donde empezar, este es tu blog. Aquí no se hablará de cómo inicializar SDL y cosas por el estilo, ya hay cientos de tutoriales que lo explican con todo lujo de detalles. Aquí se mostrará cómo programar juegos completos paso a paso, no técnicas aisladas.

El enfoque que pienso seguir es presentar un juego (por ahora clásicos sencillos), mostrar cómo lo concibo con una pequeña maqueta de sus pantallas, elementos, etc. y luego programarlo, explicando paso a paso el proceso. Obviamente este "paso a paso" no será una explicación línea a línea sino sólo de las partes significativas, pero ya se irá viendo el nivel de detalle que requiere cada caso. Por supuesto en los comentarios se podrá podrá preguntar cualquier aspecto que no haya quedado claro.

El entorno que pienso usar será Visual C++ Express 2010 con las librerías SFML. Obviamente esto implica que usaré C++ para programar los juegos. Es un lenguaje que me gusta y además permite trabajar tanto a alto como a bajo nivel. Aunque los proyectos se suban como ficheros de proyectos de VC++ también subiré el proyecto en cmake, para que se pueda compilar sin demasiados problemas en otros entornos y sistemas operativos. No descarto en absoluto usar otro lenguaje o entorno para futuros proyectos, pero por ahora usaré este.

Por último comentar que lo que se mostrará aquí no será la mejor forma ni la definitiva de hacer las cosas, se mostrará mi forma de implementar los diferentes aspectos de los juegos. Simplemente una alternativa entre otras muchas.

Lo dicho, bienvenidos de nuevo a este blog. ¡Espero que disfrutemos juntos de la programación de juegos!