Temario/Primeros pasos con SDL

De Tutorial LibSDL

[editar] Primeros pasos con SDL

Tabla de contenidos


[editar] Introducción

A estas alturas del tutorial podemos decir que estamos preparados para comenzar a trabajar con la librería SDL. Sentirás curiosidad por entender el ejemplo que expusimos en el último apartado del capítulo anterior. En él aparecían funciones que no eran nativas del lenguaje de programación, en este caso C.

En este capítulo daremos los primeros pasos con SDL para que te vayas familiarizando con la librería

[editar] Objetivos

Los objetivos de este capítulo son muy concisos:

  1. Conocer el entorno de desarrollo a utilizar.
  2. Conocer los distintos subsistemas disponibles en SDL.
  3. Aprender a compilar un programa.
  4. Iniciar SDL y sus librerías adicionales para que puedan ser usadas en C/C++.

[editar] El entorno de desarrollo

En esta sección debería hablarte de las cualidades de un u otro entorno de desarrollo integrado y el porqué nos hemos decidido por uno en concreto. Ante esto una gran objeción. Desde este tutorial no somos partidarios de ligarnos a ninguno de ellos por lo que te aconsejamos que tú tampoco lo hagas. No es necesario un IDE de programación para el seguimiento del tutorial pero puedes usar el que más te guste.

El primer requisito que debe de cumplir nuestro entorno es ser legal. Yo decidí utilizar GNU Emacs como editor de textos, GNU GCC como compilador y Linux como sistema operativo. Estas tres cosas más las librerías SDL compiladas e instaladas son más que suficientes para seguir este curso. Puedes seguir el tutorial desde un sistema libre o propietario, que sea de pago o gratuito, pero por favor sé legal. Puedes usar Kdevelop, Anjuta... el que te sea más cómodo, cualquiera nos vale.

[editar] SDL y los subsistemas

Un subsistema engloba a una parte específica del hardware con un propósito específico. En SDL es una parte de la librería que nos ofrece soporte a diferentes partes del hardware. En el capítulo anterior veíamos las diferentes APIs que ofrece SDL, cada una de esas APIs responden ante un subsistema ya que los elementos que las componen tienen un objetivo común.

Todos estos subsistemas comprenden la parte principal del temario de este tutorial. Como fueron introducidos en el capítulo anterior vamos a ofrecer una vista rápida de cada uno de ellos para que conozcas las posibilidades que te ofrece SDL.

Los subsistemas soportados por SDL son:

  • Subsistema de Video: Engloba todo lo referente al hardware de video y lo referente a los gráficos.
  • Subsistema de Audio: Es el encargado de la reproducción de sonidos y de la gestión del hardware destinado al audio.
  • Subsistema de Gestión de Eventos: Es la base de la interactividad con el usuario y con ciertos aspectos del sistema. Con este subsistema sabemos, por ejemplo, que desea hacer el usuario.
  • Joysticks: Al no existir un estándar este subsistema nos permite un mayor control sobre los dispositivos de juegos.
  • CD-ROM: Nos permite controlar la reproducción de CD-Audio.
  • Timers: Nos permite sincronizar nuestro videojuego para que tenga la misma respuesta en diferentes tipos de sistemas.
  • Otros: Ademas de todos estos subsistemas existen diferentes extensiones que nos facilitarán el trabajo con SDL como SDL_image, SDL_ttf... Además de añadir funcionalidad, como SDL_net para el manejo de redes o SDL_mixer que nos proporciona que el subsistema de audio sea realmente manejable.

Como ya hemos comentado profundizaremos en el estudio de cada uno de estos subsistemas. Cuando realices tus aplicaciones podrás dividir el desarrollo en tareas teniendo como discriminante el subsistema que vayas a utilizar. Por ejemplo en un primer momento puedes empezar a desarrollar la parte gráfica, luego, en una segunda tarea proporcionar a ese entorno gráfico de manejabilidad mediante eventos. Una vez depurada estas dos fases adentrarte en añadir audio a la aplicación... Con esta división en tareas podrás centrarte exclusivamente en un subsistema a la vez lo que te permitirá tener un mayor control sobre tu código.

En uno de los capítulos finales del tutorial desarrollaremos un videojuego paso por paso. Podrás tomarlo como guía la hora de empezar con el desarrollo de tu propio videojuego.

[editar] Compilando

Si has seguido los pasos de este tutorial para instalar la librería SDL ya habrás conseguido compilar tu primer programa SDL. En este apartado vamos a profundizar más en la semántica de las órdenes que introduciste así como en la utilización de las diferentes opciones de sdl-config que nos pueden ayudar a la hora de realizar la compilación.

El compilador elegido para el desarrollo del curso es el compilador GCC de GNU que nos proporciona la capacidad de poder construir nuestros proyectos implementados en C y en C++. Más que un compilador es una suite de compilación ya que realiza más tareas que el simple compilado. Por esto y porque es una de las herramientas libres por excelencia nos hemos decidido por esta suite. Aún así puedes usar cualquier compilador de C/C++.

Vamos a partir de las órdenes lanzadas en el capítulo anterior para estudiar el porqué de ejecutar dichas ordenes. Aunque se suponen ciertos conocimientos de programación y como consecuencia de compilación no queremos dejar nada en el tintero. La primera orden fue:

g++ -o test test1.c -lSDL 

Como ya sabes g++ es la orden que nos permite construir nuestro programa con el compilador GNU de C++. Cuando llamamos a este compilador con g++ o c++ normalmente realizamos el preprocesado, la compilación y el enlazado. Mediante el uso de opciones podemos parar este proceso en un punto intermedio. GCC acepta opciones, nombres de ficheros y operandos. Acepta varias opciones pero éstas no pueden ser agrupadas y tienen que ser especificadas por separado. Cuando vayamos haciendo uso de las opciones iremos explicando en qué consiste cada una de ellas ya que no es objetivo de este tutorial un profundo estudio del compilador, analizando solamente lo necesario.

En nuestra orden le pasamos como opción -o que nos permite especificar el nombre del fichero ejecutable que se va a crear después de compilar y enlazar el programa. El nombre de nuestro fichero ejecutable será test. Sólo hemos necesitado un fichero por lo que en la misma orden vamos a compilar y enlazar el programa. El fichero a compilar es test1.c.

Finalmente aparece en la orden -lSDL. Esta es la parte del comando que nos permite indicarle al compilador que debe enlazar nuestro fichero con la librería SDL. Si pruebas a intentar compilar el programa de prueba sin esta opción recibirás un mensaje de error como el siguiente:

In function `main':
test1.c:(.text+0x19): undefined reference to `SDL_Init'
test1.c:(.text+0x27): undefined reference to `SDL_GetError'
test1.c:(.text+0x4a): undefined reference to `SDL_Quit'
test1.c:(.text+0x73): undefined reference to `SDL_SetVideoMode'
test1.c:(.text+0x81): undefined reference to `SDL_GetError'
test1.c:(.text+0xb1): undefined reference to `SDL_WM_SetCaption'
test1.c:(.text+0xd2): undefined reference to `SDL_PollEvent'
collect2: ld devolvió el estado de salida 1

Puedes ver todos los errores que nos muestra el compilador son referencias sobre funciones que, como puedes observar por el prefijo, pertenecen a SDL. La solución: indicarle al compilador que debe de enlazar contra las librerías de SDL mediante -lSDL.

El proceso de compilación no se complica mucho más por añadir la biblioteca SDL. Para cada una de las librerías adicionales de SDL existe una forma equivalente de indicar al compilador que las utilice en el proceso de enlazado. Vamos a observar la segunda orden que ejecutamos en el capítulo anterior, se trata de:

g++ -o test test1.c -lSDL -lSDL_image -lSDL_ttf -lSDL_mixer -lSDL_net 

Como puedes ver hemos añadido -lSDL_image -lSDL_ttf -lSDL_mixer -lSDL_net con esto indicamos que queremos enlazar con todas las librerías auxiliares que instalamos también en el capítulo anterior. Si no necesitamos alguna de estas librerías adicionales lo mejor es no incluirlas en el momento de compilación ya que haría crecer el tamaño de nuestro fichero ejecutable sin sentido alguno.

En el caso de que necesitamos compilar un fichero fuente SDL, pero no enlazarlo el proceso es el mismo que para cualquier aplicación escrita en C/C++. La orden desde consola es la siguiente:

g++ -c mi_fichero.cpp

Con esto conseguiremos compilar el fichero objeto para luego enlazarlo. g++ compilará el fichero y como resultado obtendremos el objeto del fichero fuente. Estos ficheros son fácilmente reconocibles ya que poseen, normalmente, la extensión .o

Es una buena práctica especificar al compilador las banderas propias de SDL. Con este fin vamos a estudiar la utilidad sdl-config.

[editar] La herramienta sdl-config

SDL proporciona una herramienta de consulta que nos va a ser útil a la hora de realizar la compilación de nuestro proyecto. Se trata de sdl-config. Si introduces esta orden en un terminal y pulsas enter podrás observar que hay disponibles varias opciones. Vamos a detallar las más relevantes:

  • sdl-config --version: Nos muestra la versión de la librería SDL instalada en el equipo.
  • sdl-config --cflags: Indica las banderas u opciones que le podemos indicar al compilador de C/C++ cuando utilicemos SDL. Normalmente trabajaremos con makefiles por lo que será interesante iniciar la variable CXXFLAGS con el valor devuelto por este comando. Es una buena práctica que el compilador conozca estas banderas a la hora de crear un fichero objeto.
  • sdl-config --static-libs Estas dos opciones nos permiten indicarle al compilador las librerías sobre las que se debe de enlazar nuestro programa SDL así como la ruta donde están las mismas.
  • sdl-config --libs: Ídem

Haciendo uso de esta utilidad podemos compilar nuestro fichero fuente de la siguiente manera:

g++ -o test test1.c 'sdl-config --cflags --libs'

Esta utilidad nos permite no tener que recordar que librerías y que banderas tenemos que añadir a la línea de compilación. Recopilando, cuando queramos obtener un fichero objeto la orden a ejecutar es:

g++ -c `sdl-config --cflags` mifuente.c 

Cuando queramos enlazar nuestros ficheros objeto utilizaremos la siguiente orden:

g++ -o miaplicacion mifuente.o `sdl-config --libs' <bibliotecas adicionales>

[editar] Los tipos de datos en SDL y la portabilidad

Antes de empezar a presentar funciones debes conocer algunas peculiaridades sobre los tipos de datos en SDL. No podemos dejar pasar la oportunidad de aclarar conceptos antes de seguir caminando con este tutorial.

Como ya sabemos SDL es multiplataforma. Necesitamos que los tipos de datos que utilicemos sean iguales independientemente del sistema en el que desarrollemos nuestra aplicación. Seguramente queramos compilarla en otro entorno y es importante que lo tengamos todo controlado. Por ejemplo el tipo de datos int puede tener rangos diferentes según el sistema donde compilemos nuestra aplicación o en versiones de compilador diferentes. Esto es debido a que se reservan diferentes números de bits para la representación de este tipo de dato. Para que nuestro código sea portable y no tengamos comportamientos no deseados estos bits tienen que ser los mismos sea cual sea el sistema al que portemos el código.

Con el fin de conseguir esta unicidad SDL redefine estos datos según el número de bytes que necesitemos y si el dato debe de tener signo o no. La forma de identificar las redeficiones de los tipos de datos es muy simple como vamos a ver. Estas redificiones siguen una regla básica. Observa la siguiente palabra:

GtttBB

En G indicamos si la variable que vamos a definir es con signo (S) o sin signo (U). En la parte que referenciamos aquí con ttt indicamos el tipo de datos que vamos a utilizar, es decir, si el tipo de dato a declarar o definir es un int, float... Como puedes ver el que hayamos puesto tres t no condiciona el número de carácteres de esta parte de la definición. El último de los campos se refiere al número de bits que necesitamos para este tipo de dato. No podemos poner cualquier número si no sólo aquellos que sean múltiplos de 8, desde el mismo 8 hasta el 64, es decir que para el número de bits los valores posibles son 8, 16, 32 y 64.

Ninguno de los campos que hemos expuesto son omitibles. Así si queremos definir una variable entera de 32 bits sin signo lo haríamos de la siguiente manera:

Uint32 mi_variable; 

Si por ejemplo queremos definir un entero de 16 bits con signo pues bastaría con que el tipo de datos fuese Sint16. Estos tipos tendrán las mismas características sea cual sea el sistema en el que vayamos a portar nuestro código. Como puedes ver esta redefinición de datos no tiene una sintaxis compleja y va a ser de mucha utilidad en el desarrollo de nuestras aplicaciones.

La mayoría de los parámetros y tipos de datos más complejos definidos en SDL se construyen sobre este tipo de datos. Lógico, ya que garantiza la homogeneidad de la que hace gala para los diferentes sistemas.

Con esta notación podemos utilizar fácilmente tipos de datos ajustados a nuestras necesidades en todo momento lo que es un punto a favor de SDL. Veamos un ejemplo por si todavía no has visto la ventaja que supone tener definidos este tipo de datos. Imagínate que desarrollas tu aplicación sobre un sistema que define el tipo int con 32 bits. Ahora decides portar tu aplicación a un sistema Linux con un compilador que reserva para los enteros de tipo int sólo 16 bits. Puede darse el caso de que en un primer momento usases valores que ahora mismo estuviesen fuera de rango para el sistema al que portas la aplicación. El esfuerzo de los creadores para que SDL fuese portable cae en saco roto. La solución es bastante clara en vez de utilizar int en el código original, y si necesitamos 32 bits, es definir nuestras variables de este tipo con Sint32 y solucionaríamos este problema.

[editar] SDL_GetError() y SDL_WasInit()

Vamos a presentar en esta sección dos funciones que nos van a ser de gran ayuda a la hora de realizar nuestras aplicaciones. Sin más pasamos a detallarlas.

La función SDL_WasInit() determina que subsistema o subsistemas están inicializados. El prototipo de la función es el siguiente:

Uint32 SDL_WasInit(Uint32 flags);

Esta función toma un sólo parámetro, un entero de 32 bits sin signo, y devuelve un valor de las mismas características. Esta función actúa de la siguiente manera. Le pasamos como parámetro una de las banderas que utilizamos para inicializar la librería SDL, por ejemplo SDL_INIT_AUDIO. Ahora bien si la función nos devuelve el valor 0 es que dicho subsistema no ha sido inicializado, pero si nos devuelve el valor que le hemos pasado como parámetro, en nuestro caso SDL_INIT_AUDIO el significado será que el subsistema ha sido inicializado y que está disponible en dicho instante.

Si, por ejemplo, le pasamos otro valor combinado, como puede ser SDL_INIT_AUDIO | SDL_INIT_VIDEO la función puede devolver tres valores diferentes. El primero de ellos 0, que significará que ninguno de los dos subsistemas ha sido inicializado. Si devuelve SDL_INIT_AUDIO | SDL_INIT_VIDEO tendremos los dos subsistemas inicializados. Ahora bien, la función SDL_WasInit() tiene la capacidad de discernir si ha sido inicializado sólo uno de los subsistemas que ha recibido como parámetro, por lo que devolverá el valor de la constante del subsistema que, habiendo sido pasado como parámetro de la función, esté inicializado. Así si el subsistema de audio no está inicializado y el de video sí, la función devolverá el valor SDL_INIT_VIDEO.

Esto nos permite comprobar si tenemos disponible un subsistema o si debemos de inicializarlo.

La función SDL_GetError() es fundamental para el programador SDL. Nos muestra información del último error producido en SDL. Proporciona un mensaje de error para cualquier función fallida de SDL. El prototipo de la función es:

char *SDL_GetError(void);

Como puedes ver devuelve una cadena de carácteres que podemos mostrar en pantalla, normalmente por consola, para poder revisar cual ha sido el último error interno de SDL. Esta cadena es asignada estáticamente y no debe ser liberada por el usuario en ningún momento.

Cada vez que realicemos una llamada a una función que informe de un estado de error deberemos de realizar la comprobación, mediante una estructura selectiva, de existencia de error teniendo que hacer uso de esta función para informar del mismo. Las funciones en SDL suelen devolver el valor -1 si se ha producido un error en ellas pero esto no es suficiente en la mayoría de los casos. No se proporciona más información que el propio error. Ahí es donde sale a nuestra ayuda SDL_GetError() aportándonos información adicional.

Cuando utilicemos esta función en nuestro código y no ejecutemos nuestra aplicación desde consola se creará un fichero stdout.txt o stderr.txt en el directorio de trabajo desde donde hayamos lanzado la aplicación. Se creará un fichero u otro dependiendo de donde hayamos direccionado el error. En nuestro caso usaremos la función fprintf() de C o cerr en C++ para indicar que dicho mensaje debe ir a la salida estándar de errores. Como puedes suponer dichos ficheros contendrán los errores ocurridos en la ejecución del programa.

Este trozo de código ilustra las líneas anteriores:

<cpp>
// En C

if(funcion() != OK) {
   fprintf(stderr, "Error en funcion: %s", SDL_GetError());
   exit(1);

}

// En C++

if(funcion() != OK) {

   cerr << "Error en funcion(): " << SDL_GetError() << endl;
   exit(1);
}

</cpp>

Esta función te será de mucha utilidad durante el desarrollo de tus aplicaciones. No dudes en utilizarla ya que te ahorrá muchos quebraderos de cabeza.

Puede que estés deseando que dejemos de dar vueltas por elementos accesorios de SDL y nos pongamos mano a la obra con los subsistemas. Piensa que esto que estamos viendo ahora mismo es realmente importante y nos ayudará a situarnos el tiempo que estemos realizando aplicaciones en SDL.

[editar] Inicialización y Finalización de SDL

En esta sección vamos a mostrar como preparar nuestro código para que acepte funciones y tipos de datos SDL. Este proceso es fundamental ya que será necesario para todas y cada uno de las aplicaciones y ejemplos que creemos con esta librería.

Los primero que debemos hacer cuando vamos a escribir nuestro programa SDL es incluir el fichero de cabecera de la biblioteca. SDL se instala en una ruta donde el enlazador va a comprobar si existe la librería a la hora de realizar el enlazado de nuestro programa con dicha librería. Para incluir el fichero de cabecera sólo debemos añadir esta línea junto a los demás "includes" de nuestro programa:

#include <SDL/SDL.h>

Si has optado por seguir el curso sobre otro sistema operativo distinto a GNU/Linux debes de comprobar que la ruta donde instalaste la librería está incluida en el library path del sistema. Con este "include" nuestra suite de compilación GCC podrá comprobar los problemas de sintaxis en las funciones y tipos de datos propios de SDL.

Una vez incluido el fichero de cabecera debemos de iniciar la utilización de la librería. En cada uno de los apartados del tutorial encontrarás un recordatorio de cómo inicializar el subsistema específico que se esté tratando en dicho apartado.

SDL proporciona una función que realiza la tarea de inicializar la librería con unas determinadas características. Es la función:

int SDL_Init(Uint32 flags);

Esta función recibe como parámetro un entero sin signo de 32 bits y devuelve un 0 si la inicialiazación fue correcta. En el caso de no serla devuelve el valor -1. El parámetro flags puede tomar distintos valores según los subsistemas que queramos inicializar. Estos valores vienen definidos en SDL por unas constantes que son:

  • SDL_INIT_VIDEO: Inicializa el subsistema de video.
  • SDL_INIT_AUDIO: Inicializa el subsistema de audio.
  • SDL_INIT_TIMER: Inicializa el subsistema de timers.
  • SDL_INIT_CDROM: Inicializa el subsistema de CD-ROM.
  • SDL_INIT_JOYSTICK: Inicializa el subsistema de joystick.
  • SDL_INIT_EVERYTHING: Inicializa todos los subsistemas.
  • SDL_INIT_NOPARACHUTE: Prepara a SDL para capturar señales de error fatal.

A la hora de inicializar el sistema podemos combinar varios de estos subsistemas en la misma llamada. Estas constantes definen valores de bit. Para combinarlas haremos uso del operador |. Por ejemplo si queremos iniciar el sistema con los subsistema de audio, video y CD-ROM pasaríamos como parámetro a la función SDL_Init el valor:

SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_CDROM

Quedando la llamada a función como SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_CDROM). Si queremos activar todo solamente pasaremos como parámetro SDL_INIT_EVERYTHING.

Como decíamos en el apartado anterior, es una buena costumbre hacer uso de la función SDL_GetError() en las llamadas a funciones que nos devuelven un posible estado de error. SDL_Init() es una de estas funciones. Para comprobar si la inicialización se ha realizado correctamente podemos utilizar el siguiente esquema:

<cpp>
// Ejemplo de inicialización de subsistemas
// Inicializamos video y audio

if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {

    fprintf(stderr, "Error en SDL_Init(): %s", SDL_GetError());
    exit(1);
}

</cpp>

En este código inicializamos los subsistemas de video y audio a la vez. Si existe algún problema informamos a través de la salida estándar de errores y terminamos el programa. Como puedes ver se trata de código C, pero válido en C++. Como comentamos en el primer capítulo iremos haciendo una introducción progresiva al desarrollo en C++.

La tarea de inicializar el sistema sólo se puede realizar una vez. Si necesitamos inicializar otro subsistema en el transcurso de nuestra aplicación podemos hacerlo con la función:

int SDL_InitSubSystem(Uint32 flags);

De la misma manera que lo hacíamos con la función principal. Por ejemplo si queremos inicializar el subsistema de audio después de haber llamando ya a la función SDL_Init() la sintaxis correcta sería:

SDL_InitSubSystem(SDL_INIT_AUDIO);

Esta función devolvería el correspondiente valor que nos permite verificar si la inicialización fue correcta. Igual que necesitamos inicializar SDL para comenzar a trabajar con ella, tenemos que cerrarla una vez que hayamos concluido de utilizar la librería. Con este objetivo SDL proporciona la función:

void SDL_Quit(void);

Se encarga de cerrar todos los subsistemas de SDL además de liberar todos los recursos ocupados por la librería que fueron reservados inicialmente mediante SDL_Init(). Como ya hemos dicho siempre tiene que ser llamada antes de salir del programa. Como puedes observar la función ni devuelve ni recibe ningún parámetro por lo que la hace ideal para pasarla como parámetro a la función atexit(). Por si no conocías la función atexit() recibe un puntero a función que ejecutará a la terminación del programa.

Introduciendo en nuestro código la sentencia atexit(SDL_Quit), siempre que sea después de SDL_Init() claro está, podremos olvidarnos de realizar la llamada a la función de terminación en puntos concretos del código. Esta es una solución válida para realizar pequeños programas. Para un código más avanzado debemos de cerrar la librería manualmente cuando ya no sea necesaria.

Tal como pasaba con la inicialización de SDL podemos cerrar un sólo subsistema en un momento dado. Para ello deberemos de hacer uso de la función:

void SDL_QuitSubSystem(Unit32 flags);

Por ejemplo si quisieramos desactivar el subsistema de audio introduciríamos en nuestro código la llamada a la función como SDL_QuitSubsystem(SDL_INIT_AUDIO). Como puedes observar la función recibe las mismas banderas que la función de inicialización de la librería.

En la siguiente tabla tienes un resumen de las funciones que acabamos de presentar:.

Resumen funciones básicas
Función Descripción
SDL_Init() Inicializa SDL con uno o más subsistemas.
SDL_InitSubSystem() Inicializa un subsistema de SDL en particular. Sólo podemos usar esta función después de haber usado SDL_Init()
SDL_Quit() Termina todos los subsistemas de SDL.
SDL_QuitSubSystem() Termina un subsistema de SDL en particular.
SDL_WasInit() Nos permite comprobar que subsistemas están inicializados actualmente
SDL_GetError() Devuelve el último error interno informado por SDL.

Llegados a este punto sabemos iniciar la librería, así como compilar con ella. Es hora de, con ayuda de un par de funciones más realizar nuestro primer ejemplo con SDL.

[editar] Hola Mundo

Es muy difícil definir que tipo de programa es un "Hola mundo" en SDL. Por simplicidad vamos a intentar poner en marcha SDL inicializando el subsistema de video en modo ventana. Colocaremos en esa ventana el nombre de nuestra aplicación y daremos por finalizado nuestro ejemplo. Vamos a utilizar código C en este ejemplo, según vayamos avanzando en el tutorial iremos introduciendo un mayor número de líneas de código C++ y mostrando las equivalencias en C para no desviarnos de nuestro objetivo principal.

El código que responde a esta especificación es:

<cpp>
// Listado: prueba.cpp
// Hola Mundo

#include <stdio.h>
#include <SDL/SDL.h>             // Incluimos la librería SDL


int main() {

    SDL_Surface *pantalla;       // Definimos una superficie
    SDL_Event evento;            // Definimos una variable de eventos


    // Inicializamos SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0) {
	
	// En caso de error
	
	fprintf(stderr, "Error al inicializar SDL: %s\n", SDL_GetError());
	exit(1);
    }

    atexit(SDL_Quit);            // Al salir, cierra SDL

    // Establecemos el modo de pantalla

    pantalla = SDL_SetVideoMode(640, 480, 0, SDL_ANYFORMAT);

    if(pantalla == NULL) {

	// Si no hemos podido inicializar la superficie
	
	fprintf(stderr, "Error al crear la superficie: %s\n", SDL_GetError());
	exit(1);
    
    }

    // Personalizamos el título de la ventana

    SDL_WM_SetCaption("HOLA MUNDO", NULL);

    
    // Bucle infinito

    for(;;) {
	
	 // Consultamos los eventos
 
	while(SDL_PollEvent(&evento)) {

	    if(evento.type == SDL_QUIT) // Si es de salida
		return 0;
	}
    }

}
</cpp>

Para compilar el ejemplo sólo debes de introducir en la consola, una vez situado en el directorio donde guardes el fichero de prueba la orden:

g++ -o test prueba.cpp `sdl-config --cflags --libs`

Con lo que crearemos un ejecutable llamado test resultado de la compilación. Si ejecutas el programa observarás una ventana cuyo título será "Hola Mundo". Vamos a estudiar que hemos hecho en esta primera prueba.

Lo primero que hacemos en la función principal es definir dos variables que utilizaremos con posterioridad. La primera es de tipo superficie (SDL_Surface). Este tipo de variables es muy común en el tratamiento de gráficos en SDL. Se estudiará en profunidad en el capítulo que trata sobre el subsistema de video. Esta superficie es la principal de la aplicación que mostrará todos aquellos gráficos que maquetemos en ella. Para realizar esta tarea existen varias técnicas, que como hemos indicado, estudiaremos más adelante.

La segunda variable que creamos es la de tipo evento (SDL_Event). Mediante esta variable vamos a controlar una acción del usuario que nos permita dar por finalizada la ejecución de la aplicación. También estudiaremos los eventos en profundidad en un capítulo posterior.

Hola mundo
Hola mundo

El siguiente paso en el código es iniciar la librería SDL con el subsistema de video. Para ello, como puedes observar utilizamos la función SDL_Init(). Esta parte del código es fundamental ya que será común en todas las aplicaciones que realicemos especificando qué subsistemas queremos que se inicialicen con la librería. El hecho de realizar la inicialización dentro del campo de condición de un if simplemente es para comprobar que sea correcta, y si no lo es, poder mostrar el correspondiente mensaje en la salida estándar de errores.

Lo siguiente que nos encontramos en el código es la llamada a la función atexit() que recibe como parámetro la función SDL_Quit. Esto produce que a la salida de la aplicación sea cerrada la librería SDL y sus subsistemas.

Es el momento de establecer el modo de video. Para esto sirve la función SDL_SetVideoMode(). Esta función devuelve la superficie que mostraremos en pantalla. Esta función y los conceptos asociados a ellas serán estudiados con profundidad. Una vez establecido el modo de video comprobamos que se haya realizado correctamente.

Nuestro Hola mundo se basa en la personalización de una ventana. Con ese objetivo realizamos la llamada a la función:

SDL_WM_SetCaption("HOLA MUNDO", NULL)

Esta funcion establece en el título de la ventana el primer parámetro que recibe, mientras que el segundo (en este caso a NULL), sirve para especificar el icono para la aplicación. Como todas las demás funciones de este ejemplo será vista con más profundidad en capítulos posteriores.

El último trozo de código es conocido como game loop. El game loop de un videojuego es un bucle que se repite indefinidamente en el que se realizan varias acciones. Las acciones más comunes son las de responder a las acciones producidas por el usuario, actualizar la lógica del juego, así como los gráficos... En este caso nuestro bucle sólo reacciona al evento de salida de la aplicación. Por el momento esto es todo lo que debes de saber sobre el código del ejemplo.

[editar] Trabajando con las librerías adicionales

Cada una de las librerías adicionales SDL que utilicemos en nuestro código tienen que ser inicializadas y cerradas individualmente. El proceso es análogo al utilizado con SDL con la particularización de las funciones que realizan esta tarea en las diferentes librerías.

En esta sección vamos a ver como podemos realizar esta tarea para cada una de las librerías. Incluiremos un pequeño recordatorio en los capítulos donde tratamos estas librerias.

[editar] SDL_image

Preparar el código para hacer uso de esta librería es un proceso muy sencillo. Simplemente necesitamos especificar en nuestro fichero fuente que vamos a utilizarla mediante su fichero de cabecera. Para esto añadimos:

#include <SDL/SDL_image.h>

A parte de esto debes recordar especificar mediante -lSDL_image al compilador que quieres que esta librería sea enlazada por nuestra aplicación.

[editar] SDL_ttf

Para utilizar esta librería auxiliar tenemos que seguir unos pasos que son comunes a la mayoría de las librerías auxiliares que utilizamos con SDL. El primer paso es incluir su cabecera en los ficheros fuentes donde vayamos a utilizar funciones o tipos de datos definidas en esta biblioteca. Para ello añadimos:

#include <SDL/SDL_ttf.h>

en nuestro código fuente. El segundo paso es el de inicializar la librería. De esto se hace cargo la propia librería que nos proporciona una función que en este caso es:

int TTF_Init(void);

Esta función no recibe ningún parámetro y devuelve 0 si la inicialización fue correcta. En el caso de que ocurriese algún error la función devolverá el valor -1. Al terminar de utilizar la librería deberemos de cerrarla. Para realizar esta tarea tenemos otra función cuyo prototipo es:

void TTF_Quit(void);

Como puedes ver esta función no recibe ni devuelve ningún valor lo que la hace compatible con la función atexit() lo que nos permitirá, si hacemos uso de ella, descuidarnos de la tarea de cerrar dicha librería. Un esquema de proceso de uso de esta librería podría ser el siguiente:

<cpp>
// ... otros includes ...

#include <SDL/SDL_ttf.h>

// ... código ...

SDL_Init(SDL_INIT_VIDEO);
    

// Inicializamos SDL_ttf
if(!TTF_Init() < 0) {
    
    fprintf(stderr, "No se puedo inicializar SDL_ttf\n");
    exit(1);
    
 }

atexit(TTF_Quit());

// ... código donde utilizamos SDL_ttf ...
</cpp>

Recuerda que tienes que indicar al compilador el uso de esta librería mediante -lSDL_ttf cuando quieras construir la aplicación.


[editar] SDL_mixer

Para utilizar esta librería auxiliar tenemos que seguir los pasos comunes a la mayoría de las librerías auxiliares que utilizamos con SDL. El primero es incluir su cabecera en los ficheros fuente donde vayamos a utilizar funciones o tipos de datos definidas en la biblioteca en cuestión. Para ello añadimos #include <SDL/SDL_mixer.h> a nuestro código fuente donde hagamos uso de esta librería.

El segundo paso es el de inicializar la librería. De esto se hace cargo la propia librería mediante una función que en el caso de SDL_mixer es:

int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);

Esta función recibe varios parámetros. El nombre que recibe cada uno de ellos es descriptivo pero, si aún así no conoces los conceptos que representan, no te impacientes ya que hay un capítulo entero donde desarrollamos la utilización de esta librería. La función devuelve 0 si la inicialización fue correcta. En el caso de que ocurriese algún error la función devolverá el valor -1. Al terminar de utilizar la librería deberemos de cerrarla para ello tenemos otra función cuyo prototipo es:

void Mix_CloseAudio(void);

Como puedes ver esta función no recibe ni devuelve ningún valor lo que la hace compatible con la función atexit() lo que nos permitirá, siempre que hagamos uso de ella, no tener que preocuparnos de cerrar dicha librería. Un esquema de proceso de uso de esta librería es análogo al presentado en la inicialización-cierre de SDL_ttf.

Recuerda que tienes que indicar al compilador el uso de esta librería mediante -lSDL_mixer cuando quieras construir la aplicación.

[editar] SDL_net

Para hacer uso de esta librería tenemos que realizar unos pasos que te van a resultar, a estas alturas, familiares. El primero de ellos es incluir el fichero de cabecera de la librería en los ficheros fuente donde necesitemos de la utilización de funciones y tipos de datos definidos en SDL_net. Para incluir el fichero utilizamos:

#include <SDL/SDL_net.h>

El siguiente paso es inicializar la propia librería antes de hacer uso de ella. Para esto la librería proporciona la función:

int SDLNet_Init();

Esta fución no recibe ningún parámetro y devuelve -1 en caso de que exista algún error. Si la inicialización ha sido correcta devuelve el valor 0. Una vez inicializada la librería podemos hacer uso de ella. SDL debe estar inicializada antes de realizar una llamada a esta función.

Cuando la aplicación llegue a su fin, o bien, no necesitemos más de esta librería debemos de cerrarla. Para ello existe otra función propia de SDL_net. Se trata de:

void SDLNet_Quit();

Como puedes ver esta función no recibe ni devuelve ningún valor. Esto la hace compatible con la función atexit() lo que nos permitirá, siempre que hagamos uso de ella, no tener que preocupardos de cerrar dicha librería. Esta función se encarga de finalizar la API para los servicios de red y realiza la limpieza de los recursos utilizados. Después de la llamada a esta función se cierran todos los sockets y no se permite el uso de ninguna función de la biblioteca SDL_net.

Claro está, para poder volver a utilizar funciones de SDL_net después de la llamada a esta función habría que llamar de nuevo a int SDLNet_Init().

[editar] Recopilando

En este capítulo hemos dado los primeros pasos con SDL. Han sido un poco pesados porque detallan tareas, como la de inicialización, que van a ser comunes a todas las aplicaciones SDL que desarrollemos.

Una vez establecido el entorno de desarollo se han presentado los subsistemas de SDL en un segundo contacto, ya que forman gran parte del contenido del tutorial.

Hemos detallado los pasos necesarios para compilar cualquier programa SDL con la ayuda de la herramienta sdl-config.

SDL presenta características, funciones y tipos de datos para que nuestro código sea totalmente portable, así como funciones auxiliares que nos sirven de ayuda a la hora de desarrollar nuestra aplicación.

La inicialización y finalización de SDL y las distintans librerías auxiliares es fundamental para el correcto funcionamiento de nuestro programa.

Hemos implementado, compilado y probado nuestro primer programa "Hola Mundo".

Herramientas personales