Curso completo de DarkGdk
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 15.2
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