Vamos fazer um jogo de MSX – Estrutura e Organização

Algumas semanas atrás, começamos o design do conceito do jogo e adicionamos alguns códigos básicos que resultaram no carregamento do nosso sprite na tela do modo gráfico, o que foi legal e divertido. O problema é que nosso código parece apressado, com muitos endereços de memória codificados e comandos repetidos em todo o lugar. Hoje vamos organizar tudo e garantir que as futuras adições sempre respeitem um padrão de estrutura mínima que nos será familiar nos próximos artigos e facilitará a manutenção do programa quando alguma atualização ou correção for necessária.

Temos que ser inclusivos, sempre

A maioria dos compiladores de montagem suporta o uso da instrução include para adicionar código externo ao arquivo de programa principal, e o IDE 8bitworkshop não é diferente em relação a esse recurso. Para adicionar um arquivo externo ao nosso código, basta escolher onde queremos que a instrução seja adicionada ao código, ir ao menu IDE, selecionar File e Add Include File, depois inserir o nome do arquivo que queremos usar, e finalmente clicar em OK. Vou criar meu primeiro nome de arquivo incluído System_BIOSCalls.asm e, em seguida, mova as rotinas de BIOS existentes para este novo arquivo:

Nosso primeiro arquivo de inclusão

Notaremos que a instrução include é adicionada automaticamente ao código e, no menu do lado esquerdo, encontraremos o novo nome do arquivo exibido na lista:

Novo arquivo e instrução de inclusão adicionados ao projeto

Clicar no nome do arquivo no menu mostrará uma janela de código vazia, então agora podemos recortar as entradas de rotina do BIOS do arquivo TheMeteorGame.asm para este novo:

Nossas chamadas de BIOS têm um lugar próprio agora.

Observe que se você deixar o código rodando no IDE você terá muitos erros após recortar o código do arquivo principal, mas tudo é corrigido novamente após colar as linhas no novo arquivo. Isso é importante observar, pois queremos que o código se comporte da mesma maneira que quando todas as linhas de código estavam localizadas no mesmo arquivo.

Também aproveitei a oportunidade e criei o arquivo System_SystemVariables.asm , e em seguida movi as entradas referentes às variáveis do código principal para este outro lugar, e também o System_CartridgeHeader.asm para remover o cabeçalho do cartucho do código principal e manter as coisas ajustadas. Observe que a instrução org ainda precisa ser posicionada antes da instrução header, caso contrário o programa não funcionará corretamente:

Ficou bem melhor na minha opinião!

Lembra-se daquela rotina maluca Set16x16Sprites que adicionamos no último artigo para fazer o VDP entender que 16x16 pixels é o tamanho do sprite que queremos usar e não o tamanho 8×8 que é o padrão para o TMS9918, por que não também mover essas linhas para outro arquivo específico para rotinas gráficas? eu criei o Function_SetScreen.asm arquivo para armazenar qualquer código relacionado às configurações de tela e, em seguida, movi o código para lá.

Trabalhe de forma inteligente, não dura

Mover o código é fácil, mas há um limite de quão útil isso pode ser. Estou almejando ter um código reutilizável e evitar ter que repetir os mesmos comandos através do programa, e uma forma de conseguir isso que encontrei foi usar endereços de memória como variáveis, depois chamar as rotinas após definir os valores das variáveis. Muito complicado? Deixe-me demonstrar e tornar isso mais fácil de entender.

Vou pegar as linhas que alteram os valores das cores de primeiro plano, plano de fundo e borda e mover toda esta seção para o Function_SetScreen.asm e fazê-lo usar os registradores B,C e D para definir os valores de cor:

; Define as cores de primeiro plano, plano de fundo e borda, chamando INITGRP em seguida
InitGraphicMode: 
	ld hl,FORCLR ; Carrega a variável sys FORCLR em HL 
	ld (hl),b ; Carrega o valor de B na posição apontada por HL 
	ld hl,BAKCLR ; Carrega a variável sys BAKCLR em HL 
	ld (hl),c ; Carrega o valor de B na posição apontada por HL 
	ld hl,BDRCLR ; Carrega a variável sys BDCLR em HL 
	ld (hl),d ; Carrega o valor de B na posição apontada por HL 
	call INIGRP ; Inicializa o modo gráfico VDP 

	ret ; Retornar à rotina do chamador

Em seguida, precisamos substituir o código do arquivo principal pelas linhas abaixo, que carrega os registradores com os valores de cor desejados e então chama a rotina InitGraphicMode que criamos no arquivo Function_SetScreen.asm:

Init: 
	;Vamos inicializar as cores da tela 
	ld b,15 ; Cor do primeiro plano 
	ld c,13 ; Cor de fundo 
	ld d,13 ; Cor da borda chamada 
	InitGraphicMode ; Inicializa o modo gráfico com a cor em BCD 

Basicamente, substituímos as 7 linhas de código do código original por um total de 14 linhas no novo código, o que não parece ser uma melhoria, mas lembre-se de que provavelmente mudaremos as cores novamente no futuro durante o desenvolvimento do jogo, o que eventualmente justificará essa mudança.

Código é uma coisa difícil quando as coisas são codificadas de forma fixa.

No artigo anterior, usei os endereços VRAM para carregar os padrões e atributos de cada sprite na memória. Isso exigia algum cálculo manual para descobrir onde os próximos valores deveriam ser carregados e também tornava o código bastante inflexível e difícil de ler e manter.

Felizmente, o MSX BIOS possui duas rotinas que calculam o endereço de memória do sprite com base em seu ID carregado em A, e retornam o valor no registrador HL, facilitando a compreensão e alteração do código:

CALPAT: equ 00084h ; Função: Retorna o endereço da tabela de padrões de sprite
		; Entrada: A-ID do Sprite
		; Saída: HL - Para o endereço
		; Registros: AF, DE, HL 
CALATR: equ: 00087h ; Função: Retorna o endereço da tabela de atributos do sprite
		; Entrada: A - Número do Sprite
		; Saída: HL - Para o endereço
		; Registros: AF, DE, HL

CALPAT carregará em HL a posição da VRAM relacionada ao padrão do sprite cujo ID é carregado em A antes de chamar a rotina, e CALATR fará o mesmo, mas desta vez para os atributos do sprite. Observe que há um pequeno problema aqui porque o próximo passo é usar LDIRVM para mover os dados de padrão e atributos de RAM para VRAM, mas a rotina requer que o endereço do padrão seja carregado em HL e o endereço VRAM em DE, e CALPAT/CALATR retornará o endereço de destino em HL!

LDIRVM: equ 0005Ch ; Função:  Transferência em blocos para VRAM da memória
		; Entrada: BC - Comprimento do bloco
		;		  DE - Endereço inicial da VRAM 
		;		  HL - Endereço inicial da memória

Como não há nenhum comando que carrega HL diretamente no DE, há uma maneira rápida de fazer isso: PUSH-ando o conteúdo do HL para a pilha e, em seguida, POP-indo de volta em DE. Aqui está o trecho de código que carrega o sprite com id 0 na VRAM:

        ld a,0 ; Carregue o sprite ID 0 em A 
	call CALPAT ; Retorna o endereço VRAM do padrão 0 do sprite em HL 
	push hl ; Enviar dados HL para o stack 
	pop de ; então retorne novamente em DE 
	ld bc,64 ; Nosso padrão de sprite usa 64 bytes 
	ld hl,MeteorIdleSprite01 ; Carregue o endereço padrão na chamada HL 

	call LDIRVM; Copie os blocos BC de dados do endereço RAM HL para o endereço VRAM DE 

	ld a,0 ; Carregue o sprite ID 0 em A 
	call CALATR ; Retorna o endereço VRAM do atributo sprite 0 em HL 
	push hl ; Enviar dados HL para o stack pop de ; então retorne novamente em DE 
	ld bc,4 ; Nosso atributo sprite usa 4 bytes 
	ld hl,MeteorIdleSprite01_attrib ; Carregue o endereço do atributo na chamada HL 
	
	call LDIRVM; Copie blocos BC de dados do endereço RAM HL para o endereço VRAM DE
        

Para carregar os próximos 3 sprites tudo o que precisamos fazer é duplicar essas linhas de código substituindo o valor carregado em A e os rótulos relacionados aos padrões e atributos do sprite. Mais uma vez, essa mudança não está reduzindo o tamanho do código nem evitando a repetição do código, mas é mais fácil de entender e manter, além de facilitar a melhoria da rotina no futuro, o que faremos no próximo artigo.

Um toque final antes de encerrar o dia

Existe outra seção do código que podemos mover para outro arquivo: os padrões e atributos do sprite. Para isso, criei o arquivo de inclusão “Asset_MeteorData.asm”, depois movi os rótulos com os valores hexadecimais do código principal para este arquivo, que agora está assim:

; The Meteor Game

	include "System_BIOSCalls.asm"
	include "System_SystemVariables.asm"

	org 0x4000
        
	include "System_CartridgeHeader.asm"

; initialize the whole thing here
Init:
	;Let's initialize the screen colors
	ld b,15			; Foreground color
        ld c,13			; Background color
        ld d,13			; Border color
        call InitGraphicMode	; Initializes graphic mode with the color at BCD 
    
        call Set16x16Sprites	; Set the sprite size to 16x16 pixels
        
        ld   a,0		; Load the sprite ID 0 into A
        call CALPAT		; Return the sprite 0 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite01 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,0		; Load the sprite ID 0 into A
        call CALATR		; Return the sprite 0 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite01_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address

        ld   a,1		; Load the sprite ID 1 into A
        call CALPAT		; Return the sprite 1 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite02 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,1		; Load the sprite ID 1 into A
        call CALATR		; Return the sprite 1 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite02_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        ld   a,2		; Load the sprite ID 2 into A
        call CALPAT		; Return the sprite 2 pattern VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        LD   bc,64		; Our sprite pattern uses 64 bytes
        LD   hl,MeteorIdleSprite03 ; Load the pattern address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
        
        ld   a,2		; Load the sprite ID 2 into A
        call CALATR		; Return the sprite 2 attribute VRAM address in HL 
        push hl			; Send HL data to he stack
        pop  de			; then return it bacl in DE
        ld   bc,4		; Our sprite attribute uses 4 bytes
        LD   hl,MeteorIdleSprite03_attrib ; Load the attribute address in HL
        
        call LDIRVM		; Copy BC blocks of data from RAM HL address into VRAM DE address
    

            
ForeverLoop:
    jr ForeverLoop    ; Repeats the loop indefinitely
        

	include "Function_SetScreen.asm"        
	include "Asset_MeteorData.asm"
        
        end Init

É tudo por agora

Mudamos e mudamos bastante nosso código, e pode ser de alguma forma decepcionante que no final não tenha havido nenhuma melhoria no jogo real e ainda estejamos vendo o cara maluco do meteoro preso na mesma posição na tela, mas acredite em mim que esta foi realmente uma excelente melhoria feita através deste artigo e será muito útil no futuro. O código completo está disponível em https://github.com/oddbitmachine/TheMeteorGame, então fique à vontade para conferir o progresso feito até agora. Voltaremos em breve para melhorar a rotina de carregamento de sprites, então fique atento!

Algumas semanas atrás, iniciamos o design do conceito do jogo e adicionamos alguns códigos básicos que resultaram no carregamento do nosso sprite na tela do modo gráfico, o que foi legal e divertido. O problema é que nosso código parece apressado, com muitos endereços de memória codificados e comandos repetidos em todo o…

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

pt_BRPortuguês do Brasil