SCREEN 4 e SCROLL4: o modo tile do MSX 2
O V9938 nao tem scroll horizontal continuo. Em modo tile, porem, existe um caminho quase gratuito: R#18 faz o sub-pixel, uma name table dupla faz o passo de 8 pixels. E o truque do Space Manbow.
1. Por que SCREEN 4
O SCREEN 5 e bonito, mas e bitmap: mover a imagem inteira custa blitter, e o blitter nao cabe no VBlank. O SCREEN 4 (modo Graphic 3 do V9938) mantem o motor de tiles do SCREEN 2 — mesmas tres zonas de 256 padroes, mesma Pattern Table, mesma Color Table — e acrescenta duas coisas do MSX 2:
- paleta programavel de 512 cores (
PALETTE); - sprite mode 2: 8 sprites por linha e uma cor por linha de sprite.
Como a tela e descrita por uma name table de bytes, rolar a imagem significa reescrever bytes de indice — nao pixels. E ai o custo despenca.
2. Ativando o modo
USES SCREEN 4, TILES, SCROLL4, PALETTE
SCREEN 4
CLS
Tudo o que voce ja sabe do SCREEN 2 continua valendo: TILECOLOR zona, fg, bg
antes de TILECREATE, indices id, id+256 e id+512 para as tres zonas
verticais, SPRITE, SPRITECOLOR, BPRINT.
BPRINT em SCREEN 4
BPRINT col, lin, texto, cor[, fundo] usa o mesmo caminho por tiles do
SCREEN 2: a fonte 8x8 e carregada uma vez por zona vertical nos padroes
128..223 e cada caractere custa um byte na Name Table. Declare
USES SCREEN 4, BPRINT.
USES SCREEN 4, BPRINT
SCREEN 4
CLS
BPRINT 4, 2, "SCORE", 15
BPRINT 12, 2, pontos, 10
Com o SCROLL4 ativo o texto e escrito sempre na name table visivel
(o runtime acompanha a troca dupla de tabelas). Como a tabela escondida e
reconstruida a partir do mapa, um texto fixo deve ser reimpresso a cada
quadro, ou colocado em linhas que o mapa nao reconstroi.
3. PALETTE
PALETTE indice, r, g, b
indice vai de 0 a 15; r, g e b vao de 0 a 7 (3 bits por componente,
512 combinacoes). A mudanca e imediata e afeta tudo que ja esta na tela — util
para fade e para piscar um inimigo sem tocar na VRAM.
PALETTE 4, 1, 1, 5 ' azul noite
PALETTE 15, 7, 7, 7 ' branco puro
4. SCROLL4 — a arquitetura
O V9938 so tem scroll de hardware no eixo vertical. O SCROLL4 explora isso ao maximo e resolve o horizontal com dois mecanismos que nao custam VRAM:
- Vertical — registrador R#23. A name table e montada com 32 linhas
(1024 bytes) e o hardware envolve sozinho a cada 256 linhas. Custo: 1
OUTpor frame. Quando a camera cruza um limite de 8 pixels, uma unica linha de 32 bytes e repintada fora de vista. - Horizontal sub-pixel — registrador R#18 (Display Adjust). Ele
desloca a imagem de 0 a 7 pixels para a esquerda. Custo: 1
OUT. Zero VRAM. - Horizontal passo de 8 px — duas name tables: a visivel em
$1800e uma escondida em$4000. Enquanto o R#18 percorre os 8 sub-pixels, o runtime reconstroi a tabela escondida ja deslocada de uma coluna, em fatias de poucas colunas porTICK. Quando o oitavo pixel chega, o VDP simplesmente troca de tabela mudando R#2. Nada e copiado na tela: nao ha tearing nem pico de CPU.
E exatamente a divisao de trabalho que o Space Manbow usa.
5. API
| Comando | Efeito |
|---|---|
SCROLL4 MAP mapa, largura, altura | Define o mapa de tiles (array BYTE ou label ASM) em tiles. Largura minima 32; altura recomendada 32. Zera a camera e desenha as duas name tables. |
SCROLL4 HSCROLL v | Velocidade horizontal em pixels por TICK (signed). 0 desativa. |
SCROLL4 VSCROLL v | Velocidade vertical em pixels por TICK (signed), via R#23. |
SCROLL4 MASK ON[, tile] / SCROLL4 MASK OFF | ON cobre as duas colunas de cada borda (0, 1, 30 e 31) com tile — que por padrao e o tile 0. |
SCROLL4 MASK ROW linhaIni, linhaFim, tile | Define uma regiao de mascara: nas linhas do mapa de linhaIni a linhaFim a borda usa tile em vez do tile padrao. Ate 4 regioes. Liga a mascara automaticamente. |
SCROLL4 MASK CLEAR | Remove todas as regioes; a borda volta a usar so o tile padrao. |
SCROLL4 MASK SPRITE ON[, cor] / SCROLL4 MASK SPRITE OFF | Cortina de sprites nas duas bordas (16 px de cada lado), com granularidade de 1 pixel. Desliga a mascara por tiles. |
SCROLL4 TICK | Avanca a camera, adianta a tabela escondida e atualiza R#18/R#23. Chame uma vez por frame. |
SCROLL4 STOP | Para o scroll e zera R#18 e R#23. |
SCROLL4SUB() | Funcao: devolve o sub-pixel atual da camera (0..7). |
Sprites parados enquanto o cenario desliza
O R#18 desloca toda a imagem — sprites inclusive. Um sprite com X fixo
parece andar 1 pixel para tras a cada frame e saltar 8 pixels quando a name
table troca. A cura e somar o sub-pixel ao X:
sp = SCROLL4SUB()
SPRITE 0, px + sp, py, 0, 15
Com isso px passa a ser a posicao na tela fisica: se voce quiser que o
objeto seja arrastado pela camera (um destroco a deriva), diminua px na
mesma velocidade do HSCROLL.
Enquanto o SCROLL4 esta ativo, os comandos SPRITE nao escrevem direto na
tabela de atributos: eles gravam num shadow em RAM que o TICK descarrega
inteiro no inicio do VBlank, junto com R#18/R#23 — e ainda corrige X e Y pelo
quanto a camera andou desde a escrita. E isso que elimina o rastro e o
"salta para a frente e volta" de sprites atualizados no meio do quadro.
Como o shadow SAT funciona por dentro
O MSX nao tem nada parecido com isso: nem o VDP nem a BIOS oferecem "SAT dupla" ou escrita sincronizada. O shadow SAT e uma construcao do runtime do CrossScript, gerada em Z80 junto com o seu programa.
O problema que ele resolve. A SAT (Sprite Attribute Table) e lida pelo VDP
enquanto o raster desenha. Escrever nela no meio do quadro significa que a
metade de cima da tela ja saiu com a posicao antiga e a de baixo sai com a
nova — dai o rastro. Pior: o TICK atualiza R#18 e R#23 no primeiro
microssegundo do frame, entao o cenario ja andou 1 pixel enquanto o
sprite so vai andar quando o seu SPRITE rodar, alguns milissegundos
depois. O olho le isso como "a nave e empurrada para tras e depois salta para
a frente".
O que o runtime faz:
SPRITE n, x, y, ...comSCROLL4ativo nao toca na VRAM. Grava os 4 bytes do plano num buffer de 32 bytes em RAM (o shadow), junto com o sub-pixel e oCAMYdo instante da escrita.SCROLL4 TICK, na primeira coisa do frame, escreve R#23, R#18 e R#2.- Logo em seguida — ainda dentro do VBlank, com o raster fora da area visivel — despeja o shadow inteiro na SAT de uma vez, aplicando a diferenca entre a camera de agora e a camera registrada na escrita.
O passo 3 e o que importa: os 32 bytes viram uma escrita atomica do ponto de vista do raster. Ou o quadro sai todo com o estado antigo, ou todo com o novo — nunca meio a meio. E como a correcao de camera e aplicada no mesmo instante em que os registradores de scroll mudam, sprite e cenario andam exatamente juntos.
Na pratica isso significa que voce pode chamar SPRITE onde quiser no seu
laco — no meio da fisica, dentro de um IF, depois da colisao — sem pensar em
VBlank. O custo e uma copia de 32 bytes por frame.
O mapa envolve sozinho nos dois eixos: ao passar do ultimo tile ele volta ao primeiro, entao um mapa de 64x32 gera um mundo circular de 512x256 pixels.
6. Exemplo minimo
USES SCREEN 4, TILES, SCROLL4, PALETTE
DIM MAPA(2047) AS BYTE
DIM i AS WORD
SCREEN 4
CLS
PALETTE 5, 2, 3, 7
TILECOLOR 0, 5, 4
TILECREATE 1, $00,$00,$00,$00,$00,$00,$00,$00
TILECOLOR 0, 15, 4
TILECREATE 2, $01,$03,$07,$0F,$1F,$3F,$7F,$FF
FOR i = 0 TO 2047
MAPA(i) = 1
NEXT i
SCROLL4 MAP MAPA, 64, 32
SCROLL4 HSCROLL 1
WHILE 1
SCROLL4 TICK
WAIT
WEND
O exemplo completo, com montanhas e chao, esta em SCROLL4 fino S4 (MSX 2) na lista de exemplos da IDE.
7. Regras de ouro
- Um
TICKpor frame. Chame-o do laco principal, seguido deWAIT. Nao coloqueWAITdentro da rotina de VBlank. - Defina os tiles nas tres zonas. O mapa rola verticalmente por toda a tela; um tile que so existe na zona 0 aparece em branco nas outras.
- O
TICKescreve os registradores primeiro. R#23, R#18 e R#2 sao atualizados nos primeiros microssegundos do frame, antes de qualquer reconstrucao de tabela. Se essas escritas caissem no meio do quadro, o raster ja teria desenhado o topo da tela com os valores do frame anterior: apareceria uma linha de corte na altura da escrita e o topo pareceria andar de 8 em 8 pixels. Por isso oTICKdeve ser a primeira coisa do laco, logo apos oWAIT. - Velocidades altas custam. A tabela escondida precisa ficar pronta um
frame antes da troca, entao o runtime reconstroi
ceil(32 x |v| / (8 - |v|))linhas porTICK— 5 linhas com velocidade 1, 11 com 2, 20 com 3, 32 acima disso. A carga e a mesma em todos os frames, inclusive no da troca, e e isso que elimina o solavanco periodico. - Sprites nao sao afetados por R#18 e R#23 da mesma forma que o fundo — ajuste a posicao do jogador manualmente se quiser que ele acompanhe a camera.
- A tela mostra 24 linhas de tiles. O mapa tem 32 linhas porque o R#23
envolve em 256 pixels, mas com a camera em
Y = 0so as linhas 0 a 23 aparecem. Cenario desenhado nas linhas 24 a 31 fica invisivel — e a causa mais comum de "tela de uma cor so".
8. Escondendo a borda lateral
Scroll diagonal
Como o eixo vertical e o horizontal sao independentes, basta manter HSCROLL
ligado e mudar VSCROLL conforme o comando do jogador para obter diagonal:
d = STICK(0)
vs = 0
IF d = 4 THEN vs = 1 ' descendo: o chao se aproxima
IF d = 6 THEN vs = 1
IF d = 2 THEN vs = 255 ' subindo (255 = -1): o ceu se aproxima
IF d = 8 THEN vs = 255
SCROLL4 VSCROLL vs
Na pratica convem limitar o curso vertical a 2 ou 3 tiles e travar: assim o jogador sente o movimento sem que a camera fuja do cenario. Basta contar o deslocamento acumulado e zerar a velocidade nos extremos:
cy = 24 ' centro; curso util de 0 a 48 px (3 tiles)
...
IF vs = 1 THEN
IF cy < 48 THEN cy = cy + 1 ELSE vs = 0
ENDIF
IF vs = 255 THEN
IF cy > 0 THEN cy = cy - 1 ELSE vs = 0
ENDIF
SCROLL4 VSCROLL vs
O vertical e R#23: custo zero de CPU, sempre 60 fps. A pergunta natural — "preciso de mais tiles na parte invisivel da tela?" — tem resposta objetiva: sim, as linhas 24 a 31 do mapa precisam estar desenhadas. A tela mostra 24 linhas, o R#23 envolve a cada 256 pixels (32 linhas), entao tudo que entra pela borda quando a camera desce vem dessas 8 linhas. Se elas ficarem vazias, o cenario "acaba" numa faixa de uma cor so e volta a aparecer depois.
Trate o mapa como um cilindro vertical: o que estiver na linha 31 encosta na linha 0. Voce pode usar as linhas invisiveis como cenarios diferentes. Um layout de shooter que funciona bem e:
' 64x32 tiles
' 0..19 espaco visivel
' 20..23 horizonte/solo visivel
' 24..26 cenario de baixo: planeta, solo, predios
' 27..28 transicao/espaco
' 29..31 cenario de cima: nuvens, nebulosa, teto
Quando a camera desce, entram as linhas 24..26 pela base; quando sobe, o wrap do R#23 traz as linhas 31..29 pelo topo. Como o curso vertical costuma ser limitado a 2 ou 3 tiles, voce nao precisa desenhar outro mapa inteiro: basta preencher essas faixas invisiveis com a arte que deve aparecer no mergulho ou na subida.
O tile que entra na tela aparece primeiro pela metade, porque o R#18 arrasta a imagem inteira ate 7 pixels. Por isso a lateral "pisca" com meio tile. A cura e a mascara:
SCROLL4 MASK ON, 1 ' cobre as colunas 0, 1, 30 e 31 com o tile 1
Duas colunas de cada lado sao necessarias: com apenas uma, o deslocamento
sub-pixel deixa escapar ate 7 pixels do tile seguinte. O tile da mascara deve
ser um tile solido, na cor do fundo (ou preto) — se voce passar um tile que
nao existe, ou um tile de cenario, a borda continua aparecendo. Use
TILECOLOR/TILECREATE para definir esse tile nas tres zonas se o mapa
rolar verticalmente.
A area util passa a ser de 28 colunas (224 pixels). Se quiser a tela cheia sem mascara, o caminho e desenhar o cenario de modo que as bordas sejam sempre uniformes (ceu ou chao continuo), e nao objetos com contorno.
Mascara por regiao
Um tile unico so funciona quando a tela inteira tem a mesma cor de fundo. Com
ceu em cima e chao verde embaixo, a borda precisa mudar de cor na altura do
horizonte — e para isso existe SCROLL4 MASK ROW:
SCROLL4 MASK ON, 1 ' tile padrao da borda: ceu
SCROLL4 MASK ROW 19, 31, 3 ' linhas 19..31 do mapa: borda com o tile de chao
As linhas sao linhas do mapa (0 ate altura-1), nao linhas de tela: a
mascara acompanha a camera quando o mapa rola na vertical. Sao permitidas ate
4 regioes; linhas fora de qualquer regiao usam o tile de MASK ON. Regioes
sobrepostas resolvem pela primeira declarada. SCROLL4 MASK CLEAR zera a
lista.
Cortina de sprites — entrada e saida pixel a pixel
A mascara por tiles tem um limite honesto: ela e feita de tiles, entao ela anda junto com o cenario. Quando o R#18 completa os 8 sub-pixels e a name table troca, o tile escondido reaparece ja inteiro — e a impressao e a de um tile "pulando" para dentro da tela.
SCROLL4 MASK SPRITE ON, cor resolve isso. A borda passa a ser desenhada por
24 sprites solidos (12 de cada lado, 16 px de largura), e a cada TICK o
runtime soma o sub-pixel atual ao X de cada sprite. Como o R#18 desloca a
tela inteira para a esquerda e o X do sprite tem precisao de 1 pixel, a
cortina fica parada na tela fisica enquanto o cenario desliza por baixo
dela: o tile entra e sai um pixel por vez.
SCROLL4 MASK SPRITE ON, 1 ' cortina na cor 1 (preto)
...
SCROLL4 MASK SPRITE OFF
O que voce precisa saber:
- Ela usa os planos de sprite 0 a 23 e o padrao 252, ficando acima de todos
os sprites do jogo. Os seus comandos
SPRITE 0..7/SPRITECOLOR 0..7sao remapeados automaticamente para os planos fisicos 24..31 — assim o heroi, inimigos e tiros passam por tras da cortina quando encostam na borda. - Liga sprites 16x16 sem magnificacao (R#1).
- Pinta a borda da tela (backdrop, R#7) com a cor da cortina, e restaura a
cor anterior no
OFF. Isso e necessario porque o R#18 empurra ate 7 pixels de cenario para dentro da borda esquerda e deixa 7 pixels de backdrop na direita; com a borda na cor da cortina as duas faixas ficam continuas e imoveis. - A cortina esquerda usa o bit EC dos sprites: ela cobre de -8 a +7, ou seja, entra na borda para esconder o cenario que vaza por ali.
- Desliga a mascara por tiles automaticamente; as duas nao devem conviver.
- A area util fica em torno de 232 pixels, e a transicao e suave.
SCROLL4 HUD: placar fixo no topo (tecnica do Space Manbow)
O V9938 nao tem "janela de scroll": o R#23 (vertical) e o R#18 (sub-pixel horizontal) valem para a tela inteira. Se voce imprimir um placar na primeira linha, ele rola junto com o cenario.
O Space Manbow resolveu isso com split screen por interrupcao de linha — e o CrossScript embrulha a tecnica em um comando:
SCROLL4 HUD 3 ' congela as 3 primeiras linhas de tile (24 px)
SCROLL4 HUD 0 ' volta ao scroll de tela cheia
Como funciona por dentro
- O runtime programa o R#19 com a scanline
N*8 - 1. O VDP passa a ligar o flag FH (bit 0 do S#1) no instante em que o raster chega ao fim do HUD. - O
SCROLL4 TICK, chamado logo apos oWAIT— ou seja, no fim do VBlank — escreve primeiroR#23 = 0eR#18sem sub-pixel e limpa o FH: o topo ja comeca a desenhar parado. Isso e barato (poucosOUT) e nao espera nada. Em seguida oTICKfaz o trabalho que precisa do VBlank — descarga do shadow SAT e troca de name table (R#2) — e so entao entra na espera do FH. Se a espera viesse antes, osN*8primeiros scanlines seriam gastos com as interrupcoes desligadas e a SAT acabaria sendo escrita com o raster ja na area visivel: o resultado e tela piscando e sprites deformados. - Quando o FH acende, o raster acabou de terminar a ultima linha do placar. O
runtime escreve
R#23 = camYeR#18 = sub-pixelali mesmo, dentro do horizontal blanking, e devolve o ponteiro de status para o S#0. Resultado: as N primeiras linhas desenham paradas e, a partir dali, a tela rola normalmente. - A espera e feita por polling do FH, e nao por interrupcao de linha. A
interrupcao de linha (bit IE1 do R#0) so chega ao programa atraves do hook
H.KEYI, e nem toda BIOS ou emulador a entrega por esse caminho — quando
nao entrega, o split simplesmente nunca acontece e o placar rola junto com o
cenario, sem nenhuma mensagem de erro. O flag FH, ao contrario, e ligado
pelo VDP em qualquer maquina. O custo do polling e a espera das
N*8primeiras linhas: cerca de 8% do quadro comN = 3. Ha um timeout na espera, entao um FH que nunca acenda nao trava o jogo. - Como o contador que o R#19 compara e o de linhas de VRAM, e nao o de
linhas de tela, o R#19 e reprogramado a cada frame no momento em que o
R#23vale 0 — so assim a linha do split casa com a linha do placar. - As linhas
0..N-1da name table viram area reservada: o reconstrutor de colunas/linhas nunca as toca, e o runtime espelha o HUD para a name table escondida a cada frame — sem isso o placar piscaria a cada troca de banco (R#2). - Todo par de bytes de endereco de VRAM e escrito com as interrupcoes
desligadas, para que uma interrupcao no meio do par nao embaralhe o latch
da porta
$99.
Regras de uso
Nvai de 0 a 4 linhas de tile (ate 32 pixels de placar).- Desenhe o conteudo do HUD nas linhas 0..N-1 do seu mapa, antes do
SCROLL4 MAP. Depois disso elas ficam congeladas: para atualizar o placar, escreva direto na name table (VPOKE) ou redesenhe o mapa. SCROLL4 STOPdesliga o split e devolve o scroll de tela cheia.- A cortina de sprites comeca logo abaixo da faixa do HUD: como o topo nao rola, nao ha nada para esconder ali e o placar ocupa a largura inteira da tela.
SCROLL4 HUD 0repinta as duas name tables: as linhas antes congeladas voltam a receber o cenario, sem deixar restos do placar no topo.- A espera do split roda com as interrupcoes desligadas. Isso atrasa em poucos milissegundos o tratamento de VBlank da BIOS, sem efeito pratico sobre teclado ou som.
Exemplo prático: KILL BALL
O exemplo KILL BALL (jogo completo SCROLL4) reúne as técnicas deste capítulo em um jogo curto: abertura, fase normal, placar, chefe, vitória e GAME OVER. Ele está na lista Samples da IDE. A seguir, o programa é percorrido na ordem em que aparece no código, trecho por trecho.
1. Declarações e variáveis
USES SCREEN 4, SCREEN 5, TILES, SCROLL4, SPRITES, PALETTE, KEYBOARD, SOUND, BPRINT
DIM MAPA(2047) AS BYTE
DIM SN(31) AS BYTE
DIM EX(3) AS BYTE ' X dos inimigos / partes do chefe
DIM EF(3) AS BYTE ' fase da onda / orbita
DIM EB(3) AS BYTE ' altura base
DIM EY(3) AS BYTE ' Y atual
DIM EA(3) AS BYTE ' inimigo comum ativo
DIM EH(3) AS BYTE ' chefe: energia (centro=1, protecoes=3)
DIM ET(3) AS BYTE ' animacao de explosao
DIM BX(2) AS BYTE ' tiros: X
DIM BY(2) AS BYTE ' tiros: Y
DIM BA(2) AS BYTE ' tiros ativos
' ... variaveis simples (px, py, score, hisc, lives, boss, bt...)
hisc = 5000
POKE $F3DB, 0 ' desliga o clique das teclas (CLIKSW)
USES informa ao compilador quais partes do motor entram na ROM; só o que é
declarado ocupa espaço. MAPA guarda o cenário circular de 64×32 tiles. SN
é uma tabela de seno com 32 passos, usada tanto pela onda dos inimigos quanto
pela órbita do chefe. Os vetores E.. descrevem os quatro inimigos (que depois
viram as quatro partes do chefe), e os vetores B.. descrevem os três tiros.
hisc recebe o recorde inicial uma única vez, fora da partida, por isso ele
sobrevive ao GAME OVER. POKE $F3DB, 0 zera a variável de sistema CLIKSW
e silencia o clique do teclado do MSX.
2. Tela de abertura
TitleScreen:
SCREEN 5
COLOR 15, 4, 4
CLS
PALETTE 4, 0, 0, 2
...
BPRINT SIZE 3
BPRINT 2, 7, "KILL BALL", 10
BPRINT SIZE 1
BPRINT 8, 19, "PRESSIONE TIRO", 15
WHILE STRIG(0) = 0
WAIT
WEND
A abertura usa SCREEN 5 porque BPRINT SIZE só amplia texto em modos bitmap.
SIZE 3 triplica a largura e a altura das letras e forma o logotipo sem
nenhuma imagem pronta; SIZE 1 volta ao tamanho normal para a mensagem. O
WHILE espera o disparo, e WAIT libera um quadro por volta para não
consumir a CPU à toa.
3. Preparação da partida: tela, cores e tiles
StartGame:
SCREEN 4
SPRITESIZE 1
SPRITEMAG 0
PALETTE 5, 1, 1, 5
...
TILECOLOR 0, 5, 4
TILECREATE 1, $00,$00,$00,$00,$00,$00,$00,$00
TILECREATE 2, $00,$00,$00,$10,$00,$00,$00,$00
TILECOLOR 0, 12, 4
TILECREATE 3, $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
TILECOLOR 0, 11, 4
TILECREATE 4, $18,$3C,$7E,$FF,$E7,$C3,$81,$00
...
TILECOLOR 0, 11, 4
TILECREATE 16, $3C,$66,$6E,$76,$66,$66,$3C,$00 ' digito 0
...
SCREEN 4 reorganiza a VRAM para o modo de tiles que o SCROLL4 usa.
SPRITESIZE 1 escolhe sprites 16×16 sem ampliação. Cada TILECOLOR define a
cor de frente e de fundo dos tiles criados em seguida; cada TILECREATE recebe
as oito linhas do desenho em hexadecimal (um bit = um pixel).
| Tile | Uso |
|---|---|
1 | céu vazio |
2 | estrela pequena e isolada |
3 | solo sólido |
4 | contorno das montanhas |
5, 6 | peças decorativas |
7 | régua abaixo do placar |
16–25 | dígitos 0 a 9 do placar |
26–33 | letras S C R E H I L V do placar |
Para trocar a arte, abra o Tile Editor e redesenhe o tile com o mesmo número: o mapa continua válido.
4. Montagem do mapa
FOR i = 0 TO 2047
y = i / 64
MAPA(i) = 1
IF y > 20 THEN MAPA(i) = 3
NEXT i
FOR n = 0 TO 63
d = n AND 7
IF d = 1 THEN
y = ((n * 3) AND 15) + 4
MAPA(y*64+n) = 2
ENDIF
IF d > 3 THEN d = 7 - d
y = 19 - d
MAPA(y*64+n) = 4
NEXT n
FOR n = 0 TO 63
MAPA(n) = 1
MAPA(64 + n) = 1
MAPA(128 + n) = 7
NEXT n
MAPA(65)=26 : MAPA(66)=27 : MAPA(67)=16 : MAPA(68)=28 : MAPA(69)=29
MAPA(77)=30 : MAPA(78)=31
MAPA(86)=32 : MAPA(87)=31 : MAPA(88)=33 : MAPA(89)=29 : MAPA(90)=26
O índice de uma célula é linha * 64 + coluna. O primeiro laço pinta tudo de
céu e as linhas abaixo da 20 de solo. O segundo percorre as 64 colunas:
n AND 7 repete um ciclo de oito colunas; em uma delas cai uma estrela, em
altura variável. O mesmo valor, espelhado (7 - d), sobe e desce a linha das
montanhas, formando picos largos. O terceiro laço limpa as linhas 0 e 1 e
coloca a régua na linha 2. Por fim, os tiles de letras escrevem
SCORE, HI e LIVES na linha 1.
5. Tabela de seno, sprites e estado inicial
SN(0)=24 : SN(1)=29 : SN(2)=33 : SN(3)=38
...
SPRITECREATE 0, 0,0,0,3,15,63,255,255
...
SPRITECOLOR 0, 15
...
px=32 : py=88 : cd=0 : hit=0
score=0 : lives=3
boss=0 : bf=0 : hud=1
EX(0)=240 : EF(0)=0 : EB(0)=40
...
FOR n = 0 TO 3
EA(n)=1 : EH(n)=0 : ET(n)=0 : EY(n)=EB(n)
NEXT n
FOR n = 0 TO 2
BA(n)=0
SPRITE 5+n, 0, 200, 8, 11
NEXT n
GOSUB DrawHud
SN vai de 0 a 48 e volta: somar esse valor a uma altura base produz uma
onda suave sem cálculo de ponto flutuante. Cada figura 16×16 é formada por
quatro SPRITECREATE de 8×8: 0–3 nave, 4–7 bola, 8–11 tiro e 12–15 explosão.
SPRITECOLOR pinta cada plano lógico.
Em seguida a partida é zerada: posição da nave, pontos, três vidas, quatro
inimigos com posições e fases diferentes (para não andarem juntos) e os três
tiros escondidos em Y=200, fora da tela. DrawHud escreve os números.
6. Ligando o motor de scroll
SCROLL4 MAP MAPA, 64, 32
SCROLL4 HUD 3
SCROLL4 MASK SPRITE ON, 1
SCROLL4 HSCROLL 1
SCROLL4 VSCROLL 0
hud=0
MAP entrega o mapa ao motor. HUD 3 deixa as três primeiras linhas paradas
como placar. MASK SPRITE ON cria a cortina lateral com sprites, que esconde
a borda onde os tiles novos entram; ela ocupa 24 dos 32 planos, sobrando oito
planos lógicos para o jogo. HSCROLL 1 rola um pixel por quadro para a
esquerda e VSCROLL 0 mantém a tela sem rolagem vertical.
7. Laço principal: quadro e nave
WHILE 1
SCROLL4 FRAME
SCROLL4 TICK
sp=SCROLL4SUB()
d=STICK(0)
IF px>8 THEN px=px-1
IF d=1 THEN py=py-2
...
IF py<24 THEN py=24
IF py>150 THEN py=150
IF px<8 THEN px=8
IF px>224 THEN px=224
SPRITE 0, px+sp, py, 0, 15
SCROLL4 FRAME espera um quadro novo; SCROLL4 TICK avança a rolagem.
SCROLL4SUB() devolve o deslocamento fino (0 a 7 pixels) do quadro atual.
Todo sprite recebe +sp na coordenada X para ficar alinhado com o cenário
enquanto o VDP desloca a tela.
STICK(0) retorna a direção (1 = cima, no sentido horário até 8). A nave é
sempre puxada um pixel para a esquerda, como se o cenário a empurrasse; as
setas vencem essa corrente. Os limites mantêm a nave abaixo do placar e dentro
da tela.
8. Som das explosões e tiros
IF cd>0 THEN cd=cd-1
IF bt>0 THEN
bt=bt-1
SOUND NOISE 5+(bt AND 7), bt
ENDIF
IF STRIG(0)<>0 THEN
IF cd=0 THEN
FOR n=0 TO 2
IF BA(n)=0 THEN
IF cd=0 THEN
BA(n)=1 : BX(n)=px+12 : BY(n)=py+4
cd=10
ENDIF
ENDIF
NEXT n
ENDIF
ENDIF
FOR n=0 TO 2
IF BA(n)=1 THEN
IF BX(n)>236 THEN
BA(n)=0
SPRITE 5+n, 0, 200, 8, 11
ELSE
BX(n)=BX(n)+6
SPRITE 5+n, BX(n)+sp, BY(n), 8, 11
ENDIF
ENDIF
NEXT n
bt conta quantos passos faltam no som de explosão. A cada quadro ele desce
um, o ruído muda de tom (5 + (bt AND 7)) e o volume cai junto com bt até
chegar a zero. Como é apenas um passo por volta do laço, o jogo nunca para
para tocar o som, e o último passo sempre deixa o canal em silêncio.
cd é o intervalo mínimo entre disparos (10 quadros). Os tiros formam um
pool de três posições fixas: ao disparar, o programa procura um lugar livre
(BA(n)=0) e o ocupa na frente da nave. Cada tiro ativo anda 6 pixels por
quadro e é escondido ao passar da borda direita.
9. Fase normal: inimigos em onda
IF boss=0 THEN
FOR n=0 TO 3
IF ET(n)>0 THEN
ET(n)=ET(n)-1
SPRITE 1+n, EX(n)+sp, EY(n), 12, 10
IF ET(n)=0 THEN
EX(n)=248 : EB(n)=24+n*16 : EA(n)=1
ENDIF
ELSE
IF EX(n)<4 THEN EX(n)=248 ELSE EX(n)=EX(n)-2
EF(n)=(EF(n)+1) AND 31
EY(n)=EB(n)+SN(EF(n))
SPRITE 1+n, EX(n)+sp, EY(n), 4, 8+n
ENDIF
NEXT n
boss separa as duas fases dentro do mesmo laço. Se o inimigo está explodindo
(ET(n)>0), mostra o desenho da explosão até o contador zerar e então o
recoloca na borda direita. Caso contrário, ele anda 2 pixels para a esquerda,
avança a fase EF(n) (o AND 31 faz a volta de 31 para 0) e calcula a altura
somando o seno à altura base.
10. Tiro contra inimigo e entrada do chefe
FOR n=0 TO 3
IF EA(n)=1 THEN
FOR i=0 TO 2
IF BA(i)=1 THEN
IF BX(i)>EX(n) THEN dx=BX(i)-EX(n) ELSE dx=EX(n)-BX(i)
IF BY(i)>EY(n) THEN dy=BY(i)-EY(n) ELSE dy=EY(n)-BY(i)
IF dx<12 THEN
IF dy<12 THEN
EA(n)=0 : ET(n)=12 : BA(i)=0
GOSUB SfxBoom
SPRITE 5+i, 0, 200, 8, 11
score=score+100
IF score>hisc THEN hisc=score
hud=1
ENDIF
ENDIF
ENDIF
NEXT i
ENDIF
NEXT n
IF score>=2000 THEN
boss=1 : bf=0
EX(0)=184 : EY(0)=88 : EH(0)=1 : ET(0)=0
EH(1)=3 : EH(2)=3 : EH(3)=3
EF(1)=0 : EF(2)=10 : EF(3)=21
FOR n=0 TO 3
EA(n)=0 : ET(n)=0
NEXT n
ENDIF
A colisão é uma caixa: calcula a distância positiva em X e em Y e, se as duas
forem menores que 12 pixels, houve acerto. Não usamos COLLISION() porque a
cortina também é feita de sprites e mantém a flag de colisão do VDP sempre
ligada. No acerto, o inimigo passa a explodir, o tiro some, o som começa
(SfxBoom), a pontuação sobe e hud=1 pede atualização do placar.
Aos 2.000 pontos, boss=1. Os mesmos quatro planos viram o chefe: o 0 é o
núcleo com 1 de energia, e os planos 1 a 3 são as proteções com 3 de energia
cada, começando em pontos diferentes da órbita.
11. Chefe: movimento e órbita
ELSE
i=(bf+8) AND 31
EX(0)=166+(SN(i)/4)
EY(0)=68+(SN(bf)/3)
IF (bf AND 2)=0 THEN SPRITECOLOR 1, 15 ELSE SPRITECOLOR 1, 8
SPRITE 1, EX(0)+sp, EY(0), 4, 15
FOR n=1 TO 3
IF EH(n)>0 THEN
EF(n)=(EF(n)+1) AND 31
EX(n)=EX(0)+SN(EF(n))-24
i=(EF(n)+8) AND 31
EY(n)=EY(0)+SN(i)-24
SPRITE 1+n, EX(n)+sp, EY(n), 4, 8+n
ELSE
IF ET(n)>0 THEN
ET(n)=ET(n)-1
SPRITE 1+n, EX(n)+sp, EY(n), 12, 10
ELSE
SPRITE 1+n, 0, 200, 4, 8+n
ENDIF
ENDIF
NEXT n
bf=(bf+1) AND 31
O núcleo oscila usando a mesma tabela de seno: em X com a fase adiantada
8 passos (um quarto de volta) e amplitude reduzida (/4), em Y com amplitude
/3. A diferença de fase faz o movimento desenhar uma curva em vez de uma
linha. A cor alterna entre branco e vermelho a cada dois quadros para piscar.
Cada proteção viva usa seno para X e o seno adiantado um quarto de volta para
Y — isso é um cosseno, e seno + cosseno formam um círculo. Como o círculo é
somado à posição do núcleo (EX(0), EY(0)), a órbita acompanha o chefe.
Proteções destruídas mostram a explosão e depois somem.
12. Chefe: proteções e núcleo
FOR i=0 TO 2
IF BA(i)=1 THEN
FOR n=1 TO 3
IF EH(n)>0 THEN
' ... mesma caixa de colisao (dx<12, dy<12) ...
EH(n)=EH(n)-1 : BA(i)=0
SPRITE 5+i, 0, 200, 8, 11
IF EH(n)=0 THEN
ET(n)=12 : score=score+300 : hud=1
GOSUB SfxBoom
IF score>hisc THEN hisc=score
ENDIF
ENDIF
NEXT n
ENDIF
NEXT i
press=0
FOR n=1 TO 3
IF EH(n)=0 THEN press=1
NEXT n
IF press=1 THEN
FOR i=0 TO 2
IF BA(i)=1 THEN
' ... caixa menor (dx<10, dy<10) contra EX(0), EY(0) ...
EH(0)=0 : BA(i)=0
score=score+1000
IF score>hisc THEN hisc=score
GOTO BossExplosion
ENDIF
NEXT i
ENDIF
ENDIF
Cada tiro que acerta uma proteção tira 1 de energia; no terceiro ela explode,
vale 300 pontos e abre uma brecha. press vira 1 se ao menos uma proteção foi
destruída — só então o núcleo pode ser atingido. A caixa do núcleo é menor
(10 pixels), exigindo mira. O acerto vale 1.000 pontos e salta para a cena
final.
13. Nave atingida, piscar e placar
IF hit>0 THEN hit=hit-1
IF hit=0 THEN
FOR n=0 TO 3
press=0
IF boss=0 THEN press=EA(n)
IF boss=1 THEN press=EH(n)
IF press>0 THEN
' ... caixa de 13 pixels entre a nave e o alvo ...
hit=40 : px=32 : py=88
IF boss=0 THEN EA(n)=0 : ET(n)=12
GOSUB SfxBoom
IF lives>0 THEN lives=lives-1
hud=1
IF lives=0 THEN GOTO GameOver
ENDIF
NEXT n
ENDIF
IF hit>0 THEN
IF (hit AND 2)=0 THEN SPRITECOLOR 0, 15 ELSE SPRITECOLOR 0, 4
ENDIF
IF hud=1 THEN
GOSUB DrawHud
SCROLL4 HUD REFRESH
hud=0
ENDIF
WEND
hit é o tempo de invencibilidade depois de uma batida (40 quadros). Só é
testado contra alvos vivos: inimigos ativos na fase normal ou partes do chefe
com energia. Na batida, a nave volta à posição inicial, a explosão toca, uma
vida é perdida e, se acabaram as vidas, o jogo vai para GameOver. Enquanto
hit conta, a nave alterna entre branco e a cor do fundo, piscando.
O placar só é redesenhado quando algo mudou: DrawHud troca os números no
mapa e SCROLL4 HUD REFRESH copia as três linhas para as duas páginas de
vídeo do motor.
14. Explosão final do chefe
BossExplosion:
SPRITE 0, 0, 200, 0, 15
SPRITE 2, 0, 200, 12, 10
SPRITE 3, 0, 200, 12, 10
SPRITE 4, 0, 200, 12, 10
ExplosionBurst(0)
ExplosionBurst(1)
ExplosionBurst(2)
ExplosionBurst(3)
ExplosionBurst(0)
GOTO Victory
PROCEDURE ExplosionBurst(n AS BYTE)
BEGIN
FOR t=0 TO 15
SCROLL4 FRAME
SCROLL4 TICK
sp=SCROLL4SUB()
SPRITE 1, EX(n)+sp, EY(n), 12, 10
GOSUB BoomStep
NEXT t
SPRITE 1, 0, 200, 12, 10
END
BoomStep:
SOUND NOISE 5+(t AND 7), 15-t
RETURN
A nave e as outras partes são escondidas. Cada ExplosionBurst mostra a
explosão na última posição de uma das partes do chefe durante 16 quadros,
e a última volta ao núcleo. Dentro do laço, SCROLL4 FRAME e SCROLL4 TICK
continuam sendo chamados, então o cenário segue rolando durante toda a cena.
BoomStep faz o ruído mudar de tom e o volume cair de 15 a 0 a cada estouro.
15. Fim de jogo e volta à abertura
GameOver:
ending=0
GOTO FinishGame
Victory:
ending=1
FinishGame:
SCROLL4 STOP
SCROLL4 MASK SPRITE OFF
SCROLL4 HUD 0
SCREEN 4
COLOR 15, 4, 4
CLS
IF ending=0 THEN
BPRINT 11, 10, "GAME OVER", 8
ELSE
BPRINT 5, 10, "KILL BALL DESTRUIDA", 10
ENDIF
BPRINT 8, 19, "PRESSIONE TIRO", 15
WaitAndTitle:
SOUND OFF
WHILE STRIG(0)<>0
WAIT
WEND
WHILE STRIG(0)=0
WAIT
WEND
GOTO TitleScreen
ending escolhe a mensagem, e as duas saídas usam o mesmo código. O motor é
desligado na ordem inversa da ligação (rolagem, cortina e placar), a tela é
reiniciada e a mensagem aparece. SOUND OFF zera todos os canais. O primeiro
WHILE espera o botão ser solto — senão o mesmo toque que matou o chefe
pularia esta tela — e o segundo espera um novo disparo antes de voltar à
abertura.
16. Sub-rotinas de apoio
SfxBoom:
bt=16
RETURN
DrawHud:
t=score : hc=75
GOSUB PutNum
t=hisc : hc=84
GOSUB PutNum
MAPA(92)=16+lives
RETURN
PutNum:
FOR n=0 TO 4
MAPA(hc)=16+(t MOD 10)
t=t/10 : hc=hc-1
NEXT n
RETURN
SfxBoom apenas arma o contador bt; quem toca o som é o laço principal
(trecho 8). DrawHud escreve os pontos a partir da coluna 11 da linha 1
(64 + 11 = 75), o recorde a partir da coluna 20 e as vidas na coluna 28.
PutNum escreve cinco dígitos da direita para a esquerda: t MOD 10 é o
último dígito, 16 + converte o dígito no tile correspondente e t / 10
descarta o dígito já escrito.
Ferramentas da IDE usadas para criar o jogo
As abas de ferramentas podem gerar os dados e inseri-los diretamente no código:
- Sprite Editor (MSX1 to MSX2+) — desenha sprites 8×8, 16×16 e
metasprites 32×32. Mantém até 32 quadros, escolhe a cor de cada quadro,
espelha, gira, amplia e envia
SPRITECREATE/SPRITEpor Apply to Script. É a ferramenta indicada para refazer a nave, as bolas e a explosão. - Import Image → Sprite Editor — aberto por Import dentro do editor de sprites. Converte PNG/JPG em arte monocromática; permite enquadrar, ampliar, girar, espelhar e ajustar brilho, contraste e limiar antes de inserir.
- Tile Editor — desenha padrões 8×8, com duas cores por tile em SCR1 ou duas cores por linha em SCR2/SCREEN 4. Expand to Combo transforma um desenho maior em uma combinação de tiles. Use-o para estrelas, montanhas, chão, régua e caracteres especiais do placar.
- Screen Editor (32x24) — monta o cenário escolhendo tiles como peças de
um mosaico. O alvo SCR 4 (MSX2) exporta um mapa 32×32 pronto para
SCROLL4; Import IMG aproxima uma imagem 256×192 com os tiles carregados. As guias SmoothRollMix ajudam a alinhar faixas de paralaxe. - Paint Editor — cria ou adapta uma imagem e a envia para TILE, SPRITE ou SCR, servindo como ponto de partida para os três editores.
Fluxo recomendado: desenhe os padrões no Tile Editor, envie-os ao Screen Editor, monte o mapa e aplique ao código; depois crie nave, inimigos e efeitos no Sprite Editor. O exemplo KILL BALL deixa os dados gerados próximos do trecho que os utiliza, facilitando trocar a arte sem alterar a lógica.
Parallax por bandas — SCROLL4 BAND
O SCROLL4 HUD prova que da para trocar registradores do VDP no meio do
quadro. O SCROLL4 BAND generaliza essa ideia: em vez de uma troca (para
congelar o topo), o tratador de interrupcao faz ate quatro, cada uma
programando um R#18 diferente. O resultado sao faixas horizontais que rolam
com velocidades distintas — o efeito de profundidade dos shoot'em ups.
SCROLL4 BAND id, linhaIni, linhaFim, divisor
SCROLL4 BANDOFF
| Parametro | Faixa | Significado |
|---|---|---|
id | 0..3 | Indice da banda (declare em ordem, de cima a baixo) |
linhaIni | 0..23 | Primeira linha de tile da banda |
linhaFim | 0..23 | Ultima linha de tile da banda (inclusive) |
divisor | 0,1,2,4,8 | 1 = velocidade mestra, 2 = metade, 4 = um quarto, 8 = um oitavo, 0 = fixa |
Exemplo (o mesmo do sample SCROLL4 Parallax):
SCROLL4 MAP MAPA, 64, 32
SCROLL4 BAND 0, 0, 5, 8 ' ceu, quase parado
SCROLL4 BAND 1, 6, 12, 4 ' montanhas ao fundo
SCROLL4 BAND 2, 13, 20, 1 ' solo, velocidade mestra
SCROLL4 BAND 3, 21, 23, 0 ' painel fixo
SCROLL4 HSCROLL 4
Por que so potencias de dois
A velocidade de cada banda e camX >> shift. Com potencias de dois o passo
grosso (a coluna do mapa) e o passo fino (o sub-pixel do R#18) saem do mesmo
deslocamento de bits: nao ha divisao, nao ha resto e — o mais importante — nao
ha erro acumulado. Uma banda a 3/8 da velocidade exigiria uma multiplicacao
por quadro e, pior, um acumulador que dessincroniza do mestre depois de alguns
milhares de colunas.
O que muda no motor
- Reconstrucao de coluna. O reconstrutor calcula um
colBaseproprio por banda (colBaseMestre >> shift, modulo a largura do mapa) e so redesenha as linhas daquela faixa. Bandas fixas nao sao tocadas — custo zero de CPU. - Interrupcao de linha. A cada quadro o ISR percorre a tabela de bandas:
programa o
R#18da banda corrente, arma oR#19na primeira linha da proxima e volta. Quatro bandas custam quatro interrupcoes de linha. - Cortina de sprites. Cada plano da cortina recebe a compensacao de sub-pixel da banda em que ele esta, de modo que as bordas continuam perfeitamente coladas mesmo com fatias andando em ritmos diferentes.
Regras de uso
- Ate 4 bandas; elas devem ser contiguas e cobrir a tela de cima a baixo.
- Com bandas ativas o scroll vertical fica desligado: as fronteiras entre as faixas sao posicoes fixas da tela, e mover a camera em Y as arrastaria junto.
- O mapa precisa de 32 linhas e largura multipla de 8 tiles, como no scroll normal.
- Uma banda com divisor
0nasce limpa nas duas name tables: desenhe nela o que quiser (placar, painel) comBPRINTouVPOKEdepois de declara-la. SCROLL4 BANDOFFvolta ao scroll de tela cheia e repinta tudo.