Desenvolvimento de jogos/Programação Megadrive
Introdução
Este wikibook destina-se àqueles que estejam interessados no desenvolvimento de jogos para o videogame conhecido como Mega Drive.
(Em construção)
Ferramentas
Montadores
Também conhecidos pelo termo inglês assemblers, geram o código de máquina referente ao código fonte escrito na linguagem de montagem (assembly). No caso do Mega Drive, o código é escrito para os processadores Motorola 68000 (também conhecido como M68k ou M68000) e Z80, da Zilog, sendo o Z80 um coprocessador de áudio e para "compatibilidade" com jogos de Master System.
GAS
O GNU Assembler (GAS) [1]. Ele é bem completo, pleno de recursos e genérico quanto a processador. Sua sintaxe segue o padrão AT&T em vez do da Intel. Caso seja necessário compilar o GAS para o M68k, utilize os seguintes comandos:
./configure -target=m68k-coff make
O primeiro comando configura o alvo de montagem ser o processador família M68k, sem sistema operacional. O segundo efetivamente compila os programas componentes do GAS, que são:
- addr2line
- ar
- as
- cxxfilt
- ld
- nm
- objcopy
- objdump
- ranlib
- readelf
- size
- strings
- strip
Os mais usados, para fins de programas para Mega Drive, são o as (o montador/assembler) e o ld (o ligador/linker).
Usando o as
as -m68000 -o arquivo.o arquivo.s
É importante o uso do parâmetro -m68000, porque o padrão é 68020. O parâmetro -o serve para definir o nome do arquivo objeto de saída (o padrão é "a.out"). Por fim, o não opcional: o código-fonte.
Usando o ld
Após feita as compilações de todas os códigos do programa, é preciso ligá-los.
ld --oformat=binary -Ttext=0 -o rom.bin arquivo.o [outros .o]
- --oformat=binary e -Ttext=0 são necessários para o correto formato do arquivo binário.
- -o, como já mencionado para o as, define o nome do arquivo binário de saída, no caso "rom.bin"
- O parâmetro obrigatório para o ld é, obviamente, o(s) arquivo(s) objeto(s) que compõe o programa.
Compiladores
BasiEgaXorz
Compilador de BASIC para Mega Drive, muito fácil de usar, e vem com muitos exemplos.
Pode ser baixado neste site
SGCC - Sega Genesis C Compiler
Compilador de C para Mega Drive. Embora tenha alguns problemas, como o fato de usar um dialeto arcaico da linguagem C, (Sintaxe K&R, para ser mais exato), ele é extremamente simples de usar, bastando descompactar e usar, além de já vir com alguns exemplos simples.
Pode ser baixado deste site, ou deste
GCC68k - GNU C Compiler for 68k
Versão do GNU C adaptado para o processador MC68000. Suporta tanto C como C++, podendo também suportar outras linguagens, como Fortran, Pascal e outros, dependendo da configuração. Tem a desvantagem de ser ligeiramente mais difícil de usar do que o SGCC.
Neste site, você pode baixar uma versão deste compilador já preparada para o Mega Drive.
Conversores de imagens
Básico
Para os exemplos desta seção, será utilizado o BasiEgaXorz, por ser mais simples de usar.
Escrevendo texto na tela
Usando o BasiEgaXorz, esta tarefa se torna bastante simples:
print "Ola, mundo!!!"
Definindo a palheta de cores
O Megadrive possui quatro palhetas de cores, cada uma com 16 cores, selecionáveis dentre 512.
O BasiEgaXorz possui dois comandos para setar as palhetas do Megadrive:
PALLETTE seta uma única cor de uma única palheta. A sua sintaxe é:
Pallette <código RGB>, <n° da palheta>, <n° da cor>
- <código RGB> é um número hexadecimal que repesenta os tons de vermelho, verde e azul que compõem a cor desejada. Para calcular o código equivalente à cor, utilize a fórmula (R*2)+(G*32)+(B*512), onde:
- R representa o tom de vermelho, entre 0 e 7
- G representa o tom de verde, entre 0 e 7
- B representa o tom de azul, entre 0 e 7
- <n° da palheta> indica o número da palheta que se deseja modificar
- <n° da cor> indica qual das cores desta palheta será alterada
Exemplo:
ink 0 print "Texto usando a palheta0" ink 1 print "Texto usando a palheta1" ink 2 print "Texto usando a palheta2" ink 3 print "Texto usando a palheta3" ' ink 0 print print "Pressione qualquer botao..." while joypad()=0: wend waitpadup 0 ' pallette 14, 0, 1 pallette 224, 1, 1 pallette 3584, 2, 1 pallette 238, 3, 1 ' ink 1 print "Observe como a cor do texto mudou ao" print "alterar a palheta"
O exemplo acima imprime uma linha de texto com cada palheta, espera o usuário pressionar qualquer botão do joystick, e então altera a cor 1 de cada palheta, a qual corresponde à cor utilizada pelo texto; como consequência, o texto muda de cor.
PALLETTES pode setar várias cores de uma determinada palheta de uma só vez. A sua sintaxe é:
Pallettes <rótulo dos dados>, <n° da palheta>, <cor inicial>, <n° de cores>, [Deslocamento]
- <rótulo dos dados> indica qual rótulo de dados será lido para definir as cores da palheta indicada (veja o exemplo abaixo para entender melhor)
- <n° da palheta> número da palheta que será alterada
- <cor inicial> cor inicial a ser alterada. Por exemplo, se a cor inicial for 4 e o número de cores for 3, serão alteradas as cores 4, 5 e 6
- <n° de cores> o número de cores que serão alteradas
- [Deslocamento] parâmetro opcional que indica a posição dentro dos dados indicados pelo rótulo a partir da qual os dados serão lidos.
Exemplo:
ink 0 print "Texto usando a palheta0" ink 1 print "Texto usando a palheta1" ink 2 print "Texto usando a palheta2" ink 3 print "Texto usando a palheta3" ' ink 0 print print "Pressione qualquer botao..." while joypad()=0: wend waitpadup 0 ' pallettes pallette1, 0, 0, 16 pallettes pallette2, 1, 0, 16 pallettes pallette3, 2, 0, 16 pallettes pallette4, 3, 0, 16 ' ink 1 print "Observe como a cor do texto mudou ao" print "alterar a palheta, assim como a cor" print "do fundo." ' pallette1: DATAINT $0ECA,$00EE,$0EA8,$0CA6,$0CAC,$08CA,$08AA,$0A68 DATAINT $0866,$0ACE,$0442,$0686,$066A,$0AAE,$06AA,$0000 pallette2: DATAINT $0002,$00AA,$002A,$004A,$006E,$02AE,$002C,$04AE DATAINT $02EE,$04EE,$0046,$0468,$04AA,$028A,$08CC,$0000 pallette3: DATAINT $0620,$0088,$0A62,$0CEC,$0644,$0CA4,$0684,$06A8 DATAINT $0868,$0424,$0444,$0222,$0648,$0428,$0000,$0000 pallette4: DATAINT $0422,$0044,$0426,$0446,$086A,$0CEE,$044A,$0866 DATAINT $06AE,$0642,$0202,$0244,$0662,$0000,$0000,$0000
No exemplo acima, pallette1, palette2, palette3 e palette4 são rótulos que apontam para dados, os quais, neste caso, são definidos pelo comando DATAINT. O cifrão na frente de cada número serve para indicar que o número em questão está em notação hexadecimal. Isso torna mais fácil visualizar a cor que está sendo definida, visto que os três últimos dígitos do código da cor em hexa correspondem justamente aos valores de azul, verde e vermelho, respectivamente. Observe, também, que a cor do fundo também foi alterada, porque a cor zero da palheta zero também mudou.
Importante: Se for copiar e colar este exemplo para o BasiEgaXorz, lembre-se de tirar o espaço à esquerda dos rótulos, mas apenas dos rótulos. Isto é necessário, porque o BasiEgaXorz pressupõe que qualquer linha que comece com um ou mais espaços à esquerda não contenha um rótulo.
Lendo o joystick
Movendo objetos pela tela
Exibindo uma imagem no fundo da tela
Seu primeiro jogo: Como fazer um clone do "Pong"
Fazendo a bola ricochetear pela tela
Checagem de colisão: Rebatendo a bola
Marcando a pontuação
Inteligência artificial básica: Jogador 1 versus CPU
Múltiplos inimigos na tela: Fazendo seu próprio Galaxian
Intermediário
Visão geral do hardware do Mega Drive
Movimentando os planos de fundo
Som básico: Usando o PSG
Avançado
Descrição das portas de I/O do Mega Drive
VDP
$C00000 (Porta de dados)
$C00004 (Porta de controle)
Leitura
* | * | * | * | * | * | VAZIO | CHEIO |
F | SOVR | C | IMPAR | VB | HB | PAL |
VAZIO 1: FIFO de escrita vazio 0: CHEIO 1: FIFO de escrita cheio 0: F 1: Ocorreu interrupção vertical SOVR 1: Ocorreu estouro de sprites. Muitos na mesma linha. Mais de 17 no modo de 32 células. Mais de 21 no modo de 40 células. C 1: Ocorreu colisão entre dois pixels não nulos entre dois sprites. 0: IMPAR 1: Quadro ímpar no modo entrelaçado. 0: Quadro par no modo entrelaçado. VB 1: Durante o "blanking" vertical 0: HB 1: Durante o "blanking" horizontal 0: DMA 1: Realizando DMA 0: PAL 1: Modo PAL 0: Modo NTSC
Escrita 1: Enviar dados ao registrador
1 | 0 | 0 | RS4 | RS3 | RS2 | RS1 | RS0 |
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
RS4 ~ RS0 : Número do registrador D7 ~ D0 : Dados a enviar
- As portas do VDP devem ser acessadas via "word" ou "long word", para correto funcionamento.
Escrita 2: Seleção de endereço
1ª Escrita
CD1 | CD0 | A13 | A12 | A11 | A10 | A9 | A8 |
A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
2ª Escrita
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
CD5 | CD4 | CD3 | CD2 | 0 | 0 | A15 | A14 |
CD5 ~ CD0 : Código de indentificação A15 ~ A0 : Endereço de destino
Modo de Acesso | CD5 | CD4 | CD3 | CD2 | CD1 | CD0 |
Escrita em VRAM | 0 | 0 | 0 | 0 | 0 | 1 |
Escrita em CRAM | 0 | 0 | 0 | 0 | 1 | 1 |
Escrita em VSRAM | 0 | 0 | 0 | 1 | 0 | 1 |
Leitura da VRAM | 0 | 0 | 0 | 0 | 0 | 0 |
Leitura da CRAM | 0 | 0 | 1 | 0 | 0 | 0 |
Leitura da VSRAM | 0 | 0 | 0 | 1 | 0 | 0 |
$C00008 (Contador horizontal/vertical)
Modo não entrelaçado
VC7 | VC6 | VC5 | VC4 | VC3 | VC2 | VC1 | VC0 |
HC8 | HC7 | HC6 | HC5 | HC4 | HC3 | HC2 | HC1 |
Modo entrelaçado
VC7 | VC6 | VC5 | VC4 | VC3 | VC2 | VC1 | VC8 |
HC8 | HC7 | HC6 | HC5 | HC4 | HC3 | HC2 | HC1 |
HC8 ~ HC1 : Posição horizontal VC8 ~ VC0 : Posição vertical
Registradores
REG 0: Registrador de modo número 1
0 | 0 | 0 | IE1 | 0 | 1 | M3 | 0 |
IE1 1: Habilita interrupção horizontal (68000 nível 4) 0: Desabilita interrupção horizontal (REG #10) M3 1: Parar o contador HV 0: Habilita o contador HV para leitura
REG 1: Registrador de modo número 2
0 | DISP | IE0 | M1 | M2 | 1 | 0 | 0 |
DISP 1: Habilita display 0: Desabilita display IE0 1: Habilita interrupção vertical (68000 nível 6) 0: Desabilita interrupção vertical M1 1: Habilita DMA 0: Desabilita DMA M2 1: Modo 30 células na horizontal (modo PAL) 0: Modo 28 células na horizontal (Modo PAL, sempre 0 em modo NTSC)
REG 2: Endereço base do mapa do fundo A
0 | 0 | SA15 | SA14 | SA13 | 0 | 0 | 0 |
Endereço da VRAM: $XXX0_0000_0000_0000
REG 3: Endereço base do mapa da janela
0 | 0 | WD15 | WD14 | WD13 | WD12 | WD11 | 0 |
WD11 deve ser 0 no modo de 40 células horizontais Endereço da VRAM: $XXXX_X000_0000_0000 (32 células horizontais) Endereço da VRAM: $XXXX_0000_0000_0000 (40 células horizontais)
REG 4: Endereço base do mapa do fundo B
0 | 0 | 0 | 0 | 0 | SB15 | SB14 | SB13 |
Endereço da VRAM: $XXX0_0000_0000_0000
REG 5: Endereço base da tabela de atributos dos sprites
0 | AT15 | AT14 | AT13 | AT12 | AT11 | AT10 | AT9 |
WD9 deve ser 0 no modo de 40 células horizontais Endereço da VRAM: $XXXX_XXX0_0000_0000 (32 células horizontais) Endereço da VRAM: $XXXX_XX00_0000_0000 (40 células horizontais)
REG 7: Cor do fundo
0 | 0 | CPT1 | CPT0 | COL3 | COL2 | COL1 | COL0 |
CPT1 ~ CPT0 : Número da palheta COL3 ~ COL0 : Código da cor
REG 10: Interrupção horizontal
BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
Conta o número da linha horizontal da tela sendo atualmente renderizado
REG 11: Registrador de modo número 3
0 | 0 | 0 | 0 | IE2 | VSCR | HSCR | LSCR |
IE2 1: Habilita interrupção externa (68000 nível 2) 0: Desabilita interrupção externa
Modo de deslocamento horizontal
VSCR | Função |
0 | Scroll completo |
1 | Scroll a cada duas células |
Modo de deslocamento vertical
HSCR | LSCR | Função |
0 | 0 | Scroll completo |
0 | 1 | Inválido |
1 | 0 | Scroll célula a célula |
1 | 1 | Scroll linha a linha |
REG 12: Registrador de modo número 4
RS0 | 0 | 0 | 0 | S/TE | LSM1 | LSM0 | RS1 |
RS0 0: Modo de 32 células horizontais 1: Modo de 40 células horizontais RS1 0: Modo de 32 células horizontais 1: Modo de 40 células horizontais * É recomendável setar o mesmo valor para RS0 e RS1. S/TE 1: Habilita sombreamento e realce 0: Habilita sombreamento e realce LSM1, LSM0: Modo de entrelaçamento
LSM1 | LSM2 | Função |
0 | 0 | Sem entrelaçamento |
0 | 1 | Entrelaçado |
1 | 0 | Inválido |
1 | 1 | Entrelaçado (Dupla resolução) |
REG 13: Endereço da tabela de deslocamento horizontal
0 | 0 | HS15 | HS14 | HS13 | HS12 | HS11 | HS10 |
Endereço da VRAM: $XXXX_XX00_0000_0000
REG 15: Dados de auto incremento
A cada vez que a CPU acessa a memória da VDP do Mega Drive, a posição atual desta memória é incrementada. Este registrador define o tamanho do incremento.
0 | 0 | HS15 | HS14 | HS13 | HS12 | HS11 | HS10 |
INC7 ~ INC0: Tamanho do incremento ( 0 ~ $FF )
REG 16: Tamanho do plano de fundo
0 | 0 | VSZ1 | VSZ0 | 0 | 0 | HSZ1 | HSZ0 |
VSZ1 | VSZ0 | Função |
0 | 0 | 32 células na vertical |
0 | 1 | 64 células na vertical |
1 | 0 | Inválido |
1 | 1 | 128 células na vertical |
HSZ1 | HSZ0 | Função |
0 | 0 | 32 células na horizontal |
0 | 1 | 64 células na horizontal |
1 | 0 | Inválido |
1 | 1 | 128 células na horizontal |
REG 17: Posição horizontal da janela
RIGT | 0 | 0 | WHP5 | WHP4 | WHP3 | WHP2 | WHP1 |
RIGT 0: Janela está à esquerda do ponto base 1: Janela está à direita do ponto base WHP5 ~ WHP1 Posição horizontal em células, isto é de 8 em 8 pixels
REG 18: Posição vertical da janela
DOWN | 0 | 0 | WVP4 | WVP3 | WVP2 | WVP1 | WVP0 |
DOWN 0 : Janela está acima do ponto base 1 : Janela está abaixo do ponto base WVP4 ~ WVP0 Posição vertical em células
REG 19: Byte menos significativo do contador de DMA
LG7 | LG6 | LG5 | LG4 | LG3 | LG2 | LG1 | LG0 |
REG 20: Byte mais significativo do contador de DMA
LG15 | LG14 | LG13 | LG12 | LG11 | LG10 | LG9 | LG8 |
REG 21: Porção inferior do endereço de origem de DMA
SA8 | SA7 | SA6 | SA5 | SA4 | SA3 | SA2 | SA1 |
REG 22: Porção central do endereço de origem de DMA
SA16 | SA15 | SA14 | SA13 | SA12 | SA11 | SA10 | SA9 |
REG 23: Porção superior do endereço de origem de DMA
DMD1 | DMD0 | SA22 | SA21 | SA20 | SA19 | SA18 | SA17 |
SA22 ~ SA1 : Endereço de origem de DMA DMD1, DMD0 : Modo de DMA
DMD1 | DMD0 | Função |
0 | SA23 | Memória para vídeo |
1 | 0 | Preenche VRAM |
1 | 1 | Vídeo para vídeo |
Exemplo de utilização das portas
void init_GFX() { register unsigned int *pw; pw = (uint *) 0xC00004; /* Aponta para a porta de controle */ *pw = 0x8016; /* reg. 0 - Habilita HBL */ *pw = 0x8174; /* reg. 1 - Habilita display, VBL, DMA e seta a largura */ *pw = 0x8230; /* reg. 2 - Plano A =$30*$400=$C000 */ *pw = 0x832C; /* reg. 3 - Janela =$2C*$400=$B000 */ *pw = 0x8407; /* reg. 4 - Plano B =$7*$2000=$E000 */ *pw = 0x855E; /* reg. 5 - Tabela de sprites em $BC00=$5E*$200 */ *pw = 0x8700; /* reg. 7 - Cor do fundo */ *pw = 0x8a01; /* reg 10 - HInterrupt timing */ *pw = 0x8b00; /* reg 11 - $0000abcd a=interrupção externa b=scroll horiz. cd=scroll vert. */ *pw = 0x8c81; /* reg 12 - células horiz + sombreamento/realce + modo entrelaçamento (40 células, sem sombreamento, sem entrelaçamento)*/ *pw = 0x8d2E; /* reg 13 - Tabela de deslocamento horizontal = $B800 */ *pw = 0x8f02; /* reg 15 - auto incremento */ *pw = 0x9011; /* reg 16 - tamanho dos planos de fundo (64x64) */ *pw = 0x9100; /* reg 17 - posição horizontal da janela */ *pw = 0x92ff; /* reg 18 - posição vertical da janela */ };
Programação assembly 68k
Documentação Assembly
A extensa documentação do assembly do 68k pode ser encontrada no Devega.
Cabeçalho da ROM
O cabeçalho da ROM para Mega Drive consiste de duas seções, cada uma de 256 bytes. A primeira é "exigida" pelo processador, e indicam o início da pilha, o endereço da rotina principal e os endereços das rotinas de exceções (traps) e interrupções. Já a segunda seção é uma padronização realizada pela SEGA para identificação do cartucho (direitos autorais, nome do produto, etc.) e informações sobre utilização de recursos do hardware (mapeamento de memória, periféricos, etc.).
Item | Endereço | Descrição |
---|---|---|
1 | 0x000000 | Endereço do início da pilha (geralmente próximo ao fim da memória RAM) |
2 | 0x000004 | Endereço da rotina principal (valor inicial do PC, também para quando resetado) |
26 | 0x000068 | Interrupção de nível 2: no Mega Drive, Externa. |
28 | 0x000070 | Interrupção de nível 4: no Mega Drive, Retraço Horizontal. |
30 | 0x000078 | Interrupção de nível 6: no Mega Drive, Retraço Vertical. |
O conteúdo de cada elemento do Vetor de Exceções é o endereço absoluto (em relação ao primeiro byte do arquivo binário) da respectiva rotina de tratamento da exceção. Cada elemento consiste de 4 bytes.
O costume é, quando for desejado não tratar certas exceções, elas reiniciem o software, ou seja, apontem para o começo do programa. Vale destacar que as interrupções de retraço devem ser tratadas, nem que seja não fazer nada, já que são efetivamente geradas a tempo constante.
As demais exceções/interrupções não citadas podem ser encontradas no Apêndice B do MOTOROLA M68000 FAMILY Programmer's Reference Manual disponível no Devega.