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.344 ]

Por: Samuel Leonardo em 18/11/2013 | Blog: https://nerdki.blogspot.com/


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

Dicas para aprender programação

Algoritmo Antissocial - Recuperando o Controle da sua Mente

Tutorial SDL

Desenhando um avatar do Tux no InkScape

Leitura recomendada

O Produtor e o Consumidor

GNA: um Coprocessador para Aceleração Neural

Como funcionam os alocadores de memória do STD C?

OneAPI: A plataforma da Intel para facilitar o desenvolvimento com chips Intel, AMD, ARM, NVIDIA POWER e FPGA

Bug afeta todas as distros

  
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