Curso completo de DarkGdk
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 15.2

index << >>



15.2 Modelo 3D animado


1 Visão geral


De maneira geral, uma animação consiste de um conjunto de fotografias
ou quadros estáticos que são mostrados sequencialmente para produzir 
o efeito de animação. Geralmente a taxa de apresentação dos quadros
é dada em quadros por segundo ou frames por segundo (fps). As taxas
mais populares são 24 e 30 quadros por segundo usadas no cinema e na 
televisão.

Em um modelo 3d os quadros não são fotos prontas mas sim configurações
matemáticas das transformações (rotação, movimento e escala) que o
objeto e suas partes apresentam nestes quadros. Naturalmente que o 
renderizador da DarkGdk pega estes "quadros de informações" e os 
transforma em imagens para se produzir a animação.

Em um modelo 3d é comum a presença de várias animações pré-configuradas
que podem ser acessadas e tocadas em ocasiões convenientes ao jogo.
Em nosso programa exemplo carregamos um soldado com um total de 136
quadros de animação, numerados de 0 a 135, abarcando animações de andar,
morrer, sofrer impacto, e posicionamento ocioso. Cada animação veio 
representada por um subconjunto de quadros que podem ser acessados
individualmente.

Há dois aspectos importantes que você deve controlar para ter objetos
bem animados no seu jogo: tempo e quadros. Naturalmente você vai ter
no seu programa um "relógio" e variáveis para mapear o tempo e os 
quadros a serem tocados.

01.1 Tocando uma animação
// 0 - 50 Morrendo...
dbLoadObject ("H-German-Die.x",1);
Como vimos na aula anterior, esta função carrega um modelo 3d. Este 
arquivo possui uma animação de 50 quadros com o personagem 'morrendo'.

Para tocar essa animação você usa a função dbSetObjectFrame() dentro
de um laço for. Esta função fixa o objeto no posicionamento dado pelo
quadro estabelecido. 

Exemplo: 
for (nFrame = 0; nFrame <= 50; nFrame++) dbSetObjectFrame (1, nFrame);

O exemplo acima anima o objeto passando-o por todos os seus quadros
sequencialmente. Porém, essa animação não está correta pois nos modernos
computadores será apresentada violentamente rápida. É necessário adicionar
um timer para animar o objeto com a temporização adequada. Para isso você
vai usar a função dbTimer() dentro destes moldes:

// Controle de tempo int TempoFinal = 0; int TempoInicial = 0; int TempoDecorrido = 0; // Controle de quadro // [111,136] Intervalo da animação caminhando int nframe = 111; int fr_inicial = 111; int fr_final = 136; TempoFinal = dbTimer(); // Verifica se passou 30ms para avançar o quadro TempoDecorrido = TempoFinal - TempoInicial; if (TempoDecorrido > 30) { nframe = nframe + 1; TempoInicial = dbTimer(); } // Se o frame for o último volte para o começo! if (nframe >= fr_final) nframe = fr_inicial; // Configura o quadro desejado dbSetObjectFrame (1, nframe);
Em resumo você precisa medir o tempo decorrido entre um quadro e outro e 'tocar' o próximo quadro dentro de um intervalo estabelecido. Este intervalo pode ser diferente para os vários objetos em sua cena. Um bom intervalo, testado no programa exemplo, é 30ms (microsegundos). A função dbTimer() retorna um número inteiro que representa o relógio do sistema dado em microsegundos. Um segundo é equivalente ao 1000 ms. 01.2 Verificando o total de quadros de um objeto nframes = dbTotalObjectFrames( nObj); Esta função retorna o número total de quadros que um modelo possui. 01.3 Carregando várias animações dbAppendObject ( sFilename, nObj, nQuadroInicial); dbAppendObject ("H-German-Attack1.x", 1, nQuadroInicial); A função dbAppendObject() adiciona as informações de animação do objeto apontado em um outro objeto existente. Os dois objetos devem ter certamente a mesma geometria. Em nosso exemplo, as várias animações do soldado estão em arquivos separados e foram combinados em um só objeto. Os argumentos dessa função são os seguintes: sFilename - nome do arquivo que contém o objeto para ser adicionado nObj - Objeto que vai receber os dados de animação nQuadroInicial - Define a partir de que quadro a animação adicionada vai começar. Geralmente uma boa configuração para esta variável é o: total de quadros do objeto que está recebendo a animação + 1. Veja agora o programa exemplo completo:

// modeloAnimado.cpp // Esse programa ilustra como animar um modelo #include "DarkGDK.h" #include "windows.h" // Protótipo das funções void initsys(); // inicializa o sistema void teclado(); // Interpreta o teclado void texturizar(); // Texturiza a matrix // Rotação do objeto float xobj_rot = 0, yobj_rot = 0, zobj_rot = 0; // Configuração dos modos. Vamos permitir rotacionar o objeto int modo = 5; int modo_obj_rot = 5; int terminar = 0; // Controle de quadro int nframe = 111; int fr_inicial = 111; int fr_final = 136; // Controle de tempo int TempoFinal = 0; int TempoInicial = 0; int TempoDecorrido = 0; // ---------------------------------------------------------------------------- void DarkGDK ( void ) { // Começo da aplicação DarkGdk // Carrega modelo e fixa pasta de trabalho dbSetWindowTitle("Aguarde..."); dbLoadImage ("c:\\gameprog\\gdkmedia\\bitmap\\textura2x2.bmp",1); dbSetDir ("c:\\gameprog\\gdkmedia\\Modelos\\German"); // Carrega textura do soldado dbLoadImage ("german.dds",2); // 0 - 50 Morrendo... dbLoadObject ("H-German-Die.x",1); int nframes = 0; TempoInicial = dbTimer(); // 51-75 Atirando.... nframes = dbTotalObjectFrames(1) + 1; dbAppendObject ("H-German-Attack1.x", 1, nframes); // 76-86 Impacto (Soldado sendo atingido) nframes = dbTotalObjectFrames(1) + 1; dbAppendObject ("H-German-Impact.x", 1, nframes); // 87-110 Ocioso nframes = dbTotalObjectFrames(1) + 1; dbAppendObject ("H-German-Idle.x", 1, nframes); // 111-135 Caminhando nframes = dbTotalObjectFrames(1) + 1; dbAppendObject ("H-German-Move.x", 1, nframes); // Texturiza o soldado dbTextureObject (1,2); initsys(); // Faz o terreno do cenário dbMakeMatrix (1,2000,10000,10,50); dbPrepareMatrixTexture (1,1,2,2); texturizar(); // Ajusta escala do objeto e ponto de apoio dbScaleObject (1,12000,12000,12000); dbFixObjectPivot(1); // Ajusta câmera e posiciona objeto na cena dbPositionCamera (1005, 475, -600); dbPositionObject (1, 970, 300, -185); dbSyncOn(); // Looping principal while ( LoopGDK ( ) ) { TempoFinal = dbTimer(); if (terminar == 0) teclado(); dbSync ( ); if (terminar == 1) { dbDeleteImage (1); dbDeleteMatrix (1); dbDeleteObject (1); return; } // endif } // fim do while return; } // fim da função: DarkGDK // ---------------------------------------------------------------------------- void initsys() { // Esta função inicializa o sistema dbSyncOn( ); dbSetWindowTitle("modeloAnimado.cpp"); dbDisableEscapeKey(); dbSyncRate(60); dbSetAmbientLight(100); } // initsys().fim // ---------------------------------------------------------------------------- // texturizar() - Aplica aleatóriamente a textura na matrix void texturizar() { int linha, coluna; for (coluna = 0; coluna < 10; coluna++) { for (linha = 0; linha < 50; linha++) { int tile = dbRnd(4) + 1; dbSetMatrixTile (1, coluna, linha, tile); } // linha } //coluna dbUpdateMatrix (1); } // texturizar().fim // ---------------------------------------------------------------------------- // teclado() - Lê o teclado e executa comandos do usuário void teclado() { char sinfo[255]; // Lê o teclado char *stecla="??"; stecla = dbInKey(); if (dbEscapeKey()) terminar = 1; // Mostre total de frames se tecla = 'p' if (!strcmp(stecla, "p")) { int nfram = dbTotalObjectFrames (1); sprintf (sinfo, "total de frames: %d", nfram); dbSetWindowTitle (sinfo); } // fim do if // Morrer if (!strcmp(stecla, "s")) { fr_inicial = 0; fr_final = 50; dbSetWindowTitle ("[00,50] Morrer - 's' "); } // endif // Atirando... if (!strcmp(stecla, "a")) { // Atirar fr_inicial = 51; fr_final = 75; dbSetWindowTitle ("[51,75] Atirar - 'a' "); } // endif // Levando tiro... if (!strcmp(stecla, "z")) { fr_inicial = 76; fr_final = 86; dbSetWindowTitle ("[76,86] Sendo atingido - 'z'"); } // endif // ocioso if (!strcmp(stecla, "x")) { fr_inicial = 87; fr_final = 110; dbSetWindowTitle ("[87,110] Ocioso - 'x'"); } // endif // ocioso if (!strcmp(stecla, "d")) { fr_inicial = 111; fr_final = 135; dbSetWindowTitle ("[111,135] Caminhando - 'd'"); } // endif // Rotação do soldado via teclado if (dbUpKey()) zobj_rot++; if (dbDownKey()) zobj_rot--; if (dbLeftKey()) xobj_rot--; if (dbRightKey()) xobj_rot++; if (dbKeyState(201)) yobj_rot++; if (dbKeyState(209)) yobj_rot--; // Mostra informações na tela sprintf (sinfo, " s - morrer\n a - atirar\n z - atingido\n x - ocioso\n d - andar"); dbText (10,30, sinfo); dbRotateObject (1,xobj_rot, yobj_rot, zobj_rot); // Resseta o teclado strcpy(stecla,"??"); // Verifica se passou 30ms para avançar o quadro TempoDecorrido = TempoFinal - TempoInicial; if (TempoDecorrido > 30) { nframe = nframe + 1; TempoInicial = dbTimer(); } // Se o frame for o último volte para o começo! if (nframe >= fr_final) nframe = fr_inicial; // Configura o quadro desejado dbSetObjectFrame (1, nframe); } // teclado().fim

index << >>

Produzido por Gameprog: Jair Pereira - Setembro/2013 © gameprog.br@gmail.com http://www.gameprog.com.br http://www.nucleoararat.com.br