quarta-feira, 30 de julho de 2008

Imagens lições 1 e 2

Abaixo, o resultado das lições anteriores.

A primeira lição apenas preencheu os pixels da tela. O emprego de uma função de interpolação resultou na imagem colorida.



A segunda lição fez uma composição de superfícies: a tela sobreposta por um imagem de fundo e uma figura semelhante a um personagem - ambos divinamente ilustrados no Microsoft Paint.



sexta-feira, 20 de junho de 2008

Lição 2: Mostrando Imagens

Nesta segunda lição será mostrado como carregar imagens com a SDL, resultando numa tela de fundo e uma figura que se move pelo teclado. Lembre-se que é necessário incluir os parâmetros do linker (-lmingw32 -lSDLmain -lSDL) nas opções do projeto como comentado na primeira lição.

1. Cabeçalhos e objetos globais

Iniciamos incluindo cabeçalhos e declarando alguns objetos globais, entre superfícies desenháveis e inteiros para armazenar a posição da figura na tela.

#include MENORQstdio.hMAIORQ
#include MENORQstdlib.hMAIORQ
#include MENORQSDL/SDL.hMAIORQ

SDL_Surface *back; // imagem de fundo
SDL_Surface *image; // figura controlada
SDL_Surface *screen; // será tela completa

int xpos = 0, ypos = 0; // posição da figura

Em seguida foi declarada uma função auxiliar que carrega as imagens a partir de arquivos bitmap e preenche as superfícies. Para isso, a função SDL_LoadBMP() recebe um nome de arquivo e retorna um ponteiro para o tipo SDL_Surface.
int InitImages()
{
back = SDL_LoadBMP("bg.bmp");
image = SDL_LoadBMP("image.bmp");
return 0;
}

2. Mostrando na tela

Continuando, as próximas duas funções fazem o blit (cópia) da imagem para a tela usando a função SDL_BlitSurface(), cujo protótipo é definido como

int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect,
SDL_Surface *dst, SDL_Rect *dstrect);

onde src é a superfície a ser copiada e dst é a superfície onde src será copiada.

srcrect permite que apenas uma parte da imagem de origem seja copiada, especificada por seus membros x, y, w e h (esquerda, topo, largura e altura).

dstrect indica a posição (x,y de dst) onde srcrect será colocado. Note que se srcrect é substituído por NULL, a imagem toda é copiada. É desta forma que mostramos uma figura sobre o plano de fundo.

void DrawIMG(SDL_Surface *img, int x, int y)
{
SDL_Rect dest;
dest.x = x;
dest.y = y;
SDL_BlitSurface(img, NULL, screen, &dest);
}

Se NULL não é usado, temos um overload (sobrecarga) da função auxiliar DrawIMG() que permite o blit de apenas uma parte específica da imagem de origem, como comentado.
void DrawIMG(SDL_Surface *img, int x, int y,
int w, int h, int x2, int y2)
{
SDL_Rect dest;
dest.x = x;
dest.y = y;
SDL_Rect dest2;
dest2.x = x2;
dest2.y = y2;
dest2.w = w;
dest2.h = h;
SDL_BlitSurface(img, &dest2, screen, &dest);
}

Aqui, a região (dest2) da imagem (img) é desenhada na tela (screen) na posição (dest). Simples.

Na seqüência, criamos mais uma função para desenhar o plano de fundo. Basicamente, a superfície (back) é colocada no início da tela (posição x,y = 0,0 sobre a superfície screen).

void DrawBG()
{
DrawIMG(back, 0, 0);
}

Em seguida, outra função foi criada, desta vez, para eliminar o rastro da figura que se move pela tela e também para redesenhá-la em seguida, normalmente. O rastro é eliminado redesenhando-se a região onde está a figura, com uma "folga" de alguns pixels em x, y, w e h. A tela toda poderia ser redesenhada, mas para melhor desempenho, atualiza-se somente a região da figura. Por fim, é feita a troca (flip) dos buffers através da função SDL_Flip().
void DrawScene()
{
DrawIMG(back, xpos-2, ypos-2, 132, 132, xpos-2, ypos-2);
DrawIMG(image, xpos, ypos);
SDL_Flip(screen);
}

Já se aproximando do fim desta lição, vamos à função main(). A porção inicial do código inicializa a SDL, como já foi tratado, e será mostrada no exemplo completo mais abaixo. Em seguida são chamadas as funções auxiliares para carregar as imagens e desenhar o fundo da tela. Na seqüência, entramos no game loop, também já comentado na primeira lição.

3. Código completo

#include MENORQstdio.hMAIORQ
#include MENORQstdlib.hMAIORQ
#include MENORQSDL/SDL.hMAIORQ

SDL_Surface *back; // imagem de fundo
SDL_Surface *image; // figura controlada
SDL_Surface *screen; // será tela completa

int xpos = 0, ypos = 0; // posição da figura

int InitImages()
{
back = SDL_LoadBMP("bg.bmp");
image = SDL_LoadBMP("image.bmp");
return 0;
}

void DrawIMG(SDL_Surface *img, int x, int y)
{
SDL_Rect dest;
dest.x = x;
dest.y = y;
SDL_BlitSurface(img, NULL, screen, &dest);
}

void DrawIMG(SDL_Surface *img, int x, int y,
int w, int h, int x2, int y2)
{
SDL_Rect dest;
dest.x = x;
dest.y = y;

SDL_Rect dest2;
dest2.x = x2;
dest2.y = y2;
dest2.w = w;
dest2.h = h;

SDL_BlitSurface(img, &dest2, screen, &dest);
}

void DrawBG()
{
DrawIMG(back, 0, 0);
}

void DrawScene()
{
DrawIMG(back, xpos-2, ypos-2, 132, 132, xpos-2, ypos-2);
DrawIMG(image, xpos, ypos);
SDL_Flip(screen);
}

int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) MENORQ 0)
{
printf("Erro: %s\n", SDL_GetError());
exit(1);
}
atexit(SDL_Quit);

screen = SDL_SetVideoMode(640, 480, 32,
SDL_SWSURFACE | SDL_FULLSCREEN);

if (screen == NULL) exit(1);

InitImages();
DrawBG();

int done = 0;
while (done == 0)
{
SDL_Event event;

while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT) { done = 1; }

if (event.type == SDL_KEYDOWN &&
event.key.keysym.sym == SDLK_ESCAPE)
done = 1;
}

Uint8 *keys = SDL_GetKeyState(NULL);
if (keys[SDLK_UP]) { ypos -= 1; }
if (keys[SDLK_DOWN]) { ypos += 1; }
if (keys[SDLK_LEFT]) { xpos -= 1; }
if (keys[SDLK_RIGHT]) { xpos += 1; }

DrawScene();
}

return 0;
}

Neste exemplo, foi acrescentado o tratamento do teclado. De maneira resumida, verificamos se o evento ocorrido foi um pressionamento de tecla (event.type == SDL_KEYDOWN) e, em caso afirmativo, se foi a tecla esc (event.key.keysym.sym == SDLK_ESCAPE). Em seguida, pegamos o estado do teclado através da função SDL_GetKeyState() e atualizamos a posição x,y da figura conforme a tecla pressionada. Por último, a função auxiliar DrawScene() é chamada para atualizar tudo o que vemos na tela.

Lembre-se de criar duas imagens (bg.bmp e image.bmp) para o fundo e a figura. O fundo pode ter 640x480 pixels e a figura é recomendável que tenha 128x128 para que coincida com o exemplo da "folga" na função DrawScene().

Lembre-se também de substituir qualquer MENORQ e MAIORQ pelos respectivos símbolos mátemáticos, até eu me acertar com o blogger.

quarta-feira, 11 de junho de 2008

Lição 1: Iniciando com SDL

Esta será uma introdução à programação de jogos 2D em C++ com SDL. Vamos começar seguindo o tutorial GFX with SDL, do Marius Andra, disponível no site Game Dev, porém não farei uma tradução ao pé da letra, apenas seguirei os passos principais.

1. Preparando o ambiente

Embora o texto original comente sobre o Dev-C++ e o MS Visual C++, vou tratar somente do primeiro, por simplicidade. O Dev-C++ pode ser baixado (7.5MB) no site da Bloodshed e o VC++ no site da Microsoft, na versão Express.

Também serão necessários alguns arquivos (cabeçalhos SDL*.h e SDL.dll), já zipados pelo Marius. Este zip deve ser descomprimido dentro da pasta de instalação do Dev-C++ para que os arquivos sejam extraídos nas pastas lib e include corretamente.

Para uma versão atualizada da SDL, visite o site.

Atenção 1: Lembre-se de acrescentar a linha abaixo em todos os projetos com SDL, indo no menu Projeto -> Opções do Projeto, aba Parâmetros, caixa Linker. (Sim, Dev-C++ em português!)

-lmingw32 -lSDLmain -lSDL

Atenção 2: Para rodar programas com a SDL, copie o arquivo SDL.dll para a pasta \Windows\System32 ou para a pasta do próprio programa, se for distribuí-lo por aí.

Por último, quando usar a função printf() com a SDL, a saída será o arquivo stdout.txt e não a tela.

2. Inicializando a SDL

Já no Dev-C++, o primeiro passo é incluir o cabeçalho da SDL no topo no arquivo-fonte, além da biblioteca cstdlib, que também será necessária (obs.: substitua todos os MENORQ e MAIORQ pelo símbolo de menor que e maior que, respectivamente, até eu descobrir uma forma que o blogger entenda):

#include MENORQcstdlibMAIORQ
#include MENORQSDL/SDL.hMAIORQ

Antes de usar as funções da biblioteca SDL, é necessário inicializar os dispositivos que pretendemos usar. Para isso, chama-se a função SDL_Init() com alguma combinação dos parâmetros abaixo:
SDL_INIT_TIMER
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_CDROM
SDL_INIT_JOYSTICK
SDL_INIT_NOPARACHUTE
SDL_INIT_EVENTTHREAD
SDL_INIT_EVERYTHING

Por exemplo, para iniciar o vídeo e o som:
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) MENORQ 0)
{
printf("Erro iniciando SDL: %s\n", SDL_GetError());
exit(1);
}
else
printf("Som e video OK.\n");

atexit(SDL_Quit);
return 0;
}

Neste código, se SDL_Init() retornar um valor negativo, algo saiu errado. Então, uma descrição da falha é registrada e a aplicação finalizada.

A linha atexit(SDL_Quit) chama a função SDL_Quit() quando a aplicação estiver finalizando. Isso evita a necessidade de colocá-la antes de cada return da função main(), já que ela se encarrega de fazer todas as "limpezas" necessárias.

Caso algum erro ocorra, é possível identificá-lo com a função SDL_WasInit(). Por exemplo, uma aplicação pode seguir somente com o vídeo caso o som falhe. Para fazer a verificação, é necessário comparar bit a bit, como abaixo:

if (SDL_WasInit(SDL_INIT_VIDEO) & SDL_INIT_VIDEO)
{
printf("Video OK\n");
}

3. Superfícies

Superfícies, ou surfaces, são regiões "desenháveis" sobrepostas entre si para formar o que vemos no monitor. Na SDL, uma superfície é um ponteiro para a estrutura SDL_Surface, podendo tanto representar a tela toda, como apenas algum personagem.

Por exemplo, o trecho seguinte gera uma área de 640x480 pixels, com 32 bits de cores, além de indicar que a memória de vídeo e o double buffering serão usados.

SDL_Surface *screen;
screen = SDL_SetVideoMode(640, 480, 32,
SDL_HWSURFACE | SDL_DOUBLEBUF);

Basicamente, o que o double buffering faz é exibir uma superfície que já foi toda desenhada enquanto outra ainda está sendo preenchida. Caso algum erro ocorra, SDL_SetVideoMode() retornará NULL.

Os flags possíveis para a função SDL_SetVideoMode() são:

SDL_SWSURFACE
SDL_HWSURFACE
SDL_ASYNCBLIT
SDL_ANYFORMAT
SDL_HWPALETTE
SDL_DOUBLEBUF
SDL_FULLSCREEN
SDL_OPENGL
SDL_OPENGLBLIT
SDL_RESIZABLE
SDL_NOFRAME

Antes de prosseguir, é importante saber que a SDL define alguns tipos de inteiros freqüentemente usados, listados a seguir. U e S indicam inteiros sem (Unsigned) e com (Signed) sinal, respectivamente. 8, 16, 32 e 64 é o tamanho em bits do valor.
Uint8, Uint16, Uint32, Uint64,
Sint8, Sint16, Sint32, Sint64

Prosseguindo, a tarefa de desenhar pixels é simplificada por uma função pronta, tirada da documentação da SDL. Os parâmetros são a superfície sobre a qual pretendemos desenhar, as coordenadas do pixel e as componentes de cor RGB.
void DrawPixel(SDL_Surface *screen, int x, int y,
Uint8 R, Uint8 G, Uint8 B);

A implementação desta função será mostrada no código completo no final do texto.

Como algumas placas de vídeo exigem que a superfície seja travada antes de ser desenhada e destravada em seguida, usamos as funções SDL_LockSurface() e SDL_UnlockSurface(). Por ser desnecessário verificar se a placa exige esse travamento, a função SDL_MUSTLOCK() não será usada, diferente de como foi feito no tutorial original. Vamos então codificar mais uma função, que fará o desenho de toda a tela.

void DrawScene(SDL_Surface *screen)
{
SDL_LockSurface(screen);

for (int x = 0; x MENORQ 640; x++)
for (int y = 0; y MENORQ 480; y++)
DrawPixel(screen, x, y, y/2, y/2, x/3);

SDL_UnlockSurface(screen);
SDL_Flip(screen);
}

O que a função SDL_Flip() faz é basicamente mostrar uma superfície após ela ter sido completamente desenhada, melhorando o desempenho e evitando o chamado "flickering".

4. Game loop

Basta agora tratar sobre o chamado game loop, que é um trecho de código executado repetidamente enquanto o jogo estiver rodando. Neste laço serão tratados os eventos relevantes ao funcionamento da aplicação, como o pressionamento de teclas.

Para adiantar, esse trecho será algo como abaixo. As explicações vem em seguida.

while (1)
{
SDL_Event event;
SDL_WaitEvent(&event);

if ((event.type == SDL_QUIT) ||
(event.type == SDL_KEYDOWN &&
event.key.keysym.sym == SDLK_ESCAPE))
return 0;

DrawScene(screen);
}

Primeiramente, criamos um loop infinito com while(1). Em seguida, um objeto do tipo SDL_Event é usado pela função SDL_WaitEvent() para detectar quando algo ocorreu. Nesse momento, verificamos se o evento foi um clique no botão fechar ou o pressionamento de tecla ESC. Em qualquer um desses casos, a aplicação é finalizada chamando return. Se isto não ocorrer, a tela (cena) é atualizada e o jogo continuará aguardando o próximo evento ocorrer.

5. Código completo

#include MENORQcstdlibMAIORQ
#include MENORQSDL/SDL.hMAIORQ

void DrawPixel(SDL_Surface *screen, int x, int y,
Uint8 R, Uint8 G, Uint8 B)
{
Uint32 color = SDL_MapRGB(screen->format, R, G, B);
switch (screen-MAIORQformat-MAIORQBytesPerPixel)
{
case 1: // Assuming 8-bpp
{
Uint8 *bufp;
bufp = (Uint8 *)screen-MAIORQpixels +
y*screen-MAIORQpitch + x;
*bufp = color;
}
break;
case 2: // Probably 15-bpp or 16-bpp
{
Uint16 *bufp;
bufp = (Uint16 *)screen-MAIORQpixels +
y*screen-MAIORQpitch/2 + x;
*bufp = color;
}
break;
case 3: // Slow 24-bpp mode, usually not used
{
Uint8 *bufp;
bufp = (Uint8 *)screen-MAIORQpixels +
y*screen-MAIORQpitch + x * 3;
if(SDL_BYTEORDER == SDL_LIL_ENDIAN)
{
bufp[0] = color;
bufp[1] = color MAIORQMAIORQ 8;
bufp[2] = color MAIORQMAIORQ 16;
} else {
bufp[2] = color;
bufp[1] = color MAIORQMAIORQ 8;
bufp[0] = color MAIORQMAIORQ 16;
}
}
break;
case 4: // Probably 32-bpp
{
Uint32 *bufp;
bufp = (Uint32 *)screen-MAIORQpixels +
y*screen-MAIORQpitch/4 + x;
*bufp = color;
}
break;
}
}

void DrawScene(SDL_Surface *screen)
{
SDL_LockSurface(screen);

for (int x = 0; x MENORQ 640; x++)
for (int y = 0; y MENORQ 480; y++)
DrawPixel(screen, x, y, y/2, y/2, x/3);

SDL_UnlockSurface(screen);
SDL_Flip(screen);
}

int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) MENORQ 0)
{
printf("Erro iniciando SDL: %s\n", SDL_GetError());
exit(1);
}
atexit(SDL_Quit);

SDL_Surface *screen;
screen = SDL_SetVideoMode(640, 480, 32,
SDL_HWSURFACE | SDL_DOUBLEBUF);

if (screen != NULL)
{
while (1)
{
SDL_Event event;
SDL_WaitEvent(&event);

if ((event.type == SDL_QUIT) ||
(event.type == SDL_KEYDOWN &&
event.key.keysym.sym == SDLK_ESCAPE))
return 0;

DrawScene(screen);
}
}
else
exit(1);

return 0;
}

domingo, 1 de junho de 2008

O Propósito

Como havia comentado no blog Communicare, estes dois novos - Hello, Avatar! e AVE MVNDE, são apenas uma questão de organização dos conteúdos.

No entanto, neste em particular, será abordada a programação de jogos. Também como havia comentado, meus conhecimentos nesta área ainda são limitados, portanto aqui vão as evoluções dos meus estudos.

De qualquer maneira, começarei tratando sobre o uso de C++ em conjunto com a excelente biblioteca multimídia SDL para a criação de jogos 2D. Simulação de física, detecção de colisão entre objetos e outros tópicos serão tratados de maneira clara e introdutória, porém funcional. Nada de comentários sem um código-fonte rodando para acompanhar.

Mais à frente, pretendo abordar também sobre C# e XNA.