E aqui voltamos para a segunda parte da dissecção do passarinho retardado que decidiu dar uma revoada por algum mundo perdido onde mora aquele encanador italiano daquela empresa japonesa. Nesta parte temos o desenho do pássaro, e a coisa mais importante em um jogo, a temporização.
Não custa lembrar que o código fonte do jogo está disponível no GitHub, e sendo assim não irei replicá-lo, apenas citá-lo aqui.
“Little birdie why do you fly upside down?”
De todos os gráficos do jogo, o pássaro foi a parte mais simples do jogo já que o desenho foi copiado descaradamente do jogo original. Ou quase copiado, já que precisei adaptar alguns detalhes com conta das limitações que os sprites tem de tamanho (16×16 e não reclame) e cor (monocromáticos mas empilhando até 4 eu conseguiria algumas cores). A rotina que cuida do desenho é a DRAWBIRD (criativo, não?) e ela cuida de duas coisas.
DRAWBIRD
O que ele faz é pegar o número do quadro do pássaro, multiplicar por 4 e daí fazer um equivalente a quatro “PUT SPRITE…” seguidos. Duas outras coisas são também realizadas: a primeira é corrigir um bug besta no VDP da Texas Instruments. Você posiciona um sprite na linha N e ele sempre o desenhará em N+1. Duvida?
10 COLOR 15,0,0:SCREEN 2,2:SPRITE$(0)=STRING$(32,255) 20 LINE (128,32)-STEP(15,0),8:K$=INPUT$(1) 30 PUT SPRITE 0,(128,16),1,0' E CADE A LINHA VERMELHA? 40 GOTO 40
A outra coisa que faz é atualizar automaticamente a variável BIRDFRAM, assim a cada nova execução da rotina ela desenhará um quadro diferente da animação. Aliás este é o motivo da animação ter quatro quadros ao invés dos três do jogo original — preferi simplificar a programação já que eu podia desperdiçar VRAM cin padrões de sprites.
A BIRDFRAM é inicializada no começo do jogo e eu simplesmente vou esquecer que ela existe (sempre que chega en 3 ela volta para 0)! Obviamente a única exceção disto no jogo está na rotina GAMEOVER onde eu forço constantemente BIRDFRAM com o valor 4 (ou seja, o pássaro caindo).
“Where do I start; Where do I begin”
Sincronização é uma coisa importante nos jogos, principalmente os de ação que precisam de um processamento fluído, caso contrário a jogabilidade vai para o nível do insuportável. Sendo assim eu preciso fazer com que cada laço do jogo seja executado na mesma quantidade de tempo para que a sensação seja de fluidez seja convincente. Basicamente é algo que eu usei e expliquei na segunda parte do desmonte do Retromania (mais precisamente na linha 190).
A lógica é a mesma, executar o laço e esperar dar o tempo necessário. Mas acontece que defini que não faria versões distintas do jogo para modelos de MSX europeus ou japoneses (sim, havia um escopo de projeto na minha cabeça). O jogo deveria sozinho dar seu jeito de resolver o problema. E como resolvê-lo? Simples, o MSX foi projetado para ser ligado em aparelhos e T, portanto há uma necessidade do Z80 e o VDP viverem em harmonia.
De forma BEM resumida o MSX gera uma interrupção para o Z80 a cada ciclo do VDP e estes ciclos estão ligados com frequência de operação do dispositivo de vídeo do sistema, a TV. Os modelos brasileiros, coreanos e japoneses operam a 60Hz enquanto que os demais a 50Hz. Daí basta fazer as contas corretamente para saber que a cada segundo o VDP do HOTBIT HB-8000 produz 60 interrupções enquanto que o do PHILIPS VG-8020 apenas 50. E a BIOS do MSX já faz a tarefa para você, faça um teste:
TIME=0:FOR I=0 TO 9999:NEXT I:PRINT TIME
No exemplo acima um computador rodando a 60Hz imprimirá 95 enquanto que o outro, a 50Hz, informará 115! Aliás está aqui a razão pela qual os jogos japoneses sempre rodaram de forma arrastada para os europeus e sobre o quanto achávamos a música dos jogos europeus vindos do ZX Spectrum estranhamente aceleradas (se bem que eu prefiro a trilha do Astro Marine Corps em 60Hz!).
INITENV
Lembram que no 1º grau aprendemos sobre uma coisa chamada máximo divisor comum? Então este negócio, se aplica aqui! O maior número que posso usar para dividir 50Hz e 60Hz e continuar obtendo um número inteiro é 10! Assim eu tenho para modelos PAL o valor 5 e para os NTSC o 6 representando o mesmo intervalo de tempo 0,1s (sim, você pode pensar assim, o jogo inteiro roda a 10fps).
E para facilitar a minha vida eu trabalho com duas constantes chamadas (quase erroneamente) de VDPCICLE1 e VDPCICLE5, a primeira contendo o valor 5 (ou 6) e a segunda 50 (ou 60) para indicar, respectivamente, 0,1s e 1s e assim temporizar todos os meus laços e pausas (olhando o código fonte você verá um uso extensivo delas, até mesmo há uma rotina chamada WAITASEC, específica para fazer o MSX esperar uns instantes).
A detecção sobre quais valores utilizar está na função INITENV, a ROM do jogo vem pronto para NTSC e lá eu me encarrego de alterar os valores caso tenha descoberto que o código roda em uma máquina PAL (a informação está no 8º bit do byte 0x002B da BIOS do MSX).
GAME
O jogo propriamente dito está na rotina GAME, ele é razoavelmente simples de ser explicada mas precisava de toda a teoria acima como prólogo:
; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; * ; * o jogo propriamente dito está aqui (ela é quase críptica!) ; * ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; GAME: call SNDBEEP ; emito um beep call WRITEFB (...) GAME0: ld hl,JIFFY ld (hl),0 ; zero o temporizador (...) ld hl,VDPCICLE1 ld b,(hl) call WAITASEC ; aguardo 1/10s jp GAME0
Eu zero o contador de tempo (variável de ambiente JIFFY, que equivale ao TIME do MSX-BASIC), executo o que preciso ser executado referente ao jogo e depois espero completar 1/10s, daí começo tudo novamente.
Segundo epílogo
E aqui se encerra este pacote de dicas, creio que deixei o melhor para o final 🙂