--==[ PARTE 7 ]==--
Para obter os programas mencionados neste tutorial, por favor baixe o zip com a versão antiga do tutorial, neste link.
[Nota: essas coisas entre colchetes foram adicionadas pelo Snownan. O texto original ficou quase todo inalterado, exceto pela inclusão do material em C++]
Introdução
Alô! Por solicitação popular, essa parte é toda sobre animação. Vou falar sobre três métodos de fazer animação no PC, e me concentrarei especificamente em uma, que será demonstrada no código de amostra, em anexo.
Embora não seja muito usada em programação de demos, a animação é geralmente usada em programação de jogos, o que pode ser quase recompensante 😉
Nesta parte também serei muito mais mesquinho com código assembly 🙂 Teremos incluída uma putpixel em assembly puro muito rápida, um comando de flip de tela em asm, uma de flip-parcial, e uma ou duas outras. Vou explicar como elas funcionam em detalhe, então isso pode ser usado como um pouco de treinamento em assembly também.
A propósito, perdoem-me por essa parte ter demorado tanto a sair, mas eu só terminei minhas provas há poucos dias atrás, e é claro que elas tinham preferência ;-). Eu tenho também notado que a Mailbox BBS não funciona mais, então o treinamento será colocado regularmente para as listas de BBS mostradas no fim desse tutorial.
Se você gostaria de me contactar, ou ao time, há muitos modos que você pode fazê-lo:
1) Escrever uma mensagem para Grant Smith/Denthor/Asphyxia em email privado na ASPHYXIA BBS.
2) Escrever uma mensagem aqui na conferência de Programação, na
For Your Eyes Only BBS (do qual eu sou Moderador)
De preferência use isso se você tem uma pergunta geral
de programação ou problemas de que outros se beneficiariam.
4) Escrever para Denthor, Eze ou Livewire na Connectix.
5) Escrever para:
Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Ligar para mim (Grant Smith) no número (031) 73 2129
(deixe uma mensagem se você ligar quando eu estiver na faculdade)
7) Escreva para mcphail@beastie.cs.und.ac.za na InterNet, e
mencione a palavra Denthor perto do topo da carta.
(by Renato: tem algo de errado na numeração do Denthor… HAHAHAH Olha lá em cima de novo e você vai ver que ele pulou do 2 para o 4… Viva o Copy/Paste :-)))) )
OBS1 : Se você é um representante de uma companhia ou BBS e quer que a ASPHYXIA faça um demo para você, mande um email pra mim; podemos discutir.
OBS2 : Se você fez/tentou fazer um demo, MANDE PARA MIM! Estamos nos sentindo muito solitários e queremos encontrar/ajudar/trocar código com outros grupos de demos. O que você tem a perder? Mande uma mensagem aqui e podemos ver como transferir. Nós realmente queremos ouvir de você.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Os princípios da Animação
Tenho certeza de que todos vocês já viram um jogo de computador com animação uma vez ou outra. Há umas poucas coisas que uma sequência de animação deve fazer de modo a dar uma impressão de realismo. Primeiramente, ela deve se mover, de preferência usando frames diferentes para aumentar o realismo (por exemplo, com um homem andando você deveria ter frames diferentes com os braços e pernas em posições diferentes). Em segundo lugar, ela não deve destruir o fundo, mas restaurá-lo depois que passar por ele.
Isso parece óbvio o suficiente, mas pode ser muito difícil de codificar quando você não tem ideia de como fazer pra alcançar aquilo.
Nesse treinamento vamos discutir vários métodos para conseguir esses dois objetivos.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Frames e Controle de Objeto
É muito óbvio que para a maioria das animações terem sucesso, você precisa ter muitos frames do objeto em várias poses (tal como um homem com vários frames dele andando). Quando mostrados um após o outro, estes dão a impressão de movimento natural.
Então, como armazenamos esses frames? Ouço vocês gritando. Bem, o método óbvio é armazenar em arrays. Após desenhar um frame em Autodesk Animator e salvar como um arquivo .CEL, geralmente usamos o seguinte código para carregá-lo:
TYPE icon = Array [1..50,1..50] of byte; VAR tree : icon; Procedure LoadCEL (FileName : string; ScrPtr : pointer); var Fil : file; Buf : array [1..1024] of byte; BlocksRead, Count : word; begin assign (Fil, FileName); reset (Fil, 1); BlockRead (Fil, Buf, 800); { Lê e ignora o cabeçalho de 800 bytes } Count := 0; BlocksRead := $FFFF; while (not eof (Fil)) and (BlocksRead <> 0) do begin BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead); Count := Count + 1024; end; close (Fil); end; BEGIN Loadcel ('Tree.CEL',addr (tree)); END.
[Nota: Isso poderia ter sido facilmente convertido para C++, mas eu acabei de converter aquele programa gigantesco, e eu não estou com vontade fazer isso. 🙂 Se você não sabe Pascal, vai ter que brigar com esse.]
Agora temos a figura de 50×50 de TREE.CEL em nosso array tree. podemos acessar esse array da maneira normal (ex.: col:=tree [25,30]). Se o frame é grande, ou se você tem muitos frames, tente usar ponteiros (veja partes anteriores)
Agora que temos a figura, como controlamos o objeto? E se quisermos manipular várias árvores passeando pela tela cada uma fazendo seu próprio trajeto? A solução é ter um registro de informação para cada árvore. Uma estrutura de dados típica poderia parecer com o seguinte:
TYPE Treeinfo = Record x,y:word; { Onde a árvore está } speed:byte; { O quão rápido ela está se movendo } Direction:byte; { Para onde a árvore está indo } frame:byte { Em que frame de animação a árvore está nesse momento } active:boolean; { É para a árvore ser mostrada nesse momento? } END; VAR Forest : Array [1..20] of Treeinfo;
Você tem agora 20 árvores, cada uma com sua própria informação, posição, etc. Essas são acessadas desse modo:
Forest [15].x:=100;
Isso setaria a coordenada x da 15a árvore para 100.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Restaurando o Background que foi sobreescrito
Vou discutir três métodos de fazer isso. Esses NÃO SÃO NECESSARIAMENTE O ÚNICO OU MELHOR MODO DE FAZER ISSO! Você deve experimentar e decidir o que é o melhor para seu tipo de programa em particular.
MÉTODO 1 :
Passo 1 : Criar duas páginas virtuais, Vaddr e Vaddr2.
[Nota: a versão em C++ usa Vaddr1 e Vaddr2]
Passo 2 : Desenhe o fundo em Vaddr2.
Passo 3 : “Flip” Vaddr2 para Vaddr.
Passo 4 : Desenhe todos os objetos do primeiro plano em Vaddr.
Passo 5 : “Flip” Vaddr para VGA.
Passo 6 : Repetir a partir do 3 continuamente.
Em ASCII, se parece com isso:
+---------+ +---------+ +---------+
| | | | | |
| VGA | <======= | VADDR | <====== | VADDR2 |
| | | (bckgnd)| | (bckgnd)|
| | |+(icons) | | |
+---------+ +---------+ +---------+
As vantagens desse abordagem são que ela é clara, a leitura contínua do fundo não é necessária, não há flicker e é simples de implementar. As desvantagens são que duas telas virtuais de 64000 bytes são necessárias, e o procedimento não é muito rápido por causa da velocidade do “flip”.
MÉTODO 2 :
Passo 1 : Desenhar o background na VGA.
Passo 2 : Capture a porção do fundo onde o ícone será colocado.
Passo 3 : Coloque o ícone.
Passo 4 : Substitua a porção do background do passo 2 sobre o ícone.
Passo 5 : Repetir a partir do passo 2 continuamente.
Em termos de ASCII…
+---------+
| +--|------- + Background restaurado (3)
| * -|------> * Background gravado na memória (1)
| ^ |
| +--|------- # Ícone colocado (2)
+---------+
A vantagem desse método é que muito pouca memória é necessária. As desvantagens são que escrever na VGA é mais lento que escrever na memória, e há muito flicker.
MÉTODO 3 :
Passo 1 : Sete uma tela virtual, VADDR.
Passo 2 : Desenhe o fundo em VADDR.
Passo 3 : “Flip” VADDR para VGA.
Passo 4 : Desenhe o ícone na VGA.
Passo 5 : Transfira a porção do background de VADDR para a VGA.
Passo 6 : Repetir a partir do passo 4 continuamente.
Em ASCII…
+---------+ +---------+
| | | |
| VGA | | VADDR |
| | | (bckgnd)|
| Icon>* <|-----------|--+ |
+---------+ +---------+
As vantagens são que escrever para a tela virtual é mais rápido que na VGA, e há menos flicker que no método 2. As desvantagens são que você usa uma tela virtual de 64000 bytes, e você tem flicker se usar um grande número de objetos.
No programa de amostra em anexo, eu uso uma mistura dos Métodos 3 e 1. É mais rápido que usar só o método 1, e não tem flicker, diferente do método 3. O que eu faço é usar VADDR2 para o fundo, antes de jogar para a VGA.
No programa de amostra, você verá que eu restauro o fundo inteiro para cada ícone, e então coloco os ícones de cada objeto individualmente, se dois objetos ficam um por cima do outro, um deles será parcialmente sobrescrito.
As seções seguintes são explicações de como as várias rotinas de assembly funcionam. Isso será provavelmente bastante chato para você se você já sabe assembly, mas deveria ajudar principiantes e amadores.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
A Putpixel em ASM
Para começar, vou explicar um pouco das variáveis de funções em ASM:
<< NOTE QUE ESSA É UMA VISÃO EXTREMAMENTE SIMPLISTA DA LINGUAGEM ASSEMBLY!
Há vários livros para aumentar seu conhecimento, e o guia de assembly do Norton podem ser de valor incalculável para as pessoas começando a codificar em assembly. Eu não te dei as figuras lindas que você deveria ter para te ajudar a entender melhor, eu meramente a descrevi como uma linguagem de programação com suas procedures especiais. >>
Existem 4 variáveis de registro: AX, BX, CX, DX. Elas são words (dois bytes) com uma escala de 0 a 65535. Você pode acessar os bytes altos e baixos, apenas trocando o X com um “H” para alto e “L” para baixo. Por exemplo, o AL vai de 0 a 255.
Você pode também ter dois ponteiros: ES:DI e DS:SI. A parte da esquerda é o segmento para que você está apontando (ex.: $a000), e o lado direito é o offset 9deslocamento), que é o quão longe para dentro do segmento você está apontando. O Turbo Pascal coloca uma variável acima de 16k na “base” do segmento, ou seja, DI ou SI será zero no início da variável.
Se você quiser apontar para o pixel 3000 na VGA (veja partes anteriores para ver o layout da tela VGA), ES deveria ser igual a $a000 e DI seria igual a 3000. Você pode facilmente fazer ES ou DS ser igual ao offset de uma tela virtual (by Renato: acho que ele queria dizer endereço da tela… segmento, ao invés de offset…)
Aqui estão algumas poucas funções que você vai precisar saber:
mov destino,fonte Isso move o valor de fonte para
o destino. ex.: mov ax,50
add destino,fonte Isso soma fonte com destino,
o resultado é guardado no destino
mul fonte Isso multiplica AX por fonte. Se
o fonte é um byte, a fonte é
multiplicada por AL, o resultado
é guardado em in AX. Se fonte
é uma word, a fonte é
multiplicada por
AX, e o resultado fica em DX:AX
movsb Isso move o byte que DS:SI está
apontando para ES:DI, e
incrementa SI e DI.
movsw O mesmo que movsb exceto que
ele move uma word ao invés
de um byte.
stosw Isso move AX para ES:DI. stosb
move AL para ES:DI. DI é então
incrementado.
push registro Isso salva o valor do registro
colocando- na pilha. O
registro pode ser então alterado,
mas será restaurado ao seu valor
original quando for "POPado"
pop registro Isso restaura o valor de um
registrador "PUSHado".
NOTA: valores Pushados
devem ser popados na MESMA
ORDEM, mas AO CONTRÁRIO.
rep comando Isso repete o comando quantas
vezes for o valor em CX
SHL Destino, contagem;
e
SHR Destino, contagem;
precisam de um pouco mais de explicação. Como você sabe, computadores pensam em um’s e zero’s. Cada número pode ser representado nessa operação de base 2. Um byte consiste de 8 um’s e zero’s (bits), e têm valores de 0 a 255. Uma word consiste de 16 um’s e zero’s (bits), e têm valores de 0 a 65535. Uma double word consiste de 32 bits.
O número 53 pode ser representado assim: 00110101. Pergunte a alguém que parece esperto para explicar a você como converter de binário para decimal e vice-versa.
O que acontece se você deslocar tudo para a esquerda? Tirar o número mais à esquerda e colocar um zero à direita? Isso é o que acontece:
00110101 = 53
<-----
01101010 = 106
Como você pode ver, o número dobrou! Da mesma forma, deslocando o uma vez para a direita, você divide o valor ao meio! Esse é um modo MUITO rápido de multiplicar ou dividir por 2. (note que para dividir deslocando temos o valor truncado… ou seja 15 shr 1 = 7)
Em assembly o formato é “SHL destino,contagem” Isso desloca (shift) o destino tantos bits quanto for o valor de contagem (1=2, 2=4, 3=8, 4=16 etc) Note que um shift leva apenas 2 clocks, enquanto um MUL pode demorar até 133 clocks. Uma diferença grande, né? Só os 286 ou superiores podem ter “contagem” maior que um.
É por isso que fazer o seguinte para calcular as coordenadas, para uma putpixel, é muito lento:
mov ax,[Y]
mov bx,320
mul bx
add ax,[X]
mov di,ax
Mas, que droga! Ouço vocês chorando. 320 não é um número que você pode shiftar, já que você só pode shiftar por 2, 4, 8, 16, 32, 64, 128, 256, 512 etc etc… A solução é bem esperta. Observe.
mov bx, [X] // seta BX para X
mov dx, [Y] // seta DX para Y
push bx // salva BX (nosso valor X)
mov bx, dx // agora BX e DX são iguais a Y
mov dh, dl // copia DL para DH (multiplica Y por 256)
xor dl, dl // zera DL
shl bx, 6 // shifta BX à esquerda 6 casas (multiplica Y por 64).
add dx, bx // soma BX a DX (Y*64 + Y*256 = Y*320)
pop bx // restaura BX (coordenada X)
add bx, dx // soma BX a DX (Y*320 + X). isso te dá
// o offset na memória que você quer
mov di, bx // mover para DI
Vamos dar uma olhada nisso mais de perto?
bx=dx=y dx=dx256 ; bx=bx64 ( Note, 256+64 = 320 )
dx+bx=O valor y correto, apenas some X!
Como você pode ver, em assembly o código mais curto frequentemente não é o mais rápido.
A procedure putpixel completa é a seguinte:
[Pascal] Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { This puts a pixel on the screen by writing directly to memory. } BEGIN Asm push ds {; Make sure these two go out the } push es {; same they went in } mov ax,[where] mov es,ax {; Point to segment of screen } mov bx,[X] mov dx,[Y] push bx {; and this again for later} mov bx, dx {; bx = dx} mov dh, dl {; dx = dx * 256} xor dl, dl shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; get back our x} add bx, dx {; finalise location} mov di, bx {; di = offset } {; es:di = where to go} xor al,al mov ah, [Col] mov es:[di],ah {; move the value in ah to screen point es:[di] } pop es pop ds End; END; [C++] ///////////////////////////////////////////////// // Putpixel() - This puts a pixel on the //screen by writing directly to // memory. ///////////////////////////////////////////////// void Putpixel (word X, word Y, byte Col, word Where) { asm { push ds // save DS push es // save ES mov ax, [Where] // move segment of Where to AX mov es, ax // set ES to segment of Where mov bx, [X] // set BX to X mov dx, [Y] // set DX to Y push bx // save BX (our X value) mov bx, dx // now BX and DX are equal to Y mov dh, dl // copy DL to DH (multiply Y by 256) xor dl, dl // zero out DL shl bx, 6 // shift BX left 6 places (multiply Y by 64). add dx, bx // add BX to DX (Y64 + Y256 = Y320) pop bx // restore BX (X coordinate) add bx, dx // add BX to DX (Y320 + X). this gives you // the offset in memory you want mov di, bx // move the offset to DI xor al, al // zero out AL mov ah, [Col] // move value of Col into AH mov es:[di], ah // move Col to the offset in memory (DI) pop es // restore ES pop ds // restore DS } }
Note isso com DI e SI, quando você os usa:
mov di,50 Move di para a posição 50
mov [di],50 Move 50 para a posição que di está apontando
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
A procedure de Flip
Isso é bastante direto. Fazemos ES:DI apontar para o início da tela de destino, e DS:DI para o início da tela fonte, então fazemos 32000 movsw (64000 bytes).
[Pascal] procedure flip(source,dest:Word); { Isso copia a tela inteira no "fonte" para o destino } begin asm push ds mov ax, [Dest] mov es, ax { ES = Segmento do destino } mov ax, [Source] mov ds, ax { DS = Segmento da fonte } xor si, si { SI = 0 Mais rápido que mov si,0 } xor di, di { DI = 0 } mov cx, 32000 rep movsw { Repete movsw 32000 vezes } pop ds end; end; [C++] ///////////////////////////////////////////////// // Flip() - Isso copia a tela inteira // no "fonte" para o destino. ///////////////////////////////////////////////// void Flip(word source, word dest) { asm { push ds // salva DS mov ax, [dest] // copia o segmento do destino para AX mov es, ax // seta ES para apontar para o destino mov ax, [source] // copia o segmento da fonte para AX mov ds, ax // seta DS para apontar par a fonte xor si, si // zera SI xor di, di // zera DI mov cx, 32000 // seta nosso contador para 32000 rep movsw // move fonta para o destino em words. decrementa // CX de 1 a cada vez até CX = 0 pop ds // restaura DS } }
A procedure CLS funciona do mesmo modo, apenas ele move a cor para AX e depois usa um “rep stosw” (veja o programa para maiores detalhes).
O comando PAL é quase o mesmo que seu equivalente em Pascal (veja os tutoriais anteriores). Olhe o código do exemplo para ver como ele usa os comandos IN e OUT.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Fechando
Os procedimentos em Assembly apresentados a você aqui não estão no seu melhor. A Maioria delas ASPHYXIA abandonou por outras melhores depois de meses de uso.Mas, como você verá em breve, são MUITO mais rápidas que as equivalentes em Pascal que lhe dei no início. No futuro, espero lhe dar mais e mais procedimentos em assembly para suas coleções cada vez maiores. Mas, como você sabe, não sou muito rápido com essa série (eu nem mesmo sei se alguma dessas partes foi lançada em semanas consecutivas), então, se você quiser alguma coisa feita, tente fazer você mesmo. O que você tem a perder, além do seu temperamento e uns poucos reboots criativos? 😉
O que eu deveria fazer no próximo treinamento? Um tutorial simples de 3-d? Você pode não gostar disso, porque eu vou entrar em detalhes pequenos de como funciona 🙂 Deixe algumas sugestões para treinamentos futuros de alguma das maneiras discutidas no topo desse treinamento.
Depois da quote costumeira, vou colocar uma lista de BBS’s que sei que têm essa série, atualmente. Se sua BBS a recebe regularmente, não importa onde você esteja, mande uma mensagem para mim e vou adicioná-la à lista. Vamos fazer isso mais conveniente para as pessoas não precisarem de fazer uma ligação a longa distância 😉
[ Ali estavam sentados, a turma pré-escolar, cercando
sua mentora, a professora substituta.
"Agora, classe, hoje vamos conversar sobre o que você quer
ser quando crescer. Não é divertido?" A professora
olha ao redor e pára na criança, silenciosa,
separada dos outros e pensativa. "Jonny, por que você
não começa?" ela o encoraja.
Jonny olha para os lados, confuso, seu pensamento interrompido.
Ele se recompõe, e olha para a professora com um olhar
fixo. "Eu quero programar demos", ele diz, suas palavras
ficando cada vez mais forte e mais confiantes ao
passo que ele fala. "Eu quero escrever algo que
vai mudar a percepção da realidade das pessoas. Eu quero
que elas se afastem do computador deslumbradas,
inseguras de seu andar e sua visão. Eu
quero escrever algo que vai sair da tela
e pegá-las, fazendo as batidas do coração e
a respiração ficarem devagar, quase parando. Quero
escrever algo que quando acabar, elas fiquem relutantes
de sair, sabendo que nada que experimentarem
aquele dia será tão real, tão compreensível, tão bom.
Eu quero escrever demos."
Silêncio. A classe e a professora olham para Jonny, perpelxos.
É a vez da professora ficar confusa. Jonny fica vermelho,
sentindo que falta alguma coisa. "Ou isso ou
vou querer ser um bombeiro."
]
- Grant Smith
14:32
21/11/93
Vejo vocês na próxima vez,
- DENTHOR
- KRULL
Essas BBS’s legais têm a SÉRIE DE TREINAMENTO DE DEMOS ASPHYXIA: (alfabeticamente)
ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ» ºBBS Name ºTelephone No. ºOpen ºMsgºFileºPastº ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹ ºASPHYXIA BBS #1 º(031) 765-5312 ºALL º * º * º * º ºASPHYXIA BBS #2 º(031) 765-6293 ºALL º * º * º * º ºConnectix BBS º(031) 266-9992 ºALL º * º * º * º ºFor Your Eyes Only BBS º(031) 285-318 ºA/H º * º * º * º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ
Open = Open at all times or only A/H
Msg = Available in message base
File = Available in file base
Past = Previous Parts available