Palestra apresentada em 27/Ago/2011 no Dev in Sampa (http://devinsampa.com.br/), em 06/Nov/2011 no Dev In Vale e em 11/Fev/2012 no Campus Party Brasil (5a. edição, #cpbr5)
O vídeo da palestra no Dev In Sampa (filmada pelo @agaelebe) está disponível em: http://chester.blog.br/archives/2011/08/palestra-sobre-programacao-para-atari-2600-no-dev-in-sampa-2011.html
2. Proposta
Entender o que torna o Atari
tão diferente de outros sistemas,
aprendendo o básico para escrever
um “Hello, World” e poder apreciar
clássicos como Enduro ou Pitfall! pela
habilidade de seus criadores
http://slideshare.net/chesterbr
18. VRAM
Quanto mais memória (VRAM),
maior a resolução, e variedade de
cores. Memória era cara nos anos
70/80, levando a um tradeoff.
Quanta VRAM o Atari tem?
19. Mapa da Memória
0000-002C – TIA (Escrita)
0030-003D – TIA (Leitura)
0080-00FF – RIOT (RAM)
0280-0297 – RIOT (I/O, Timer)
F000-FFFF – Cartucho (ROM)
25. Scanlines
60 quadros
(frames)
por segundo
Fonte: How Stuff Works
26. TIA opera em scanlines
Para cada scanline, você escreve em
posições de memória do TIA que
configuram “objetos desenháveis”
É difícil mudar a cor/forma de um
objeto numa mesma scanline
28. E que objetos são esses?
● Playfield (PF)
● Players (P0, P1)
● Missiles/Ball (M0, M1, BL)
29. Playfield
Um padrão de 20 bits (representando
cor de frente e cor de fundo) que
ocupa o lado esquerdo da scanline.
O lado direito repete o mesmo
padrão, ou, opcionalmente, uma
versão “espelhada” dele
42. Players
Cada um dos players é um padrão
de 8 bits com sua própria cor
Ex.: 10100001 → ████████
Os dois padrões (GRP0/GRP1)
podem aparecer na mesma scanline
54. Missiles/Ball
Cada um representa um pixel na
scanline, mas pode ter sua largura
ampliada em 2, 4 ou 8 vezes.
Os missiles têm as cores dos players,
enquanto ball tem a cor do playfield.
59. Idéia geral
Para cada scanline, você configura o
formato dos objetos (playfield, players,
missiles/ball) e as cores/efeitos deles.
O que você configura em uma scanline
vale para as seguintes, mas ainda assim
o tempo é um problema
60. Contas de padaria:
6502 ≈ 1,19Mhz (1.194.720 ciclos/seg)
NTSC: 60 frames (telas) por seg
1.194.720/60 ≅ 19.912 ciclos por tela
61. Contas de padaria:
CPU: 19.912 ciclos por tela
NTSC: 262 scanlines por frame
19.912 / 262 = 76 ciclos por scanline
62. Contas de padaria:
CPU: 19.912 ciclos por tela
NTSC: 262 scanlines por frame
19.912 / 262 = 76 ciclos por scanline
e o que se faz com “76 ciclos”?
(aliás, o que exatamente é um “ciclo”?)
66. 6502 (no Atari)
Executa instruções armazenadas na
ROM que manipulam e transferem
bytes entre o RIOT (RAM + I/O +
timers) e o TIA, com o apoio de
registradores internos.
67. Instruções
Cada instrução é composta por um
opcode (1 byte) seguido por um
parâmetro (0 a 2 bytes)
Dependendo do opcode, a instrução leva
de 2 a 6 ciclos para ser executada
68. Registradores do 6502
A = Acumulador (8 bits)
X,Y= Índices (8 bits)
S = Stack Pointer (8 bits)
P = Status (flags, 8 bits)
PC = Program Counter (16 bits)
69. Exemplo de Programa
● Ler o byte da posição de memória
0x0200 para o acumulador (A)
● Somar 1 (um) no A
● Guardar o resultado (A) na posição
de memória 0x0201
70. Código de Máquina 6502
AD Opcode (Memória→A)
00 2a. Parte de “0200”
02 1a. Parte de “0200”
69 Opcode (valor+A→A)
01 valor “01”
8D Opcode (A→Memória)
01 2a. Parte de “0201”
02 1a. Parte de “0201”
73. Código de Máquina 6502
AD Opcode (Memória→A)
00 2a. Parte de “0200”
02 1a. Parte de “0200”
69 Opcode (valor+A→A)
01 valor “01”
8D Opcode (A→Memória)
01 2a. Parte de “0201”
02 1a. Parte de “0201”
74. Assembly 6502
AD LDA $0200
00
02
69 ADC #01
01
8D STA $0201
01
02
75. Assembler (Montador)
Programa que lê um arquivo-texto
escrito em linguagem Assembly e
monta o arquivo binário (código de
máquina) correspondente
foo.asm foo.bin
LDA $0200 ASSEMBLER AD000269
ADC #01 018D0102
STA $0201 ...
...
77. Notação (para hoje)
#... = valor absoluto
$... = endereço, em hexa
$..., X = endereço + X, em hexa
#$... = valor absoluto em hexa
http://www.obelisk.demon.co.uk/6502/addressing.html
87. Hello, World!
Escrever na horizontal é complicado
(muitos pixels/elementos por scanline)
88. Hello, World!
É mais fácil escrever
na vertical →
(menos pixels/scanline)
Podemos usar um
player ou o playfield
89. Display kernel
É a parte do programa que roda
quando o canhão está desenhando a
tela propriamente dita (através do
playfield, players e missiles/ball)
90. (3+37+30).76 = 5320 ciclos
LÓGICA DO JOGO
KERNEL
Fonte: Stella Programmers Guide, Steve Wright, 1979
93. Estrutura do programa
VSYNC
VBLANK
Loop 11 chars x
Principal 8 linhas x
(eterno) 2 linhas por Kernel
scanline = loop X: 0 a 191
176 (192 scanlines)
scanlines
OVERSCAN
95. Início do frame (loop principal)
InicioFrame:
lda #%00000010 ; VSYNC inicia
sta VSYNC ; setando o bit 1
REPEAT 3 ; e dura 3 scanlines
sta WSYNC ; (WSYNC = aguarda fim
REPEND ; da scanline)
lda #0 ; VSYNC finaliza
sta VSYNC ; limpando o bit 1
VSYNC
VBLANK
KERNEL
OVERSCAN
96. Desligando elementos
lda #$00
sta ENABL ; Desliga ball
sta ENAM0 ; Desliga missiles
sta ENAM1
sta GRP0 ; Desliga players
sta GRP1
VSYNC
VBLANK
KERNEL
OVERSCAN
97. Configurando o Playfield
sta COLUBK ; Cor de fundo (0=preto)
sta PF0 ; PF0 e PF2 ficam apagados
sta PF2
lda #$FF ; Cor do playfield
sta COLUPF ; (possivelmente amarelo)
lda #$00 ; Reset no bit 0 do CTRLPF
sta CTRLPF ; para duplicar o PF
ldx #0 ; X=contador de scanlines
VSYNC
VBLANK
KERNEL
OVERSCAN
98. VBLANK propriamente dito
REPEAT 37 ; VBLANK dura 37 scanlines,
sta WSYNC ; (poderíamos ter lógica
REPEND ; do jogo aqui)
lda #0 ; Finaliza o VBLANK,
sta VBLANK ; "ligando o canhão"
VSYNC
VBLANK
KERNEL
OVERSCAN
99. Kernel
Scanline:
cpx #174 ; Se acabou a frase, pula
bcs FimScanline; o desenho
txa ; Y=X/2 (usando o shift
lsr ; lógico para dividir,
tay ; que só opera no A)
lda Frase,y ; Frase,Y = mem(Frase+Y)
sta PF1 ; PF1 = bits 5 a 11 do
; playfield VSYNC
VBLANK
KERNEL
OVERSCAN
100. Kernel (continuação)
FimScanline:
sta WSYNC ; Aguarda fim da scanline
inx ; Incrementa contador e
cpx #191 ; repete até até a
bne Scanline ; completar a tela
VSYNC
VBLANK
KERNEL
OVERSCAN
101. Fechando o loop principal
Overscan:
lda #%01000010 ; "Desliga o canhão":
sta VBLANK ; 30 scanlines de
REPEAT 30 ; overscan...
sta WSYNC
REPEND
jmp InicioFrame ; ...e começa tudo de
; novo!
VSYNC
VBLANK
KERNEL
OVERSCAN
102. A frase, bit a bit
Frase:
.BYTE %00000000 ; H
.BYTE %01000010
.BYTE %01111110
.BYTE %01000010
.BYTE %01000010
.BYTE %01000010
.BYTE %00000000
.BYTE %00000000 ; E
.BYTE %01111110
...
103. A frase, bit a bit
...
.BYTE %00000000 ; D
.BYTE %01111000
.BYTE %01000100
.BYTE %01000010
.BYTE %01000010
.BYTE %01000100
.BYTE %01111000
.BYTE %00000000 ; Valor final do PF1
104. Configurações finais
ORG $FFFA ; Ficam no final da
; ROM (cartucho)
.WORD InicioFrame ; Endereço NMI
.WORD InicioFrame ; Endereço BOOT
.WORD InicioFrame ; Endereço BRK
END
108. Placar com playfield
Para identificar os placares, é
possível usar as cores dos players
no playfield, setando o bit 1 do
registrador CTRLPF (score mode)
O lado esquerdo fica com a cor do
P0, e o direito com a cor do P1
117. Pitfall!
Cada uma das 256 telas é definida
(objetos, árvores, paredes...) por 1 byte,
que deveriam ser armazenados no
cartucho (ROM)
Tamanho da tabela: 256 bytes
118. Pitfall!
Solução: gerador de sequência com
aleatoriedade aceitável e que também
gera o valor anterior a partir do atual,
para voltar telas (LFSR bidirecional)
Tamanho do código: 50 bytes
http://en.wikipedia.org/wiki/Linear_feedback_shift_register
119. River Raid
A mesma solução é aplicada com um
gerador de 16 bits (que eventualmente
se repete), com pequenos ajustes para
tornar os primeiros setores mais fáceis.
Ao passar a ponte, o jogo guarda o
valor atual, recuperando em caso de
morte para voltar no mesmo setor
121. Posição horizontal
Não existe um registrador para
determine a posição horizontal de
players, missiles ou ball
Você tem que contar o tempo
até que o canhão esteja na posição
e acionar o strobe correspondente
123. Dá pra calcular...
1 ciclo de CPU = 3 pixels
WSYNC = 20 ciclos
posição x ≈ (ciclos – 20) * 3
...mas é aproximado, porque o TIA só lê
os registros a cada 5 ciclos de CPU,
tornando inviável para movimento ↔
124. Soluções
Você pode mover player, missile ou
ball relativamente à posição anterior,
usando um registrador de 4 bits
(isto é, movendo de -7 a +8 pixels)
E o missile pode ser posicionado no
meio do player correspondente,
tornando fácil “atirar” ele (daí o nome)
128. Placar com múltiplos dígitos
O truque é o mesmo do placar com
playfield: mudar a imagem com o
canhão andando, mas o timing tem que
ser muito mais preciso
Digamos que o placar seja 456789...
129. Placar com múltiplos dígitos
Comece cada scanline com a linha
do 4 no GRP0 e do 5 no GRP1.
Configure NUSIZ0 e NUSIZ1 para
repetir três vezes:
4 4 4 5 5 5
Player 0 Player 1
130. Placar com múltiplos dígitos
Posicione o player 1 à direita do
player 0, encavalando as cópias:
Player 1
454545
Player 0
131. Placar com múltiplos dígitos
Troque o desenho dos players
(GRP0/GRP1) sincronizando com o
canhão, assim:
454545
CANHÃO
132. Placar com múltiplos dígitos
Quando o canhão estiver terminando
a 1ª cópia do player 1, altere o player
0 para 6 e o player 1 para 7:
454545
CANHÃO
133. Placar com múltiplos dígitos
Repita o truque ao final da 2ª cópia
do player 2, dessa vez alternado o
player 0 para 8 e o player 1 para 9
456767
CANHÃO
134. Placar com múltiplos dígitos
Faça a mesma coisa para
cada scanline do placar!
456789
CANHÃO
135. Placar com múltiplos dígitos
É mais difícil do que parece: não dá
tempo de carregar bitmaps da memória
quando o canhão passa, e só temos 3
registradores para guardar 4 dígitos...
...mas é isso que torna divertido!
137. Tirando leite de pedra
Quando observar um jogo de Atari,
tente identificar os truques que o(a)
programador(a) usou: como dividiu a
tela, o que tem em cada scanline, como
gastou a RAM e a ROM...
138. Mãos à obra!
Você pode fazer seu jogo de Atari – é
um desafio de programação divertido!
Será preciso estudar várias coisas que
não detalhamos: contagem de ciclos,
som, leitura de joysticks... mas dá!
139. Para aprender mais
O nosso Hello, World: http://pastebin.com/abBRfUjd
Sorteio 2600 http://github.com/chesterbr/sorteio2600
Racing The Beam (livro): http://bit.ly/dSqhjS
Palestra David Crane (Pitfall): http://youtu.be/MBT1OK6VAIU
Tutoriais do Crane para iOS: http://bit.ly/9pwYHs e http://bit.ly/qWBciZ
Stella Programmer's Guide: http://emu-docs.org/?page=Atari%202600
Código-fonte de jogos clássicos: http://classicdev.org/wiki/2600/Source_Code
Especificações do Atari: http://nocash.emubase.de/2k6specs.htm
Referência 6502: http://bit.ly/hxG5c6
Emulador no browser: http://jogosdeatari.com.br/
Tutorial Andrew Dave: http://bit.ly/ptQDdA (o site todo é bom)
Cartucho com leitor de SD: http://harmony.atariage.com/
BAtari (compilador BASIC): http://bataribasic.com
Exemplos de som no TIA: http://bit.ly/tnbPrp
Bankswitching (mais ROM/RAM): http://bit.ly/tqhLZk