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)
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.
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.
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 !
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.
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.
“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
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.
Otimo artigo, muito bem explicado, parabéns
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!
Eu já tinha lido seu texto, pois tenho seu blog no meu reader. Realmente muito bom! Recomendo a todos os leitores!
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.
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.
Gostaria de saber se é possivel colocar barras de rolagem em uma janela SDL?
obrigada
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.
Ok, obrigada, vou dar uma olhada nesse aedgui e wgui.
Cara eu vou tentar usar esse aedgui porém não consigo compilar vc saber os linkers que eu tenho que colocar pra conseguir compilar??
Não sei não, só lendo a documentação mesmo.