Projeto de MAC-211 -- 2009
Protetores do Espaço Sideral 2: A Missão
Fabio Kon - Marcelo Reis
Inicia-se agora a segunda parte de nossa odisseia espacial!
Segunda Fase: Visualização Gráfica
Agora que a geração dos asteróides e dos cristais está funcionando, vamos
apresentá-los em uma janela gráfica, no gerenciador de janelas de sua preferência (Gnome,
KDE, Xwindows, etc.); ou seja, você deverá adaptar o código que escreveu na fase
anterior para que o mesmo utilize elementos gráficos ao invés de usar caracteres em modo texto, na
representação do espaço, das espaçonaves, dos cristais e dos asteróides.
Além disso, você deve fazer a sua simulação ser executada em tempo real: ou seja, as naves,
os asteróides e os cristais devem se movimentar na tela com o passar do tempo numa velocidade
"razoável". Assim, 1 segundo simulado será bem próximo de 1 segundo do tempo real (aquele
tempo que o seu relógio de pulso mostra). Você ainda não precisa se preocupar com o controle das
espaçonaves nem com a interação com o usuário: esses aspectos serão desenvolvidos
na próxima fase.
Cada asteróide ou cristal irá corresponder a um quadrado ou círculo na tela; se
quiser, você pode usar formas irregulares para dar mais realismo ao formato
dos objetos, mas é desejável que seu formato sempre se aproxime de um
quadrado ou de um círculo. Não use um formato muito esdrúxulo pois isso
dificultará a detecção de colisões.
O número de pixels que representam cada asteróide ou cristal na tela deve variar de acordo
com a massa do objeto, mas deve sempre ser próximo ao valor D, que deve
ser uma constante definida no programa (ou seja, objetos de tamanho
"médio" são representados com D pixels, objetos "grandes" com mais de
D pixels, e objetos "pequenos", com menos de D pixels). Inicialmente
o valor de D será 10, mas poderá ser alterado em função da
resolução e da velocidade do programa final. Fica a seu critério escolher valores para as
constantes de forma a favorecer a "jogabilidade" do seu jogo.
Espaçonaves
"... to boldly go where no man has gone before."
As espaçonaves de Kirk e de Spock deverão ser representadas por triângulos
isósceles, uma de cada cor; o tamanho das espaçonaves deve ser maior que o tamanho do maior dos
asteróides e dos cristais, porém não muito maior, para não inviabilizar o jogo. Um dos
lados do triângulo é a "traseira" da nave, onde encontra-se o jato propulsor; o vértice oposto
a esse lado é que dispara o phaser que deve destruir os asteróides.
A implementação do phaser e do campo de força, assim como os controles das
espaçonaves, ficarão para a próxima fase; por hora, vamos apenas simular a movimentaçao
das naves pelo espaço sideral. A simulação será feita da seguinte forma:
- As duas espaçonaves são inicializadas em algum canto da tela, com velocidade e
direção aleatórias;
- Se uma espaçonave "se choca" com um cristal, esse é recolhido e removido do jogo;
- Se uma espaçonave se choca com um asteróide, ambos são destruídos e a nave deve ser
reinicializada em alguma borda da tela, com velocidade e direção aleatórias;
- Caso ambas as espaçonaves se choquem, nenhuma das duas é destruída e há
um choque elástico com a simulação física apropriada baseada nas velocidades
vetoriais de cada nave. Pode-se supor que ambas possuem a mesma massa;
- Se uma nave, ao se movimentar, se chocar com uma borda da tela, a mesma não é avariada e há
um choque elástico com a simulação física apropriada baseada na sua velocidade
vetorial;
- (opcional para esta fase) Implementar a aceleração e frenagem das espaçonaves, testando com
chamadas aleatórias dessas funcionalidades; a implementação precisa contar com
simulação física;
- (opcional para esta fase) Implementar o "giro" das espaçonaves (i.e., rotacionar a nave em
sentido horário ou anti-horário), testando com chamadas aleatórias dessas funcionalidades; a
implementação não precisa contar com simulação física (i.e.,
as naves giram em uma taxa fixa); quem quiser pode também oferecer a opção da
simulação física para o giro, como item opcional.
Como em toda boa ficção científica, para fins dramáticos, uma pequena explosão
deve ocorrer nas colisões envolvendo uma das naves e um asteróide. Aqui vale a sua criatividade para
elaborar esse efeito! :-)
Bibliotecas Gráficas
Os desenhos e a animação serão feitos com base em uma das duas bibliotecas: xwc ou Allegro. A xwc nada mais é do que
uma casca em cima da biblioteca básica do gerenciador de janelas, a libX11; ela é simples o
suficiente para permitir facilmente a construção de extensões: assim, se a xwc não
faz algo que você gostaria que ela fizesse, você pode estendê-la. Veja abaixo a seção
xwc. Já a Allegro é uma biblioteca bastante abrangente, voltada
para o desenvolvimento de jogos 2D em C/C++; a biblioteca não só conta com recursos gráficos,
como também de som, gerenciamento de teclado, mouse e joysticks, etc. Veja abaixo a seção Allegro. Cada grupo deve escolher uma dessas duas bibliotecas para a
implementação desta fase.
Lembretes
- Você deverá trabalhar com uma janela (seção do espaço) com uma
resolução de 1024x768 pixels. Se desejar (e seu computador permitir :-) você pode trabalhar com
uma resolução maior, desde que mantenha como alternativa trabalhar também em
1024x768.
- As figuras das espaçonaves devem ser atualizadas, no mínimo, 10 vezes por segundo;
já as figuras dos asteróides e dos cristais, que são mais lentas que as naves, devem ser
atualizadas no mínimo 1 vez por segundo.
- Para garantir uma boa velocidade nas funções de desenho, procure não desenhar
pixel por pixel, mas sim retângulos, polígonos ou círculos diretamente, usando as
funções das bibliotecas, que são descritas abaixo, em suas respectivas
seções.
- Coloque as funções de desenho em um arquivo à parte. Procure também ter um
arquivo separado dedicado à animação.
xwc
Esta seção apresenta um pequeno resumo das funções do xwc. Para mais
detalhes, veja xwc.h, xwc.c e principalmente os
programas de testes. Em particular, teste2.c contém testes de
animação.
O objeto básico de trabalho no xwc é do tipo WINDOW. Este tipo
corresponde a janelas e áreas de trabalho.
Inicialização e finalização.
Para abrir uma janela e inicializar o sistema, use a função
- InitGraph(int larg, int alt, char *nome)
Esta função retorna um ponteiro para um objeto WINDOW, com largura
larg, altura alt e nome nome. A janela é criada
e apresentada. Esta função deve ser chamada antes de qualquer outra
do xwc. Pode ser chamada mais de uma vez.
Para encerrar e liberar os recursos alocados, use
- CloseGraph()
Termina o processamento com janelas, libera memória e demais recursos.
Desenhando
Existem várias funções de desenho. Todas elas permitem a especificação de uma
cor, indicada através de um número inteiro. A correspondência entre número
e cor depende da tabela (colormap) ativa no momento. Para alocar
entradas nesta tabela e definir suas próprias cores, use:
- WNamedColor(char *nome)
Recebe o nome de uma cor e retorna o índice correspondente na tabela. Se
não houver mais entradas disponíveis, retornará o índice de uma cor
próxima.
O nome pode ser retirado de /usr/X11R6/lib/X11/rgb.txt, ou indicado
por #rrggbb
, onde rr, gg e bb indicam a
intensidade, em hexadecimal, correspondente às componentes vermelha, verde e
azul, respectivamente (2 dígitos cada).
No que segue, em geral, win se refere à janela destino e c
é a cor.
As funções de desenho propriamente ditas são:
- WPlot(WINDOW *win, int x, int y, int c)
Desenha um ponto em win, com a cor c, na posição
(x,y)
- WLine(WINDOW *win, int x1, int y1, int x2, int y2, int c)
Desenha uma linha de (x1, y1) a (x2,
y2).
- WRect(WINDOW *win, int x, int y, int w, int h, int c)
Desenha um retângulo em (x, y), de largura w e
altura h.
- WFillRect(WINDOW *win, int x, int y, int w, int h, int c)
Idem, mas preenche o retângulo.
- WArc(WINDOW *win, int x, int y, int a1, int a2, int w, int h,
int c)
Desenha um arco de elipse inscrito no retângulo indicado por x,
y, w e h (como em WRect) começando no
ângulo a1 e terminando em a2. Uma unidade de ângulo
corresponde a 1/64 de grau.
- WPrint(WINDOW *win, int x, int y, char *msg)
Desenha o texto msg na posição (x, y) da janela.
- WCor(WINDOW *win, int c
Seleciona a cor default. Serve para WPrint por exemplo.
- WClear(WINDOW *win)
Limpa a janela.
Existem outras funções de desenho no libX, que podem ser usadas. Dê
uma olhada em xwc.c para ver como isso pode ser feito.
Área de rascunho
Áreas de desenho independentes de janelas são chamadas "PICs". Um
PIC pode ter qualquer tamanho, mas deve estar associado a uma janela
já existente.
Todas as funções de desenho podem ser indistintamente aplicadas a
PICs da mesma forma que são aplicadas a janelas. Um PIC
é equivalente a WINDOW *.
As funções específicas de PICs são:
- NewPic(WINDOW *win, int w, int h)
Retorna um novo PIC associado a win, de largura w
e altura h
- FreePic(PIC pic)
Destroi pic.
- PutPic(PIC pic1, PIC pic2, int x0, int y0, int w, int h, int x,
int y
Desenha o retângulo indicado por x0, y0, w e
h de pic2 em pic1, posição (x,
y).
Máscaras
Máscaras são figuras de duas cores que restringem a cópia de um pic em outro.
Apenas os pixels correspondentes a pontos pintados na máscara são copiados.
Isto permite criar áreas transparentes em retângulos. Na verdade as janelas de
formatos genéricos usam esta técnica.
As funções de desenho se aplicam a máscaras da mesma forma que se aplicam a
pics e janelas. A única restrição são as cores. Use apenas 0 e 1 como índice
de cor.
As funções específicas são:
- NewMask(WINDOW *win, int w, int h)
Cria uma máscara de tamanho w X x adequada para
objetos associados à janela win.
- SetMask(PIC p, MASK mask
Associa a máscara mask ao pic p. Toda vez que p,
ou parte de p for copiado para outro lugar, a imagem será
"filtrada" por mask.
- UnSetMask(PIC p)
Libera p de filtragem por máscaras, até o próximo
SetMask.
Veja teste2.c para ver uma utilização interessante de máscaras.
XPM
O formato XPM é um formato especial para indicar figuras no Xwindows.
É um formato caro em termos de tamanho, mas fácil de mexer e documentar (é uma
cadeia de caracteres, legível). teste3.c ilustra a utilização de
arquivos e figuras neste formato.
As funções são:
- ReadPic(WINDOW *w, char *fname, MASK m)
Lê a imagem no arquivo de nome fname e desenha na janela (ou pic)
w. Se o arquivo especificar uma cor "None" e se
m for não nulo, será gerada uma máscara para a imagem e colocada em
m.
- WritePic(PIC p, char *fname), MASK m
Grava a imagem em p, mascarada por m no arquivo
fname
- MountPic(WINDOW *w, char **data), MASK m
Desenha a imagem descrita em data na janela w, com a
eventual máscara em m.
Download da biblioteca
O tar.gz do xwc pode ser baixado deste link.
Allegro
Esta seção apresenta um pequeno resumo das funções da Allegro. Para mais
detalhes, veja a página oficial da biblioteca, no SourceForge, especialmente os tutoriais que ensinam como utilizar as funções gráficas.
Seguem abaixo algumas instruções para se dar o pontapé inicial na utilização desta biblioteca.
Inicialização e finalização.
Para inicializar a biblioteca, use a função:
- int allegro_init();
Ela devolve zero se a biblioteca for inicializada com sucesso. Ela deve ser chamada
antes de qualquer outra coisa, apenas uma vez.
Para definir o número de cores, use a função:
- void set_color_depth(int depth);
Esta função define o número de cores; depth pode receber os valores 8, 15, 16, 24 e 32.
ela deve ser chamada após a inicialização da biblioteca Allegro e antes de set_gfx_mode.
Para inicializar o modo gráfico, use a função:
- int set_gfx_mode (int CARD, int W, int H, int V_W, int V_H);
Esta função incializa o modo gráfico, utilizando a placa de vídeo CARD (inicialmente teste com GFX_AUTODETECT), com largura W e altura H. V_W e V_H são utilizados caso você queira trabalhar com uma tela virtual; no nosso caso, pode-se deixar ambos os parâmetros como 0. Após a chamada da função, a janela é criada e apresentada; ela deve ser chamada após a inicialização da biblioteca Allegro e da definição do número de cores.
Para encerrar e liberar os recursos alocados, use:
- void allegro_exit();
Termina o processamento, libera memória e demais recursos.
Bitmaps
A Allegro trabalha com objetos do tipo BITMAP. Bitmaps são matrizes de pixels, onde cada pixel representa uma cor. A tela é um tipo especial de BITMAP, chamado screen. Para alocar um objeto do tipo BITMAP:
- BITMAP *meu_bitmap = (* BITMAP) create_bitmap(int width, int height);
create_bitmap devolve NULL caso não tenha sido possível alocar o objeto.
Para desalocar um objeto do tipo BITMAP:
- void destroy_bitmap(BITMAP *meu_bitmap);
O comando destroy_bitmap desaloca a memória utilizada pelo objeto.
Desenhando
Dado um objeto do tipo BITMAP, você pode criar figuras nele; seguem abaixo algumas das várias funções de desenho:
- void putpixel(BITMAP *meu_bitmap, int x, int y, int cor);
Desenha um pixel de cor cor em meu_bitmap, nas coordenadas (x,y).
- void line(BITMAP *meu_bitmap, int x1, int y1, int x2, int y2, int cor);
Desenha uma linha de cor cor em meu_bitmap, de (x1,y1) até (x2,y2).
- void circle(BITMAP *meu_bitmap, int x, int y, int raio, int cor);
Desenha uma circunferência de cor cor em meu_bitmap, com centro em (x,y) e raio raio.
- void blit(BITMAP *fonte, BITMAP *dest, int fonte_x, int fonte_y, int dest_x, int dest_y, int larg, int alt);
Copia um quadrado de largura larg e altura alt, nas coordenadas (fonte_x, fonte_y) do bitmap fonte para as coordenadas (dest_x, dest_y) do bitmap dest.
A biblioteca Allegro oferece muitas outras funções além das aqui descritas, incluindo mais funções de desenho, manipulação de paleta de cores, de fontes, etc. Existem diversos tutoriais (inclusive em português) e exemplos pela Internet que as explicam em detalhes.
Download da biblioteca
A Allegro pode ser baixada na página oficial da biblioteca ou, dependendo da distro, através de algum gerenciador de pacotes (e.g. em Debian, utilizando o apt).
Tempo Real
Para controle do tempo real você poderá utilizar a seguinte função POSIX:
#include <time.h>
int nanosleep(const struct timespec *requested, struct timespec *remaining);
Dê uma olhada na página de manual do nanosleep para ver como ele
funciona.
Nesta fase do projeto, você pode iniciar o desenvolvimento gerando uma
"foto" ou quadro por segundo, chamando a função nanosleep para
dormir 1 bilhão de nanosegundos entre um quadro e outro. Depois que o seu
programa estiver funcionando bem, você deve reconfigurá-lo para gerar 10 quadros
por segundo para dar impressão de movimento (dormindo 100 milhões nanosegundos
entre um quadro e outro). Lembre-se que 10 quadros por segundo é o número mínimo de vezes necessário que você deve redesenhar as espaçonaves (os asteróides e os cristais, que se movem mais lentamente, podem ser atualizados pelo menos 1 vez por segundo). Se o programa estiver funcionando muito bem, você pode ainda tentar 30 quadros por segundo, que é uma taxa comumente utilizada em vídeos e jogos.
Otimização do Joguinho:
Melhorar a simulação do tempo real medindo o
tempo gasto no processamento de cada quadro e descontar esse tempo no tempo em
que o programa dorme (com o nanosleep). Essa melhoria é importante
quando se tem muitos quadros por segundo e muita coisa acontecendo
(por exemplo, muitos asteróides e cristais, as espaçonavas se mexendo, música tocando etc.). Se
você não implementar essa melhoria (que é obrigatória neste EP), há o
perigo do seu jogo ficar muito lento caso tenha muita coisa acontecendo ou outros programas sendo
executados na mesma máquina.
Para medir o tempo gasto no processamento de cada quadro, você pode usar, por
exemplo:
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
Dê uma olhada na página de manual para aprender como ela funciona. Cuidado,
pois ela devolve o tempo em microsegundos (10^-6 seg) e não em
nanosegundos (10^-9 seg).
Data de entrega
A segunda fase deverá ser entregue até o dia 27 de maio (quarta-feira). Dúvidas podem
ser discutidas no fórum da disciplina no Moodle,
para o aproveitamento de todos.
Página de MAC211
Página do Fabio
Página do DCC