segunda-feira, 30 de abril de 2012

paCamera, Vector3D e texturas

Funcionando o desenho em OpenGL, as próximas preocupações envolveram como fazer o jogo funcionar, ou seja, lidar com as matrizes OpenGL e com as transformações de translação, rotação e escala.
A forma elegante com que implementei algumas funcionalidades de modo a facilitar a manipulação dessas matrizes pelo usuário foi retornar o ponteiro this a fim de cada método envolvendo a conversão de matriz e transformação poder ser mais bem organizado. Para exemplificar, observe o código abaixo:

//Seta a projeção utilizada (perspectiva)
jogo->setProjectionMatrix()->setIdentity()->setPerspectiveProjection(fov,1.5,20.0);
//Matriz de transformação
jogo->setModelViewMatrix()->setIdentity();
//Transformação e desenho de uma esfera
glPushMatrix();
             jogo->translate(2,0,0)->rotate(angle+1,0,1,0);
             glColor3f(0.8,0.3,0);
             glutWireSphere(0.5,30,10);
glPopMatrix();

Essa implementação foi feita com algumas dependências e modularizações erradas. Ainda pretendo planejar melhor o funcionamento das mesmas.

Ainda implementei uma classe de câmera que chamei de paCamera. Esta classe basicamente contém o update da câmera e os cálculos para strafe, movimentação para frente e para trás e rotação da câmera conforme o mouse. Para o cálculo matemático, integrei as classes de vetores desenvolvidas pelo professor Vinicius Mendonça. Essa classe de câmera ainda necessita de alguns ajustes, mas com essas implementações ja foi possível oferecer um bom controle da câmera para jogos em geral.

Por fim, as ultimas implementações da engine foram as envolvendo o desenho de texturas. Para tal tive que levar em consideração que a SDL carrega as texturas invertidas. A solução foi inverter os píxels um a um e recalcular seu RGB em um código que demorei dias para entender com perfeição. Entender esse código me fez conhecer melhor os recursos que tornam a SDL uma lib tão poderosa para tratamento de imagens.
O suporte para conversão da textura é implementado em paSDL e o próprio carregamento em paOpenGL.

Em geral essas foram as primeiras implementações. Muita coisa falta ser implementada e modificada ainda. A forma como será desenvolvido o projeto ao longo das semanas será melhor explicado pela Keli nos próximos posts.

Até lá...

paOpenGL

Fazendo uma retrospectiva do que foi implementado, a engine possui:

1- Uma classe denominada paSDL, responsável por gerenciar recursos básicos em SDL, como criação de surfaces, desenho de surfaces e controle de game loop.

2- Uma classe denominada paSDLSprite, responsável por desenhar sprites com quadros.

3- Uma classe denominada paGameLoop, responsável por oferecer uma interface semi implementada para classes de game loop para aqueles que desejam uma abordagem mais complexa para seu jogo.

Agora vou falar sobre a implementação inicial da classe OpenGL denominada paOpenGL.

O plano é utilizar a engine, além do projeto "Ticket to Ride", também para o projeto de Computação Gráfica, ou seja, ela deve possuir um mínimo de suporte 3D.

O plano inicial foi integrá-la à paSDL, de forma que a engine OpenGL tratasse o modo de desenho SDL quando esta estivesse em cena. As primeiras alterações necessárias envolveram pensar em como os métodos continuariam sincronizando e limpando a tela, mas em OpenGL. Para tal, pensei em um sistema de heranças em que paOpenGL é uma paSDL, levando em consideração o desenho. Provavelmente essa estrutura necessitará de novas modularizações, mas por enquanto está suficiente para resolver o problema.
Na abordagem utilizada, portanto, esquematizei que a janela é criada na paSDL e é configurada na paOpenGL. As principais configurações envolvem limpeza dos buffers, escolha da cor de limpeza da tela padrão, habilitação do teste de profundidade e posicionamento da área de desenho (view port).

Ainda criei métodos de retorno de strings contendo nome da placa de vídeo e seu vendedor, e também da versão OpenGL usada; método para seleção da qualidade de renderização e escolha do modo de shade.

Essas alterações ja possibilitaram o desenho usando OpenGL na tela.

No próximo post falarei das últimas integrações feitas na engine antes da data de criação do blog.

paGameLoop

Parece até engraçado eu ter falado de controle de tempo e de uma classe que rode animações no tempo certo independente de FPS sem ter construído um game loop. Pois é, eu ainda não havia.
O fato é que sem o "coração do jogo" seria impossível continuar o desenvolvimento da engine e a partir dai me foquei em resolver esse problema em questão.
Confesso que não foi muito fácil a criação de um game loop para a engine. Me atrasei em praticamente todas as matérias da faculdade enquanto tentava encontrar a abordagem ideal para o projeto da engine.
Pesquisei cerca de dez abordagens de game loop (inclusive o game loop usando na franquia Prince of Persia, onde é possível se voltar no tempo), e percebi que não existe um game loop ideal, mas sim game loops para casos diferentes. Esse estudo melhorou e muito a minha noção de tempo em jogos.
Quando tomei consciência desse fator, me decidi pela abordagem de um game loop semelhante ao usado na Chien 2D (lib desenvolvida pelo professor Radke e pelo professor Binder). Este game loop basicamente recebe a quantidade de FPS do jogo e sincroniza. Contextualizarei ele abaixo:
1- A quantia de FPS é inicializada a partir do construtor da paSDL (ou seja, permanece estática durante todo o jogo).
2- Para atualização do tempo, obrigatóriamente a função membro updateTicks() deve ser chamada no início do game loop.
3- Toda a burocracia de controle de sincronização é feita na função membro sync(), que deve obrigatóriamente ser chamada em algum lugar no game loop.

Um código base para o game loop descrito acima pode ser visto abaixo:

//Inicializa o jogo (nome da janela + FPS)
paSDL *jogo = new paSDL("Exemplo GameLoop basico usando SDL",60);
//Inicia GameLoop
do{
       //Atualiza tempo
       jogo->updateTicks();
       //Limpa tela
jogo->clean();
//Funções para processar o jogo
       process();
       draw();
       //Sincroniza de acordo com a taxa de FPS ajustada
       jogo->sync();
}while(1);
delete jogo;

Mas não é isso que um game loop tem que fazer? Por que fiquei tanto tempo procurando abordagens diferenciadas?
O grande problema desse game loop é que ele só cobre o caso de máquinas rápidas, dando ao processador uma "folga" para que ele não rode 100% do tempo no jogo. Em máquinas lentas, o jogo vai simplesmente rodar mais lento, e dependendo do fator de atraso a cada game loop, este pode acabar ficando injogável.

A solução para este problema existe em vários tipos de game loop. A paSDL oferece, portanto, maneiras do usuário desativar o game loop padrão e implementar sua própria abordagem. Basta para isso chamar a função membro setDefaultSync, que recebe um boleano indicando se o game loop padrão deve estar ativo ou não. Caso o mesmo seja desativado, a função membro sync apenas vai atualizar a tela, cabendo ao usuário o controle da sincronização do jogo como um todo.

Complicado? Para a satisfação dos usuários, a engine também oferece um game loop mais complexo, o qual controla toda a burocracia do game loop na engine para o usuário. Este é implementado na classe paGameLoop. Para utilizá-lo, basta criar uma classe filha de paGameLoop, a qual deverá implementar as funções membro draw() e processLogics().

Este game loop leva em conta que o gargalo de todo o jogo está no desenho, e portanto pula até 6 quadros de animação para dar a ideia de que o jogo continua rodando suavemente. Também trata da captura das exceções disparadas pela engine para o usuário, atualização do tempo e sleep do jogo, tendo o usuário apenas que informar na classe filha como se portará o desenho e a lógica do jogo.

Para contextualizar, imagine uma classe derivada de paGameLoop denominada myGameLoop. Esta classe deverá ter um construtor que inicialize paGameLoop. A única exigência da classe pai é que no construtor da classe filha se inicialize a META da classe pai. Esta funcionalidade evita problemas de inicialização na ordem das classes, uma vez que o gerenciador do tempo está em paSDL, e a classe paGameLoop fará uso desta funcionalidade, mas é criada em memória em primeiro lugar. Dar a alternativa de inicializar a META depois oferece ao usuário a flexibilidade de iniciar o tempo do game loop quando todos os requisitos iniciais estiver corretamente ajustados.

Em seguida, é necessário implementar as funções membro draw e processLogics. É importante separar esses dois blocos de código, pois a paGameLoop irá gerenciar os pulos de quadro por meio de processLogics, ou seja, todos os updates de quadro devem ser dados nessa área. Por fim, se os quadros estiverem corretamente ajustados e o game loop não estiver "atrasado", os desenhos definidos em draw são exibidos.

No próximo post falarei sobre as funcionalidades inseridas na classe de tratamento das funcionalidades OpenGL inicialmente implementadas.

paSDLSprite

Neste post falarei sobre o desenvolvimento da classe de desenho de quadros usando SDL.

Criar uma janela com SDL e desenhar surfaces com imagens completas parece interessante, mas e no caso dos desenhos de quadros? Foi nisso que foquei a partir desse momento.
Essa parte da engine serviria de base para quando eu fosse desenhar as texturas em quadros usando OpenGL, texturas estas que ainda não havia aprendido a manipular em aula.

Dei o nome a esta parte da engine de paSDLSprite, e foquei nos seguintes principios:

1- A classe deveria receber variados tipos de formato de configuração de quadros pelo usuário

2- A classe deveria controlar o tempo de transição de quadros.

3- A classe deveria ser portavel para abordagens de game loop que utilizem o desenho separado do processamento.

Para dar conta do primeiro quesito, criei três construtores para abordagens diferentes. Um deles recebe a altura e largura do quadro (1), outro recebe o número total de quadros na imagem na altura e largura (2), e por fim, o ultimo recebe além do número total de quadros na altura e largura, uma variável com o número total de quadros que serão exibidos e um ponteiro para um vetor contendo a ordem dos quadros (3). A segunda abordagem somente iria rodar a animação com todos os quadros em sequência.

Caso o usuário resolvesse não mais utilizar os quadros escolhidos na terceira abordagem, criei uma função membro que permitisse que este resselecionasse o número de quadros e o ponteiro para o vetor da ordem dos quadros.

A classe possui dois métodos para o desenho: Um que recebe apenas a posição em que se desejará desenhar o sprite, tendo a classe o trabalho de rodar a animação setada e o outro que recebe também o quadro atual que se deseja desenhar, tendo o usuário da classe o trabalho de setar o quadro da animação manualmente.

No caso do tempo, o usuário deve passar em cada um dos construtores o tempo por quadro. Esse tempo deve ser dado em segundos e independe dos frames por segundo. Isso porque a paSDL resolve o problema da contagem do tempo e da sua conversão de milissegundos para segundos, ou seja, basta usar a composição de paSDL em paSDLSprite e utilizar seus métodos de contagem de tempo.

Levando em conta o terceiro quesito, a classe possui a função membro de atualização do quadro separada da função membro de desenho, possibilitando ao usuário atualizar os quadros na posição do código em que ele achar conveniente.

Um ultimo problema que gostaria de abordar sobre a construção dessa classe foi o caso dos quadros ordenados em mais de uma linha. Entender a lógica de exibição dos quadros em uma imagem onde todos eles estão dispostos em uma única linha é facil. Para exemplificar:

- Se tenho um sprite com 10 quadros dispersos em uma única linha, para desenhar o sexto quadro basta enviar à função membro de desenho: posicione na tela o quadro 6!
- Agora, se tenho um sprite com 10 quadros ordenado em duas linhas, terei 5 quadros em cada linha. Dessa forma, como farei para desenhar o sexto quadro?

Felizmente, a paSDLSprite cuida dessa burocrácia para o usuário usando o seguinte algorítmo:


//Seta o índice correto do quadro
int indCol = quads[quad] % this->colq;
int indRow = quads[quad] / this->colq;

//QUAL REGIÃO DA IMAGEM SERÁ DESENHADA
SDL_Rect source;
source.x = this->wq * indCol;
source.y = this->hq * indRow;
source.h = hq;
source.w = wq;

Este algoritmo dá conta de saber de qual quadro o usuário esta falando, independente se o strip estiver ordenado em mais de uma linha.
No próximo post falarei sobre a produção do game loop da engine.

segunda-feira, 23 de abril de 2012

paSDL

A ENGINE PA

O blog é recente, as implementações nem tanto...
Estive trabalhando no desenvolvimento de uma engine em SDL+OpenGL para o projeto durante a maior parte do tempo até aqui. O grande fator que me fez levar tanto tempo foi o aprendizado de boa parte dos recursos OpenGL e o aprofundamento do conhecimento que ja tinha sobre a SDL.

O objetivo inicial do desenvolvimento da engine foi propiciar um ambiente devido em SDL para receber bibliotecas OpenGL. Para isso iniciei o desenvolvimento de uma classe que oferecesse ao projeto as principais funcionalidades necessárias, dentre elas:

- Métodos de criação e configuração da janela tanto para projetos SDL como para projetos OpenGL

- Métodos de carregamento de imagens de todos os formatos, com configuração de compatibilidade, colorkey e desenho básico na tela.

- Métodos de leitura das informações sobre a janela.

- Método básico para desenho usando Surfaces.


Todos esses objetivos foram alcançados. Para essa parte da engine eu dei o nome de paSDL.


No próximo post, falarei sobre os passos seguintes que dei na implementação do desenho com SDL.

terça-feira, 17 de abril de 2012

Hello World

Olá a todos e sejam bem vindos ao blog!

O objetivo desta equipe será o de desenvolver neste prazo a maior quantidade possível do jogo, um editor gráfico de leveis e mais uma engine utilizando SDL + OpenGL.