Programação de Jogos com SDL

Este é um tutorial 2 em 1, vamos programar passo a passo dois jogos. O primeiro jogo será um jogo de labirinto e o segundo um snake (jogo da cobrinha). Os jogos serão feitos usando linguagem C e a biblioteca SDL.

[ Hits: 25.666 ]

Por: Samuel Leonardo em 18/11/2013


Jogo da cobrinha



Este é um pequeno jogo da cobrinha, que fiz em 2009.

O jogo funcionará da seguinte maneira:
  • O jogador começará com um certo tamanho da cobra e usará as setas do teclado para mudar a direção dela.
  • O objetivo do jogo é comer maçãs e alcançar o maior tamanho que puder.
  • O jogo não tem um fim, o jogador vai até onde conseguir ir.

Baixe as imagens abaixo, elas serão usadas no jogo:
Linux: Programação de Jogos com SDL   Linux: Programação de Jogos com SDL   Linux: Programação de Jogos com SDL

Obs.: quando executar o jogo, as imagens devem estar na mesma pasta do executável.

Arquivo snake.c:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <SDL/SDL.h>

/* direções da cobra */
#define CIMA 0
#define DIREITA 1
#define BAIXO 2
#define ESQUERDA 3

#define TAMANHOIMAGEM  32


struct Pedaco
{
int coorX;
int coorY;
int direcao; // direção
} pedaco[256], maca; // pedaco[0] = ponta do rabo E pedaco[tamanho - 1] = cabeça

/* Imagens e tela principal */
SDL_Surface *tela, *img_maca, *img_snakes, *img_cabeca;

int seta_cima = 0, seta_baixo = 0, seta_esquerda = 0, seta_direita = 0;
int  colisao = 0; // identifica colisao da cabeça com outras partes do corpo
// tamanho = tamanho atual da cobra
int tamanho = 5, tamanho_anterior = 5;
int velX = 0, velY = 0; // para mover a cobra
int mapa_largura = 25, mapa_altura = 20; // dimensões do mapa
char hud[256]; // informações passadas ao usuario

/* Funcao que controla o fps */
void controla_fps ( int tempo_inicial )
{
int fps = 1000/7; // converte 7 FPS para milissegundos
int tempo_agora = SDL_GetTicks() - tempo_inicial;

if(tempo_agora < fps)
SDL_Delay(fps - tempo_agora);
}

int carrega_imagens (  )
{
img_maca = SDL_LoadBMP("apple.bmp");
if (img_maca == NULL)
{
printf("Não carregou apple.bmp\n");
return 0;
}

img_snakes = SDL_LoadBMP("piece.bmp");
if (img_snakes == NULL)
{
printf("Não carregou piece.bmp\n");
return 0;
}

img_cabeca = SDL_LoadBMP("head.bmp");
if (img_cabeca == NULL)
{
printf("Não carregou head.bmp\n");
return 0;
}

return 1;
}

void posiciona_maca (  )
{
int i;
int repetir;
do
{
// escolhe aleatoriamente as coordenadas
maca.coorX = rand() % mapa_largura;
maca.coorY = rand() % mapa_altura;

repetir = 0;
for (i = 0; i < tamanho; i++)
{
// verifica em todas as peças se as coordenadas delas
// são iguais as novas coordenadas da maçã.

if ((pedaco[i].coorX == maca.coorX) && (pedaco[i].coorY == maca.coorY))
{
// Se forem iguais então pare o loop e
// repita o procedimento para escolher outra coordenada para a maçã.

repetir = 1;
break;
}
}
// enquanto for para repetir continue escolhendo outra coordenada para a maçã.
} while (repetir);
}

void inicia_jogo (  )
{
tamanho_anterior = tamanho; // para o hud
//Reinicie o jogo

tamanho = 5;
colisao = 0;
velX = 0;
velY = 0;

// reinicializando as peças
// inicializando a parte A - a cabeça

pedaco[4].coorX = 5;
pedaco[4].coorY = 3;
pedaco[4].direcao = DIREITA;

// inicializando a parte B
pedaco[3].coorX = 4;
pedaco[3].coorY = 3;
pedaco[3].direcao = DIREITA;

// inicializando a parte C
pedaco[2].coorX = 3;
pedaco[2].coorY = 3;
pedaco[2].direcao = DIREITA;

// inicializando a parte D
pedaco[1].coorX = 2;
pedaco[1].coorY = 3;
pedaco[1].direcao = DIREITA;

// inicializando a parte E - o rabo
pedaco[0].coorX = 1;
pedaco[0].coorY = 3;
pedaco[0].direcao = DIREITA;

// inicializando as coordenadas da maçã.
posiciona_maca();
}

void controla_snake ( SDL_Event evento )
{
if (evento.type == SDL_KEYDOWN)
{
switch (evento.key.keysym.sym)
{
case SDLK_RIGHT:
seta_direita = 1;
break;

case SDLK_LEFT:
seta_esquerda = 1;
break;

case SDLK_UP:
seta_cima = 1;
break;

case SDLK_DOWN:
seta_baixo = 1;
break;

case SDLK_p:
velX = 0;
velY = 0;
break;

default:
break;
}
}
else if (evento.type == SDL_KEYUP)
{
switch (evento.key.keysym.sym)
{
case SDLK_RIGHT:
seta_direita = 0;
break;

case SDLK_LEFT:
seta_esquerda = 0;
break;

case SDLK_UP:
seta_cima = 0;
break;

case SDLK_DOWN:
seta_baixo = 0;
break;

default:
break;
}
}
}

void move_snake (  )
{
if (seta_direita && pedaco[tamanho - 1].direcao != ESQUERDA)
{
velX = 1; // move horizontalmente a cabeça para direita
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = DIREITA;
}
else if (seta_esquerda && pedaco[tamanho - 1].direcao != DIREITA)
{
velX = -1;  // move horizontalmente a cabeça para esquerda
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = ESQUERDA;
}
else if (seta_cima && pedaco[tamanho - 1].direcao != BAIXO)
{
velX = 0; // para de mover horizontalmente
velY = -1;  // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = CIMA;
}
else if (seta_baixo && pedaco[tamanho - 1].direcao != CIMA)
{
velX = 0; // para de mover horizontalmente
velY = 1; // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = BAIXO;
}

// depois ajustando as posições das outras peças (partes)
// primeiro move as partes do corpo

if (velX || velY) // se estiver movendo
{
int i;
for (i = 0; i < tamanho - 1; i++)
{
// faça a peça de trás (pedaco[i]) igual a peça da frente (pedaco[i + 1])
pedaco[i].coorX = pedaco[i + 1].coorX;
pedaco[i].coorY = pedaco[i + 1].coorY;
pedaco[i].direcao = pedaco[i + 1].direcao;
}
}
// agora move a cabeça
pedaco[tamanho - 1].coorX += velX;
pedaco[tamanho - 1].coorY += velY;

// Verifica os limites do movimento da cobra
// Para o eixo X
// se estiver além da largura da tela/mapa

if (pedaco[tamanho - 1].coorX >= mapa_largura)
{
// volte para posição coorX = 0
pedaco[tamanho - 1].coorX = 0;
}
else if (pedaco[tamanho - 1].coorX < 0) // se estiver além de 0
{
// volte para a posição da largura do mapa
pedaco[tamanho - 1].coorX = mapa_largura - 1;
}

// Para o eixo Y
if (pedaco[tamanho - 1].coorY >= mapa_altura)
{
pedaco[tamanho - 1].coorY = 0;
}
else if (pedaco[tamanho - 1].coorY < 0)
{
pedaco[tamanho - 1].coorY = mapa_altura - 1;
}
}

void desenha_snake (  )
{
int i;
SDL_Rect destino;
// blitando as img_macas das peças
for (i = 0; i < tamanho - 1; i++)
{
destino.y = pedaco[i].coorY * TAMANHOIMAGEM;
destino.x = pedaco[i].coorX * TAMANHOIMAGEM;

SDL_BlitSurface(img_snakes, NULL, tela, &destino);
}
// blitando a cabeça
destino.y = pedaco[tamanho - 1].coorY * TAMANHOIMAGEM;
destino.x = pedaco[tamanho - 1].coorX * TAMANHOIMAGEM;

SDL_BlitSurface(img_cabeca, NULL, tela, &destino);
}


int main (int argc, char **args)
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("ERROR: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}

SDL_Event evento;

// controle do FPS
Uint32 tempo_inicial;

tela = SDL_SetVideoMode(mapa_largura * TAMANHOIMAGEM, mapa_altura * TAMANHOIMAGEM, 16, SDL_SWSURFACE);
if (tela == NULL)
{
printf("ERROR: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}

if (carrega_imagens() == 0)
{
printf("ERROR: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}

// inicia o jogo
inicia_jogo();

srand(time(NULL));
int i;
int fim = 0; // variável de controle do loop principal
while (!fim)
{
tempo_inicial = SDL_GetTicks();
sprintf(hud, "SNAKE by Sam L. - TAMANHO: ATUAL = %d | ANTERIOR = %d", tamanho, tamanho_anterior);
SDL_WM_SetCaption(hud, NULL);
while (SDL_PollEvent(&evento))
{
if (evento.type == SDL_QUIT)
{
fim = 1;
break;
}

controla_snake(evento);
}

// move a cobra
move_snake();

// colisão com a maçã
if ((pedaco[tamanho - 1].coorX == maca.coorX) &&
(pedaco[tamanho - 1].coorY == maca.coorY))
{
tamanho++;
pedaco[tamanho - 1].coorX = maca.coorX;
pedaco[tamanho - 1].coorY = maca.coorY;
pedaco[tamanho - 1].direcao = pedaco[tamanho - 2].direcao;

// reinicializando a posição da maçã
// escolhe uma posição diferente das peças da serpente

posiciona_maca();
}

/* Colisão só será atualizada no próximo loop, pois o usuário deve ver as peças sobrepostas */
if (colisao)
{
// reinicia o jogo e seta a variavel colisao para 0
inicia_jogo();
}

/* Colisão entre cabeça e outras partes da cobra */
for (i = 0; i < tamanho - 2 && colisao == 0; i++)
{
if ((pedaco[tamanho - 1].coorX == pedaco[i].coorX) &&
(pedaco[tamanho - 1].coorY == pedaco[i].coorY))
colisao = 1;
}


// Blitagem
// Pintando o tela de branco

SDL_FillRect(tela, NULL, SDL_MapRGB(tela->format, 255, 255, 255));

SDL_Rect destino;
destino.x = maca.coorX * TAMANHOIMAGEM;
destino.y = maca.coorY * TAMANHOIMAGEM;

SDL_BlitSurface(img_maca, NULL, tela, &destino);

desenha_snake();

// atualizando a tela
SDL_UpdateRect(tela, 0,0,0,0);

controla_fps(tempo_inicial);
}

SDL_Quit(); // fecha o SDL
return 0;
}

Para compilar, rode:

gcc -o snake snake.c -lSDL

Controle de FPS

void controla_fps ( int tempo_inicial )
{
  int fps = 1000/7; // converte 7 FPS para milissegundos
  int tempo_agora = SDL_GetTicks() - tempo_inicial;
  if(tempo_agora < fps)
    SDL_Delay(fps - tempo_agora);
}

Para o controle do FPS, usei a mesma função do jogo do labirinto, mudou apenas o valor do FPS, que agora é 7 FPS (1000/7 é o valor em milissegundos).

Carregando as imagens

int carrega_imagens (  )
{
img_maca = SDL_LoadBMP("apple.bmp");
if (img_maca == NULL)
{
printf("Não carregou apple.bmp\n");
return 0;
}

img_snakes = SDL_LoadBMP("piece.bmp");
if (img_snakes == NULL)
{
printf("Não carregou piece.bmp\n");
return 0;
}

img_cabeca = SDL_LoadBMP("head.bmp");
if (img_cabeca == NULL)
{
printf("Não carregou head.bmp\n");
return 0;
}

return 1; }

A função carrega_imagens(), faz o mesmo da outra do jogo do labirinto, retorna 0 caso alguma imagem não tenha sido carregada e retorna 1, quando todas as imagens foram carregadas corretamente.

Posicionando a maçã

void posiciona_maca (  )
{
int i;
int repetir;
do
{
// escolhe aleatoriamente as coordenadas
maca.coorX = rand() % mapa_largura;
maca.coorY = rand() % mapa_altura;

repetir = 0;
for (i = 0; i < tamanho; i++)
{
// verifica em todas as peças se as coordenadas delas
// são iguais as novas coordenadas da maçã.

if ((pedaco[i].coorX == maca.coorX) && (pedaco[i].coorY == maca.coorY))
{
// Se forem iguais então pare o loop e
// repita o procedimento para escolher outra coordenada para a maçã.

repetir = 1;
break;
}
}
// enquanto for para repetir continue escolhendo outra coordenada para a maçã.
} while (repetir);
}

Essa função posiciona a maçã na tela. Ela escolhe uma posição diferente das posições das partes da cobra.

Iniciando o jogo

void inicia_jogo (  )
{
tamanho_anterior = tamanho; // para o hud
//Reinicie o jogo

tamanho = 5;
colisao = 0;
velX = 0;
velY = 0;

// inicializando os pedaços
// inicializando a parte A - a cabeça

pedaco[4].coorX = 5;
pedaco[4].coorY = 3;
pedaco[4].direcao = DIREITA;

// inicializando a parte B
pedaco[3].coorX = 4;
pedaco[3].coorY = 3;
pedaco[3].direcao = DIREITA;

// inicializando a parte C
pedaco[2].coorX = 3;
pedaco[2].coorY = 3;
pedaco[2].direcao = DIREITA;

// inicializando a parte D
pedaco[1].coorX = 2;
pedaco[1].coorY = 3;
pedaco[1].direcao = DIREITA;

// inicializando a parte E - o rabo
pedaco[0].coorX = 1;
pedaco[0].coorY = 3;
pedaco[0].direcao = DIREITA;

// inicializando as coordenadas da maçã.
posiciona_maca();
}

Função para iniciar o jogo. Ela define as posições iniciais dos pedaços da cobra, aqui são somente 5 pedaços, e reposiciona a maçã na tela com posiciona_maca(). Sempre será chamada quando ocorrer colisão da cabeça com os outros pedaços da cobra.

Controlando a cobra

void controla_snake ( SDL_Event evento )
{
if (evento.type == SDL_KEYDOWN)
{
switch (evento.key.keysym.sym)
{
case SDLK_RIGHT:
seta_direita = 1;
break;

case SDLK_LEFT:
seta_esquerda = 1;
break;

case SDLK_UP:
seta_cima = 1;
break;

case SDLK_DOWN:
seta_baixo = 1;
break;

case SDLK_p:
velX = 0;
velY = 0;
break;

default:
break;
}
}
else if (evento.type == SDL_KEYUP)
{
switch (evento.key.keysym.sym)
{
case SDLK_RIGHT:
seta_direita = 0;
break;

case SDLK_LEFT:
seta_esquerda = 0;
break;

case SDLK_UP:
seta_cima = 0;
break;

case SDLK_DOWN:
seta_baixo = 0;
break;

default:
break;
}
}
}

Para controlar a cobra, usei a função controla_snake(). Ela fica responsável por ajustar o valor da variáveis das setas. Quando uma seta do teclado é pressionada, então, identifica qual foi e define o valor da variável correspondente para 1. Se a tecla for solta, o valor da variável correspondente será 0.

Movendo a cobra

void move_snake (  )
{
if (seta_direita && pedaco[tamanho - 1].direcao != ESQUERDA)
{
velX = 1; // move horizontalmente a cabeça para direita
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = DIREITA;
}
else if (seta_esquerda && pedaco[tamanho - 1].direcao != DIREITA)
{
velX = -1;  // move horizontalmente a cabeça para esquerda
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = ESQUERDA;
}
else if (seta_cima && pedaco[tamanho - 1].direcao != BAIXO)
{
velX = 0; // para de mover horizontalmente
velY = -1;  // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = CIMA;
}
else if (seta_baixo && pedaco[tamanho - 1].direcao != CIMA)
{
velX = 0; // para de mover horizontalmente
velY = 1;  // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = BAIXO;
}

// depois ajustando as posições das outras peças (partes)
// primeiro move as partes do corpo

if (velX || velY) // se estiver movendo
{
int i;
for (i = 0; i < tamanho - 1; i++)
{
// faça a peça de trás (pedaco[i]) igual a peça da frente (pedaco[i + 1])
pedaco[i].coorX = pedaco[i + 1].coorX;
pedaco[i].coorY = pedaco[i + 1].coorY;
pedaco[i].direcao = pedaco[i + 1].direcao;
}
}
// agora move a cabeça
pedaco[tamanho - 1].coorX += velX;
pedaco[tamanho - 1].coorY += velY;

// Verifica os limites do movimento da cobra
// Para o eixo X
// se estiver além da largura da tela/mapa

if (pedaco[tamanho - 1].coorX >= mapa_largura)
{
// volte para posição coorX = 0
pedaco[tamanho - 1].coorX = 0;
}
else if (pedaco[tamanho - 1].coorX < 0) // se estiver além de 0
{
// volte para a posição da largura do mapa
pedaco[tamanho - 1].coorX = mapa_largura - 1;
}

// Para o eixo Y
if (pedaco[tamanho - 1].coorY >= mapa_altura)
{
pedaco[tamanho - 1].coorY = 0;
}
else if (pedaco[tamanho - 1].coorY < 0)
{
pedaco[tamanho - 1].coorY = mapa_altura - 1;
}
}

Para mover, a cobra usei a função move_snake(). Ela move cada parte da cobra para as novas posições. Se o valor de velX, ou velY, é diferente de zero, significa que deve-se atualizar as partes anteriores a cabeça.

A cabeça é a única peça que realmente move, as outras partes apenas movem para posições antigas da cabeça. É como um trem, onde a locomotiva (a cabeça) que puxa os vagões (as outras partes) sobre os trilhos.

if (seta_direita && pedaco[tamanho - 1].direcao != ESQUERDA)
{
velX = 1; // move horizontalmente a cabeça para direita
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = DIREITA;
}
else if (seta_esquerda && pedaco[tamanho - 1].direcao != DIREITA)
{
velX = -1;  // move horizontalmente a cabeça para esquerda
velY = 0; // e para de mover verticalmente
pedaco[tamanho - 1].direcao = ESQUERDA;
}
else if (seta_cima && pedaco[tamanho - 1].direcao != BAIXO)
{
velX = 0; // para de mover horizontalmente
velY = -1;  // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = CIMA;
}
else if (seta_baixo && pedaco[tamanho - 1].direcao != CIMA)
{
velX = 0; // para de mover horizontalmente
velY = 1;  // e move verticalmente a cabeça
pedaco[tamanho - 1].direcao = BAIXO;
}

As macros CIMA, DIREITA, BAIXO e ESQUERDA, são a direção que a cobra pode seguir.

A cobra é direcionada pelas setas do teclado. Cada seta está responsável pela mudança de uma direção. As setas direita e esquerda pela direção na horizontal (eixo X), e as setas para cima e para baixo, pela direção na vertical (eixo Y). Coloquei ainda uma tecla responsável por parar a cobra, esse é o "pause game".

A cobra não pode mover para uma direção inversa. Por exemplo, a direção inversa de DIREITA é a ESQUERDA, e a inversa de CIMA é a BAIXO. Se fosse permitido ir numa direção inversa, a cabeça colidiria com uma parte do corpo da cobra.

O movimento da cobra é feito alterando a velocidade X e Y e a direção da cabeça. As outras partes do corpo da cobra vão ter as velocidades alteradas a seguir, onde cada parte da cobra ficará com as propriedades da parte da frente. Ou seja, no movimento, a parte anterior fica com as coordenadas, velocidades e direção iguais à da parte da frente. Por isso a cabeça é a única parte a ser movida, já que as outras partes vão herdar as mesmas coordenadas, velocidade e direção que a cabeça.

Os valores das velocidade ao -1, 0 ou 1. No eixo X, velX igual a -1 é o mesmo que ir para esquerda, se velX igual a 1 é o mesmo que ir para direita e se velX igual a 0 está parada no eixo X. O mesmo vale para o eixo Y.

* Nota: as partes são contadas da esquerda para direita (do 0 ao tamanho - 1). O rabo (pedaco[0]) é parte mais para atrás e a cabeça (pedaco[tamanho - 1]), a parte mais a frente. Veja na image:

// se estiver além da largura da tela/mapa
if (pedaco[tamanho - 1].coorX >= mapa_largura)
{
// volte para posição coorX = 0
pedaco[tamanho - 1].coorX = 0;
}
else if (pedaco[tamanho - 1].coorX < 0) // se estiver além de 0
{
// volte para a posição da largura do mapa
pedaco[tamanho - 1].coorX = mapa_largura - 1;
}

Aqui verificamos se a cabeça (pedaco[tamanho - 1]) chegou nos limites da tela. O máximo que a cabeça pode ir para direita na tela, é até mapa_largura, chegou a isso ou passou, a cabeça volta para a posição 0 no eixo X. No contrário, se a cabeça passa a esquerda da posição 0 no eixo X, ela vai para a posição mapa_largura - 1 no eixo X. A mesma lógica se aplica ao movimento no eixo Y.

Observe que estou dividindo a tela numa espécie de matriz, como no jogo do labirinto, só que sem usar uma matriz de verdade. Veja abaixo como a tela está conceitualmente dividida:

A cobra move uma célula de cada vez, por isso velX ou velY é 1 ou -1. Cada célula do mapa conceitual mede TAMANHOIMAGEM (definido como 32 pixels). A macro TAMANHOIMAGEM serve para posicionar as imagem na tela no momento da blitagem.

O destino de cada imagem é definido pela posição da coordenada de cada peça, ou seja, a coordenada (X ou Y) vezes TAMANHOIMAGEM é o destino (X ou Y) em pixels.

Blitagem da cobra

void desenha_snake (  )
{
int i;
SDL_Rect destino;
// blitando as img_macas das peças
for (i = 0; i < tamanho - 1; i++)
{
destino.y = pedaco[i].coorY * TAMANHOIMAGEM;
destino.x = pedaco[i].coorX * TAMANHOIMAGEM;
destino.w = TAMANHOIMAGEM;
destino.h = TAMANHOIMAGEM;

SDL_BlitSurface(img_snakes, NULL, tela, &destino);
}
// blitando a cabeça
destino.y = pedaco[tamanho - 1].coorY * TAMANHOIMAGEM;
destino.x = pedaco[tamanho - 1].coorX * TAMANHOIMAGEM;
destino.w = TAMANHOIMAGEM;
destino.h = TAMANHOIMAGEM;

SDL_BlitSurface(img_cabeca, NULL, tela, &destino);
}

A função de saída, blita toda a cobra na tela. É sempre uma das últimas funções a ser chamada no loop principal. Não tenho muito o que falar sobre ela, tem um funcionamento muito simples, dá pra entender de boa.

// O loop principal
while (fim == 0)
{
tempo_inicial = SDL_GetTicks();
sprintf(hud, "SNAKE by Sam L. - TAMANHO: ATUAL = %d | ANTERIOR = %d", tamanho, tamanho_anterior);
SDL_WM_SetCaption(hud, NULL);
while (SDL_PollEvent(&evento))
{
if (evento.type == SDL_QUIT)
{
fim = 1;
break;
}

controla_snake(evento);
}

// move a cobra
move_snake();

// colisão com a maçã
if ((pedaco[tamanho - 1].coorX == maca.coorX) &&
(pedaco[tamanho - 1].coorY == maca.coorY))
{
tamanho++;
pedaco[tamanho - 1].coorX = maca.coorX;
pedaco[tamanho - 1].coorY = maca.coorY;
pedaco[tamanho - 1].direcao = pedaco[tamanho - 2].direcao;

// reinicalizando a posição da maçã
// escolhe uma posição diferente das peças da serpente

posiciona_maca();
}

/* Colisão só será atualizada no próximo loop, pois o usuário deve ver as peças sobrepostas */
if (colisao)
{
// reinicia o jogo e seta a variável colisao para 0
inicia_jogo();
}

/* Colisão entre cabeça e outras partes da cobra */
for (i = 0; i < tamanho - 2 && colisao == 0; i++)
{
if ((pedaco[tamanho - 1].coorX == pedaco[i].coorX) &&
  (pedaco[tamanho - 1].coorY == pedaco[i].coorY))
colisao = 1;
}


// Blitagem
// Pintando o tela de branco

SDL_FillRect(tela, NULL, SDL_MapRGB(tela->format, 255, 255, 255));

SDL_Rect destino;
destino.x = maca.coorX * TAMANHOIMAGEM;
destino.y = maca.coorY * TAMANHOIMAGEM;

SDL_BlitSurface(img_maca, NULL, tela, &destino);

desenha_snake();

// atualizando a tela
SDL_UpdateRect(tela, 0,0,0,0);

controla_fps(tempo_inicial);
}

Vamos debulhar aos poucos cada parte do loop principal.

if ((pedaco[tamanho - 1].coorX == maca.coorX) &&
(pedaco[tamanho - 1].coorY == maca.coorY))
{
tamanho++;
pedaco[tamanho - 1].coorX = maca.coorX;
pedaco[tamanho - 1].coorY = maca.coorY;
pedaco[tamanho - 1].direcao = pedaco[tamanho - 2].direcao;

// reinicalizando a posição da maçã
// escolhe uma posição diferente das peças da serpente

posiciona_maca();
}

A colisão com a maçã é muito simples, apenas verifique se as coordenadas da cabeça são iguais às coordenadas da maçã. Se for, então o tamanho da cobra deve aumentar mais um. Isto significa que a cabeça (pedaco[tamanho - 1]) será as mesmas coordenadas da maçã e sua direção será a mesma do pedaço anterior (pedaco[tamanho - 2]). Por fim, a maçã deve ser reposicionada na tela com posiciona_maca().

if (colisao)
{
// reinicia o jogo e seta a variável colisao para 0
inicia_jogo();
}

/* Colisão entre cabeça e outras partes da cobra */
for (i = 0; i < tamanho - 2 && colisao == 0; i++)
{
if ((pedaco[tamanho - 1].coorX == pedaco[i].coorX) &&
  (pedaco[tamanho - 1].coorY == pedaco[i].coorY))
colisao = 1;
}

A colisão da cabeça com outros pedaços do corpo é feita comparando-se as coordenadas da cabeça e as coordenadas dos pedaços. O único pedaço que não é comparado com a cabeça, é o que está atrás da cabeça, o pedaco[tamanho - 2], ele não colidiria em hipótese nenhuma com a cabeça, por isso a variável i vai só até tamanho - 2 no vetor pedaco.

Quando é detectada uma colisão, a variável colisao é setada para 1 e a colisão só é tratada mesmo na próxima iteração do loop principal, veja que o if (colisão) está antes de setar a variável colisão para 1. Eu fiz isso para dar tempo do jogador poder ver a cabeça sobrepondo um dos pedaços do corpo da cobra.

// Blitagem
// Pintando a tela de branco

SDL_FillRect(tela, NULL, SDL_MapRGB(tela->format, 255, 255, 255));

SDL_Rect destino;
destino.x = maca.coorX * TAMANHOIMAGEM;
destino.y = maca.coorY * TAMANHOIMAGEM;

SDL_BlitSurface(img_maca, NULL, tela, &destino);

desenha_snake();

// atualizando a tela
SDL_UpdateRect(tela, 0,0,0,0);

controla_fps(tempo_inicial);

Chegamos a parte final do loop principal. A blitagem das imagens é feita aqui. A maçã é blitada na tela, usando um SDL_Rect de destino, que irá por na tela a imagem da maçã.

Em seguida, é blitada a cobra na tela com desenha_snake(), depois apenas atualizamos a tela com as imagens blitadas. E por fim, executamos controla_fps(), que fará ou não uma chamada a SDL_Delay(), a variável tempo_inicial, que foi setada com SDL_GetTicks() no início do loop, é passada para a função controla_fps().

O que poderia ser colocado no jogo

Poderia ter passagens onde a cabeça passaria e apareceria em outra parte da tela, seria como um tele transporte.

Ou, poderia ter obstáculos fixos, que se a cobra passasse morreria, isso dificultaria mais o jogo.

Isso foi só o que consegui pensar, tente criar mais desafios ou obstáculos para o jogo.

Página anterior    

Páginas do artigo
   1. Introdução
   2. Jogo do labirinto
   3. Jogo da cobrinha
Outros artigos deste autor

Criatividade para TI parte 1

Algoritmo Antissocial - Recuperando o Controle da sua Mente

Dicas para aprender programação

Desenhando fácil um pinguim no Inkscape

Tutorial SDL

Leitura recomendada

Cuidado com números em Ponto Flutuante

Bug afeta todas as distros

Alocação dinâmica

Ponteiros - Saindo de Pesadelos

Android NDK: Desmistificando o acesso a códigos nativos em C

  
Comentários
[1] Comentário enviado por danniel-lara em 18/11/2013 - 08:11h

Parabéns pelo Artigo muito bom

[2] Comentário enviado por removido em 18/11/2013 - 19:18h

muito bom o artigo
preciso usar o sdl e gostaria de saber se vc tem os comandos para setar diretamente os pixels na tela
valeu

[3] Comentário enviado por SamL em 18/11/2013 - 19:33h

Antes de acessar os pixels é preciso mudar as permissões de leitura/escrita na SDL_Surface, para isso use SDL_LockSurface e SDL_UnlockSurface.
Por exemplo:
SDL_Surface * surface; // uma surface

SDL_LockSurface(surface); // ativa a escrita direta nos pixels de surface

// agora aqui você faria alguma coisa com os pixels
faça algo com surface->pixels

// depois de feito deve-se usar unlocksurface
SDL_UnlockSurface(surface);

Tem outra função que manipula pixels que está na documentação do SDL:
http://sdl.beuc.net/sdl.wiki/Pixel_Access
Mas observe que ainda será preciso usar SDL_LockSurface e SDL_UnlockSurface para acessar os pixels com putpixel e getpixel.

[4] Comentário enviado por removido em 06/12/2013 - 14:37h

Parabéns cara,você foi genial,gostei muito do seu artigo.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts