Programación de juegos con SDL

Cable


Table of Contents
1. Introducción.
2. Instalando la librería
3. Librerías adicionales
4. Un programa simple
5. Compilando con SDL
6. Conceptos básicos
7. Estructura de un programa interactivo
8. Un programa interactivo
9. Programa interactivo con gráficos
10. Conclusión
11. Apéndice: fuentes comprimidos

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found in GNU Free Documentation License.


1. Introducción.

Una de las principales dificultades a las que se enfrenta un programador de juegos, son las complejas librerías que tiene que dominar para poder acceder al sistema operativo. Un juego requiere acceso a mas funciones del sistema que cualquier otro programa: gráficos, sonido, redes, ficheros, etc.

La libreria Simple Directmedia Layer (SDL) fue diseñada para evitar todo ese trabajo; proporciona un acceso simple, fácil de aprender, a todos los servicios del sistema que se requieren para hacer un juego.

En lugar de perder semanas escribiendo código para lograr que X-Windows te permita generar gráficos a pantalla completa, SDL se encarga de ello; en lugar de perder días escribiendo una funcion para cargar imágenes, SDL provee las suyas; ni siquiera necesitas liarte con los sockets, pues SDL contiene funciones para establecer sesiones de juego en red.

Añade a eso el hecho de que la SDL es portable (existe SDL para Windows y MacOS) lo cual permite que tu código pueda ser usado en otros sistemas operativos sin alterar una sola línea, y comprenderás por qué cada vez más juegos para Linux se están haciendo en SDL.

SDL es para linux lo que DirectX es para Windows; o lo sería, si DirectX fuese multiplataforma y fácil de aprender :)


2. Instalando la librería

Si no tienes la SDL instalada, el primer paso es descargarla de la dirección http://www.libsdl.org/; las versiones de la librería se hallan concretamente en <http://www.libsdl.org/release/>.

Puedes elegir descargar una distribucion binaria o el codigo fuente; para hacer tus propios programas el SDL, necesitarás el fuente; puedes descargar el tar.gz de la siguiente dirección: <http://www.libsdl.org/release/SDL-1.2.4.tar.gz> para la version 1.2.4 (la mas avanzada al momento de escribir esto; descárgate la más avanzada que haya disponible).

Luego, puedes instalarla de los fuentes con:

  tar -zxf SDL-1.2.4.tar.gz
  cd SDL-1.2.4
  ./configure
  make
  su root
  make install

3. Librerías adicionales

Aunque SDL te proporciona todo lo necesario para crear programas multimedia y juegos, existen muchas librerias que puedes instalar para obtener funciones extra; todas ellas están disponibles en <http://www.libsdl.org/libraries.php>.

No vamos a tratar aquí esas librerías, pues ello tomaría demasiado espacio; pero aquí mencionamos algunas de ellas:

* SDL_image: SDL provee funciones para cargar imágenes en formatos BMP (sin comprimir); pero con la librería SDL_image podrás cargar también los formatos más eficientes y utilizados, como JPEG, PNG y TIFF.

* SDL_mixer: SDL puede tocar sonido en formato WAV. Pero la SDL_mixer te permite cargar y tocar sonido y música en formatos como MOD, XM, S3M, MIDI, Ogg Vorbis y MP3.

* SDL_net: esta librería te facilita la creación de juegos en red (multijugador).

* SDL_ttf: te permite cargar fuentes TrueType.

Ten en mente, sin embargo, que si utilizas cualquiera de las librerías adicionales, aquellas máquinas donde se vaya a utilizar tu programa requerirá tener dichas librerías instaladas también, a menos que generes un ejecutable estático (el cual será más grande cuantas más librerías incluyas).


4. Un programa simple

A continuación veremos un programa elemental que utilice SDL; todo lo que hace es mostrar en pantalla tres rectángulos de colores concéntricos, permanece así durante tres segundos, y luego finaliza.

Antes que nada, aquí está el código fuente (para extraer todos los fuentes de éste artículo, ver el apéndice al final):

-------------------<-Cut Here!-><primersdl.c><-Cut Here!->--------------------
#include <SDL.h>   /* Insertamos la cabecera de SDL*/
#include <stdio.h>

main(){
  SDL_Surface *pantalla;  /* Esta variable representa la pantalla */
  SDL_Rect rect;  /* Esta otra representa un área rectangular en la pantalla */
  Uint32 color;  /* Y esta representa el color de un pixel en la pantalla */

  if(SDL_Init(SDL_INIT_VIDEO) == -1){  /* Inicializamos la libreria */
    printf("No se pudo iniciar el video: %s\n", SDL_GetError());
    exit(-1);
  }

  /* Ponemos el sistema en modo 640x480, pantalla completa */
  pantalla = SDL_SetVideoMode(640,480,16, SDL_ANYFORMAT | SDL_DOUBLEBUF
                                                             | SDL_FULLSCREEN);
  if(!pantalla){
    printf("No se pudo iniciar el modo de pantalla: %s\n", SDL_GetError());
    SDL_Quit();
    exit(-1);
  }

  /* Dibujamos un rectángulo de color rojo */
  color = SDL_MapRGB(pantalla->format, 255, 0, 0);
  rect = (SDL_Rect) {100,100,440,280};
  SDL_FillRect(pantalla, ▭, color);

  /* Dibujamos un rectángulo de color verde */
  color = SDL_MapRGB(pantalla->format, 0, 255, 0);
  rect = (SDL_Rect) {150,150,340,180};
  SDL_FillRect(pantalla, ▭, color);

  /* Dibujamos un rectángulo de color azul */
  color = SDL_MapRGB(pantalla->format, 0, 0, 255);
  rect = (SDL_Rect) {200,200,240,80};
  SDL_FillRect(pantalla, ▭, color);

  SDL_ShowCursor(SDL_DISABLE);

  SDL_Flip(pantalla); /* Refrescamos la pantalla */

  SDL_Delay(3000); /* Esperamos a que pasen 3 segundos */
  SDL_Quit();  /* Quitamos la imagen y desinicializamos la SDL */
}
-----------------<-Cut Here!-><End primersdl.c><-Cut Here!->------------------

5. Compilando con SDL

Despues de recortar el código indicado arriba, y salvarlo como "primersdl.c", lo puedes compilar (en Unix) con los siguientes comandos:

  gcc `sdl-config --cflags` -c primersdl.c
  gcc -o primersdl primersdl.o `sdl-config --libs`

¡No olvides las comillas invertidas ``! Sirven para ejecutar el programa "sdl-config" y reemplazar lo que produzca por los flags y librerias de SDL, respectivamente.

Pero, si estas compilando un programa bajo una plataforma no-unix (por ejemplo, Windows o MacOS), no podrás utilizar el truco de las comillas inversas. En ese caso, ejecuta por separado los comandos

  sdl-config --cflags
  sdl-config --libs

y sustituye sus resultados en el compilador; por ejemplo, si el comando "sdl-config --cflags" produjo como resultado las opciones de compilación "-I/usr/local/include -I/usr/local/include/SDL -D_REENTRANT", y el comando "sdl-config --libs" te da como resultado las opciones de enlace "-L/usr/local/lib -lSDL -lpthread", sustitúyelos en los comandos:

  gcc -I/usr/local/include -I/usr/local/include/SDL -D_REENTRANT -c primersdl.c
  gcc -L/usr/local/lib -lSDL -lpthread -o primersdl primersdl.o

Sigue un proceso similar para compilar todos los programas de éste artículo, así como los programas en SDL que tú mismo hayas creado; o, mejor aún, incorpóralo todo en un cómodo Makefile:

-------------------<-Cut Here!-><Makefile><-Cut Here!->--------------------
LIBS= `sdl-config --libs`                                                       
INCS= `sdl-config --cflags`                                                     
                                                                                
PROG= primersdl                                                                 
                                                                                
all: $(PROG)                                                                    
                                                                                
primersdl:  primersdl.o                                                         
                                                                                
$(PROG):                                                                        
        gcc -o $@ $^ $(LIBS)                                                    
        strip $@                                                                
                                                                                
.c.o:                                                                           
        gcc $(INCS) -c $< -o $@                                                 
                                                                                
clean:                                                                          
        rm $(PROG) core *.o

-------------------<-Cut Here!-><Makefile><-Cut Here!->--------------------

ALERTA: Las identaciones del texto en un Makefile deben hacerse con *tabuladores*, no con espacios; de lo contrario el "make" no funcionará.

Para modificar este Makefile para ser usado con los demás programas, tan sólo necesitas modificar las líneas 4 y 8; és decir, decirle al Makefile cómo se llama el ejecutable (en este caso, primersdl) y decirle cómo se llaman los archivos objeto necesarios para crear el ejecutable (en este caso, "primersdl.o" para crear "primersdl").


6. Conceptos básicos

Antes de proseguir con programas más complejos, conviene dar una breve explicación de cómo funciona el programa básico que hemos visto anteriormente.

Primero que nada, compila el programa y ejecútalo. Si todo ha salido bien, verás como tu pantalla se pone en blanco, y aparecen tres rectángulos concéntricos, uno rojo otro verde, y otro azul. Éstos permanecerán durante tres segundos, y luego desaparecerán, permitiéndote interactuar nuevamente con tu entorno gráfico (el programa mismo no tiene nada con qué interactuar). Ahora, veamos cómo se logró ese efecto.

Lo primero: todos los archivos con código en lenguaje-C que vayan a hacer uso de la librería SDL deben insertar esta cabecera:


  #include <SDL.h>   /* Insertamos la cabecera de SDL*/

(La de stdio.h no es necesaria para usar SDL, pero este programa hace uso de la función printf, por lo que ha de incluirla).

Para casi todo lo que hagamos en SDL, necesitaremos una (o varias) variables de tipo SDL_Surface:

  SDL_Surface *pantalla;  /* Esta variable representa la pantalla */

Una de las cosas importantes que se debe tener en mente al escribir un juego o programa multimedia, es que *no* se deben dibujar los gráficos directamente en la pantalla; si se hiciera eso, el usuario vería las imágenes aparecer una tras otra, a medida que el programa va dibujando las cosas, o, si se dibujan con rapidez, en un parpadeo estroboscópico.

En lugar de ello, se deben dibujar los gráficos en una matriz de pixeles en memoria y, cuando se haya terminado, volcar el contenido de la matriz en la pantalla; así el usuario tendrá la sensación de que todo sucede instantáneamente.

Para eso es la estructura SDL_Surface; dicha estructura se utiliza para guardar imágenes; si cargas un archivo BMP, por ejemplo, la imágen será almacenada en una variable SDL_Surface; y, en lugar de dibujar las cosas directamente en la pantalla, las dibujamos en una imágen en memoria, representada por una variable tipo SDL_Surface (que en este caso llamamos "pantalla"); cuando hemos acabado de dibujar todo, volcamos el contenido de la variable "pantalla" en la pantalla del ordenador, usando la función SDL_UpdateRect.

En este ejemplo, no cargaremos imágenes de BMPs; generamos los gráficos usando la función SDL_FillRect, que rellena un área rectangular dentro de un SDL_Surface con un mismo color. SDL utiliza variables de tipo SDL_Rect para definir áreas rectangulares:

  SDL_Rect rect;  /* Esta otra representa un área rectangular en la pantalla */

También usaremos la variable "rect" con SDL_UpdateRect, pues esta requiere que se le indique en qué parte de la pantalla va a volcar la imágen; usualmente definiremos para ello un área rectangular que abarque la pantalla entera.

Tambien necesitaremos especificar el color con el que vamos a dibujar esas áreas rectangulares; en SDL, los colores se representan con números de 32 bits; en Linux, una variable tipo "int" utiliza 32 bits, así que podrías usar "int color"; pero si algún día quieres compilar tus programas en un sistema operativo de 16 bits (DOS) o 64 bits (algún día en el futuro), ese código no funcionará; por ello, es mejor utilizar el tipo de datos "Uint32" que viene con SDL:

  Uint32 color;  /* Y esta representa el color de un pixel en la pantalla */

Esas son todas las variables que usamos; ahora pasaremos a la acción: primero, inicializamos la libreria SDL:

  if(SDL_Init(SDL_INIT_VIDEO) == -1){  /* Inicializamos la libreria */
    printf("No se pudo iniciar el video: %s\n", SDL_GetError());
    exit(-1);
  }

El argumento SDL_INIT_VIDEO indica que sólo vamos a usar las rutinas de Video de SDL, no las de sonido ni ninguna otra; luego, ponemos el vídeo en modo pantalla completa a 640x480, 16 bits por pixel (casi todos los juegos modernos funcionan a esta resolución):

  /* Ponemos el sistema en modo 640x480, pantalla completa */
  pantalla = SDL_SetVideoMode(640,480,16, SDL_ANYFORMAT | SDL_DOUBLEBUF
                                                             | SDL_FULLSCREEN);
  if(!pantalla){
    printf("No se pudo iniciar el modo de pantalla: %s\n", SDL_GetError());
    SDL_Quit();
    exit(-1);
  }

Nótese como, después de haber hecho SDL_Init, siempre hay que hacer SDL_Quit antes de salir, aún si es en caso de error, como en este caso.

El argumento SDL_ANYFORMAT indica que no nos importa si la pantalla no se puede poner a 16 bits por pixel, nos da igual si lo pone a 8, 24 o 32; con muchos juegos, esto es aceptable, con otros no.

El argumento SDL_DOUBLEBUF le indica que la variable "pantalla" contendrá dos buffers; escribirá a uno mientras muestra el otro por pantalla, y luego los intercambia con SDL_Flip; ello le permitirá generas las animaciones con mayor suavidad.

El argumento SDL_FULLSCREEN le indica que se ponga en modo de pantalla completa; sin este argumento, el programa mostrará los gráficos en una ventana de 640x480.

Atención: no es recomendable que uses el modo de pantalla completa mientras estás probando tu programa; lo mejor es que no utilices el SDL_FULLSCREEN hasta que estés seguro de que tu programa no se cuelga fácilmente; si tu programa se cuelga mientras está en modo de ventana, sólo cierras la ventana o haces un kill al programa, y problema resuelto; pero si se cuelga en modo pantalla completa, será muy dificil cancelarlo, ya que no podrás acceder al entorno gráfico.

Mientras tu programa esté en fase de pruebas, puedes anular el modo de pantalla completa poniendo el argumento entre comentarios:

  pantalla = SDL_SetVideoMode(640,480,16, SDL_ANYFORMAT /*| SDL_FULLSCREEN*/);

A continuación, vamos a dibujar un rectángulo en la pantalla; de ello se encargan estas tres líneas:

  color = SDL_MapRGB(pantalla->format, 255, 0, 0);
  rect = (SDL_Rect) {100,100,440,280};
  SDL_FillRect(pantalla, ▭, color);

La primera línea nos guarda en la variable "color" el color rojo; todos los colores se representan mezclando tres colores básicos: rojo, verde y azul; las proporciones de los tres colores se expresan en números enteros entre 0 y 255; así, el color rojo se representa como <255,0,0> (todo rojo, nada verde, nada azul), el color verde como <0,255,0>, el azul <0,0,255>; el amarillo se expresa como <255,255,0> (mezcla de rojo y verde), el negro como <0,0,0> y el blanco como <255,255,255>; en el ejemplo usamos <255,255,0>, es decir, el rojo.

La segunda línea define un rectángulo cuya esquina superior izquierda está en las coordenadas 100,100 de la pantalla, y que tiene 440 pixeles de ancho y 280 de alto.

La tercera línea se encarga de dibujar un rectángulo en la variable "pantalla", correspondiente al rectángulo que definimos en la variable "rect" en la línea anterior, y lo rellena con el color que pusimos en la variable "color".

Este proceso se repite otras dos veces, para crear los rectángulos verde y azul.

Ahora, usamos esta función para hacer que desaparezca el cursor, pues este programa no utiliza el ratón, y el cursor sólo sería un estorbo:

  SDL_ShowCursor(SDL_DISABLE);

Una vez dibujados los tres rectángulos, intercambiamos el doble-buffer; con ello el buffer al que hemos dibujado todo se pintará instantáneamente en la pantalla:

  SDL_Flip(pantalla); /* Refrescamos la pantalla */

Luego, utilizamos la función SDL_Delay para que el programa espere 3 mil milisegundos (es decir, 3 segundos) antes de continuar; si no hiciéramos esto, los tres rectángulos sólo durarían una fracción de segundo, y el usuario no tendría tiempo de verlos:

  SDL_Delay(3000); /* Esperamos a que pasen 3 segundos */

Y, finalmente, quitamos el modo pantalla completa con SDL_Quit:

  SDL_Quit();  /* Quitamos la imagen y desinicializamos la SDL */

7. Estructura de un programa interactivo

Cuando hacemos un programa interactivo, como un juego, hay un montón de cosas que este programa tiene que hacer *al mismo tiempo*; dibujar las imágenes en pantalla, tocar los sonidos, leer el input del usuario (teclado, ratón y joystick) y, si es multijugador, leer/escribir datos a la conexión de red. Todo ello debe suceder simultáneamente, pero los programas informáticos sólo pueden ejecutar un paso a la vez.

Estarás pensando: "pero los sistemas operativos modernos (como linux, windows y Mac OS X) són capaces de ejecutar varios procesos al mismo tiempo; así que es cosa de delegar cada tarea a un proceso".

Pero la realidad es que dividir una tarea entre múltiples procesos añadiría la complejidad de coordinarlos entre sí, lo cual se convierte rápidamente en una pesadilla; además, los juegos han hecho todas esas cosas a la vez desde los días de DOS, cuando no había multitarea.

Todo es cuestión de estructurar el programa: dividir todas esas tareas en pasos breves, y ejecutarlos todos en un bucle principal; el pseudocódigo sería algo parecido a esto:

Inicio
  InicializaPrograma;
  Mientras No Termine el Juego:
  Inicio
    DibujaCuadroPantalla;
    LeeEventosTecladoRatonJoystick;
    MovilizaMonstruos;
    TocaSonidos;
    CompruebaRed;
  Fin
  FinalizaPrograma;
Fin

Esa sería la estructura general de todo videojuego; la idea básica es tener un bucle principal donde se ejecutan todas las operaciones del juego; si cada una de dichas operaciones se lleva a cabo lo bastante rápido, parecerá como si todas se llevasen a cabo simultáneamente.

Para ello, debes asegurarte de dividir cada una de esas tareas en fragmentos breves; la rutina "DibujaCuadroPantalla", por ejemplo, sólo debería dibujar un cuadro de animación, no más; la rutina "MovilizaMonstruos" sólo debería hacer que los monstruos se muevan una pequeña distancia, etc. Todo juego puede organizarse de esa forma.

También es posible utilizar las funciones de SDL para crear múltiples hilos de ejecución (threads), pero ello va más allá de lo que se puede abarcar con este artículo, y además acarrearía las complicaciones de coordinar todas las tareas; encontrarás mucho más fácil dividir las tareas en pasos breves (sobre todo porque, con la rapidez típica de cualquier computador, hacerlo "breve" da para mucho) y ejecutarlos en secuencia en un bucle.


8. Un programa interactivo

Procederemos a examinar un programa con interacción; lo que hace es dibujar un cuadrado rojo en la pantalla, el cual puede ser movido de un lado a otro con las teclas direccionales. Además, es posible moverlo haciendo click con el ratón en cualquier parte de la pantalla. Puedes salir con la tecla de Escape.

He aquí el código fuente completo; luego lo examinamos en detalle (te recuerdo que en el apéndice al final de éste artículo encontrarás instrucciones para extraer los fuentes):

--------------------<Cut Here!-><interac.c><-Cut Here!->----------------------
#include <SDL.h>
#include <stdio.h>


main(){
  SDL_Surface *pantalla;
  SDL_Rect rect;
  Uint32 color;
  int terminar = 0;  /* 0 si no hemos terminado, 1 si es hora de acabar */
  Uint8 *keys;     /* Esto contendrá la información de las teclas */
  int x = 100, y = 100; /* Estas serán las coordenadas del cuadrado */
  int xm,ym;       /* Estas serán las coordenadas del cursor */

  if(SDL_Init(SDL_INIT_VIDEO) == -1){
    printf("No se pudo iniciar el video: %s\n", SDL_GetError());
    exit(-1);
  }

  pantalla = SDL_SetVideoMode(640,480,16, SDL_ANYFORMAT | SDL_DOUBLEBUF
                                                         | SDL_FULLSCREEN);
  if(!pantalla){
    printf("No se pudo iniciar el modo de pantalla: %s\n", SDL_GetError());
    SDL_Quit();
    exit(-1);
  }

  while( ! terminar ){  /* Bucle principal del programa */
    SDL_PollEvent(NULL);  /* Comprobamos los eventos del usuario */

    /* Comprobamos si el usuario está presionando alguna tecla */
    /* Con ESC finaliza el programa, con las teclas de dirección se mueve el
       cuadrado */
    keys = SDL_GetKeyState(NULL);
    if( keys[SDLK_ESCAPE] ) terminar = 1;
    if( keys[SDLK_UP] )     y -= 2;
    if( keys[SDLK_DOWN] )   y += 2;
    if( keys[SDLK_LEFT] )   x -= 2;
    if( keys[SDLK_RIGHT] )  x += 2;

    /* Comprobamos si el usuario ha hecho click con el botón izquierdo del
       ratón; de ser así, mueve el cuadrado a donde está el cursor */
    if(SDL_GetMouseState(, ) & SDL_BUTTON(1)){ x = xm; y = ym; }

    /* Borramos la pantalla */
    color = SDL_MapRGB(pantalla->format, 0, 0, 255);
    rect = (SDL_Rect) {0,0,640,480};
    SDL_FillRect(pantalla, ▭, color);

    /* Dibujamos el cuadrado */
    color = SDL_MapRGB(pantalla->format, 255, 0, 0);
    rect = (SDL_Rect) {x,y,10,10};
    SDL_FillRect(pantalla, ▭, color);

    SDL_Flip(pantalla);

    SDL_Delay(50);
  }

  SDL_Quit();
}
--------------------<Cut Here!-><interac.c><-Cut Here!->----------------------

Examinaremos sólamente las partes del programa que han cambiado con respecto al anterior; primero, tenemos las siguientes nuevas variables:

  int terminar = 0;  /* 0 si no hemos terminado, 1 si es hora de acabar */
  Uint8 *keys;     /* Esto contendrá la información de las teclas */
  int x = 100, y = 100; /* Estas serán las coordenadas del cuadrado */
  int xm,ym;       /* Estas serán las coordenadas del cursor */

Dado que el programa funciona con un bucle principal (como indicamos en la sección anterior), usaremos una variable llamada "terminar" para saber si es hora de salirse del bucle; la variable "keys" se utilizará para almacenar un mapa del teclado, lo cual nos permitirá ver qué teclas ha presionado el usuario.

Las variables "x" e "y" nos sirven para recordar la posición actual del cuadrado rojo que vamos a mover por la pantalla, y en "xm" e "ym" guardaremos la posición del cursor del ratón.

Una vez inicializado todo, damos comienzo al bucle principal, el corazón mismo del programa:

  while( ! terminar ){  /* Bucle principal del programa */

Entre otras cosas, hemos de controlar constantemente los eventos producidos por el usuario, con el teclado y el ratón, con el fin de responder a los mismos inmediatamente (eso es lo que significa "interactivo" después de todo :-)); la función SDL_PollEvent se encarga de recoger todos los eventos producidos por el usuario, ya sea con el teclado, el ratón o el joystick:

    SDL_PollEvent(NULL);  /* Comprobamos los eventos del usuario */

Luego usamos la función SDL_GetKeyState para obtener un mapa del teclado y guardarlo en la variable "keys"; dicha variable es un arreglo, que contiene un numero por cada tecla: 1 si la tecla esta presionada, 0 si no:

    /* Comprobamos si el usuario está presionando alguna tecla */
    /* Con ESC finaliza el programa, con las teclas de dirección se mueve el
       cuadrado */
    keys = SDL_GetKeyState(NULL);
    if( keys[SDLK_ESCAPE] ) terminar = 1;
    if( keys[SDLK_UP] )     y -= 2;
    if( keys[SDLK_DOWN] )   y += 2;
    if( keys[SDLK_LEFT] )   x -= 2;
    if( keys[SDLK_RIGHT] )  x += 2;

Controlamos los valores de las Teclas Escape y direccionales con las macros SDLK_ESCAPE, SDLK_UP, SDLK_DOWN, SDLK_LEFT y SDLK_RIGHT; puedes ver las macros de todas las teclas editando el archivo /usr/local/include/SDL/SDL_keysym.h

Básicamente, cuando el usuario presiona Escape, ponemos un 1 en la variable "terminar", causando que salgamos del bucle y el programa finalice. Con las teclas direccionales, modificamos las variables "x" e "y", causando que el cuadrado se mueva por la pantalla (2 pixeles a la vez, en este caso).

Después, usamos la función SDL_GetMouseState para enterarnos de la posición del cursor y botones del ratón clickeados:

    /* Comprobamos si el usuario ha hecho click con el botón izquierdo del
       ratón; de ser así, mueve el cuadrado a donde está el cursor */
    if(SDL_GetMouseState(, ) & SDL_BUTTON(1)){ x = xm; y = ym; }

SDL_GetMouseState coloca las coordenadas del cursor en xm e ym; el "& SDL_BUTTON(1)" nos permite saber si el botón izquierdo ha sido presionado; para el botón derecho usamos SDL_BUTTON(2) y SDL_BUTTON(3) para el del centro.

Tras haber atendido los eventos del usuario, nuestro siguiente paso es dibujar el "mundo virtual" en pantalla; primero, la borramos:

    /* Borramos la pantalla */
    color = SDL_MapRGB(pantalla->format, 0, 0, 255);
    rect = (SDL_Rect) {0,0,640,480};
    SDL_FillRect(pantalla, ▭, color);

Con esto, pintamos toda la pantalla de color azul, cubriendo lo que tuviese dibujado anteriormente.

Después, dibujamos el cuadrado rojo que podemos mover de un lado a otro con las teclas o el ratón:

    /* Dibujamos el cuadrado */
    color = SDL_MapRGB(pantalla->format, 255, 0, 0);
    rect = (SDL_Rect) {x,y,10,10};
    SDL_FillRect(pantalla, ▭, color);

Por último, usamos la función SDL_Delay para que el programa se quede quieto durante 50 milisegundos (1/20 de segundo); el propósito de ésto es asegurarnos de que, en máquinas con un CPU potente, el juego no vaya excesivamente rápido.

    SDL_Delay(50);

El retraso causado por SDL_Delay es constante, totalmente regular, no depende de la velocidad de la máquina; SDL_Delay(50) retrasará 50 milisegundos en cualquier máquina.


9. Programa interactivo con gráficos

Todos los ejemplos que hemos visto hasta ahora sólo dibujan cuadrados o rectángulos; pero eso es muy aburrido; queremos poder dibujar edificios, monstruos, armas, etcétera. para ello, hemos de saber cargar y dibujar imágenes de pixeles (o pixmaps); nuestro último ejemplo muestra como hacerlo.

Al contrario de los ejemplos anteriores, éste programa necesita de otros tres archivos para funcionar: imagen1.bmp, imagen2.bmp y cursor.bmp; dichas imágenes se encuentran en el archivo comprimido adjunto con éste artículo; para extraerlo sigue las instrucciones que hay en el apéndice.

Este ejemplo funciona exactamente igual que el anterior, excepto que nos dibuja una imágen de fondo, aparece una imágen en lugar del cuadrado rojo, y el cursor del ratón adopta la forma que le indiquemos.

Como siempre, colocamos el fuente primero, y luego lo examinamos:


--------------------<Cut Here!-><pics.c><-Cut Here!->----------------------
#include <SDL.h>
#include <stdio.h>


main(){
  SDL_Surface *pantalla;
  SDL_Surface *imagen1, *imagen2, *imagen3; /* Las tres imágenes */
  SDL_Rect rect;
  Uint32 color;
  int terminar = 0;
  Uint8 *keys;
  int x = 100, y = 100;
  int xm,ym;
  int i,j;

  if(SDL_Init(SDL_INIT_VIDEO) == -1){
    printf("No se pudo iniciar el video: %s\n", SDL_GetError());
    exit(-1);
  }

  pantalla = SDL_SetVideoMode(640,480,16, SDL_ANYFORMAT | SDL_DOUBLEBUF
                                                         | SDL_FULLSCREEN);
  if(!pantalla){
    printf("No se pudo iniciar el modo de pantalla: %s\n", SDL_GetError());
    SDL_Quit();
    exit(-1);
  }

  /* Cargamos la imágen de fondo */
  imagen1 = SDL_LoadBMP("imagen1.bmp");
  if(!imagen1){
    printf("No se pudo cargar imagen1.bmp\n");
    SDL_Quit();
    exit(-1);
  }

  /* Cargamos la imágen del muñeco */
  imagen2 = SDL_LoadBMP("imagen2.bmp");
  if(!imagen2){
    printf("No se pudo cargar imagen2.bmp\n");
    SDL_Quit();
    exit(-1);
  }
  /* Especificamos que el color negro indica transparencia */
  color = SDL_MapRGB(imagen1->format, 0,0,0);
  SDL_SetColorKey(imagen1, SDL_SRCCOLORKEY | SDL_RLEACCEL, color);

  /* Cargamos el cursor del ratón */
  imagen3 = SDL_LoadBMP("cursor.bmp");
  if(!imagen2){
    printf("No se pudo cargar cursor.bmp\n");
    SDL_Quit();
    exit(-1);
  }
  /* Especificamos que el color negro indica transparencia */
  color = SDL_MapRGB(imagen3->format, 0,0,0);
  SDL_SetColorKey(imagen3, SDL_SRCCOLORKEY | SDL_RLEACCEL, color);

  /* No mostrar el cursor, pues vamos a dibujar el nuestro */
  SDL_ShowCursor(SDL_DISABLE);

  while( ! terminar ){  /* Bucle principal del programa */
    SDL_PollEvent(NULL);

    keys = SDL_GetKeyState(NULL);
    if( keys[SDLK_ESCAPE] ) terminar = 1;
    if( keys[SDLK_UP] )     y -= 2;
    if( keys[SDLK_DOWN] )   y += 2;
    if( keys[SDLK_LEFT] )   x -= 2;
    if( keys[SDLK_RIGHT] )  x += 2;

    if(SDL_GetMouseState(, ) & SDL_BUTTON(1)){ x = xm; y = ym; }

    /* Dibujamos el fondo como un mosaico */
    for(i=0; i<40; i++){
      for(j=0; j<15; j++){
        rect = (SDL_Rect) {16*i,32*j, 0,0};
	SDL_BlitSurface(imagen2, NULL, pantalla, ▭);
      }
    }

    /* Dibujamos el muñeco */
    rect = (SDL_Rect) {x,y, 0,0};
    SDL_BlitSurface(imagen1, NULL, pantalla, ▭);

    /* Dibujamos el cursor del ratón */
    SDL_GetMouseState(, );
    rect = (SDL_Rect) {xm-16,ym-16, 0,0};
    SDL_BlitSurface(imagen3, NULL, pantalla, ▭);

    SDL_Flip(pantalla);

    SDL_Delay(50);
  }

  SDL_Quit();
}

--------------------<Cut Here!-><pics.c><-Cut Here!->----------------------

Al igual que antes, examinaremos sólamente las partes del código que difieren del ejemplo anterior. Primero que nada, tenemos estas nuevas variables:

  SDL_Surface *imagen1, *imagen2, *imagen3; /* Las tres imágenes */
  int i,j;

imagen1, imagen2 e imagen3 almacenarán las tres imágenes con las que vamos a trabajar; nótese que són de tipo SDL_Surface, al igual que la variable que representa la pantalla; ésto se debe a que la pantalla, de hecho, es sólo otra imágen, y todas las operaciones que has aprendido a hacer sobre la pantalla las puedes hacer con una imágen, tales como dibujar rectángulos sobre ellas, etcétera.

Las variables i,j són dos contadores que utilizaremos en un par de ciclos "for".

Más adelante, nos ocupamos de cargar las imágenes en memoria:

  imagen1 = SDL_LoadBMP("imagen1.bmp");
  if(!imagen1){
    printf("No se pudo cargar imagen1.bmp\n");
    SDL_Quit();
    exit(-1);
  }

La función SDL_LoadBMP sólo puede cargar imágenes en formato BMP; si deseas cargar otros formatos (PNG, JPG, etc) has de utilizar la librería SDL_Image, la cual es muy fácil de usar, pero queda fuera del ámbito de éste artículo.

La imágen imagen1.bmp, cargada en la variable imagen1, contendrá el fondo de la pantalla.

A continuación cargaremos la imágen de un muñeco rojo, que sustituirá al cuadrado rojo que movíamos por la pantalla en el ejemplo anterior:

  /* Cargamos la imágen del muñeco */
  imagen2 = SDL_LoadBMP("imagen2.bmp");
  if(!imagen2){
    printf("No se pudo cargar imagen2.bmp\n");
    SDL_Quit();
    exit(-1);
  }
  /* Especificamos que el color negro indica transparencia */
  color = SDL_MapRGB(imagen1->format, 0,0,0);
  SDL_SetColorKey(imagen1, SDL_SRCCOLORKEY | SDL_RLEACCEL, color);

Observa que las dos últimas líneas no se utilizaron para cargar la imágen de fondo; su propósito es decirle a SDL que todos los pixeles de color negro en la variable "imagen2" són transparentes; de lo contrario, nuestro muñeco aparecería enmarcado en un rectángulo.

Desafortunadamente, los archivos BMP estándar no tienen transparencia, por lo que SDL no puede simplemente cargar una imágen con fondo transparente; pero eso sí es posible con la librería SDL_Image. Pero por ahora, usaremos SDL_SetColorKey para indicar transparencia.

Finalmente, cargamos la imágen que contiene el cursor de ratón que desplegará nuestro programa; el procedimiento es idéntico al que usamos con imagen2.bmp;

  /* Cargamos el cursor del ratón */
  imagen3 = SDL_LoadBMP("cursor.bmp");
  if(!imagen2){
    printf("No se pudo cargar cursor.bmp\n");
    SDL_Quit();
    exit(-1);
  }
  /* Especificamos que el color negro indica transparencia */
  color = SDL_MapRGB(imagen3->format, 0,0,0);
  SDL_SetColorKey(imagen3, SDL_SRCCOLORKEY | SDL_RLEACCEL, color);

Luego, usamos la función SDL_ShowCursor para hacer que el cursor desaparezca de la pantalla; no queremos ver el cursor del sistema operativo, así que dibujaremos el nuestro.

  /* No mostrar el cursor, pues vamos a dibujar el nuestro */
  SDL_ShowCursor(SDL_DISABLE);

Entramos al bucle principal; el primer paso es dibujar el fondo (lo cual, de paso, borrará todo lo que contenía la pantalla); dibujamos la imágen usando la función SDL_BlitSurface.

Como la imágen que tenemos apenas tiene 16x32 pixeles, pero la pantalla tiene 640x480 pixeles, cubriremos la pantalla de repeticiones de la imágen, como un mosaico de baldosas:

    /* Dibujamos el fondo como un mosaico */
    for(i=0; i<40; i++){
      for(j=0; j<15; j++){
        rect = (SDL_Rect) {16*i,32*j, 0,0};
	SDL_BlitSurface(imagen2, NULL, pantalla, ▭);
      }
    }

luego, dibujamos el muñeco (que sustituye al cuadrado rojo de la vez anterior) usando también la función SDL_BlitSurface; tenemos que poner las coordenadas de pantalla donde lo queremos dibujar en la variable "rect", para luego pasárselas como argumento a SDL_BlitSurface:

    /* Dibujamos el muñeco */
    rect = (SDL_Rect) {x,y, 0,0};
    SDL_BlitSurface(imagen1, NULL, pantalla, ▭);

Y, de la misma forma, dibujamos el cursor del ratón, tras obtener sus coordenadas con la función SDL_GetMouseState:

    /* Dibujamos el cursor del ratón */
    SDL_GetMouseState(, );
    rect = (SDL_Rect) {xm-16,ym-16, 0,0};
    SDL_BlitSurface(imagen3, NULL, pantalla, ▭);

10. Conclusión

Esta apenas ha sido una introducción a SDL; no hemos tenido ocasión de estudiar el uso del sonido, ni de otras librerías, de lo cual nos encargaremos en una ocasión futura.

El uso del sonido en SDL es algo complicado; seguramente preferirás utilizar una librería de apoyo, como SDL_sound o SDL_mixer, que te simplifican mucho el trabajo. Y la librería SDL_Image es muy simple de usar también, pues sólo necesitas usar la función IMG_Load; puedes esperar a futuros artículos sobre SDL, o ir a páginas web como libsdl.org y aprender por tí mismo.

Sin embargo, con lo que hemos cubierto en este artículo, tendrás suficiente conocimiento como para escribir unos juegos bastante sofisticados; sólo tienes que aplicar lo que has aprendido con imaginación e ingenio.


11. Apéndice: fuentes comprimidos

El código fuente de todos los ejemplos, así como un Makefile para compilarlos todos, y las imágenes necesarias, se encuentran a continuación:

  Pincha aqui para descargarte sdlintro.tar.gz

Descomprímelo con:

  tar -zxf sdlintro.tar.gz

lo cual te creará un directorio llamado "sdlintro"; entra a ese directorio y corre "make":

  cd sdlintro
  make

con lo que, si tienes SDL instalado, se compilarán los fuentes y se generarán los programas de ejemplo.

No olvides que puedes salirte de los programas presionando la tecla Escape, excepto "primersdl", que se quita solo al cabo de 3 segundos.