O objetivo deste projeto é construir um jogo monousuário com interface gráfica 2D. O aluno deve escolher um destes jogos 1):
Clássicos:
Recentes:
Você pode propor outros jogos ao professor para análise.
Requisitos do jogo:
h
ou F1
abrem uma tela de ajuda com as instruções do jogo, nome do autor e outras informaçõesRequisitos do código:
-std=c99
, -std=gnu99
ou -std=gnu11
).c
e .h
separados, respeitando as regras de organização de código-Wall
sem erros nem avisosall
: compila e gera o executávelclean
: remove os arquivos temporários (mantém o executável)purge
: remove tudo, deixando somente os fontesresources/
dentro do diretório do código.Critérios de avaliação
A biblioteca gráfica Allegro permite a manipulação de gráficos simples e áudio, sendo bem adaptada para a construção de jogos 2D. É uma biblioteca mais simples e limitada que a famosa biblioteca SDL, sendo mais adequada para iniciantes e para projetos menores.
Algumas de suas características:
Instalação da biblioteca Allegro 5 em Linux (Ubuntu, Mint, Debian):
sudo apt-get install liballegro5-dev
Uso:
Bibliotecas gráficas como a Allegro e SDL operam usando um esquema interno de buffer duplo, ou seja, dois buffers gráficos distintos que são usados em conjunto:
Esses buffers devem ser periodicamente alternados pelo programador (pseudocódigo):
fill_color (black) ; // operações feitas no buffer 1, não são visíveis draw (...) ; // na tela neste momento. draw (...) ; ... flip_buffers () ; // troca buffer 1 com 2, mostrando o novo conteúdo
As técnicas des buffer duplo e page flipping são implementadas nativamente na biblioteca Allegro, fornecendo imagens mais estáveis e melhor desempenho gráfico.
A maioria dos jogos segue uma estrutura de código similar. O funcionamento do jogo segue uma “máquina de estados” que define o momento atual do jogo e um laço principal que implementa o jogo em si. Ambos são explicados brevemente nesta seção.
Cada entidade do jogo (jogador, bola, etc) tem um conjunto de atributos que a representam, que normalmente é implementado em um struct
. Por exemplo, no jogo Pong a bola poderia ser representada pela seguinte estrutura:
// estrutura de uma bola typedef struct { short x, y ; // posição atual short sx, sy ; // velocidade atual short w, h ; // dimensões color_t color ; // cor atual } ball_t ; // cria uma bola ball_t ball ;
Estruturas como essa devem ser construídas para cada entidade ativa do jogo.
O funcionamento do jogo é gerenciado através de uma máquina de estados. Cada estado do jogo define o que deve ser feito naquele momento, as transições possíveis entre estados e os eventos que levam de um estado a outro. Por exemplo, a máquina de estados do jogo Pong é composta dos seguintes estados:
O relacionamento entre esses estados pode ser representado graficamente:
Essa máquina de estados pode ser facilmente implementada em C:
// estados possíveis do jogo Pong enum {INICIO, SERVINDO, JOGANDO, FIMPART, FIMJOGO} state ; // programa principal do jogo int main () { state = INICIO ; for (;;) switch (state) { case INICIO : state_init () ; break ; case SERVINDO: state_serve () ; break ; case JOGANDO : state_play () ; break ; case FIMPART : state_over () ; break ; case FIMJOGO : state_close () ; break ; default: break ; } }
Dessa forma, as funções state_init()
e demais são usadas para implementar o comportamento de cada etapa do jogo. A variável state
pode ser modificada dentro dessas funções para trocar de estado conforme necessário.
Durante o jogo propriamente dito, o programa deve continuamente ler as entradas de dados (teclado, mouse, …), atualizar o estado dos jogadores e demais entidades do jogo de acordo com essas entradas e desenhar a tela. Esse comportamento é chamado o laço principal do jogo.
O pseudocódigo a seguir dá um exemplo do laço principal do jogo Pong (simplificado):
// define estado inicial das entidades inicia_jogadores () inicia_bola () // laço principal faça ler_entrada (teclado, mouse, ...) atualiza_estado_jogadores () atualiza_estado_bola () desenha_tela () até fim da partida
Esse ciclo deve ser repetido rapidamente (dezenas de vezes por segundo) para dar a impressão de fluidez do jogo.
O tutorial de jogo da biblioteca Allegro explica com detalhes o funcionamento do laço principal.
A tela do jogo deve ser atualizada com velocidade suficiente para dar fluidez ao jogo. De preferência, a taxa de atualização da tela (taxa de quadros por segundo ou frame rate) deve ser constante, para que a animação seja consistente.
Em um jogo simples, a quantidade de cálculo a efetuar antes de cada atualização de estado e de tela é geralmente pequena, se comparada à capacidade de processamento do computador. Por isso, é interessante pausar a execução por alguns milissegundos a cada ciclo, para garantir uma taxa de quadros constante (e também diminuir o consumo de CPU).
Uma forma de controlar a taxa de quadros é através de pausas (pseudocódigo):
// taxa de quadros por segundo frame_rate = 60 // duração de cada quadro, em ms t_quadro = 1000 / frame_rate // laço principal do jogo repita t_inicio = relogio_ms () // processamento do jogo ... // espera um tempo para completar o quadro t_final = relogio_ms () t_pausa = t_quadro - (t_final - t_inicio) pausa_ms (t_pausa) até o fim da partida
Outra forma, que é usada frequentemente na Allegro 5, é a programação por eventos. Neste caso específico, programa-se um temporizador (timer) para gerar um evento a cada quadro; quando o evento ocorre, o processamento do jogo é feito:
// taxa de quadros por segundo frame_rate = 60 // duração de cada quadro, em ms t_quadro = 1000 / frame_rate // define timer (um evento a cada X ms) define_timer (t_quadro) // laço principal do jogo repita // espera o próximo evento do timer espera_evento (timer) // processamento do jogo ... até o fim da partida
Desenvolvimento de jogos:
Sprites e sons: