Introducción

Logo SDL2

SDL 2 es la segunda versión es una biblioteca de desarrollo multiplataforma diseñada para proporcionar acceso de bajo nivel a hardware de audio, teclado, ratón, joystick y gráficos a través de OpenGL y Direct3D.

SDL está escrito en C, y también funciona de forma nativa con C++ y existen enlaces disponibles para otros lenguajes, como C# y Python.

SDL 2.0 y versiones posteriores están disponibles bajo la licencia zlib.

Generalmente se usa para la creación de juegos en 2D, aunque no tenemos por qué usarla exclusivamente para eso.

Nota: aunque en el momento de publicar este curso ya existe una versión 3 (preview), que aún no es estable, cuando empecé a escribirlo esa versión estaba en una fase anterior, y aún muy incompleta. Escribir estas páginas requiere mucho tiempo, y los desarrolladores no permanecen ociosos, así que es inevitable que a menudo los cursos no estén tan actualizados como me gustaría. Sin embargo, ojeando la documentación de la versión 3, considero que en la práctica todo lo que se muestra en este curso es aplicable sin cambios o con muy pocos cambios a la nueva versión.

Instalación de la librería SDL2

Nos centraremos, como de costumbre, en la instalación para Windows y la usaremos en el entorno Code::Blocks. No tendrás muchos problemas para encontrar guías para instalarla en otros sistemas operativos o para usar otros entornos de desarrollo u otros compiladores de C o C++.

Hay varias formas de instalar las librerías SDL2, pero veremos dos ejemplos.

Desde la web oficial

La instalación es muy sencilla, podemos acceder a la página de descargas desde la propia web del SDL. Descargaremos la versión que más nos convenga, dependiendo del sistema operativo y del entorno que prefiramos usar.

En el momento de publicar este curso, la última versión estable es la 2.30.8. Para usar el compilador que usaremos con el IDE de Code::Blocks, que es MinGW, descargaremos el fichero SDL2-devel-2.30.8-mingw.zip o su equivalente SDL2-devel-2.28.4-mingw.tar.gz.

Una vez descargado lo abrimos y veremos que, entre otras carpetas y ficheros, tenemos dos con los nombres:

  • i686-w64-mingw32, que contiene la librería para crear aplicaciones de 32 bits.
  • x86_64-w64-mingw32, con la librería para crear aplicaciones de 64 bits.

Vamos a crear una carpeta específica para todas las librerías relacionadas con SDL2, por ejemplo, C:\SDL2."

Elegimos la versión correspondiente a nuestro sistema operativo. y descomprimimos las carpetas bin, include y lib. No necesitaremos ningún otro fichero.

Esto añadirá los ficheros de cabecera, librerías de enlace estático y dinámico y los ejecutables. Veremos más adelante cómo compilar programas con las librerías estáticas, pero de momento usaremos las dinámicas.

Instalación de SDL_image

Necesitaremos otras librerías adicionales, entre ellas SDL_image, que nos permitirá trabajar con imágenes en diferentes formatos.

Empezaremos por descargar la versión de desarrollo desde SDL_image, ya sea el fichero SDL2_image-devel-2.8.2-mingw.zip, o SDL2_image-devel-2.8.2-mingw.tar.gz. Cuando escribo esto, la versión más reciente es la 2.6.3,

Una vez descargado, descomprimimos la versión que nos corresponda, de 32 y 64 bits, en sus carpetas correspondientes, como hicimos con las de SDL2, en las mismas carpetas.

Si queremos usar esta librería en nuestros programas deberemos añadir la la opción "SDL2_image.dll" a los "Linker settings" de nuestro proyecto.

Con esta librería instalada podemos cargar ficheros de imágenes en varios formatos, entre los que podemos incluir: BMP, GIF, JPEG, LBM, PCX, PNG, SVG y WebP, entre otros.

Instalación de SDL_ttf

Si queremos mostrar texto, será necesario instalar también la librería SDL_ttf, que nos permitirá trabajar con fuentes de caracteres TrueType.

Descargaremos la versión de desarrollo desde SDL_ttf, ya sea el fichero SDL2_ttf-devel-2.20.2-mingw.zip, o SDL2_ttf-devel-2.20.2-mingw.tar.gz. Cuando escribo esto, la versión más reciente es la 2.20.2.

Una vez descargado, descomprimimos la versión correspondiente, de 32 y 64 bits, en sus carpetas correspondientes, como hicimos con las de SDL2 o SDL_image.

Si queremos usar esta librería en nuestros programas deberemos añadir la la opción "SDL2_ttf.dll" a los "Linker settings" de nuestro proyecto.

Instalación de SDL_mixer

Aunque SDL 2 dispone de funciones para reproducir sonido, estas se limitan a ficheros WAV, y requieren pasos previos antes de poder reproducir sonidos.

Es más simple usar SDL_mixer, que se encargará de muchas tareas de bajo nivel por nosotros.

Como en casos anteriores, descargaremos la versión de desarrollo desde SDL_mixer, ya sea el fichero SDL2_mixer-devel-2.8.0-mingw.tar.gz, o SDL2_mixer-devel-2.8.0-mingw.zip. La versión más reciente cuando escribo esto es la 2.6.3.

Una vez descargado, descomprimimos las versiones de 32 y 64 bits en sus carpetas correspondientes, como hicimos con las de SDL2 o SDL_image o SDL_ttf.

Si queremos usar esta librería en nuestros programas deberemos añadir la la opción "SDL2_mixer.dll" a los "Linker settings" de nuestro proyecto.

Instalación de SDL2_gfx

Según la documentación de SDL2_gfx:

La librería SDL2_gfx proporciona las funciones básicas de dibujo como líneas, círculos o polígonos proporcionadas por SDL_gfx en SDL2 para renderizadores de SDL2.

Los componentes actuales de la biblioteca SDL2_gfx son:

  • Primitivas gráficas (SDL2_gfxPrimitives.h, SDL2_gfxPrimitives.c)
  • Rotozoom de superficie (SDL2_rotozoom.h, SDL2_rotozoom.c)
  • Control de velocidad de fotogramas (SDL2_framerate.h, SDL2_framerate.c)
  • Filtros de imagen MMX (SDL2_imageFilter.h, SDL2_imageFilter.c)
  • Fuente 8x8 incorporada (SDL2_gfxPrimitives_font.h)

Hay que tener en cuenta que SDL2_gfx es compatible con SDL versión 2.0 (no con SDL 1.2).

Es decir, permite entre otras cosas, dibujar pixels, líneas de diferentes grosores, rectángulos, círculos, arcos, elipses, polígonos, curvas bezier. Rotar y escalar gráficos, usar filtros...

Se puede consultar la documentación en SDL2_gfx.

Instalación de SDL_net

Una pequeña biblioteca de red multiplataforma, con una aplicación de ejemplo de cliente y servidor de chat.

Muy útil para crear juegos en red.

Instalar desde MSYS2

Otra alternativa es instalar las librerías desde MSYS2.

Para ello buscaremos los paquetes correspondientes a SDL2 usando la pacman. Por ejemplo, esta es la salida que obtuve al actualizar SDL2 la última vez:

$ pacman -Ss mingw-w64-x86_64-SDL2
mingw64/mingw-w64-x86_64-SDL2 2.30.3-1 [installed: 2.28.5-1]
    A library for portable low-level access to a video framebuffer, audio output, mouse, and
    keyboard (Version 2) (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_gfx 1.0.4-2
    SDL graphics drawing primitives and other support functions (Version 2) (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_image 2.8.2-3
    A simple library to load images of various formats as SDL surfaces (Version 2) (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_mixer 2.8.0-1
    A simple multi-channel audio mixer (Version 2) (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_net 2.2.0-1
    A small sample cross-platform networking library (Version 2) (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_pango 2.1.5-1
    SDL2 port of SDL_Pango (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_sound 2.0.2-1
    A library to decode several popular sound file formats, such as .WAV and .MP3 (mingw-w64)
mingw64/mingw-w64-x86_64-SDL2_ttf 2.22.0-1
    A library that allows you to use TrueType fonts in your SDL applications (Version 2) (mingw-w64)

E instalaremos o actualizaremos las librerías que queramos:

$ pacman -S mingw-w64-x86_64-SDL2
$ pacman -S mingw-w64-x86_64-SDL2_image
$ pacman -S mingw-w64-x86_64-SDL2_mixer
$ pacman -S mingw-w64-x86_64-SDL2_ttf
$ pacman -S mingw-w64-x86_64-SDL2_gfx
$ pacman -S mingw-w64-x86_64-SDL2_net

En mi opinión esta opción es mejor, ya que nos permite mantener las librerías actualizadas más fácilmente. Todos los ejemplos de este curso asumen que las librerías están instaladas usando MSYS.

Configuración de Code::Blocks

Variables globales SDL2
Variables globales SDL2

En Code::Blocks tendremos que editar las variables de globales, desde el menú Settings->Global variables. Si no existe la variable sdl2, la crearemos. Seleccionamos la variable sdl2 y rellenamos los campos base, include, lib y bin. El ejemplo asume que hemos instalado la librería en C:\SDL2, pero si hemos usado otra ruta habría que indicarla.

Si has optado por descargar las librerías desde MSYS2, las rutas serán diferentes:

  • base: C:\msys64\mingw64.
  • include: C:\msys64\mingw64\include.
  • lib: C:\msys64\mingw64\lib.
  • bin:C:\msys64\mingw64\bin.

Es necesario cerrar Code::Blocks para que los cambios tengan efecto.

Crear proyecto SDL2
Crear proyecto SDL2

Por último, ya podemos abrir de nuevo Code::Blocks y crear un proyecto nuevo usando la plantilla SDL2.

Ubicación de SDL2
Ubicación de SDL2

En un momento dado, y solo la primera vez que creemos un proyecto SDL2, nos preguntará la ubicación de SDL2, usaremos la variable que hemos definido "$(#sdl2)". La plantilla creará un programa de ejemplo básico, que creará una ventana y mostrará unas barras verticales de diferentes colores.

Ejemplo 1 SDL2
Ventana de ejemplo 1

Cuadros por segundo

La aplicación principal de SDL2 es para programar juegos, lo que requiere que se muestren en pantalla gráficos en movimiento, y para que la experiencia de juego sea agradable es necesario cumplir ciertas especificaciones. Por ejemplo, para que una sucesión de imágenes den la sensación de movimiento fluido el ojo humano requiere cierto número de imágenes por segundo, lo que se conoce como fps. En cine se ha usado tradicionalmente una velocidad de 24 fps, en televisión, al menos en Europa, se ha usado 25 fps, aprovechando la frecuencia de la red eléctrica que es de 50 Hz. Actualmente, con la mejora de las tecnologías, se usan velocidades mayores, y ya es habitual encontrar videos a 60 fps.

Imagen fantasma
Imagen fantasma

En juegos, sobre todo en juegos de acción, es preferible proporcionar velocidades aún mayores, de 144 o 240 fps. Cuantos más fps, las diferencias entre cuadros serán menores, con lo que las animaciones son más fluidas.

La explicación es que la retina no responde instantáneamente a la luz que le llega, y además tiene cierta memoria, es decir, las células tardan un pequeño tiempo en detectar la luz que les llega, y siguen dando información después de que desaparezca el estímulo. Si una animación tiene pocos fps, tendremos la sensación de ver imágenes fantasma de los fotogramas anteriores superpuestos con el actual, por lo tanto, cuanta más diferencia haya entre fotogramas consecutivos, mayor será ese efecto. A esto hay que añadir que los monitores también tienen cierta latencia, y la imagen no desaparece instantáneamente, lo que también produce el efecto de imágenes fantasma.

Rasgado
Rasgado

Mostrar muchos fotogramas por segundo también disminuye el rasgado. El rasgado se produce cuando se actualiza cada cuadro. Como la frecuencia con que se muestra la información en pantalla no coincide necesariamente con los fps, lo normal es que en un momento dado se estén mostrando dos cuadros a la vez. En la parte superior parte del cuadro nuevo, y en el resto de la inferior el cuadro anterior.

Una forma de evitar este efecto de rasgado es sincronizar los fps con la frecuencia de visualización. No tiene sentido que un juego genere 200 fps si el monitor solo puede mostrar 60 imágenes por segundo. Aquí entra el concepto de sincronización vertical, o v-sync, si sincronizamos los fps con la frecuencia de refresco del monitor, es importante tener en cuenta la señal de v-sync, de modo que no se produzca un rasgado en todos los fotogramas.

Afortunadamente, SDL2 nos proporciona las herramientas para que no tengamos que preocuparnos por estos detalles, usando la aceleración por hardware si está disponible, y la técnica de doble buffer, que consiste en que los gráficos se trazan en un buffer y la imagen mostrada se toma de otro buffer. Cuando damos una imagen por terminada se intercambian los buffers y se genera una nueva. Esto impide que se muestren imágenes incompletas o que se vea el proceso de creación de la imagen mientras se genera.

Ventanas y representaciones

Al usar la técnica de doble buffer es necesario diferenciar el área que se muestra en pantalla del área en que realizamos las operaciones gráficas.

SDL usa una estructura de ventana, SDL_Window para referirse a la representación en pantalla, y una estructura de representación, SDL_Renderer o de superficie SDL_Surface para las operaciones de trazado de gráficos.

El procedimiento es crear una imagen en el renderer o surface, y una ver terminada, mostrala en la ventana.

Bucle de juego

Un juego no es otra cosa que una aplicación en tiempo real. Es decir, los eventos que se produzcan, ya sea por acciones del jugador o del propio mecanismo del juego, se deben procesar y mostrar al usuario tan pronto como sucedan.

Este tipo de programas suelen afrontar la tarea mediante una estructura de este tipo:

  1. Inicialización
  2. Bucle de juego.
    1. Capturar los eventos.
    2. Procesar la información.
    3. Mostrar los resultados.
  3. Limpieza.

Si has programado usando el API de Windows esto te resultará familiar, es lo que se hace en la función main. Se crean las estructuras necesarias, como la ventana principal, se entra en el bucle de mensajes, que son los eventos de Windows, y se sale haciendo limpieza de los recursos utilizados por el programa.

En programación de microcontroladores, como Arduino, existe una estructura similar. Tenemos una función setup, que se ejecuta al arrancar el microcontrolador, y una función loop, que se ejecuta de forma infinita a continuación. En este caso no existe una función de limpieza, ya que los programas de microcontroladores nunca terminan de ejecutarse, salvo que se apague.

Por supuesto, podemos hacer uso de multitarea, creando hilos para procesar ciertas tareas. Estos hilos funcionan en paralelo con nuestro bucle, y en las máquinas que los soporten, funcionarán en núcleos separados del mismo procesador.

Multiplataforma

Ya hemos mencionado que SDL 2 es una librería multiplataforma. Eso significa, entre otras cosas, que muchas de sus funciones son un envoltorio para funciones del API de cada sistema operativo, lo que implica que algunas estructuras de datos no tengan una definición concreta, ya que dependen en gran medida de la plataforma. Esto pasa, por ejemplo, con la estructura SDL_Window, así que no podremos acceder a sus miembros, cosa que, por otra parte tampoco tiene mayor importancia, ya que se tratan internamente por la librería.