The hardest part was similar to the last problems I talked about: cycle timings getting messed up while crossing a page boundary.
To get a nice large "hi-res" graphic on the atari, you have to take both sprite objects, tell the Atari to repeat them 3 times close together, and then stagger them. (Imagine the 3 repeated planes on Combat, or the 3 players on Football. Then imagine 2 sets of those interleaved with each other) That gets you halfway there, but then you have to very exact timings to update the sprite's graphic data mid-scanline. Because the Atari is so doggone slow, the timings are really tricky. (Although the technique eventually became fairly well-known -- any game with a 6-digit score display most likely used this technique).
I got the code all working thanks to that sample code I posted to above, but if I fiddled much with the graphics data, the timings would get off by 1 clock cycle, and it would fail. Turns out, again, that it was crossing a page boundary. This time with a LDA instruction (loading the accumulator from memory) -- if you do a "LDA foo,X" command, which loads the accumulator with whatever byte is at memory location (foo + the value of the X register), and foo+X crosses a page boundary from foo, it takes 5 cycles instead of 4.
Luckily the assembler gives you a nice "align" pseudo-opcode that lets you force alignment of your data into page boundaries, which fixed it.
After getting the title displayed, I wanted to make it pretty. I have 8 whole clock cycles free per line to do what I want with, but to update the color of the whole thing, I have to write to 2 registers (COLUP0 and COLUP1, the color registers for each sprite), which alone takes 6 cycles. There's not enough time left to pick or calculate the color for the scanline.
So I unrolled the kernel loop by half, which lets me use 16 clock cycles every 2 lines. That's plenty of time to load a base color, add to it based on the current line, and then write it to the color registers. The end result is a pretty animated title graphic:
Right now the PASSWD option doesn't do anything :-( |
The half-unrolled loop ended up looking like:
.loop:
sty tmpVar ; 3
;update the color
tya ; 2
adc PlayerColorDef ; 3
sta COLUP0 ; 3 = 11
lda (ptr+$0),y ; 5
sta GRP0 ; 3
lda (ptr+$2),y ; 5
sta GRP1 ; 3
lda (ptr+$4),y ; 5
sta GRP0 ; 3
lda (ptr+$6),y ; 5
sta tmpVar2 ; 3 = 32
lax (ptr+$a),y ; 5
lda (ptr+$8),y ; 5
ldy tmpVar2 ; 3
sty GRP1 ; 3
sta GRP0 ; 3
stx GRP1 ; 3
sta GRP0 ; 3 = 25
ldy tmpVar ; 3
dey ; 2
;burning 3 cycles in place of the bpl since the loop is half-unrolled
sty tmpVar ;3 (in place of the bpl)
sty tmpVar ; 3
;update the color
tya ; 2
adc PlayerColorDef ; 3
sta COLUP1 ; 3 = 11
lda (ptr+$0),y ; 5
sta GRP0 ; 3
lda (ptr+$2),y ; 5
sta GRP1 ; 3
lda (ptr+$4),y ; 5
sta GRP0 ; 3
lda (ptr+$6),y ; 5
sta tmpVar2 ; 3 = 32
lax (ptr+$a),y ; 5
lda (ptr+$8),y ; 5
ldy tmpVar2 ; 3
sty GRP1 ; 3
sta GRP0 ; 3
stx GRP1 ; 3
sta GRP0 ; 3 = 25
ldy tmpVar ; 3
dey ; 2
bpl .loop ; 2/3 = 7/8
1 comment:
I'm amazed by all this, really!
(But NO, I won't start learning another assembly)
Post a Comment