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: 26.052 ]

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


Jogo do labirinto



Esse jogo, eu fiz em 2008 baseado em outro jogo feito pelo Thiago Negri (hunz). Agora atualizei todo o código para o tutorial.

O jogo funcionará da seguinte maneira:
  • O jogador começará numa posição dentro do mapa e usará as setas do teclado para mover-se.
  • No mapa, haverá 4 tipos de objetos: paredes, gramado, caminho e ponto final.
  • O jogador poderá passar sobre o caminho ou gramado. E não passará sobre as paredes.
  • O jogo terminará quando o jogador chegar ao ponto final do mapa.

Baixe as imagens abaixo, elas serão necessárias para o jogo:
Linux: Programação de Jogos com SDL   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 para funcionar.

Arquivo labirinto.c:

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

/* Definicoes, para facilitar o uso da funcao para desenhar o mapa */
#define PAREDE 1
#define VOCE   2 /* não usado */
#define GRAMA  3
#define SAIDA  4

/* Desfinicoes da configuracao do Video */
#define LARGURA 800 /* Numero total de colunas*largura_da_parede */
#define ALTURA  200 /* Numero total de linhas*altura_da_parede */
#define BPP       0 /* A Flag SDL_ANYFORMAT se encaregara da resolucao */

SDL_Surface * tela, * piso, * parede, * player, * final, * grama;

int coluna_atual = 1, linha_atual = 1, fim = 0;

/* O Mapa */
int mapa[10][40] = {
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,3,3,3,
1,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,3,3,3,
1,3,1,3,3,1,1,1,0,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,1,1,1,1,1,0,1,0,0,0,0,1,0,1,
1,3,3,1,3,3,3,3,0,1,1,0,0,0,0,0,0,1,1,1,1,1,0,1,3,3,1,1,1,1,1,0,1,1,1,1,0,1,0,1,
1,3,3,1,3,3,3,3,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,3,3,0,0,0,1,1,0,1,1,1,1,0,1,0,1,
1,3,3,3,3,1,3,3,0,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,3,3,1,1,0,1,1,0,0,0,0,0,0,1,0,1,
1,3,3,3,3,3,3,3,0,0,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,
1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1
};

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

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

/* Funcao de inicializacao */
int carrega_imagens (  )
{
  /* Carrega as Imagens */
  parede = SDL_LoadBMP("parede_muro.bmp");
  if (parede == NULL)
  {
    printf("Não carregou parede_muro.bmp\n");
    return 0;
  }

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

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

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

  return 1;
}

/* Funcao para desenhar o Mapa */
void desenha_mapa (  )
{
  SDL_Rect destino;
  int linha, coluna;

  for (linha = 0; linha < 10; linha++)
  {
    destino.y = linha * 20;
    for (coluna = 0; coluna < 40; coluna++)
    {
      destino.x = coluna * 20;
      if (mapa[linha][coluna] == PAREDE)
      {
        /* pegue a imagem parede completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(parede, NULL, tela, &destino);
      }
      else if (mapa[linha][coluna] == GRAMA)
      {
        /* pegue a imagem grama completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(grama, NULL, tela, &destino);
      }
      else if (mapa[linha][coluna] == SAIDA)
      {
        /* pegue a imagem final completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(final, NULL, tela, &destino);
      }
    }
  }
}

void move_jogador ( SDL_Event event )
{
  /*=============== Deslocamento do jogador =====================*/
  switch (event.key.keysym.sym)
  {
    /* Na vertical */
    case SDLK_UP:
      /* Se o usuario aperta a seta para cima e linha_atual for maior que 0 ... */
      if (linha_atual > 0) /* 0 = primeira linha */
      {
        /* ...subtraia 1 de linha_atual, ou seja, suba uma linha... */
        linha_atual = linha_atual - 1;
        /* ... e verifique a localizacao do jogador ...*/
        if (mapa[linha_atual][coluna_atual] == PAREDE)
         linha_atual = linha_atual + 1; /* ... Se for sobre a PAREDE volte para a posição anterior. */
      }
    break;

    case SDLK_DOWN:
      if (linha_atual < 9)
      {
        linha_atual = linha_atual + 1;
        if (mapa[linha_atual][coluna_atual] == PAREDE)
          linha_atual = linha_atual - 1;
      }
    break;

    /* Na horizontal */
    case SDLK_LEFT:
      /* Se o usuario aperta a seta para esquerda e coluna_atual for maior que 0 ... */
      if (coluna_atual > 0) /* 0 = primeira coluna */
      {
        /* ...subtraia 1 de coluna_atual, ou seja, recue uma coluna ... */
        coluna_atual = coluna_atual - 1;
        /* ... e verifique a localizacao do jogador ...*/
        if (mapa[linha_atual][coluna_atual] == PAREDE)
        coluna_atual = coluna_atual + 1; /* ... Se for sobre a PAREDE adicione 1 a coluna_atual. */
      }
    break;

    case SDLK_RIGHT:
      if (coluna_atual < 39)/* 39 = última coluna */
      {
      coluna_atual = coluna_atual + 1;
      if (mapa[linha_atual][coluna_atual] == PAREDE)
      coluna_atual = coluna_atual - 1;
      }
    break;

    default:
    break;
  }
}


int main (  )
{
  /*inicializando a SDL e verificando possiveis erros */
  if(SDL_Init(SDL_INIT_VIDEO) != 0)
  {
    printf("Erro: %s\n", SDL_GetError());
    exit(-1);
  }

  SDL_Rect destino; /* para blitar o jogador */
  SDL_Event evento; /* para os eventos */

  /* Carrega as imagens */
  if (carrega_imagens() == 0) /* Se não carregou uma ou outra imagem */
  {
    return 1; /* encerre o programa */
  }

  /* Configura o Video */
  tela = SDL_SetVideoMode(LARGURA, ALTURA, BPP, SDL_SWSURFACE | SDL_ANYFORMAT);
  if(tela == NULL)
  {
    printf("Erro: %s\n", SDL_GetError());
    return 1; /* encerre o programa */
  }

  int tempo_inicial;

  /* Loop principal */
  while (fim == 0) /* Enquanto NÃO for verdadeiro o fim */
  {
    /* Para a funcao controla_fps */
    tempo_inicial = SDL_GetTicks();

    /* Loop de eventos */
    while(SDL_PollEvent(&evento))
    {
      if(evento.type == SDL_QUIT)
          fim = 1;

      /* move o jogador */
      if (evento.type == SDL_KEYDOWN)
        move_jogador(evento);
    }

    /* Verifica se o jogador chegou ao ponto final */
    if (mapa[linha_atual][coluna_atual] == SAIDA)
    {
      printf("Chegou ao ponto final\n");
    }

    /* Pinta a tela inteira de branco antes de desenhar o mapa, esse branco eh o caminho */
    SDL_FillRect(tela, NULL, SDL_MapRGB(tela->format, 255, 255, 255));
    /* Desenha o mapa sobre a tela */
    desenha_mapa();

    /* blita o jogador na tela */
    /* para o jogador: destino.x = coluna_atual*largura_da_imagem e destino.y = linha_atual*altura_da_imagem */
    destino.x = coluna_atual * 20;
    destino.y = linha_atual * 20;
    /* pegue a imagem player completa(NULL) e jogue na tela em destino */
    SDL_BlitSurface(player, NULL, tela, &destino);

    SDL_UpdateRect(tela,0,0,0,0); /* Atualiza a tela inteira */
    controla_fps(tempo_inicial); // controla o FPS
  }

  /* Finalizando o SDL */
  SDL_Quit();
  return 0;
}

Para compilar:

gcc -o demo_fps demo_fps.c -lSDL

Ele mostra um retângulo indo e voltando na horizontal. Olhe atentamente para o retângulo movendo, não tire o olho dele. Em certos momentos, o retângulo move mais rápido e em outros, move mais devagar, isso acontece porque não há um controle de FPS dentro do loop principal.

Se você colocar a função controla_fps() e a variável tempo_inicial no programa, verá que a variação do movimento da imagem diminuirá consideravelmente, o que significa que o programa está rodando numa velocidade quase que constante.

Carregando as imagens:

/* Funcao de inicializacao */
int carrega_imagens (  )
{
  /* Carrega as Imagens */
  parede = SDL_LoadBMP("parede_muro.bmp");
  if (parede == NULL)
  {
    printf("Não carregou parede_muro.bmp\n");
    return 0;
  }

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

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

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

  return 1;
}

As SDL_Surface, que são as imagens do jogo, são variáveis globais. Usei uma única função para carregar e verificar se carregou cada imagem. Ela retorna 0, caso alguma imagem não seja carregada e retorna 1, quando todas as imagens foram corretamente carregadas.

Blitagem do mapa

Parte mais complicada é a da blitagem do mapa.

Usei uma função para ficar responsável pela blitagem do mapa. Na blitagem, dependendo do valor da célula atual, cada imagem tem sua própria SDL_Surface. As imagens da parede, por exemplo, são uma, a do ponto final outra, por isso será preciso usar ifs para identificar a imagem para o valor indicado.

Veja abaixo a função desenha_mapa():

/* Funcao para desenhar o Mapa */
void desenha_mapa (  )
{
  SDL_Rect destino;
  int linha, coluna;

  for (linha = 0; linha < 10; linha++)
  {
    destino.y = linha * 20;
    for (coluna = 0; coluna < 40; coluna++)
    {
      destino.x = coluna * 20;
      if (mapa[linha][coluna] == PAREDE)
      {
        /* pegue a imagem parede completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(parede, NULL, tela, &destino);
      }
      else if (mapa[linha][coluna] == GRAMA)
      {
        /* pegue a imagem grama completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(grama, NULL, tela, &destino);
      }
      else if (mapa[linha][coluna] == SAIDA)
      {
        /* pegue a imagem final completa(NULL) e jogue na tela em destino */
        SDL_BlitSurface(final, NULL, tela, &destino);
      }
    }
  }
}

A função percorre cada célula da matriz e verifica qual imagem blitar na tela, indicado pelo valor da célula atual. O destino de cada imagem depende do valor das variáveis linha e coluna.

Na tela cada, imagem está localizada de 20 em 20 pixels. Multiplicando o valor da coluna por 20 (tamanho da imagem), conseguimos o destino.x da imagem na tela, o mesmo vale para o destino.y, que é linha vezes 20.

Movimento do jogador

void move_jogador ( SDL_Event event )
{
  /*=============== Deslocamento do jogador =====================*/
  switch (event.key.keysym.sym)
  {
    /* Na vertical */
    case SDLK_UP:
      /* Se o usuario aperta a seta para cima e linha_atual for maior que 0 ... */
      if (linha_atual > 0) /* 0 = primeira linha */
      {
        /* ...subtraia 1 de linha_atual, ou seja, suba uma linha... */
        linha_atual = linha_atual - 1;
        /* ... e verifique a localizacao do jogador ...*/
        if (mapa[linha_atual][coluna_atual] == PAREDE)
         linha_atual = linha_atual + 1; /* ... Se for sobre a PAREDE volte para a posição anterior. */
      }
    break;

    case SDLK_DOWN:
      if (linha_atual < 9)
      {
        linha_atual = linha_atual + 1;
        if (mapa[linha_atual][coluna_atual] == PAREDE)
          linha_atual = linha_atual - 1;
      }
    break;

    /* Na horizontal */
    case SDLK_LEFT:
      /* Se o usuario aperta a seta para esquerda e coluna_atual for maior que 0 ... */
      if (coluna_atual > 0) /* 0 = primeira coluna */
      {
        /* ...subtraia 1 de coluna_atual, ou seja, recue uma coluna ... */
        coluna_atual = coluna_atual - 1;
        /* ... e verifique a localizacao do jogador ...*/
        if (mapa[linha_atual][coluna_atual] == PAREDE)
        coluna_atual = coluna_atual + 1; /* ... Se for sobre a PAREDE adicione 1 a coluna_atual. */
      }
    break;

    case SDLK_RIGHT:
      if (coluna_atual < 39)/* 39 = última coluna */
      {
      coluna_atual = coluna_atual + 1;
      if (mapa[linha_atual][coluna_atual] == PAREDE)
      coluna_atual = coluna_atual - 1;
      }
    break;

    default:
    break;
  }
}

O movimento do jogador é feito pelas setas do teclado. As setas para cima e para baixo ficam responsáveis pelo movimento vertical (no eixo Y), e as setas direita e esquerda pelo movimento horizontal (no eixo X). As variáveis linha_atual e coluna_atual indicam a posição do jogador dentro do mapa (matriz). O movimento é feito como se estivéssemos caminhando entre as linhas e colunas de uma matriz qualquer.

Mover para cima, é o mesmo que mover da linha atual para uma linha anterior (linha_atual menos 1):

O máximo que se pode subir nas linhas é até 0 (primeira linha).

Mover para baixo, é o mesmo que mover da linha atual para uma linha posterior (linha_atual mais 1):




O máximo que se pode descer nas linhas é até 9 (última linha).

Mover para esquerda, é o mesmo que mover uma coluna para esquerda (coluna_atual menos 1):

O máximo que se pode ir para esquerda nas colunas é até 0 (primeira coluna).

Mover para direita, é o mesmo que mover uma coluna para direita (coluna_atual mais 1):

O máximo que se pode ir para direita nas colunas, é até 39 (última coluna).

A colisão é feita verificando se no mapa[linha_atual][coluna_atual] tem uma PAREDE, ou seja, se é igual a 1. Toda vez que mover o jogador, seja no eixo X ou Y, e tiver uma PAREDE no mapa[linha_atual][coluna_atual], deve-se fazer o movimento inverso para retirar o jogador de cima da parede.

Isso depende do movimento feito para chegar lá, por exemplo, se mover para cima (linha_atual menos 1) e tiver uma parede no mapa[linha_atual][coluna_atual] o movimento para desfazer ir para cima é ir para baixo (linha_atual mais 1).

Se mover para direita e tiver uma parede, o movimento de desfazer ir para direita é ir para esquerda. Ou seja, no geral é se tiver uma parede volte para a posição antes de mover.

No loop principal

/* Loop principal */
  while (fim == 0) /* Enquanto NÃO for verdadeiro o fim */
  {
    /* Para a funcao controla_fps */
    tempo_inicial = SDL_GetTicks();

    /* Loop de eventos */
    while(SDL_PollEvent(&evento))
    {
      if(evento.type == SDL_QUIT)
          fim = 1;

      /* move o jogador */
      if (evento.type == SDL_KEYDOWN)
        move_jogador(evento);
    }

    /* Verifica se o jogador chegou ao ponto final */
    if (mapa[linha_atual][coluna_atual] == SAIDA)
    {
      printf("Chegou ao ponto final\n");
    }

    /* Pinta a tela inteira de branco antes de desenhar o mapa, esse branco eh o caminho */
    SDL_FillRect(tela, NULL, SDL_MapRGB(tela->format, 255, 255, 255));
    /* Desenha o mapa sobre a tela */
    desenha_mapa();

    /* blita o jogador na tela */
    /* para o jogador: destino.x = coluna_atual*largura_da_imagem e destino.y = linha_atual*altura_da_imagem */
    destino.x = coluna_atual * 20;
    destino.y = linha_atual * 20;
    /* pegue a imagem player completa(NULL) e jogue na tela em destino */
    SDL_BlitSurface(player, NULL, tela, &destino);

    SDL_UpdateRect(tela,0,0,0,0); /* Atualiza a tela inteira */
    controla_fps(tempo_inicial); // controla o FPS
  }

O destino X e Y da imagem do jogador é feito multiplicando a linha_atual e coluna_atual por 20 (tamanho da imagem).

if (mapa[linha_atual][coluna_atual] == SAIDA) :: Verifica se o jogador chegou ao final da fase. Com isso, poderíamos terminar o game (encerrar o loop) ou carregar uma nova fase. Por enquanto, apenas uma mensagem é mostrada no console.

O que poderia ser colocado no Game

Poderia ler o mapa a partir de um arquivo. O formato poderia ser assim:

111111101111100001111111111111111111111
111100000111001100111111000001111111111
111101110110011110111111011100001100411
110001110000111110001100011111001101111
111111111111111111110000111111110000111

Com cada caractere, sendo uma célula da matriz mapa. Obviamente, deve-se converter o caractere lido para o valor inteiro, basta fazer valor_do_caractere - '0'. Poderia ler o mapa de um arquivo assim que o jogador chegasse no ponto final, mas teria de definir outra posição para o jogador dentro do mapa.

Também, poderia colocar outros obstáculos, como portas que abrem ao passar sobre um botão no mapa ou obstáculos móveis, que vai e vem pelo mapa.

Bem, por enquanto, isso fica para você mesmo implementar. :)

Página anterior     Próxima página

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

Criatividade para TI parte 1

Programa IRPF - Guia de Instalação e Resolução de alguns Problemas

Algoritmo Antissocial - Recuperando o Controle da sua Mente

Desenhando fácil um pinguim no Inkscape

Dicas para aprender programação

Leitura recomendada

Parâmetros interessantes do scanf e do printf em C

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

Dynamic libraries com libtool

Cuidado com números em Ponto Flutuante

Programação com números inteiros gigantes

  
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