Introdução

Sejam bem-vindos ao meu blog, criado única e exclusivamente para aprender e ensinar, juntos, a desenvolver jogos.

Vou inicialmente fazer um tutorial (bem simples) de um jogo (mais simples ainda) em assembly z-80, e pretendo ter a colaboração de várias pessoas para incrementarmos cada vez mais os jogos.

Dicas são sempre bem-vindas, e espero que este blog possa ser útil a muitos usuários que querem se aventurar no mundo da programação.

Introdução

Tutorial: Jogo em Assembly pro MSX em 30 minutos!

Tutorial: Jogo em Assembly pro MSX em 30 minutos!

Esta é uma adaptação do tutorial “ZX Spectrum Machine Code Game in 30 minutes!”, de Jon Kingsman (bigjon), originalmente publicado em uma thread do WoSF, e reproduzido pelo usuário Argun.

Aproveitando o artigo do bigjon, resolvi adaptá-lo para o MSX, principalmente por sua simplicidade. Quero que, em breve, possa melhorá-lo junto com a comunidade, para que todos consigam entender como funciona o desenvolvimento de jogos. Um jogo “de verdade” é bem mais complexo, mas quando se quebra o desenvolvimento em blocos tudo fica mais fácil, principalmente entender a mecânica deles.

O desenvolvimento do jogo está dividido em etapas, aonde vamos incrementando o mesmo aos poucos.

Vou assumir que o leitor esteja familiarizado com o básico dos emuladores, e possa também editar um programa assembly (seja num programa como Mega Assembler ou num editor de arquivos-texto + um compilador).

Capítulo 1 – Um programa em código de máquina que retorne o SCORE para o BASIC

O nosso jogo vai ter a seguinte estrutura:

MAIN:     ; label para a secao principal do jogo<
ORG 9000h	; endereço base para o nosso jogo
		; inicializar score
		; inicializar pista e carro
MAINLOOP:	; label para o loop sob o qual o jogo vai funcionar
		; ler o teclado
		; atualizar posicao do carro
		; verificar colisao, caso positivo ir pra GAMEOVER
		; realizar o scroll na pista
		; mostrar carro na tela
		; aleatoriamente mover a pista p/esquerda ou direita
		; voltar para MAINLOOP
GAMEOVER:	; label para a rotina de finalização
		; armazenar o score na variável de saída
		; retornar para o BASIC

A partir daqui, já temos uma ideia de como o jogo vai funcionar. Depois da preparação, atualiza-se a posição do carro de acordo com as setas, faz-se o scroll da tela e verifica-se a colisão do carro com a pista.

Para que o programa possa funcionar, é necessário substituirmos a última linha por RET. Esta é uma instrução em Assembly cuja função é RETornar para aonde o código anterior foi chamado, mais ou menos como o comando RETURN dentro de uma rotina em BASIC chamada através do comando GOSUB, que em assembly z80 possui como correspondente o comando CALL.

Quando compilamos o programa (transformamos o programa em “texto” em instruções que o processador entenda) o comando RET é convertido para o valor 201 (ou 0C9h – em hexadecimal). Assim como outras instruções, este comando só possui um único byte. Outras instruções utilizam 2, 3 ou mais bytes para que o processador possa saber o que (e como) executar.

Agora, precisamos inicializar o placar e deixar este valor pronto para retornar ao BASIC. Pra isso, vamos entender um pouco mais de como funciona o Z80.

Ele realiza a maior parte do trabalho com a ajuda de 7 registradores internos, cada um com a capacidade de 1 byte, o que permite manipular informações entre 0 e 255. Os valores dos registradores são atualizados de acordo com o fluxo do processamento dos programas.

O primeiro deles é chamado de A e opera sozinho, já os outros 6 (chamados de B, C, D, E, H, L) podem trabalhar em pares, permitindo valores entre 0 e 65535 (dois bytes). Eles também podem trabalhar sozinhos, de acordo com a necessidade. Quando agrupados, temos acesso aos pares BC, DE e HL.

Existe um oitavo registrador, chamado de F (Flag = indicador), cuja função é sinalizar para o programa o estado operacional do processador (por exemplo, quando um valor ultrapassou o limite – 255 – e retornou à zero). À medida que formos evoluindo no nosso tutorial, vamos aprendendo mais sobre ele. O mesmo pode ser utilizado em conjunto com o registrador A, formando o par AF, mas neste caso somente poderemos (normalmente) efetuar leituras.

O Z80 possui um conjunto alternativo de registradores, chamados de A’, F’, B’, C’, D’, E’, H’ e L’. Os mesmos podem ser alternados através dos comandos EX e EXX. O primeiro troca o conteúdo de AF, BC, DE ou HL com seu conjunto, enquanto que o segundo troca todos os conjuntos BC, DE e HL com os seus respectivos (BC’, DE’e HL’).

Para apoiar o funcionamento do z80, existem também os registradores PC e SP. O primeiro indica ao processador o endereço da próxima instrução a ser processada, e o segundo indica uma área para armazenamento temporário de informações.

Nosso programa vai ter um indicador de placar, e ao final ele irá retornar o mesmo para o comando BASIC que executou o jogo. Para isso, inicialmente colocaremos a inicialização do placar no lugar da terceira linha:

		LD BC,0

Para colocarmos o valor inicial 0 no par de registradores BC, usamos o comando LD (LoaD).  Este comando tem funções diferentes, dependendo do contexto em que for utilizado. No caso do comando utilizado (LD BC,0) o código é 01, seguido de dois bytes com valor 0 (um byte para cada registrador – B e C -). Neste caso os valores são idênticos, mas caso o mesmo fosse diferente (por exemplo, 3257) o mesmo seria armazenado na ordem inversa (primeiro C e depois B). Como cada registrador possui 8 bits, o valor de B é igual ao valor de BC/256 (no caso do exemplo, 12) e o valor de C é o resto (3257 – 12*256, o que dá 185). Em decimal, este exemplo seria armazenado com a seguinte sequencia: 01 185 12. Em Hexadecimal, ficaria 01 0B9h 0Ch.

Para podermos retornar o valor do placar (0) ao BASIC, precisamos armazenar o mesmo em uma posição de memória padronizada pelo sistema MSX, no endereço 0F7F8h, e o tipo da variável armazenada (2 = inteiro até 65535) no endereço 0F663h. Alteramos então a linha 14 para:

		LD (0F7F8h),BC
		LD A,2
		LD (0F663H),A

Com isso, o placar está armazenado e o tipo da variável definido de forma adequada. Podemos agora compilar o nosso programa e ele irá gerar a seguinte sequencia na memória, a partir da posição 9000h:

01 00 00 ED 43 F8 F7 3E 02 32 63 F3 C9

Podemos agora criar o nosso programa em BASIC que irá chamar o nosso primeiro código compilado:

100 P=&H9000: DEFUSR=P
140 PRINT ”O PLACAR EH DE”; USR(0); “PONTOS”

Caso não tenha como compilar o programa, acrescente as seguintes linhas:

10 DATA 01, 00, 00, ED, 43, F8, F7, 3E, 02, 32, 63, F3, C9,*
110 READ A$: IF A$=”*” THEN GOTO 140
120 POKE P, VAL(“&H”+A$):P=P+1
130 GOTO 110

E pronto, podemos rodar o mesmo. O resultado será de 0 pontos. Caso queira brincar com os valores pra testar o programa, altere o segundo e terceiro bytes (ambos com 0) para valores hexadecimais entre 00 e FF. O valor correspondente em decimal irá aparecer no lugar da pontuação.

Tutorial: Jogo em Assembly pro MSX em 30 minutos!