Este sitio web usa cookies de terceros para analizar el tráfico y personalizar los anuncios. Si no está de acuerdo, abandone el sitio y no siga navegando por él. ×


10.5. Juegos multiplataforma: SDL

10.5.1. Dibujando una imagen de fondo y un personaje

Existen distintas bibliotecas que permiten crear gráficos desde el lenguaje C. Unas son específicas para un sistema, y otras están diseñadas para ser portables de un sistema a otro. Por otra parte, unas se centran en las funciones básicas de dibujo (líneas, círculos, rectángulos, etc), y otras se orientan más a la representación de imágenes que ya existan como fichero.

Nosotros veremos las nociones básicas del uso de SDL, que es una de las bibliotecas más adecuadas para crear juegos, porque es multiplataforma (existe para Windows, Linux y otros sistemas) y porque esta orientada a la manipulación de imágenes, que es algo más frecuente en juegos que el dibujar líneas o polígonos.

No veremos detalles de su instalación, porque en los sistemas Linux debería bastar un instalar el paquete SDL-Dev (que normalmente tendrá un nombre como libsdl1.2-dev), y en Windows hay entornos que permiten crear un “proyecto de SDL” tan sólo con dos clics, como CodeBlocks.

Vamos a ver algunas de las operaciones básicas, y un primer ejemplo:

Para poder utilizar SDL, debemos incluir SDL.h, así

#include <SDL/SDL.h>

 

Ya dentro del cuerpo del programa, el primer paso sería tratar de inicializar la biblioteca SDL, y abandonar el programa en caso de no conseguirlo:

if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 ) {

printf ( "No se pudo inicializar SDL: %s\n" , SDL_GetError ());

exit ( 1 );

}

 

Al terminar nuestro programa, deberíamos llamar a SQL_Quit:

SDL_Quit ();

 

Para escoger modo de pantalla de 640x480 puntos, con 16 bits de color haríamos

if ( screen == NULL ) {

printf ( "Error al entrar a modo grafico: %s\n" , SDL_GetError () );

SDL_Quit ();

return - 1 ;

}

 

Podemos cambiar simplemente el texto (caption) de la ventana:

SDL_WM_SetCaption ( "Prueba 1 de SDL" , "Prueba 1 de SDL" );

 

Para mostrar una imagen en pantalla: deberemos declararla del tipo SDL_Surface, cargarla con SDL_LoadBMP, y volcarla con SDL_BlitSurface usando como dato auxiliar la posición de destino, que será de tipo SDL_Rect:

SDL_Surface * protagonista ;

protagonista = SDL_LoadBMP ( "protag.bmp" );

SDL_Rect destino ;

destino . x = 320 ;

destino . y = 400 ;

SDL_BlitSurface ( protagonista , NULL , screen , & destino );

 

Con SDL_Flip hacemos toda la imagen visible:

SDL_Flip ( screen );

 

Y para esperar 5 segundos y que nos dé tiempo a comprobar que todo ha funcionado, utilizaríamos:

SDL_Delay ( 5000 );

 

Todo esto, junto en un programa, quedaría:

/*---------------------------*/
/*  Ejemplo en C son SDL     */
/*  sdl01.c                  */
/*                           */
/*  Ejemplo de SDL (1)       */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/

#include 
#include 
#include 
 
int main()
{
    SDL_Surface *screen;
    SDL_Surface *fondo;
    SDL_Surface *protagonista;
    SDL_Rect destino;
    int i, j;
      
    /* Tratamos de inicializar la biblioteca SDL */
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("No se pudo inicializar SDL: %s\n", SDL_GetError());
        exit(1);
    }
     
    /* Preparamos las imagenes a mostrar */
    fondo = SDL_LoadBMP("fondo.bmp");
    protagonista = SDL_LoadBMP("protag.bmp");
     
    /* Si todo ha ido bien, hacemos algo: 
        entrar a modo grafico y cambiar el título de la ventana */
    screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE );
    if( screen == NULL ) {
        printf( "Error al entrar a modo grafico: %s\n", SDL_GetError() );
        SDL_Quit();
        return -1;
    }
     
    /* Titulo de la ventana */
    SDL_WM_SetCaption( "Prueba 1 de SDL", "Prueba 1 de SDL" );
     
    /* Dibujamos la imagen de fondo */
    destino.x=0;
    destino.y=0;
    SDL_BlitSurface(fondo, NULL, screen, &destino);
   
    /* Dibujamos el protagonista */
    destino.x=320;
    destino.y=400;
    SDL_BlitSurface(protagonista, NULL, screen, &destino);
   
    /* Actualizamos la pantalla */
    SDL_Flip(screen);
     
    /* Y esperamos antes de salir */
    SDL_Delay( 5000 );
     
    /* Finalmente, preparamos para salir */
    SDL_Quit();
    return 0;
}

En principio, si sólo usamos SDL, las imágenes tendrán que ser en formato BMP, pero hay otras bibliotecas adicionales, como SDL_Image, que permiten mostrar también imágenes en formatos PNG, JPG, etc.

 

El tipo SDL_Rect representa un rectángulo, y se trata de un registro (struct), que tiene como campos:

* x: posición horizontal

* y: posición vertical

* w: anchura (width)

* h: altura (height)

(nosotros no hemos usado la anchura ni la altura, pero podrían ser útiles si no queremos volcar toda la imagen, sino sólo parte de ella).

 

Para compilar este fuente en Linux deberíamos teclear lo siguiente:

cc -o sdl01 sdl01.c `sdl-config --cflags --libs`

Hay cosas que ya conocemos: el compilador (cc), el nombre para el ejecutable (-o sdl01) y el nombre del fuente (sdl01.c). Las opciones adicionales debemos indicarlas tal y como aparecen, entre acentos graves (acentos “hacia atrás”): `sdl-config --cflags --libs`

En Windows (con entornos como CodeBlocks), bastaría con pulsar el botón “compilar el proyecto” de nuestro entorno de desarrollo.

Ejercicios propuestos :

 

10.5.2. Un personaje móvil

Para poder mover a ese protagonista por encima del fondo de forma “no planificada” necesitamos poder comprobar qué teclas se están pulsando. Lo podemos conseguir haciendo:

teclas = SDL_GetKeyState ( NULL );

if ( teclas [ SDLK_UP ])

{

posicionY -= 2 ;

}

 

Donde la variable "teclas", será un array de "unsigned int". La forma normal de declararla será:

Uint8 * teclas ;

 

Eso sí, antes de poner acceder al estado de cada tecla, deberemos poner en marcha todo el sistema de comprobación de sucesos ("events", en inglés). Al menos deberemos comprobar si hay alguna petición de abandonar el programa (por ejemplo, pulsando la X de la ventana), a lo que corresponde el suceso de tipo SDL_QUIT. De paso, podríamos comprobar en este mismo paso si se ha pulsado la tecla ESC, que es otra forma razonable de indicar que queremos terminar el programa:

while ( SDL_PollEvent (& suceso )) {

if ( suceso . type == SDL_QUIT ) terminado = 1 ;

if ( suceso . type == SDL_KEYDOWN )

if ( suceso . key . keysym . sym == SDLK_ESCAPE ) terminado = 1 ;

}

donde la variable suceso se declararía con

SDL_Event suceso ;

 

Ejercicios propuestos :

Existen distintas bibliotecas que permiten crear gráficos desde el lenguaje C. Unas son específicas para un sistema, y otras están diseñadas para ser portables de un sistema a otro. Por otra parte, unas se centran en las funciones básicas de dibujo (líneas, círculos, rectángulos, etc), y otras se orientan más a la representación de imágenes que ya existan como fichero.

10.5.3. Imágenes transparentes, escribir texto y otras mejoras

Hemos visto cómo dibujar imágenes en pantalla, y cómo comprobar qué teclas se han pulsado, para hacer que una imagen se mueva sobre la otra.

Pero tenía un defecto, que hacía que no quedara vistoso: si la imagen del “protagonista” tiene un recuadro negro alrededor, al moverse tapará parte del fondo. Esto se debe evitar: una imagen que se mueve en pantalla (lo que se suele llamar un “sprite”) debería tener zonas transparentes , a través de las que se vea el fondo.

Es algo fácil de conseguir con SDL: podemos hacer que un color se considere transparente, usando

SDL_SetColorKey ( surface , SDL_SRCCOLORKEY ,

SDL_MapRGB ( surface -> format , r , g , b ));

donde "surface" es la superficie que queremos que tenga un color transparente (por ejemplo, nuestro protagonista), y "r, g, b" son las componentes roja, verde y azul del color que queremos que se considere transparente (si es el color negro el que queremos que no sea vea, serían 0,0,0).

Así, con nuestro protagonista haríamos

/* Preparamos las imagenes a mostrar */

fondo = SDL_LoadBMP ( "fondo.bmp" );

protagonista = SDL_LoadBMP ( "protag.bmp" );

/* El protagonista debe tener contorno transparente */

SDL_SetColorKey ( protagonista , SDL_SRCCOLORKEY ,

SDL_MapRGB ( protagonista -> format , 0 , 0 , 0 ));

Si usamos la biblioteca adicional SDL_Image, podremos usar imágenes PNG con transparencia, y entonces no necesitaríamos usar esta orden.

 

Escribir texto con SDL no es algo “trivial”: A no ser que empleemos la biblioteca adicional SDL_TTF, no tendremos funciones predefinidas que muestren una frase en ciertas coordenadas de la pantalla.

Pero podríamos hacerlo “a mano”: preparar una imagen que tenga las letras que queremos mostrar (o una imagen para cada letra), y tratarlas como si fueran imágenes. Podemos crearnos nuestras propias funciones para escribir cualquier texto. Podríamos comenzar por una función “escribirLetra(int x, int y, char letra)”, y apoyándonos en ella, crear otra “escribirFrase(int x, int y, char *frase)”

 

Si queremos que nuestro juego funcione a pantalla completa , los cambios son mínimos: basta añadir SDL_FULLSCREEN a la lista de parámetros que indicamos al escoger el modo de pantalla. Estos parámetros se deben indicar separados por una barra vertical (|), porque entre ellos se va a realizar una operación OR (suma lógica) a nivel de bit:

 

screen = SDL_SetVideoMode ( 640 , 480 , 16 , SDL_FULLSCREEN | SDL_HWSURFACE );

 

Ejercicios propuestos :

 

10.5.4. El doble buffer

Si intentamos mover varias imágenes a la vez en pantalla, es probable que el resultado parpadee.

El motivo es que mandamos información a la pantalla en distintos instantes, por lo que es fácil que alguno de todos esos bloques de información llegue en un momento que no coincida con el barrido de la pantalla (el movimiento del haz de electrones que redibuja la información que vemos).

Una solución habitual es preparar toda la información, trozo a trozo, en una “imagen oculta”, y sólo volcar a la pantalla visible cuando la imagen está totalmente preparada. Esta técnica es la que se conoce como “usar un doble buffer”.

El segundo paso es sincronizar con el barrido, algo que en la mayoría de bibliotecas hace una función llamada “retrace” o “sync”, y que en SDL se hace automáticamente cuando volcamos la información con “SDL_Flip”.

 

Ya en la práctica, en SDL, comenzaremos por añadir el parámetro correspondiente (SDL_DOUBLEBUF) cuando entramos a modo gráfico:

screen = SDL_SetVideoMode ( 640 , 480 , 16 , SDL_HWSURFACE | SDL_DOUBLEBUF );

 

A la hora de dibujar, no lo hacemos directamente sobre “screen”, sino sobre una superficie (“surface”) auxiliar. Cuando toda esta superficie está lista, es cuando la volcamos a la pantalla, así:

SDL_BlitSurface ( fondo , NULL , pantallaOculta , & destino );

SDL_BlitSurface ( protagonista , NULL , pantallaOculta , & destino );

...

SDL_BlitSurface ( pantallaOculta , NULL , screen , & destino );

SDL_Flip ( screen );

 

Sólo queda un detalle: ¿cómo reservamos espacio para esa pantalla oculta?

 

Si la pantalla oculta es del mismo tamaño que nuestro fondo o que alguna otra imagen, nos puede bastar con cargar la imagen :

fondo = SDL_LoadBMP ( "fondo.bmp" );

 

Si no es el caso (por ejemplo, porque el fondo se forme repitiendo varias imágenes de pequeño tamaño), podemos usar "SDL_CreateRGBSurface", que reserva el espacio para una superficie de un cierto tamaño y con una cierta cantidad de colores, así:

pantallaOculta = SDL_CreateRGBSurface ( SDL_SWSURFACE , 640 , 480 , 16 ,

0 , 0 , 0 , 0 );

(el parámetro SDL_SWSURFACE indica que no se trabaje en memoria física de la tarjeta, sino en memoria del sistema; 640x480 es el tamaño de la superficie; 16 es la cantidad de color -16bpp = 655356 colores-; los otros 0,0,0,0 se refieren a la cantidad de rojo, verde, azul y transparencia -alpha- de la imagen).

 

Ejercicios propuestos :

 

10.5.5. El bucle de juego (game loop)

Para abordar un juego completo, es frecuente no tener claro cómo debería ser la estructura del juego: qué repetir y cuando.

Pues bien, en un juego típico encontraríamos:

Esta parte que se repite es lo que se suele llamar el “bucle de juego” (en inglés, “game loop”). Su apariencia exacta depende de cada juego, pero en la mayoría podríamos encontrar algo parecido a:


Inicializar
Mientras (partida en marcha)
    Comprobar sucesos (teclas / ratón / joystick)
    Dibujar fondo (en pantalla oculta)
    Actualizar posición de personajes (en pantalla oculta)
    Comprobar colisiones y otras situaciones del juego
    Corregir personajes según la situación
    Mostrar pantalla oculta
    Atender a situaciones especiales (una vida menos, etc)
Fin Mientras
Liberar recursos

 

 

El orden no tiene por qué ser exactamente éste, pero habitualmente será parecido. Vamos a detallarlo un poco más:

 

 

Ejercicios propuestos :