Intro to the bass (badass) 6502 assembler
Part 3
In part 2 we saw how to
-
Use macros to set up a C64 graphics screen.
-
Use
!section
to locate the graphics in memory. -
Using arrays and slicing to parse a koala image file at compile time.
Now let’s add a sprite!.
spritePtrs = screenTarget + 1016
spriteMem = $5f80
; Enable sprite 2
lda #$04
sta $d015
; Set sprite 2 pointer
lda #(spriteMem-$4000)/64
sta spritePtrs+2
; Copy sprite data
ldx #3*21
$ lda spriteData,x
sta spriteMem,x
dex
bne -
spriteData:
!rept 3*24 { !byte $ff }
The !rept
command is used to repeat any block of statements a given number
of times. Here we use it to create a solid rectangle as out sprite.
But a solid color is not very interesting. What if we want to generate some graphics for our sprite, like a geometric shape?
For this it’s time to turn to lua scripting;
%{
-- Set a pixel at (x,y) in the given target bitmap
function setPixel(target, width, x, y)
offs = width * y + (x>>3) + 1 -- 1-Indexed arrays :(
if offs >=1 and offs <= #target then
target[offs] = target[offs] | 1<<(7-(x&7))
end
end
-- Draw a circly into target bitmap
function circle(target, width, xp, yp, r)
for y=-r,r, 1 do
for x=-r,r, 1 do
if x*x+y*y <= r*r then
setPixel(target, width, xp+x, yp+y)
end
end
end
return target
end
}%
You can use %{
/ }%
at any time to switch to script mode and write
LUA code, and functions defined in LUA will be callable from the Assembler.
You can also put lua code in a lua file and pass it as a script on the command line.
The code above will give us a circle()
function that renders a circle into a vector of bits (the format used by monochrome sprites).
We can now use this function to generate a ball sprite instead of our ugly rectangle:
circle_sprite = circle(zeroes(3*21), 3, 12, 10, 10)
spriteData:
!fill circle_sprite
The lua function will take an array as input, and return an array
with the rendered circle. Just like we did with the graphics and music
earlier, we can use the !fill
command to put it into memory.
Let’s make ths sprite move. Here is some code to make the sprite move in a sine wave:
update_sprite
ldx xy
ldy xy+1
inc xy
inc xy+1
inc xy+1
lda sine,x
sta $d004
lda sine,y
sta $d005
rts
xy: !byte 0,0
sine:
!rept 256 { !byte (sin(i*Math.Pi*2/256)+1) * 100 + 24 }
Here we use the !rept
command again, this time to create a sine table.
So this is all pretty good - but can we take advantage of the fact that we can generate circles of different sizes?How about a small animation?
First, lets generate 8 sprites with different radiuses:
spriteData:
!rept 8 {
!fill circle(zeroes(3*21), 3, 12, 10, i + 3)
!byte 0
}
And we need to copy all 8 frames to sprite memory:
!rept 8 {
ldx #3*21
$ lda spriteData-1+i*64,x
sta spriteMem-1+i*64,x
dex
bne -
}
Then let’s add another sine table with an amplitude that matches the number of frames, and add some code to set the sprite pointer every frame:
lda sine2,x
adc #(spriteMem-$4000)/64
sta spritePtrs+2
rts
sine2:
!rept 256 { !byte (sin(i*Math.Pi*2/96)+1) * 3.5 }
This should be enough to make our ball appear to move in all 3 dimensions.