Criando uma janela SDL

2 12 2007

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:

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