|
No artigo anterior, você deve ter notado que o método main estava dentro de um loop. Hoje, vamos entender qual é a importância daquele loop, quais as consequências dele e como usa-lo para produzir animações.
Todo jogo roda num loop, formado pelos seguintes passos:
- Processar os eventos externos (entradas do usuário, dados da rede);
- Executar a lógica do jogo;
- Desenhar a tela.
Filmes no computador
Uma animação, no cinema, é produzida por uma série de quadros estáticos, mostrados um-a-um. O olho, incapaz de captar a troca de quadros, enxerga como se tudo estivesse em movimento. Nos computadores, todas as animações são produzidas de maneira similar.
Entretanto, temos apenas a presença de dois quadros. Isso porque o computador é rápido o suficiente para pintar um quadro enquanto enxergamos outro. Quando o novo quadro está pronto, ele rapidamente troca os dois de lugar: o quadro que estava sendo pintado passa a ser visto e o quadro que estava sendo visto é apagado e repintado. Essa técnica é conhecida como double buffering. É o comando SDL_GL_SwapBuffers() quem efetivamente troca as duas imagens de lugar. Uma aplicação sem double buffering sofre um problema chamado flickering. O usuário percebe a tela sendo apagada e redenhada e, para ele, a tela parece estar piscando.
Essa troca das duas imagens também não pode ser feita de qualquer jeito. No início, observou-se que se a imagem fosse apagada e a nova repintada a qualquer momento corria-se o risco do monitor exibir algumas vezes a imagem no meio da pintura. Ou seja, parte da imagem exibiria o conteúdo do buffer novo e parte do buffer antigo. Na prática, o usuário percebia pequenas manchas na tela, um efeito conhecido como tearing. O problema foi resolvido da seguinte forma: desenha-se o buffer em segundo plano na memória de vídeo e, quando pronto, é dado um comando ao hardware do vídeo, que só irá trocar as imagens no início do próximo ciclo de desenho. Essa técnica é conhecida como page flipping (virar a página). Veja no desenho abaixo:
Outra diferença do computador para o cinema é que o tempo entre os quadros não é constante. Na verdade, ele varia de acordo com o que é processado entre os quadros, com o equipamento utilizado para desenha-lo e com a complexidade do desenho sendo exibido em si. Por isso, entre um desenho e outro, devemos levar em consideração essas variações de tempo (para jogos e animações simples, é possível criar um algoritmo que mantém a taxa de atualizações entre quadros constante. Há uma extensa discussão sobre isso no livro Killer Game Programming in Java, disponível online no site do prof. doutor Andrew Davison ou versão impressa na Amazon
.)
O tempo entre dois quadros
Para um jogo suave e convincente, é fundamental sabermos o tempo entre dois quadros. O OpenGL não fornece nenhuma função que nos permita saber essa informação, porém, mais uma vez, a SDL vem em nosso resgate com a função SDL_GetTicks(). Essa função retorna o número de milisegundos transcorridos desde o início da aplicação.
Dessa forma, podemos calcular facilmente o tempo entre dois quadros, observe:
//lastTicks guarda quando foi pintado o último quadro
lastTicks = SDL_GetTicks();
while (true)
{
Uint32 thisTicks = SDL_GetTicks();
//Calcula em ticks a diferença de tempo
//entre o quadro atual e o anterior
ticks = thisTicks - lastTicks;
//Atualizamos lastTicks
lastTicks = thisTicks;
//Processa o jogo
processEvents();
processLogics();
draw();
}
Limpando a tela
Antes de começar a desenhar, é necessário limpar a tela. O OpenGL não faz essa operação por padrão, você pode até mesmo “reproveitar” uma tela já pintada e desenhar sobre ela apenas as diferenças. Isso, no entanto, é extremamente trabalhoso.
Primeiramente, temos que definir qual cor será aplicada na tela quando ela for apagada. Fazemos isso usando o comando:
void glColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha )
Que recebe como parâmetro a quantidade de vermelho, verde e azul, mais o componente alfa (por enquanto, deixe esse último componente em 0, ele será explicado só bem mais tarde, ao falarmos de blending). Os valores de verde, vermelho e azul podem variar entre 0, para nenhuma cor, ou 1 para o máximo de cor.
Em seguida, temos que usar o comando
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
E a tela está limpa. Os parâmetros desse comando também serão tratados em detalhes mais tarde. O que você precisa saber agora, é que ele limpa os buffers de cores e de profundidade, que é onde a imagem está.
Movendo objetos pela tela
Como os objetos se movimentam pela tela? Para dar a sensação de que um objeto se move, basta desenharmos ele um pouco deslocado a cada iteração. Voltando um pouquinho na física, lembraremos que a velocidade é dada pela fórmula:
velocidade = distância / tempo
Se você não gosta de física, basta lembrar que o velocímetro de seu carro obedece essa fórmula te indicando a velocidade através dos quilômetros (distância) por hora (tempo) percorridos. Em nosso caso, a velocidade será medida por duas unidades comuns: pixels por milissegundos, quando quisermos fazê-la andar, ou graus por milissegundos, se estivermos girando. Assim, vamos supor que queiramos que o triângulo desenhado no programa anterior rode em 360º em 2 segundos. Essa é uma velocidade efetiva de 180º por segundo ou, 0,180 graus / milisegundo.
A distância (em graus) que deveríamos mover o triângulo entre dois quadros (com tempo de ticks milisegundos entre si) então é:
velocidade = distância / tempo
distancia = velocidade * tempo
distanciaEmGrausParaGirar = 0,180º/ms * ticks
O programa abaixo, faz essa rotação. Apenas os métodos processLogics(), draw() e o main() foram alterados, preste especial atenção neles. Além disso, duas três novas variáveis globais foram criadas. Decidi manter o código todo para você poder dar copy&paste em seu IDE:
#include "SDL.h"
#include "GL/gl.h"
#include <stdexcept>
#include <iostream>
SDL_Surface* window;
//Controle do tempo
Uint32 lastTicks;
Uint32 ticks;
//Total graus para rodar
float degreesToRotate;
int createFlags(bool fullscreen)
{
//Iniciamos com a OpenGL e paleta de hardware
int flags = SDL_OPENGL | SDL_HWPALETTE;
if (fullscreen)
flags |= SDL_FULLSCREEN;
const SDL_VideoInfo* info = SDL_GetVideoInfo();
//Criarmos uma superfície de hardware,
//se este estiver disponível
if (info->hw_available)
flags |= SDL_HWSURFACE;
else
flags |= SDL_SWSURFACE;
//Aceleração por hardware?
if(info -> blit_hw)
flags |= SDL_HWACCEL;
return flags;
}
int setupOpenGL(int bpp, bool fullscreen)
{
//Atributos do opengl
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, bpp);
SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 0);
return createFlags(fullscreen);
}
void setup(int width, int height, int bpp, bool fullscreen)
{
//Inicializamos o subsistema de video.
if (SDL_Init(SDL_INIT_VIDEO) < 0)
throw std::runtime_error(SDL_GetError());
//Tentamos criar a janela
window = SDL_SetVideoMode(width, height, bpp,
setupOpenGL(bpp, fullscreen));
//Sem sucesso? Lançamos uma exceção com o erro.
if (window == NULL)
throw std::runtime_error(SDL_GetError());
glViewport(0,0, width, height);
//Configuramos a função de des-inicialização
atexit (SDL_Quit);
}
/** Espera o usuário pressionar o x da janela. */
void processEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
switch (event.type)
{
case SDL_QUIT:
exit(0); //Fechamos a apliação
break;
}
}
}
void draw()
{
glPushMatrix();
//Limpa a tela
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Gira o triângulo
glRotatef(degreesToRotate, 0,0,1);
//Desenha o triângulo
glBegin(GL_TRIANGLES);
glColor3f(1,0,0);
glVertex2f(0.0f,0.5f);
glColor3f(0,1,0);
glVertex2f(-0.5f, -0.5f);
glColor3f(0,0,1);
glVertex2f(0.5f, -0.5f);
glEnd();
glPopMatrix();
}
void processLogics()
{
//Distância para girar (em graus) =
//velocidade (0.180f) * tempo (ticks)
float distance = 0.180f * ticks;
degreesToRotate += distance;
}
int main(int argc,char* argv[])
{
try
{
setup(640, 480, 8, false); //Faz a mágica acontecer
degreesToRotate = 0;
lastTicks = SDL_GetTicks();
//Loop principal do jogo
while (true)
{
Uint32 thisTicks = SDL_GetTicks();
//Calcula em ticks a diferença de tempo entre
//o quadro atual e o anterior
ticks = thisTicks - lastTicks;
//Atualizamos lastTicks
lastTicks = thisTicks;
processEvents();
processLogics();
draw();
//Trocamos a superfície de desenho
//pela exibida na tela
SDL_GL_SwapBuffers();
}
}
catch (std::exception &e)
{
std::cout << "Error: "<< e.what();
exit(1);
}
}
Mais material para leitura
Aqui mesmo no Ponto V, encontramos dois ótimos materiais sobre esse assunto. O primeiro é o artigo Animação baseada em tempo, do Paulo V. Radtke e o segundo é o artigo O loop de animação, de minha autoria.
Além disso, você pode ler online o já citado livro Killer Game Programming in Java, mais especificamente no capítulo 2, An animation framework. O material está em inglês, mas trabalha também questões como imprecisão de timers, e diferentes abordagens de game loop.
Revisando
Você aprendeu que:
- Animações feitas pelo computador tem apenas dois quadros: o que está sendo exibido e o que está sendo pintado, e esses quadros são guardados em buffers na memória de vídeo;
- Como limpar a tela e definir a cor de fundo com o comando glClearColor e glClear;
- Que o comando GL_SwapBuffers tem duas funções básicas: Enviar os comandos do OpenGL efetivamente para o hardware de vídeo e trocar o buffer de desenho com o que está sendo exibido na tela;
- Que o loop de um jogo se divide entre processar eventos, processar lógica e pintar a tela;
- Que o tempo entre duas iterações do loop (portanto, entre dois quadros) não é constante;
- Como é possível calcular o tempo entre dois quadros usando a função SDL_GetTicks;
- E que devemos levar em consideração esse tempo na hora de fazer nossos desenhos.
-
11/04/2010 17:43:49 | Vinícius Godoy de Mendonça - Ambientes de Janela

A Qt e o Gtk tem um enfoque mais em aplicações de janelas mesmo, com botõezinhos, caixas de texto, etc...
A SDL é uma biblioteca de acesso de um nível um pouco mais baixo, mas já suficiente para jogos. O legal dela é que ela também dá suporte a controles, sockets e threads, que são bastante úteis para jogos.
Particularmente, acho uma das melhores bibliotecas gráficas para jogos, mesmo integrada a OpenGL.
Já para janelas comuns, a Qt e a wxWidgets são ótimas opções.
-
14/01/2012 18:42:49 |201.24.231.xxx| mateus - audio

Muito bom seus artigos, resolveu muitos dos meus problemas!
Mas procurei e não encontrei nenhum artigo sobre a implementação de audio!
Obrigado.











E ai, blz
Como estou pensando em fazer um curso para programação de jogos, estou começando a estudar sozinho e comprei o livro "OpenGL Uma Abordagem Prática e Objetiva"... Nele é usado a API glut para criar a janela... Tinha visto em algum outro site que a glut não é adequada para jogos, e no seu artigo anterior você disse que a glut não é adequada para jogos. Então to pensando em começar a estuda, junto com OpenGL, SDL, QT ou GTK, apesar de não saber exatamente qual a diferença entre elas.
Alguem poderia me da alguma dica?
vlw