Entendendo a SDL
Antes de entrarmos no código usando a SDL, vamos entender um pouco de seus conceitos. A SDL (Simple Directmedia Layer) é uma biblioteca multi-plataforma criada para facilitar a construção de aplicações envolvendo multimídia. A biblioteca é formada de diversos subsistemas, que fornecem serviços como:
- Inicialização e finalização: É normal que questionemos o sistema operacional a respeito dos recursos que temos disponíveis para nossos jogos. A SDL simplifica esse processo ao fornecer uma rotina chamada SDL_Init, capaz de inicializar cada módulo específico a ser utilizado. Ela mesmo pergunta ao sistema operacional se os recursos necessários estão ou não disponíveis e nos retorna um código de erro simples de ser tratado;
- Manipulação do vídeo: A SDL permite que trabalhemos numa janela pixel-a-pixel ou utilizando OpenGL. Este blog trabalhará com a segunda opção. Ela fornece uma maneira fácil e multiplataforma de criarmos a janela do jogo, ou iniciarmos o modo fullscreen. Também facilita a integração com o hardware;
- Manipulação de imagens: A SDL fornece mecanismos para carregar imagens e manipula-las pixel-a-pixel. O formato suportado nativamente é o .bmp, mas com a biblioteca SDL_Image também podemos carregar a maior parte dos formatos padrão, incluindo .jpg, .gif e .png, com ou sem canal alpha;
- Mecanismo de eventos: A SDL fornece um mecanismo de eventos simples para tratar diversas formas de entrada do usuário: isso inclui: eventos na janela, ou movimentação com mouse, teclado e joystick;
- Efeitos sonoros: Um de seus subsistemas fornece módulos para trabalhar com sons nos formados .mid, .mod e .wav. Adicionalmente a SDL_Mixer ainda suporta diversos outros formatos, incluindo .mp3 e .ogg;
- Temporização: A temporização é essencial em qualquer aplicação multimídia. Com ela conseguimos criar jogos que rodem na mesma velocidade, seja em computadores lentos ou mais rápidos.
- Acesso a redes: A SDL permite a inicialização e o uso de sockets de maneira simples, facilitando a criação de jogos em rede através do pacote adicional SDL_Net;
- Multi-threading: Permite disparar multiplas linhas de execução e criar blocos de sincronização de maneira multi-plataforma.
O main
Para construir uma aplicação de janelas é necessário criar um bloco main dependente do ambiente escolhido. No caso do windows, criaríamos o WinMain da seguinte forma:
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
Usando a SDL não precisamos nos preocupar com isso. Ela é quem toma conta de fazer as chamadas específicas. Então, para nós, resta apenas a tarefa de declarar um main padrão, com o seguinte formato:
int main(int argc, char* argv[])
Embora o C++ forneça outras alternativas para a assinatura do main, essa é a forma recomendada pela SDL. Se você realmente precisar usar o WinMain, consulte o arquivo /src/main/win32/SDL_main.c para ver como a SDL deve ser inicializada. Isso é um tema avançado e, para os exemplos desse blog, isso não será necessário.Inicialização e finalização
Cada módulo da SDL deve ser inicializado com o comando SDL_Init antes que possa ser utilizado. O comando recebe um argumento inteiro, que é composto utilizando o operador OU binário | com as seguintes flags:
- SDL_INIT_VIDEO: Inicializa o subsistema de vídeo;
- SDL_INIT_TIMER: Inicializa o subsistema de temporização;
- SDL_INIT_AUDIO: Inicializa o subsistema de som;
- SDL_INIT_JOYSTICK: Inicializa o subsistema de joystick;
- SDL_INIT_CDROM: Inicializa o subsistema de cdrom;
A sdl ainda possui os seguintes flags especiais:
- SDL_INIT_EVERYTHING: Uma maneira rápida de inicializar todos os subsistemas acima.
- SDL_INIT_NOPARACHUTE: A inicialização “sem paraquedas” faz com que a SDL ignore todas as mensagens de erros críticos do sistema. Embora seja curioso, não utilizaremos esse flag.
- SDL_INIT_EVENTTHREAD: Não documentado. Também não é necessário utiliza-lo.
Tipicamente, um jogo grande irá fazer a inicialização com SDL_INIT_EVERYTHING. Para os primeiros exemplos, podemos inicializar os subsistemas apenas de timer e vídeo, como no exemplo abaixo:
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
Os módulos são finalizados através da função SDL_Quit. Você pode simplificar o seu código e colocar a SDL_Quit como parâmetro na função atexit(), assim ela será chamada sempre que o comando exit for dado. A declaração fica assim:
atexit(SDL_Quit);
Abrindo uma janela com a SDLUma vez que o módulo de vídeo está no ar, o próximo passo é definir qual é o modo de vídeo que utilizaremos para a aplicação. A função que permite que façamos isso é a SDL_SetVideoMode e seus parâmetros são: largura, altura, número de bits por pixel (também chamado de bpp ou depth) e, novamente, um inteiro com flags. As mais importantes e comumente usadas são:
- SDL_SWSURFACE: Indica que queremos criar uma janela que não dependa de hardware de aceleração de vídeo;
- SDL_HWSURFACE: Indica que precisamos de uma janela com aceleração de vídeo. Essa flag pode ser passada em conjunto com a anterior para suportarmos os dois modos;
- SDL_ANYFORMAT: Quando o número de bits-por-pixel não é suportado, a SDL tenta emula-lo. Passando essa flag, você informa a SDL que você não se incomoda se ela simplesmente usar o número suportado no lugar;
- SDL_DOUBLEBUF: Ativa o double-buffering e page-flipping. Esse recurso só estará completamente disponível se a SDL estiver usando uma superfície de hardware, criada ao passar também a flag SDL_HWSURFACE. Caso contrário, a SDL não fará o page-flipping.
- SDL_NOFRAME: Retira a decoração da tela (barra de título, botões, etc).
- SDL_FULLSCREEN: Inicializa em tela cheia. A altura e largura passadas nos parâmetros anteriores se referirão a resolução da tela. Se a resolução não puder ser atingida, a SDL utilizará uma resolução menor mais próxima da desejada, mas a janela ficará centralizada numa tela preta. Essa flag ativa automaticamente a SDL_NOFRAME;
- SDL_OPENGL: Inicializa a janela com OpenGL;
A função SDL_SetVideoMode retorna um ponteiro para a superfícia de desenho da tela, do tipo SDL_Surface. Na SDL, qualquer elemento que possa ser desenhado ou receber desenhos é chamado de superfície (surface).
Que tal colocar tudo isso junto num único programa?
#include "SDL.h"
#include <stdexcept>
#include <iostream>
SDL_Surface* window;
void setup(int width, int height, int bpp, bool fullscreen)
{
//Inicializamos o subsistema de video.
if (SDL_Init(SDL_INIT_VIDEO) < 0)
throw std::runtime_error(SDL_GetError());
//Definimos as flags.
int flags = SDL_SWSURFACE;
if (fullscreen)
flags != SDL_FULLSCREEN;
//Tentamos criar a janela
window = SDL_SetVideoMode(width, height, bpp, flags);
//Sem sucesso? Lançamos uma exceção com o erro.
if (window == NULL)
throw std::runtime_error(SDL_GetError());
//Configuramos a função de finalização da SDL
atexit (SDL_Quit);
}
/** Espera o usuário pressionar o x da janela. */
void processEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
switch (event.type)
{
case SDL_QUIT:
exit(0); //Fechamos a apliação
break;
}
}
}
int main(int argc,char* argv[])
{
try
{
setup(640, 480, 8, false); //Faz a mágica acontecer
while (true)
processEvents();
}
catch (std::exception &e)
{
std::cout << "Error: " << e.what();
exit(1);
}
}
Rodando esse exemplo, você deve ver uma janelinha abrindo na tela. Não se preocupe com o processamento de eventos por enquanto. Ele foi usado aqui apenas para que você conseguisse ver a janela (caso contrário, ela fecharia logo que abrisse). No próximo artigo, configurar a janela para uso com OpenGL. No futuro, organizaremos tudo numa classe, para reutilizarmos em qualquer jogo que quisermos.Veja também
- O artigo SDL – A biblioteca dos jogos 2D, do Portal Cpp;
- O framework brasileiro Wintermoon;
- E o primeiro capítulo do livro SDL Passo-a-Passo, por Maurilio Guedes (BillGuedes)