1 Preparación

Ya tenemos instalada nuestra librería, y nos hemos asegurado de tener acceso a la DLL, así como a los ficheros de cabecera.

Al utilizar la plantilla de proyecto SDL2 de Code::Blocks, también tenemos correctamente configuradas las opciones de proyecto, incluyendo en las opciones de enlazado todas las librerías necesarias.

Así que podemos empezar a escribir un ejemplo más básico que el de la plantilla, sin preocuparnos de momento por el manejo de excepciones ni de crear clases, ya llegaremos a eso más tarde.

Empecemos por lo básico. Añadiremos el include del fichero de cabecera de SDL.

Lo siguiente que necesitamos, como casi siempre que trabajamos con librerías, es iniciar la librería.

SDL 2 se compone de varios subsistemas: video, audio, temporización, eventos, controladores, joystick y háptica. Se pueden iniciar todos ellos, o solo aquellos que vayamos a utilizar.

Por cierto, reconozco que lo he tenido que buscar, el sistema háptico se encarga de dar respuesta táctil al usuario. En este apartado estaría, por ejemplo la vibración del mando de juegos.

Para iniciar subsistemas usaremos la función SDL_Init, y para no complicarnos la vida de momento, iniciaremos todos los subsistemas, pasando como parámetro el valor SDL_INIT_EVERYTHING.

De forma simétrica, una vez terminada la aplicación y antes de salir, deberemos liberar los recursos asociados a la librería. Para ello llamaremos a la función SDL_Quit.

#include <sdl2/SDL.h>

int main( int argc, char * argv[] ) {
    SDL_Init(SDL_INIT_EVERYTHING);

    SDL_Quit();
    return 0;
}

Por alguna razón es necesario añadir los argumentos de la función main, aunque no los usemos, de otro modo obtendremos un mensaje de error.

Crear una ventana

Pero esto es demasiado simple, este programa no hace nada. Así que vamos a añadir una ventana, que es lo mínimo que necesitará cualquier juego.

Para crear una ventana necesitamos un puntero a una estructura SDL_Window, que recibirá el resultado de llamar a la función SDL_CreateWindow.

Solo podemos declarar punteros a estructuras SDL_Window, ya que la propia estructura no está definida en la librería. Una declaración como SDL_Window ventana; no está permitida. Podemos considerar estos punteros como manipuladores, igual que hacemos con el API de Windows.

Necesitaremos pasar algunos parámetros: el título de la ventana, su posición, sus dimensiones y unas banderas con opciones.

Como con cada recurso, cuando ya no necesitemos la ventana habrá que liberarla, usando la función SDL_DestroyWindow.

#include <sdl2/SDL.h>

SDL_Window *ventana;

int main( int argc, char * argv[] ) {
    SDL_Init(SDL_INIT_EVERYTHING);
    ventana = SDL_CreateWindow("Mi primera ventana", 
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 
        640, 480, SDL_WINDOW_SHOWN);

    SDL_Delay(5000);

    SDL_DestroyWindow(ventana);
    SDL_Quit();
    return 0;
}

Para la posición hemos usado los valores SDL_WINDOWPOS_CENTERED, que hacen que SDL calcule las coordenadas para que la ventana quede centrada en la pantalla. En parámetro de banderas hemos usado el valor SDL_WINDOW_SHOWN, que hace que la ventana se muestre en pantalla. Puedes ver los valores definidos para estas banderas en SDL_WindowFlags. Algunos de estos valores se pueden combinar.

Para que nos de tiempo a ver la ventana he añadido un tiempo de espera con la función SDL_Delay.

EL contexto de renderizado

Bueno, ahora tenemos una bonita ventana negra, al menos durante cinco segundos, pero necesitamos dibujar algo dentro de ella.

En realidad no podemos dibujar directamente en la ventana. Debido al sistema de doble buffer, tendremos que trazar nuestros gráficos en un buffer en memoria, y cada vez que queramos ver la imagen, pasar ese buffer a la ventana.

La estructura para dibujar es un contexto de renderizado, así que consigamos uno. Al igual que con la ventana, necesitaremos un puntero a una estructura SDL_Renderer.

Del mismo modo, crearemos un contexto de renderizado mediante la función SDL_CreateRenderer, a la que pasaremos como parámetros un puntero a la ventana asociada, el índice controlador de renderizado a inicializar, y un conjunto de banderas.

De momento usaremos el valor -1 para el índice, para que SDL elija un controlador por nosotros, y el valor 0 para las banderas.

Al finalizar el programa también tendremos que liberar los recursos asociados al contexto de renderizado, usando la función SDL_DestroyRenderer.

#include <sdl2/SDL.h>

SDL_Window *ventana;
SDL_Renderer *renderer;

int main( int argc, char * argv[] ) {
    SDL_Init(SDL_INIT_EVERYTHING);

    ventana = SDL_CreateWindow("Mi primera ventana",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);
    renderer = SDL_CreateRenderer(ventana, -1, 0);

    SDL_Delay(5000);

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(ventana);
    SDL_Quit();
    return 0;
}

En muchos casos podemos crear la ventana y el contexto de renderizado a la vez, usando la función SDL_CreateWindowAndRenderer. Solo necesitamos indicar como parámetros las dimensiones de la ventana, las banderas de la ventana y las referencias de los punteros para recibir los valores de las estructuras de la ventana y el contexto de renderizado:

    SDL_CreateWindowAndRenderer(640, 480, SDL_WINDOW_SHOWN, &ventana, &renderer);

Por supuesto, en este caso tendremos menos control sobre la ventana y el contexto de renderizado, y no podremos indicar un título para la ventana.

Dibujar

Antes de dibujar nada, necesitaremos que nuestro lienzo esté vacío. Y antes de eso, tendremos que decidir qué color es el de nuestro lienzo vacío.

Para establecer un color usaremos la función SDL_SetRenderDrawColor, indicando el contexto de renderizado, y los componentes de rojo, verde, azul y alfa.

Para borrar el contexto de renderizado disponemos de la función SDL_RenderClear, indicando el contexto de renderizado a borrar.

Empecemos a dibujar lo más simple: pixels. Para pintar un pixel vamos a usar la función SDL_RenderDrawPoint.

Cuando las operaciones de dibujo en el contexto de renderizado hayan terminado solo nos queda mostrarlas en la ventana. Para ello usaremos la función SDL_RenderPresent. Ya que cada contexto de renderizado está asociado a una ventana, bastará con indicar como parámetro el contexto a mostrar.

La función SDL_RenderPresent intercambia los bufferes de renderizado: el que se estaba mostrando pasa a ser el que se usará en la siguiente operación de dibujo, y el que acabamos de dibujar será mostrado.

#include <sdl2/SDL.h>
#include <cstdlib>

SDL_Window *ventana;
SDL_Renderer *renderer;

int main( int argc, char * argv[] ) {
    SDL_Init(SDL_INIT_EVERYTHING);

    ventana = SDL_CreateWindow("Mi primera ventana",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480, SDL_WINDOW_SHOWN);
    renderer = SDL_CreateRenderer(ventana, -1, 0);

    SDL_SetRenderDrawColor(renderer, 0,0,0,255);
    SDL_RenderClear(renderer);

    SDL_SetRenderDrawColor(renderer, 255,255,255,SDL_ALPHA_OPAQUE);
    for(int i=0; i<100; i++) {
        SDL_RenderDrawPoint(renderer, (640*std::rand())/RAND_MAX, (480*std::rand())/RAND_MAX);
    }
    SDL_RenderPresent(renderer);
    SDL_Delay(5000);

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(ventana);
    SDL_Quit();
    return 0;
}