Hola chicos, ¿Cómo estáis? Ya sé que llevo mucho tiempo sin escribir nada en MuyLinux, pero era porque me estaba preparando este tutorial que os traigo hoy y que espero que os guste, ya que últimamente se venía hablando sobre el tema que trataremos.
Pero atención, porque este no va a ser un tutorial cualquiera, porque os voy a enseñar lo básico para que empecéis a programar videojuegos en Linux.
Conociendo a SDL
Para empezar con buen pie vamos a dejar claras un par de cosas. En este tutorial voy a presuponer que tenéis al menos un dominio básico del lenguaje C, no estoy hablando de estructuras complejas ni operaciones con punteros de nada de eso. Simplemente saber usar funciones, bucles while, estructuras condicionales como los if/else, etc.
Otra cosa que quiero aclarar, y que seguramente habeis adivinado, es la biblioteca que usaremos para las operaciones gráficas, que será nada más y nada menos que la famosa Simple DirectMedia Layer, a partir de aquí SDL.
Me he decidido por SDL por varios motivos. Primero porque está escrito en C, así que no nos hará falta instalar bindings para otros lenguajes como pygame, simplemente necesitamos instalar los paquetes de desarrollo de SDL que podréis encontar en vuestro gestor de paquetes preferido. Otro motivo es que, al estar orientado solo a gráficos 2D, será mucho más fácil aprender desde el principio, en lugar de liarnos con cosas en 3D usando OpenGL, mucho más complejo.
Es importante mencionar que, si bien SDL nos ofrece una completa API para acceder no solo a gráficos, sino al sistema de sonido, a los sistemas de entrada (teclado, ratón, incluso joysticks), a la interacción con CDs e inclusive al juego multijugador en línea, varios de estos subsistemas son algo espartanos, por lo que existen módulos auxiliares que potencian con mucho al SDL básico, tales como SDL_Image, SDL_Mixer, etc.
En nuestro tutorial no haremos uso de estos módulos porque el fin del mismo es ser una introducción al desarrollo de juegos 2D bastante simples. Para el que quiera información en detalle sobre SDL o sus módulos puede dirigirse a la web oficial de SDL (en inglés).
Como nota, os aviso de que al final del artículo tenéis un enlace para descagaros el código fuente comentado línea a línea junto con todas las imágenes empleadas. También he de decir que, para todos los que no estéis muy versados en C, en la segunda parte del tutorial, junto con el código fuente también incluiré una versión de lo que haremos hoy aquí pero hecha en Python (también comentado lo más posible).
Poniéndonos manos a la obra
Una vez hechas las presentaciones espero que ya tengáis instalados los archivos de desarrollo de SDL y tengáis abierto vuestro editor de textos favorito porque esto empieza ya, amigos. Vamos a ir todo lo despacito que podamos así que no os perdáis.
Primero vamos con lo básico, avisarle al preprocesador qué bibliotecas ha de incluir a la hora de compilar el archivo, así que empezamos escribiendo lo siguiente:
#include <stdio.h> #include <stdlib.h> #include <SDL/SDL.h>
Las dos primeras bibliotecas no son ni más ni menos que las bibliotecas estándar de C. La tercera me parece que es obvio a quién llama. Ahora vamos a definir unas cuantas constantes para nuestro juego. Nada complicado, simplemente:
#define WIDTH 420 #define HEIGHT 360 #define BPP 24
Estas líneas le indican al preprocesador que, en todo lugar donde encuentre las palabras WIDTH, HEIGHT o BPP, se sustituya la misma por el valor que hemos definido. Las dos primeras definen el ancho y alto de la ventana en la que mostraremos los gráficos, mientras que la tercera línea se define el número de bits por pixel, comúnmente conocido como profundidad de color. Lo siguiente que debemos hacer será crear todas las variables y todos los objetos que necesitará nuestro videojuego.
SDL_Surface *image, *screen; SDL_Rect dest; SDL_Event event; int done = 0; Uint8 *keys;
Por ahora nada demasiado sorprendente: creamos dos superficies, una contendrá la imagen de nuestro jugador y la otra contendrá todos los elementos, es decir, es la pantalla principal. También creamos un elemento Rect que simplemente es un rectángulo, y que nos servirá para definir la posición en pantalla del jugador. Creamos un elemento Event que nos ayudará a saber si se sale o no del juego, y una variable llamada done que será la que marque la salida de la aplicación. El elemento keys nos servirá para monitorizar el estado de las teclas de nuestro teclado.
Acto seguido preparamos una estructura muy muy sencilla que nos facilitará darle unas coordenadas a nuestra nave. Dicha estructura quedaría tal que así:
struct nave{ int x,y; } minave;
Con esto hemos terminado de hacer los preparativos para nuestro juego. A partir de ahora todo el código deberá estar dentro de la función main del programa. Lo primero que pondremos será algo así:
atexit(SDL_Quit); if(SDL_Init(SDL_INIT_VIDEO) < 0) { printf("No se ha podido iniciar SDL: %s\n", SDL_GetError()); exit(1); }
La primera sentencia es fácil de entender, indica que ha de cerrarse SDL al salir de la aplicación. La función usada en la sentencia if ya es más especial. La función SDL_Init es la encargada de inicializar los subsistemas de SDL y admite como parámetros varias opciones como por ejemplo: SDL_INIT_VIDEO, SDL_INIT_AUDIO, SDL_INIT_CDROM, iniciando cada una de ellas el subsistema gráfico, de sonido o del CD-ROM respectivamente. En caso de usar varias a la vez, irán separadas por una barra vertical. Admite varias más pero no quiero pararme en eso ahora. Este bloque informa al usuario de si ha ocurrido algún error iniciando SDL.
Una vez que tenemos inicializado el subsistema gráfico, podemos empezar a poner en marcha la que será la pantalla principal. Vamos a activar la variable screen que hemos creado antes como nuestra pantalla por defecto, lo cual es tan simple como
screen = SDL_SetVideoMode(WIDTH, HEIGHT, BPP, SDL_HWSURFACE);
if(screen == NULL) { printf("No se ha podido establecer el modo de vídeo: %s\n", SDL_GetError()); exit(1); }
La función SDL_SetVideoMode() define una superficie como la pantalla principal. Tiene cuatro parámetros que son el ancho, el alto, la profundidad de color, y los flags. En nuestro caso hemos usado el flag SDL_HWSURFACE para que SDL active la aceleración por hardware de estar disponible. Podeis consultar qué flags admite en la web oficial de SDL. Como en el caso anterior, avisamos al usuario de si ha ocurrido algún error al establecer el modo de vídeo.
Jugando con imágenes
Vamos ahora a empezar a ver cosas tangibles en nuestra pantalla. Vamos a cargar la imagen de nuestro jugador y para eso recurriremos a la función SDL_LoadBMP() que toma como único parámetro la ruta del archivo en formato de cadena:
image = SDL_LoadBMP("img/minave.bmp"); if(image == NULL) { printf("No se ha podido cargar la imagen: %s\n", SDL_GetError()); exit(1); }
Lo que vamos a hacer ahora es darle unas coordenadas iniciales a nuestro jugador, le asignaremos una posición en pantalla usando la variable dest y acto seguido lo mostraremos en pantalla:
minave.x = 50; minave.y = 10; dest.x = minave.x; dest.y = minave.y; dest.w = image -> w; dest.h = image -> h; SDL_BlitSurface(image, NULL, screen, &dest); SDL_Flip(screen); SDL_FreeSurface(image);
Las dos primeras líneas establecen las coordenadas iniciales de la estructura de nuestra nave. Las 4 siguientes definen las coordenadas y el tamaño de la zona en pantalla que ocupará nuestra imagen. Con la función SDL_BlitSurface()copiamos el contenido de la variable image sobre la superficie screen, es decir, la mostramos en pantalla. Los parámetros de esta función son: la superficie a copiar, la zona de dicha superficie que se desea copiar, la superficie de destino, y la zona de dicha superficie donde se copiarán los datos.
La función SDL_Flip() actualiza una superficie. En nuestro caso actualizará toda la pantalla. Seguidamente, liberamos los recursos ocupados por la imagen haciendo uso de la función SDL_FreeSurface(). Vamos ahora a escribir el loop principal del juego:
while(done == 0) { while(SDL_PollEvent(&event)) { if(event.type == SDL_KEYDOWN) {done = 1;} } }
Si compilamos nuestra aplicación como la tenemos hasta ahora, obtendríamos algo similar a lo que vemos arriba, una bonita nave espacial con un feo contorno rojo.
Para los impacientes, podéis compilar el archivo hasta este punto con el comando
gcc -o navecitas navecitas.c -lSDL
Y lo ejecutáis simplemente escribiendo
./navecitas
Pero ese contorno rojo queda muy rancio y además poco estético ¿Verdad? No hay problema. SDL nos ofrece una función para utilizar un canal de color alfa que se comportará como si fuese transparente, eliminando ese feo contorno. Dicha función se llama SDL_SetColorKey y la usaremos de la siguiente manera, colocándola justo después de la carga y verificación de la imágen
SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image -> format, 255, 0, 0));
Aviso para navegantes: No os preocupéis sino entendéis el porqué de algunas funciones o parámetros que pongo aquí, pues está todo comentado y explicado en el código fuente, de manera que pretendo ahorrarme un doble esfuerzo, ya que tampoco puedo alargar mucho el post. Por ahora quedaos con que esta función colocará como color transparente en nuestra imagen el color rojo. Si compilamos de nuevo nuestro archivo con la orden anterior podremos ver algo parecido a lo siguiente:
Ahora tiene mucha mejor pinta. Para ir finalizando voy a colocar el bloque de código que se encargará de los controles de la nave. Tened en cuenta que a partir de este punto varias cosas cambian de sitio en el código fuente, y no pretendo pararme mucho. Os recomiendo revisar el código fuente y lo vereis mucho mejor.
Controlando la nave
Algún lector espabilado se habrá dado cuenta de que aún no hemos tocado la variable keys creada al comienzo. Pues a eso vamos en esta sección. Usaremos la función de SDL llamada SDL_GetKeyState() para monitorizar cuales teclas están o no pulsadas en el momento de ejecutarse dicha función. Posteriormente usaremos varias estructuras if para que, en caso de pulsarse alguna tecla de dirección, la nave se mueva en dicha dirección. El código nos quedaría de esta manera
keys = SDL_GetKeyState(NULL); if(keys[SDLK_UP] && minave.y > 0) {minave.y = minave.y - (5);} if(keys[SDLK_DOWN] && minave.y < HEIGHT) {minave.y = minave.y + (5);} if(keys[SDLK_LEFT] && minave.x > 0) {minave.x = minave.x - (5);} if(keys[SDLK_RIGHT] && minave.x < WIDTH) {minave.x = minave.x + (5);}
Y con esto y un par de instrucciones extra en el loop principal de nuestro juego, tenemos una bonita nave que podemos controlar con nuestro teclado. Sí, no es el Crysis, pero Roma no se construyó en un solo día ;). Si habeis llegado hasta aquí, os doy las gracias por tragaros todo este tocho. Espero que os haya gustado y estad atentos porque el miércoles publicaremos la segunda parte del tutorial (y el viernes la tercera y última), en el que escribriremos una pequeña biblioteca sobre la que nos apoyaremos para seguir avanzando. Recordad descargaros el código fuente junto con la imagen usada desde este enlace y seguid practicando.
Contenido del curso
Programación de videojuegos con SDL – Parte I: Introducción
Programación de videojuegos con SDL – Parte II: bibliotecas C++
Programación de videojuegos con SDL – Parte III: a jugar
Artículo de Garolard