.:: Menu Rápido ::.

Linux | C/C++ | Downloads | SDL | [×]

sábado, 22 de dezembro de 2007

SDL_Collide - Colisão 2D para jogos

A primeira regra de qualquer jogo é a colisão. É claro que isso só inclui 99,99% dos jogos.
Como será o sistema de colisão em seu jogo é um fator determinante pra conclusão do projeto.
Como sempre tenho a SDL sempre a mão e para resolver este problema vou apresentar a SDL_Collide.

» Post Completo...

return (pixelcolor == surface->format->colorkey);


int SDL_CollidePixel(SDL_Surface *as, int ax, int ay, SDL_Surface *bs, int bx, int by, int skip = 4);
Esta função faz um teste de colisão em pixel precisão. É muito lenta.
Por exemplo: SDL_CollidePixel("verde_claro",0,0,"verde_escuro",4,2,2);
Como é mostrado acima, o exemplo faria teste de colisão nos pixels de intercessão entre as imagens. O parâmetro skip (2) indica quantos pixels saltar (1 faz teste completo).
O exemplo retornou verdadeiro. Branco é nossa cor de transparência e a colisão foi detectada apenas uma vez.

int SDL_CollideBoundingBox(SDL_Surface *sa, int ax, int ay, SDL_Surface *sb, int bx, int by);
e
int SDL_CollideBoundingBox(SDL_Rect a, SDL_Rect b);

São responsáveis pela colisão entre retângulos e são muito rápidas.

int SDL_CollideBoundingBox(SDL_Rect a, SDL_Rect b){
if(b.x + b.w < a.x) //- b.direita < a.esquerda
return 0;
if(b.x > a.x + a.w) //- b.esquerda > a.direita
return 0;
if(b.y + b.h < a.y) //- b.baixo < a.cima
return 0;
if(b.y > a.y + a.h) //- b.cima > a.baixo
return 0;

return 1; //- retângulos se colidem
}

O primeiro teste "if(b.x + b.w < a.x)" é representado na figura pelas barras vermelhas.
Repare que a colisão só existe quando todos os testes falham. O algoritmo procura por algum lado que possa eliminar a colisão totalmente.

int SDL_CollideBoundingCircle(int x1, int y1, int r1, int x2, int y2, int r2, int offset);
Faz a comparação de colisão entre circulos. x e y são coordenadas do centro. use offset como 0 (deve ser alguma medida de precisão).

int SDL_CollideBoundingCircle(int x1, int y1, int r1, int x2, int y2, int r2, int offset){
int xdiff = x2 - x1; //- diferença no eixo x
int ydiff = y2 - y1; //- diferença no eixo y

/* Distância entre os centros dos círculos ao quadrado */
int dcentre_sq = (ydiff*ydiff) + (xdiff*xdiff);

/* Calcula a soma dos raios ao quadrado */
int r_sum_sq = r1 + r2;
r_sum_sq *= r_sum_sq; //- melhor que (r1+r2)*(r1+r2)

return (dcentre_sq - r_sum_sq <= (offset*offset));
}

Existe colisão quando a soma dos raios for maior que a distância.
Esta função está bem otimizada, tanto é que, para calcular a distância não foi utilizada a raiz quadrada.
A distância não é realmente necessária, ela serve apenas como comparativo. Como o cálculo da raiz quadrada não é uma operação trivial, é preferível comparar todas as somas (raios e dx dy) em potência de 2.

int SDL_CollideBoundingCircle(SDL_Surface *a, int x1, int y1, SDL_Surface *b, int x2, int y2, int offset);
É uma variação da função anterior onde o raio do circulo é calculado por aproximação (r1 = (a.w+a.h) / 4).

Download: SDL_Collide (o código fonte já vem com a versão )

*** O exemplo ***
Aproveitando o código anterior, criei um jogo de labirinto com itens (quase um Pac-Man) para que ficasse bem claro como pode ser feita a implementação de um jogo com colisões.
Para que ficasse funcional acabei complicando o código. Por isso vou explicar algumas das funções que realmente importam.
Não é sempre que se pode mover em todas as direções que pede o jogador, e é isso que a função mov() irá tratar:

bool Play::mov(int movX,int movY,Mapa *mapa){
if( !mov2(movX,movY,mapa) ){ //- teste de movimento em x e y
if( !mov2(movX,0,mapa) ){ //- teste de movimento em x
if( !mov2(0,movY,mapa) ) //- teste de movimento em y
return 0;
}
}
pegaItem(mapa);
return 1;
}

A função mov2() é a função que realmente move o nosso smiley. Ela cria um retângulo auxiliar com a nova posição e testa colisão com todos os retângulos vizinhos:

bool Play::mov2(int movX,int movY,Mapa *mapa){
SDL_Rect aux = {box.x+movX,box.y+movY,box.w,box.h}, ret;
int x,y,i,j,colisao;
char t; //- tile

x = aux.x/W; //- coordenada x no mapa (aproximada)
y = aux.y/H; //- coordenada y no mapa (aproximada)

//- loop nos tiles vizinhos
for(j=-1;j<2;j++){ //- -1,0,1
for(i=-1;i<2;i++){ //- -1,0,1
try{
t = mapa->tile(x+i,y+j);
}catch(...){ //- fora da grade
continue;
}
mapa->getRect(x+i,y+j,ret);
colisao = SDL_CollideBoundingBox(aux,ret);
if(colisao && (t==1 || t>3) ){ //- colisao com paredes
//printf("colisao> %d,%d x %d,%d\n",aux.x,aux.y,ret.x,ret.y);
return 0;
}
}
}
box.x += movX;
box.y += movY;
return 1;
}

Como eu disse, é possível otimizar as funções da SDL_Collide à sua maneira, e foi o que eu fiz:

int SDL_CollideBoundingBox(SDL_Rect a, SDL_Rect b){
if(b.x + b.w <= a.x) return 0;
if(b.x >= a.x + a.w) return 0;

if(b.y + b.h <= a.y) return 0;
if(b.y >= a.y + a.h) return 0;

return 1;
}


Fazer colisão usando o retângulo da própria imagem pode não ser uma boa. Meu personagem tem 32x32 e seu retêngulo de colisão apenas 20x20.
A movimentação não ficou muito boa. A explicação para isso é que não tratamos as colisões da melhor forma, pois sempre que há uma colisão não se faz absolutamente nada para corrigir.
Então é só ! Analise o código fonte para entender os detalhes.
Download: » código fonte

*** SinucaOS ***
Não tem como a gente falar de colisão de círculos sem lembrar de um jogo de sinuca.
Acredite, fazer um jogo de sinuca é realmente difícil. A física de um jogo de sinuca exige muito mais do que se pode ver. A tacada inicial, por exemplo, é uma explosão de colisões e isto fragiliza a implementação.
SinicaOS é um grupo que está desenvolvendo um jogo de sinuca open source em C e Allegro. Por enquanto, o foco principal é ter um simulador de física funcional.
A imagem acima(animação gif) mostra duas bolas grudadas no terceiro quadro. Isto mostra que não são poucos os problemas enfrentados.
Pode ser um bug do jogo, mas o grande causador deste problema é a quantidade de bolas, a proximidade entre elas e a alta precisão exigida. A maioria dos bons jogos de sinuca sobrem deste mesmo mal.

*** Link's ***
Download: » código fonte
» SDL - Site oficial
» SDL_Collide - Site do projeto

---
Feliz Natal !!!

quarta-feira, 12 de dezembro de 2007

1.000 Visitantes \o/

Agradeço a força de todos vocês ! Pois hoje a marca de 1.000 visitantes foi atingida !!!
Foram apenas 14 motivos para visitarem meu blog e são mais de 1.000 motivos para que eu continue com meu trabalho feito em horas vagas !!!

É com grande satisfação que apresento:

Mais uma vez... parabéns ao visitante milenar... só não tem prêmio !!!

Tocando música e sons com SDL_Mixer

SDL é uma biblioteca básica, mas possui muitas bibliotecas de apoio. Uma delas é a SDL_Mixer.
De acordo com alguns tutoriais que andei vendo por aí, a forma mais fácil de tocar música e efeitos sonoros em seu jogo feito em SDL é com SDL_Mixer.
Além de suportar mais formatos (wav, mp3, ogg, mid(Timidity instalado), ...) você não tem que se preocupar com a função callback que é utilizada na SDL.
Siga este passo a passo de como deixar o seu jogo mais divertido !!!

» Post Completo...

#include <SDL/SDL_mixer.h>
Mix_Music *musica = NULL; //- música de fundo
Mix_Chunk *bomba = NULL; //- efeito sonoro mixável.
Mix_Chunk *tiro = NULL;

Você deve inicializar e finalizar a SDL_Mixer assim como faz com a SDL:

void init(){
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
atexit(SDL_Quit);
screen = SDL_SetVideoMode( W, H, 32, SDL_SWSURFACE);
Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 1024 ); //- inicializa SDL_Mixer
atexit(Mix_CloseAudio);
}

Carrega o arquivo de música e os arquivos de som:

void carregar(){
musica = Mix_LoadMUS("007JamesBond.mid");
bomba = Mix_LoadWAV("bomba.ogg");
tiro = Mix_LoadWAV("tiro.wav");
}

Faz (uma única vez) a música tocar em loop:

Mix_PlayMusic( musica, -1 );

E chama os efeitos sonoros sempre que precisar:

Mix_PlayChannel( -1, bomba, 0 );
Mix_PlayChannel( -1, tiro, 0 );

Perceba que a principal diferença entre música e som é que música fica tocando em loop e som é um efeito sonoro que será mixado à musica sempre que for chamado.

*** O Exemplo ***
Este é o exemplo que criei para testar a SDL_Mixer:
Não chega a ser um teclado, mas a intenção foi boa ! Ainda não sei como melhorar, mas estarei estudando.
EDIT (23/12/2007): Mudei o buffer de 4096 para 1024. Isso acelerou a mixagem das notas musicais ao som de fundo.
Download: » código fonte

*** Onde Encontar ?! ***
Onde encontrar músicas e efeitos sonoros para meus jogos ?!
» Arcade Accurate (incluíndo o Pacman)
» Sunncity (mid's)
» Midiworld (mid's)
» Simply The Best
» Galbadia
» Find Sound (ou Google msm !)
No eMule uma busca por "midis" ajuda bastante:
Mas de 500 Midis Heavy Metal.rar [hash]
[Midi] Classical Piano Midis...zip [hash]
40000.mids.Nacional.e.Internacionais...rar [hash]

Uma maneira de aproveitar melhor tudo isso é saber manipular programas de audio.
Pra Linux temos:
» Audacity (sudo apt-get install audacity) - Para editar audio.
» Rosegarden (sudo apt-get install rosegarden) - Editor de arquivos midi e de partituras.
» Timidity (sudo apt-get install timidity) - Sequenciador midi. Sem ele não tem como ouvir midi no Linux. dica: $ timidity 007.mid -Ov 007.ogg - Salva saída no formato ogg.

*** Link's ***
Download: » código fonte
» SDL
» SDL_Mixer - Página do projeto
» Playing Sounds - Tutorial Lazy Foo
» OSG/SDL Tutorial 17 - Audio with SDL_Mixer
» SDL_Mixer Tutorial
» Utilizando áudio com SDL_mixer

---
Essa semana foi dura ! Aprendi muita coisa de uma vez !!!

quinta-feira, 6 de dezembro de 2007

Gerando e carregando mapas 2D em jogos

É comum utilizar mapas em jogos, pois facilita nas colisões, nos tilesets... em tudo !
No último post falei sobre SDL e no penúltimo sobre analisadores. Este irá combinar tudo isso e mais um pouco.
Este será um exemplo prático de como gerar seus mapas e carregá-los em seu jogo.

» Post Completo...

//--- Função que gera o arquivo mapa.txt ---//
void gerar(){
FILE *file = fopen("arquivo.txt","wb");
...
for(j=0;j>img->h;j++){
for(i=0;i>img->w;i++){
if(i!=0)
fprintf(file," ");
fprintf(file,"%02X",getTile( getPix(img,i,j) ));
}
fprintf(file,"\n");
}
...
}

//--- Função de conversão da cor em tile ---//
int getTile(Uint32 cor){
switch(cor){
case 0x000000:
return 1;
case 0xffffff:
return 0;
}
return 0;
}

Para usar o gerador que vem junto com o código: $ ./gerador mapa.bmp mapa.txt

*** O Analisador ***
O gerador não resolve tudo. Sempre irá existir um detalhe a mais. E mapas gerados são muito enquadrados.
Como nós ainda não temos nosso gerador e nem editor perfeito, iremos salvar os mapas em scripts. Isso facilita, pois você poderá fazer pequenos retoques à mão.
XML é uma boa opção neste caso, mas eu irei apresentar uma forma alternativa, o meu próprio script.
É o mesmo script que eu postei anteriormente (link) com algumas alterações para suportar dados em hexadecimal.
O arquivo script tem este formato:

# Informações #
tiles = "tiles.bmp"
tile_w = 20
tile_h = 20
mapa_w = 20
mapa_h = 20

[tiles[
0F 05 05 05 ...
07 02 00 00 ...
...
0D 04 04 04 ...
]]

Para rodar o analisador e ver exatamente o que está sendo lido: $ ./analisador ou $ ./analisador mapa.txt

*** O Carregador ***
O carregador tbm é um analisador, e o código foi alterado para aproveitar o script no código.

void Mapa::analisar(char *buf, int n){
static string var,val; //- strings redimensionáveis
...
switch(est){
...
case VAR: //- trata variáveis
if( let(c) || num(c) || c=='_')
var += c;
...
case VAL: //- trata valores
...
else if(c=='\n' || c=='\r'){
est = ZERO;
setVar(var,val);
}
...
}
...
}

void Mapa::setVar(const string var, const string val){
if(var.empty() || val.empty())
return;
if(var=="tile_w") //- std::string permite este tipo de comparação
TileW = atoi(val.c_str());
else if(var=="tile_h")
TileH = atoi(val.c_str());
...
}

O mapa é analisado e carregado para a classe Mapa. O resultado é o mapa "quase" pronto pra jogar !
É muito parecido com o Pacman e dá até vontade de jogar, mas não se engane !
Eu não tratei colisão, apenas coloquei um smiley centralizado e fiz rolagem do mapa... o jogo é você quem deve fazer !

*** Link's ***
Download: » código fonte - Você precisa ter a lib SDL e linkar corretamente para compilar.
» Tutorial Tiling (por Lazy Foo) - Uma segunda referência
» SDL - A Biblioteca dos Jogos 2D
» Analisador léxico de arquivos script