Curso de Jogos em Basic
Scroll em Assembly
Você está em: MarMSX >> Cursos >> Jogos em Basic
Foi visto no capítulo 2 desse curso, ao final da seção 2.2.1, que podemos aproveitar algumas características do modo de interpretação do Basic para ganhar a velocidade do Assembly, como por exemplo, enviar uma string longa para o comando PRINT em vez de caractere a caractere. Entretanto, determinadas rotinas não têm como se aproveitar disso e ficam extremamente lentas se programadas em Basic. Dessa forma, necessitamos recorrer à linguagem Assembly para aumentar a performance dessa rotina. É exatamente isso que alguns desenvolvedores de jogos em Basic fizeram em seus jogos.
Deve-se ter em mente que não é para criar o jogo todo em Assembly, mas tão somente o trecho de programa (rotina) que necessita da performance de programas feitos em Assembly. Um exemplo disso é uma rotina para realizar o scroll vertical na tela feita no jogo Slalom Gigante, publicado na revista italiana MSX Computer Magazine #1.
O melhor dessa história é que o programador Basic pode até não saber uma linha de Assembly, mas tão somente ter esse código pronto e saber usá-lo.
Adicionando código Assembly ao Basic
Após desenvolver os mnemônicos na linguagem Assembly e transformá-los em código de máquina, ou até mesmo já tê-los prontos, devemos colocá-los na memória através do comando POKE do Basic. Depois, temos que indicar ao interpretador Basic qual o endereço inicial do programa através do comando DEFUSR <n>. Por fim, a instrução USR <n> chama o programa em Assembly.
Veja o exemplo a seguir.
Código em Assembly para chamar a rotina da ROM para apagar a tela (CLS).
Cód Máq Mnemônico
---------------------------
CD C3 00 CALL &H00C3
C9 RET
Em qual endereço de memória devemos colocar nosso programa? Em tese, temos de &H8000 a &HFFFF como áreas de RAM no modo Basic. Entretanto, em &H8000 começa nosso programa em Basic e também perto do final ficam variáveis do sistema. Assim, vamos colocá-lo em uma área intermediária, longe dos dois: &HC000.
Podemos armazenar até 10 endereços de rotina em Assembly na função DEFUSR, atribuindo-se um valor de 0 a 9. Quando omitido o valor, assume-se o valor igual a 0. Assim, DEFUSR equivale a DEFUSR 0. O comando USR <n> chama a rotina do DEFUSR <n> equivalente.
Programa em Basic.
10 PRINT"Tecle algo que vou apagar a tela ..."
20 POKE &HC000,&HCD
30 POKE &HC001,&HC3
40 POKE &HC002,0
50 POKE &HC003,&HC9
60 DEFUSR=&HC000
70 A$=INPUT$(1)
80 X=USR(0)
O capítulo 19 do curso de Basic apresenta maiores detalhes sobre como inserir código Assembly em Basic.
Scroll vertical em Assembly
O que parece ser algo complicado de fazer, é na realidade muito simples. Isso porque o que necessitamos fazer é simplesmente mover um bloco de imagem da tela uma linha acima/abaixo. Veja a ilustração a seguir.
Na ilustração acima, movemos uma linha a área assinalada pelo retângulo vermelho. Podemos fazer essa movimentação nos dois sentidos: tanto da ilustração à esquerda para a direita (a tela sobe), como da direita para a esquerda (a tela desce).
Podemos notar que após a cópia do bloco em vermelho, uma linha não recebe informação e fica com "lixo". É a última linha se a imagem resultante é a da direita, e a primeira linha se a imagem resultante for a da esquerda. É nessa linha que devemos inserir informação nova da tela. Podemos fazer isso em Basic mesmo, pois a instrução PRINT pode imprimir toda a linha de uma só vez de forma rápida.
Na screen 1, iremos mover a tabela de nomes. Ela começa na posição 6144 ou &H1800. Cada linha possui 32 posições (ou &H20) de blocos de 8x8 pixels. Assim, a segunda linha começa em &H1820, a terceira em &H1840 etc.
No VDP do MSX 2 podemos mover porções de memória de vídeo dentro do próprio chip. Entretanto, no VDP do MSX 1 isso não é possível. Assim, devemos mover um bloco da VRAM para a RAM e depois devolvê-lo à VRAM.
Há duas rotinas na ROM do MSX que realizam cópias de blocos inteiros da VRAM para RAM e vice-versa. Veja no esquema a seguir.
LDIRMV - copia um bloco da VRAM para a RAM.
Ponteiro: 0059H
Entrada: BC=Comprimento
DE=Endereço RAM
HL=Endereço VRAM
LDIRVM - copia um bloco da RAM para a VRAM.
Ponteiro: 005CH
Entrada: BC=Comprimento
DE=Endereço VRAM
HL=Endereço RAM
Devemos então copiar o bloco assinalado em vermelho na figura mais acima da VRAM para a RAM e então devolver o mesmo bloco uma linha acima/abaixo.
O programa genérico em Assembly é apresentado a seguir
Cód. Máq. Mnemônicos
-------------------------------
21 20 18 LD HL,&H1820 ; Endereço inicial da VRAM (2a. linha)
11 00 C2 LD DE,&HC200 ; Endereço inicial da RAM
01 E0 02 LD BC,&H02E0 ; Comprimento do bloco (32 x 23 linhas)
CD 59 00 CALL &H0059 ; Chama VRAM -> RAM
21 00 C2 LD HL,&HC200 ; Endereço inicial da RAM
11 00 18 LD DE,&H1800 ; Endereço inicial da VRAM (1a. linha)
01 E0 02 LD BC,&H02E0 ; Comprimento do bloco
CD 5C 00 CALL &H005C ; Chama RAM -> VRAM
C9 RET ; Retorna
Nesse exemplo movemos o bloco no sentido para cima.
Nota: colocamos a área de transferência da RAM em uma região após o código do programa. Nosso programa em Assembly vai de &HC000 a &HC018. Assim, &HC200 é uma região segura para realizar a transferência, sem correr o risco de conflitar com o programa.
Vamos testar esse código em Basic.
10 SCREEN 1 : KEY OFF : E=&HC000
20 READ A$ : IF A$="M" THEN 60
30 POKE E,VAL("&H"+A$)
40 E=E+1
50 GOTO 20
60 LOCATE 0,10:PRINT"Esta tela vai se mover":PRINT"para cima ..."
70 PRINT : PRINT "TECLE ALGO ..."
80 A$=INPUT$(1)
90 DEFUSR=&HC000:X=USR(0)
100 DATA 21,20,18 : ' LD HL,&H1820
110 DATA 11,00,C2 : ' LD DE,&HC200
120 DATA 01,E0,02 : ' LD BC,&H02E0
130 DATA CD,59,00 : ' CALL &H0059
140 DATA 21,00,C2 : ' LD HL,&HC200
150 DATA 11,00,18 : ' LD DE,&H1800
160 DATA 01,E0,02 : ' LD BC,&H02E0
170 DATA CD,5C,00 : ' CALL &H005C
180 DATA C9,M, : ' RET
Obs: ao lado dos códigos em linguagem de máquina nas instruções DATA, há um comentário com o respectivo código mnemônico.
Após a execução desse programa, a tela se move uma linha para cima.
Podemos mover quantas linhas quisermos, se colocarmos um loop. Então altere/acrescente as seguintes linhas ao programa anterior.
90 DEFUSR=&HC000
95 FOR X=1 TO 5 : X=USR(0) : NEXT X
A cada iteração do loop, a rotina em linguagem de máquina para mover a tela uma linha acima é chamada. Observe a rapidez em que o movimento total é feito.
Para modificar o sentido do movimento da tela, precisamos inverter os endereços do código em linguagem de máquina, uma vez que agora vamos copiar o bloco a partir da primeira linha para a segunda linha. Assim, o endereço inicial da VRAM é a partir de &H1800 em vez de &H1820, assim como retornamos os dados em &H1820, em vez de &H1800. Altere as seguintes linhas do programa anterior:
60 LOCATE 0,10:PRINT"Esta tela vai se mover":PRINT"para baixo ..."
...
100 DATA 21,00,18 ' LD HL,&H1800
...
150 DATA 11,20,18 ' LD DE,&H1820
Observe que o código de máquina contém os valores de endereços de memória explícitos. Entretanto, para valores de 16 bits (2 bytes), o valor aparece invertido. Assim, 00 18 corresponde ao valor &H1800. Dito isso, fica fácil alterar esses valores.
Scroll parcial de tela
Se você jogou o Slalom Gigante, pôde observar que o autor do jogo reservou as duas primeiras linhas para colocar informações do jogo. Essas linhas não são alteradas pelo scroll. O que o autor fez na verdade foi começar o scroll a partir da terceira linha.
Temos que ter em mente que além de alterar a linha inicial do scroll, devemos redimensionar o tamanho do bloco a ser copiado. Veja a ilustração a seguir, que começa o scroll a partir da 12a. linha.
Vamos calcular os parâmetros necessários.
Endereço inicial para a 12a. linha:
&H1800 + 32 x 11 = &H1800 + &H0160 = &H1960
Próxima linha: &H1980
Tamanho do bloco: 12 linhas
12 x 32 = 384 = &H0180
O programa em Basic que realiza o scroll parcial de tela é apresentado a seguir.
10 SCREEN 1 : KEY OFF : E=&HC000
20 READ A$ : IF A$="M" THEN 60
30 POKE E,VAL("&H"+A$)
40 E=E+1
50 GOTO 20
60 LOCATE 0,10:PRINT"Eu NAO me movo !"
65 LOCATE 0,12:PRINT"Eu me movo!"
70 PRINT : PRINT "TECLE ALGO ..."
80 A$=INPUT$(1)
90 DEFUSR=&HC000:X=USR(0)
100 DATA 21,80,19 : ' LD HL,&H1980
110 DATA 11,00,C2 : ' LD DE,&HC200
120 DATA 01,80,01 : ' LD BC,&H0180
130 DATA CD,59,00 : ' CALL &H0059
140 DATA 21,00,C2 : ' LD HL,&HC200
150 DATA 11,60,19 : ' LD DE,&H1960
160 DATA 01,80,01 : ' LD BC,&H0180
170 DATA CD,5C,00 : ' CALL &H005C
180 DATA C9,M, : ' RET
Inserindo os dados da tela
Os dados da tela são inseridos linha a linha, conforme o scroll avance a tela. Se o sentido é para cima, devemos preencher a 24a. linha. Se for para baixo, a 1a. linha.
Para ilustrar esse mecanismo, vamos criar o cenário da praia de Copacabana no Rio de Janeiro. Assim, utilizaremos os seguintes elementos estruturantes (blocos de 8x8 pixels):
Os elementos estruturantes formarão um desenho, que repetirá o padrão de quatro em quatro linhas. O layout do desenho da praia de Copacabana foi feito em um editor de imagens para PC (Kolourpaint do Linux), o que facilita muito o trabalho.
A imagem a seguir ilustra a combinação dos elementos estruturantes a cada 4 linhas (padrão básico). O primeiro é o calçadão, seguido da rua, areia e mar.
A formação dos padrões é a seguinte:
1 2 10 10 9 9 12 13
3 4 10 11 9 9 12 14
5 6 10 10 9 9 12 13
7 8 10 11 9 9 12 14
Iremos construir esse desenho na screen 1. Assim, os caracteres utilizados para cada padrão serão os seguintes:
Grupo Caracteres Padrões Cores
---------------------------------------------
12 'abcdefg 1-8 F1
13 h 9 1A
14 pq 10-11 FE
15 xyz 12-14 74
Dessa forma, por exemplo, o caractere "x" representa o padrão 12, "y" o padrão 13 e "z" o padrão 14.
O programa a seguir modifica os padrões dos caracteres e as cores dos grupos de caracteres.
10 SCREEN 1 : WIDTH 32 : COLOR 15,0,0 : KEY OFF
20 FOR I=0 TO 63 : READ A$ : VPOKE 768+I, VAL("&H"+A$) : NEXT I
30 FOR I=0 TO 7 : READ A$ : VPOKE 832+I, VAL("&H"+A$) : NEXT I
40 FOR I=0 TO 15 : READ A$ : VPOKE 896+I, VAL("&H"+A$) : NEXT I
50 FOR I=0 TO 23 : READ A$ : VPOKE 960+I, VAL("&H"+A$) : NEXT I
55 '
60 ' Define cores de fundo
65 '
70 VPOKE &H2000+12,&HF1
80 VPOKE &H2000+13,&H1A
90 VPOKE &H2000+14,&HFE
100 VPOKE &H2000+15,&H74
195 '
200 ' Dados dos caracteres
205 '
210 DATA F0,FC,FC,FE,FE,FF,FF,FF ' Calcadao
220 DATA 0F,03,03,01,01,00,00,00
230 DATA FF,FF,FF,FE,FE,FC,FC,F0
240 DATA 00,00,00,01,01,03,03,0F
250 DATA F0,C0,C0,80,80,00,00,00
260 DATA 0F,3F,3F,7F,7F,FF,FF,FF
270 DATA 00,00,00,80,80,C0,C0,F0
280 DATA FF,FF,FF,7F,7F,3F,3F,0F
290 DATA 00,00,00,00,00,00,00,00 ' Areia
300 DATA 00,00,00,00,00,00,00,00 ' Asfalto
310 DATA 80,80,80,80,80,80,80,80
320 DATA 00,00,00,00,00,00,00,00 ' Mar
330 DATA 01,01,01,01,03,03,03,07
340 DATA 07,03,03,03,01,01,01,01
Podemos salvar a parte da tabela de cores e caracteres modificados para não ter que reescrever todo esse código.
' Padrões
BSAVE "pat.dat",768,983,S
' Cores
BSAVE "col.dat",&H200C,&H200F,S
O mesmo podemos fazer com o código do scroll (com ele carregado na memória).
BSAVE "cod.bin",&HC000,&HC018
Nota: não se preocupe, pois mais adiante você poderá fazer o download das duas tabelas e o código.
Colocaremos 31 caracteres de modo a formar cada linha. Isso porque se colocarmos a linha cheia, o Basic saltará uma linha. O padrão básico é apresentado a seguir.
As quatro linhas desse padrão serão formadas pelos seguintes caracteres:
Linha 1: xyhhhhhhhhhhhhhhhhhhhh`a`a`apppp
Linha 2: xzhhhhhhhhhhhhhhhhhhhhbcbcbcpppq
Linha 3: xyhhhhhhhhhhhhhhhhhhhhdededepppp
Linha 4: xzhhhhhhhhhhhhhhhhhhhhfgfgfgpppq
Após essa longa preparação, podemos iniciar nosso passeio por Copacabana. O código a seguir ilustra como alimentar o scroll com as linhas da praia. Mas antes, baixe aqui as tabelas e códigos prontos.
10 SCREEN 1 : WIDTH 32 : COLOR 15,0,0 : KEY OFF : DIM L$(4)
20 L$(1)="xyhhhhhhhhhhhhhhhhhhh`a`a`apppp"
30 L$(2)="xzhhhhhhhhhhhhhhhhhhhbcbcbcpppq"
40 L$(3)="xyhhhhhhhhhhhhhhhhhhhdededepppp"
50 L$(4)="xzhhhhhhhhhhhhhhhhhhhfgfgfgpppq"
60 LN=1
70 BLOAD"PAT.DAT",S
80 BLOAD"COL.DAT",S
90 BLOAD"COD.BIN"
100 DEFUSR=&HC000
110 X=USR(0)
120 LOCATE 0,23:PRINT L$(LN);
130 LN=LN+1 : IF LN>4 THEN LN=1
140 FOR T=1 TO 50 : NEXT
150 GOTO 110
Resultado:
Colocamos as strings das linhas em uma tabela, pois é mais fácil referenciá-las.
Observe na linha 90 que carregamos o código do scroll sem executá-lo (sem a extensão ,R).
A linha 140 controla a velocidade do scroll.
Podemos inverter o sentido do movimento? Sim. É só alterar/inserir as seguintes linhas:
60 L=4
...
95 POKE &HC001,0 : POKE &HC010,&H20 ' Altera código scroll
...
120 LOCATE 0,0:PRINT L$(LN);
130 LN=LN-1 : IF LN<1 THEN LN=4
Nota importante: para dar efeito de scroll, os padrões dos blocos de linhas adjacentes deverão ser diferentes. Caso contrário, essa parte do cenário parecerá estática. Foi exatamente o que fizemos no mar e no asfalto, e também o autor do jogo Slalom Gigante com as árvores.
Scroll horizontal em Assembly
O scroll horizontal irá seguir os mesmos princípios do vertical. Só que nesse caso, iremos mover a tela somente um caractere em vez de uma linha. O problema desse scroll consiste em como imprimir uma coluna de caracteres de forma rápida.
Rode o programa de Copacabana, deixe a tela se completar e dê CONTROL + STOP para parar a animação. Em seguida, pressione o CAPS LOCK e dê o seguinte comando:
POKE &hC001,0:POKE &HC010,1:X=USR(0)
A tela andou para a direita! Isso porque agora definimos o salto de 1 caractere, em vez de 32 (uma linha). Experimente dar mais comandos X=USR(0). A tela continua indo para a direita.
Para movermos no sentido contrário, devemos fazer:
POKE &hC001,1:POKE &HC010,0:X=USR(0)
Aproveitando o programa "cod.bin" do exemplo de Copacabana, vamos rodar o exemplo a seguir.
10 SCREEN 1
20 BLOAD"cod.bin"
30 DEFUSR=&HC000
40 POKE &HC001,0:POKE&HC010,1
50 FOR I=0 TO 22
60 LOCATE 0,I:PRINT"A";
70 NEXT I
80 X=USR(0)
90 GOTO 50
Esse programa faz o scroll horizontal desenhando a letra A na coluna 0. Observe o efeito de flicker (coluna piscando).
A solução para esse problema será vista na próxima seção.
Código Assembly para todo o scroll
No exemplo do scroll vertical de Copacabana, também observamos um pequeno efeito de flicker no calçadão onde a linha é desenhada. A solução para esse problema e do scroll vertical é juntar todo o procedimento em um só código em Assembly. Assim, passamos a linha/coluna para a rotina em Assembly e ela se encarrega de mover a tela e preencher a lacuna.
O curso de Basic, capítulo 19, explica como passar parâmetros para o código em Assembly através da instrução USR. Podemos passar até strings.
Assim, nossa estratégia agora é passar a linha para o código que realizará todo o processo de scroll, em vez de mover a tela em Assembly e desenhar a linha em Basic.
O código em Assembly do scroll completo vertical para cima é apresentado a seguir.
End Código Linha Instrução Comentários
-------------------------------------------------------
C000 10 ORG &HC000
C000 D5 20 PUSH DE ; Salva ponteiro String
C001 21 20 18 30 LD HL,&H1820 ; Endereço inicial da VRAM
C004 11 00 C2 40 LD DE,&HC200 ; Endereço inicial da RAM
C007 01 E0 02 50 LD BC,&H2E0 ; Comprimento do bloco
C00A CD 59 00 60 CALL &H59 ; Chama VRAM -> RAM
C00D 21 00 C2 70 LD HL,&HC200 ; Endereço inicial da RAM
C010 11 00 18 80 LD DE,&H1800 ; Endereço inicial da VRAM
C013 01 E0 02 90 LD BC,&H2E0 ; Comprimento do bloco
C016 CD 5C 00 100 CALL &H5C ; Chama RAM -> VRAM
C019 E1 110 POP HL ; Retorna DE em HL
C01A 23 120 INC HL ; Passa para ponteiro string
C01B 5E 130 LD E,(HL) ;
C01C 23 140 INC HL ; Copia end inicial da str em DE
C01D 56 150 LD D,(HL) ;
C01E EB 160 EX DE,HL ; Passa end inicial para HL (RAM)
C01F 11 E0 1A 170 LD DE,&H1AE0 ; Endereco incial da VRAM
C022 01 1F 00 180 LD BC,&H1F ; Copia 31 caracteres
C025 CD 5C 00 190 CALL &H5C ; Chama RAM -> VRAM
C028 C9 200 RET ; Retorna ao Basic
O que devemos fazer é apontar HL para o inicio da string passada como parâmetro e desenhar o bloco de 31 caracteres.
Rode o programa de Copacabana com esse novo código.
10 SCREEN 1 : WIDTH 32 : COLOR 15,0,0 : KEY OFF : DIM L$(4)
20 L$(1)="xyhhhhhhhhhhhhhhhhhhh`a`a`apppp"
30 L$(2)="xzhhhhhhhhhhhhhhhhhhhbcbcbcpppq"
40 L$(3)="xyhhhhhhhhhhhhhhhhhhhdededepppp"
50 L$(4)="xzhhhhhhhhhhhhhhhhhhhfgfgfgpppq"
60 LN=1
70 BLOAD"PAT.DAT",S
80 BLOAD"COL.DAT",S
90 BLOAD"SCROLLVU.BIN"
100 DEFUSR=&HC000
110 X$=USR(L$(LN))
120 LN=LN+1 : IF LN>4 THEN LN=1
130 FOR T=1 TO 50 : NEXT
140 GOTO 110
Nota: observe na linha 110 que devemos retornar USR em uma variável alfanumérica, que no caso é X$. Se não for assim, retorna o erro "Type mismatch".
Não se preocupe em converter os códigos em Assembly, pois todos eles já estão prontos no pacote de Copacabana. São eles:
- scrollvd - Scroll vertical para baixo.
- scrollvu - Scroll vertical para cima.
- scrollvl - Scroll horizontal para a esquerda.
- scrollvr - Scroll horizontal para a direita.
Para baixo, devemos modificar as seguintes linhas em Assembly:
C001 21 00 18 30 LD HL,&H1800 ; Endereço inicial da VRAM
...
C010 11 20 18 80 LD DE,&H1820 ; Endereço inicial da VRAM
...
C01F 11 00 18 170 LD DE,&H1800 ; Endereco incial da VRAM
E essas em Basic:
90 BLOAD"SCROLLVU.BIN"
...
120 LN=LN-1 : IF LN<1 THEN LN=4
Esse código é otimizado para o movimento vertical. Para o movimento horizontal, é necessário processar caractere por caractere. Isso se deve ao fato de que uma coluna de dados não ocupa posições contíguas de memória como uma linha.
O código em Assembly do scroll completo horizontal para a direita é apresentado a seguir.
End Código Linha Instrução Comentários
--------------------------------------------------------
C000 10 ORG &HC000
C000 D5 20 PUSH DE
C001 21 00 18 30 LD HL,&H1800
C004 11 00 C2 40 LD DE,&HC200
C007 01 00 03 50 LD BC,&H300 ; Move a tela toda
C00A CD 59 00 60 CALL &H59
C00D 21 00 C2 70 LD HL,&HC200
C010 11 01 18 80 LD DE,&H1801
C013 01 00 03 90 LD BC,&H300
C016 CD 5C 00 100 CALL &H5C
C019 E1 110 POP HL
C01A 23 120 INC HL
C01B 5E 130 LD E,(HL)
C01C 23 140 INC HL
C01D 56 150 LD D,(HL) ; Pos inicial na RAM
C01E 06 18 160 LD B,&H18 ; Total 24 caracteres
C020 21 00 18 170 LD HL,&H1800 ; Pos inicial VRAM
C023 EB 180 LOOP: EX DE,HL
C024 7E 190 LD A,(HL) ; Carrega caractere
C025 EB 200 EX DE,HL
C026 CD 4D 00 210 CALL &H4D ; Escreve byte na VRAM
C029 13 220 INC DE ; Próximo caractere
C02A C5 230 PUSH BC ; Salva BC
C02B 01 20 00 240 LD BC,&H20 ; Tamanho do salto
C02E 09 250 ADD HL,BC ; Próxima linha
C02F C1 260 POP BC ; Recupera BC
C030 10 F1 270 DJNZ LOOP
C032 C9 280 RET
Para movera tela para a esquerda, façamos as seguintes alterações:
C001 21 01 18 30 LD HL,&H1801
...
C010 11 00 18 80 LD DE,&H1800
...
C020 21 1F 18 170 LD HL,&H181F ; Pos inicial VRAM
Por fim, um pequeno teste em Basic.
10 SCREEN 1
20 BLOAD"scrollhr.bin"
30 DEFUSR=&HC000
40 A=RND(-TIME)*50+33
50 A$=STRING$(24,CHR$(A))
60 X$=USR(A$)
70 GOTO 40