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

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

/* 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);
}

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);
}

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:

// 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;
}

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;

    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);
}

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)
{
  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);
}

Vamos debulhar aos poucos cada parte do loop principal.

// 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();
}

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().

/* 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;
}

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 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);

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

Desenhando fácil um pinguim no Inkscape

Tutorial SDL

Algoritmo Antissocial - Recuperando o Controle da sua Mente

Desenhando um avatar do Tux no InkScape

Criatividade para TI parte 1

Leitura recomendada

openCertiface: Biométrica Facial em nuvem baseada em software livre

Tutorial SFML

Instalando Facebook Folly através do Conan

Desenvolvendo aplicativo para autenticação biométrica utilizando a Libfprint

Sinais em Linux

  
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