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


Ações

Informações

14 respostas

2 12 2007
Inocêncio (Grinvon)

Não sei nada de SDL, mas fiquei interessado. Apenas estudei um pouquinho de Allegro, mas isso faz muito tempo. Seu blog me motivou a estudar um pouco mais de programação de jogos e assim de forma casual, produzir quem sabe algum joguinho legal. Grande abraço Vini e continui com esse blog muito bom.

2 12 2007
vinigodoy

Obrigado! Eu não cheguei a usar o Allegro mas, pelo que já li em blogs de amigos, parece muito interessante.

A SDL foi usada alguns jogos muito famosos como Worms 3D.

2 12 2007
Diogo_RBG

Bem explicadinho, e por isso mesmo pintou umas dúvidas.
* Não sabia que eu tinha que combinar SDL_HWSURFACE | SDL_DOUBLEBUF para que o doublebuf pudesse funcionar, e tbm não sabia que SDL_HWSURFACE | SDL_SWSURFACE funcionavam juntas !
* SDL_Quit repassada para atexit será chamada se exit for chamada ou tbm quando o programa encerrar ?!
Este é o primeiro exemplo em C++ que vejo !

3 12 2007
vinigodoy

Será chamada quando a aplicação terminar também. A função atexit() é chamada sempre que há um termino normal do C++, seja através da função exit ou da finalização do bloco main.

Uma curiosidade. O C++ suporta até 32 funções passadas para o atexit. Para mais informações:
http://www.cplusplus.com/reference/clibrary/cstdlib/atexit.html

Quanto ao exemplo ser em C++. Bem, espero poder trazer um pouco de estilo C++ aqui para o blog. Muita coisa mudou na forma como se programa C++ nos últimos anos. O uso da STL, vários estilos foram melhorados, outros abandonados. Espero poder passar um pouquinho disso por aqui. :)

5 12 2007
Guedes

Olá Vinícius, tem alguns pontos que para mim, estão equivocadas:

“Acesso a redes: A SDL permite a inicialização e o uso de sockets de maneira simples…”.
O SDL não trabalha nativamente com rede, é necessário a biblioteca auxiliar SDL_net.

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”.
O main() padrão do Windows não direciona a saída padrão para a tela, direciona para o arquivo stdout.txt, geralmente os iniciantes precisam visualizar instântaneamente o resultado da saída, para resolver esse problema, usa-se o WinMain().

“Embora o C++ forneça outras alternativas para a assinatura do main, essa é a forma recomendada (para não dizer exigida) pela SDL.”. Na documentação oficial disse “You should be using main() instead of WinMain()…”, ou seja, ‘você deveria estar usando o main() ao invés de WinMain()’, não existe obrigação aqui, é apenas uma sugestão do fabricante, além do mais, existe outra citação: “you need to use WinMain(), take a look at the SDL source code in src/main/win32/SDL_main.c”, ou seja, ’se você necessita usar o WinMain(), de uma olhada no codigo fonte SDL em src/main/win32/SDL_main.c”, o próprio fabricante diz que é possível necessitar do WinMain().

Referência: http://www.libsdl.org/faq.php?action=listentries&category=4#48

5 12 2007
vinigodoy

Sua dica é realmente ótima, obrigado! Se alguém realmente necessitar por algum motivo do WinMain, é uma boa conferir esse arquivo citado na documentação. Fica bom o lembrete de que o que eu falei não é uma imposição, mas uma recomendação que deve atender ao caso geral.

Vou fazer as devidas correções no blog. :)

6 12 2007
skhaz

Otimo artigo, muito bem explicado, parabéns :D

8 12 2007
AvcF

Acompanho seus textos faz algum tempo, muito bons. Publiquei ontem um post que creio que possa ajudar o pessoal que quer desenvolver games de forma independente (http://gamehall.uol.com.br/blogs/loadingtime/2007/12/07/mais-um-pouco-de-game-design-%e2%80%93-planejamento-de-jogo/). Solto a segunda parte dele em breve.Falou!

8 12 2007
vinigodoy

Eu já tinha lido seu texto, pois tenho seu blog no meu reader. Realmente muito bom! Recomendo a todos os leitores!

21 02 2008
MarceloJava

Vini, desculpe estar mandando aquí, porém os e-mails que estou te enviando estão voltando.
Não conseguí compilar esse tutorial, criei o ambiente certinho como vc falou mais na hora de compilar, o arquivo executável não é gerado, qq outro programa que crio em C++ no codeblock gera normal

abaixo o log:
Project : Console application

Compiler : GNU GCC Compiler (called directly)

Directory : C:\DevJogos\project\

——————————————————————————–

Switching to target: default

Compiling: main.cpp

Linking console executable: C:\DevJogos\project\console.exe

mingw32-g++.exe: –mwindows: No such file or directory

Process terminated with status 1 (0 minutes, 1 seconds)

0 errors, 0 warnings

outra forma, deletei o -mwindows no codeblock e joguei o SDL.dll no mesmo diretório (também solicitou ela e não encontrou), mesmo assim ocorre a exception e o arquivo stdout.txt é gerado sem a exception escrita.
Obrigado, pode apagar do blog depois se preferir.

21 02 2008
vinigodoy

Isso geralmente está associado a configuração do compilador.
Vá em settings, compiler&debbuger, clique na aba toolchain executables e verifique se o diretório do compilador está correto na caixa “Compiler’s Instalation Directory”. Se não estiver, coloque o caminho certo (como “C:\DevJogos\MinGW”).

Também é uma boa ir nas abas Search Directories e conferir se o compiler aponta para o diretório bin do mingw e se o linker aponta para o diretório lib.

7 11 2008
lustinha

Gostaria de saber se é possivel colocar barras de rolagem em uma janela SDL?

obrigada

7 11 2008
vinigodoy

Possível certamente é. Mas não é fácil de se fazer no braço.

Talvez seja melhor usar um componente de terceiros que rode sobre SDL, como o aedgui ou o wgui.

7 11 2008
lustinha

Ok, obrigada, vou dar uma olhada nesse aedgui e wgui.

Deixe um comentário