Programming sprites on the Commodore 64 is quite simple, even with Commodore BASIC V2 (the built-in BASIC interpreter). The Commodore 64 features hardware sprites, and some VIC-II registers are used to control them. As a result, POKE instructions are required for sprites programming.
Let’s have a tour at the various registers and locations used for sprites.
VIC-II registers base address and offsets. Turning sprites on
The base address of the VIC-II chip registers is 53248. We can assign this value to a variable, then use offsets to access registers. So, we can make the assignment:
V = 53248
Adding an offset to the variable V now makes it possible to refer to a particular VIC-II register.
To turn sprites on, register V+21 is used. Eight hardware sprites are available, and each bit is used to turn on or off the sprites. That means, the instruction:
POKE V+21, 255
Will turn on all the sprites.
Sprites are numbered from 0 to 7. Sprites have display priorities according to their number. Display priority is used when sprites overlap. Sprite 0 will be shown in front of other sprites. Sprite 1 will be hidden by sprite 0, but it will be shown in front of sprite 2. Sprite 7 has the lowest display priority – all other sprites will be shown in front of it.
Sprite 0 is controlled by the bit 0 of register V+21, sprite 1 is controlled by the bit 1 of register V+21, and so on. So, to turn on the sprite number 0 only, we have to use the statement:
To turn on both sprite 0 and sprite 1:
POKE V+21, 3
It is possible to turn on or off any sprite we want to on a given time. And we can turn on some sprites and turn off others with just a single POKE instruction.
Sprite shapes in memory
A Commodore 64 sprite is a 24 x 21 pixels movable object. It can be seen as a bigger programmable character that can be moved on hardware on steps of one pixel.
Its shape – that is, the way it looks like – depends on the contents of some RAM memory area. We just have to choose which memory area to use to hold the sprite shape, then we must put sprite data on it.
The VIC-II chip can access four different memory banks, but in order to keep things simple, we will only deal with the default VIC-II memory bank.
Each sprite has a pointer that tells you where its shape lies in RAM memory. Default sprite pointers can be found in locations 2040 to 2047 decimal. 2040 is the pointer for sprite 0, 2041 is the pointer for sprite 1 and so on.
Each sprite requires 64 bytes. Actually, one byte is left unused but it must be taken into account any way. Each sprite pointer holds a value, which is the ratio between the memory location sprite shape starts from and the value 64. So, if we want sprite 0 shape data to start from location 12288, we have to code:
POKE 2040, 192
192 is the ratio between the values 12288 and 64.
Defining sprite shapes and colours
A sprite requires 63 bytes to define its shape. You have 21 rows with three bytes on each. If the pointer for sprite 0 is set like on the previous example, sprite shape data can be found from location 12288 to location 12288+62 = 12350.
If we want the sprite to be just a square (actually, a rectangle, as a sprite is 24 x 21 dots wide), we may code the following:
for t = 12288 to 12350 : poke t, 255 : next
Sprites can be either hi-res or multicolor. Anyway, we are assuming that hi-res sprites are used.
Overall colour can be defined for each hi-res sprite. Please note that there are no colour clash issues. Sprites can overlap freely with each other, and they also mix with the background nicely.
There is a colour register for each sprite. They range from register V+39 (for sprite 0) to V+46 for sprite 7. Register V+40 is used for sprite 1, register V+41 for sprite 2 and so on. Plain simple.
During sprite shape definition, if a bit is set to 1, the matching pixel will have the same colour as the one in the sprite colour register. If a bit is set to 0, the matching pixel will be transparent, so you will be able to see characters, bitmap images or sprites through it.
Setting sprites position (PAL systems)
Each sprite can be set on any part of the screen. Three registers are used for each sprite.
If we are happy with X positions from 0 to 255 only, then only two registers are required. One register will hold the X position, the other one will contain the Y position.
X positions start from register V and correspond to the even values. So, X position for sprite 0 is set by register V, X position for sprite 1 is set by register V+2, X position for sprite 2 is set by register V+4 and so on.
Y positions start from register V+1 and correspond to odd values. So, Y position for sprite 0 is set by register V+1, Y position for sprite 1 is set by register V+3, Y position for sprite 2 is set by register V+5 and so on.
Coordinates are referred to the top left-hand corner of a sprite. So, to put a sprite in the top left-hand corner of the screen, X and Y coordinates are 24 and 50 respectively. This way, the sprite will be fully visible, and it will be perfectly near the borders. The following code may be used for sprite 0:
10 v=53248:pokev+21,1:poke2040,192:fort=12288to12350:poket,255:next 20 pokev+39,1:pokev,24:pokev+1,50
To put the sprite into the bottom left-hand corner, the Y coordinate should be changed to 229 (POKE V+1, 229 in line 20). Again, the sprite is fully visible, perfectly near the borders. As a sprite is 21 pixels high, if you change that poke again into POKE V+1, 250, the sprite will not be visible any longer. The sprite is hidden by the bottom border. If you issue POKE V+1, 249, then you will see the first row of pixels of the sprite.
So, even if they are active, sprites can be made totally invisible by hiding them behind the border. This way, you can make sprites enter on the screen from the border. Please look at the following example (you can copy and paste it to Vice to run the program – remember to hit return so that line 40 is also accepted).
10 v=53248:pokev+21,1:poke 2040,192:fort=12288to12350:poket,255:next 20 pokev+39,1 25 x=0:y=50 30 pokev,x:pokev+1,y 35 wait53265,128 38 x=x+1:ifx>255thenend 40 goto 30
A similar program can be written for the Y direction:
10 v=53248:pokev+21,1:poke 2040,192:fort=12288to12350:poket,255:next 20 pokev+39,1 25 x=24:y=29 30 pokev,x:pokev+1,y 35 wait53265,128 38 y=y+1:ify>250thenend 40 goto 30
Please note that line 35 is needed to avoid flickering. This way, the sprite is moved one pixel per frame.
As a further example, the following program will line up all eight sprites, then it will move them vertically. The program is not written to be fast but just to show off sprite positioning.
10 v=53248:pokev+21,255:fort=12288to12350:poket,255:next 20 fort=0to7:pokev+39+t,1:poke2040+t,192:next 25 x=24:y=29:gosub 100 30 pokev+sx,x 35 x=x+28:sx=sx+2:if sx>14 then 45 40 goto 30 45 y=y+1:gosub 100:ify>250 then end 50 goto 45 100 fort=0to15step2:pokev+1+t,y:next 110 return
As a sprite is 24 pixels wide, the code x=x+28 in line 35 sets a four pixels horizontal gap between sprites. If you replace it with x=x+24, then you will see a moving bar, as sprites would be perfectly close to one another.
Some flickering happens, this is due to the slowness of BASIC. This is just a tutorial-like unoptimized code.
As the X coordinate is limited to 255, we cannot increase the horizontal gap between sprites by more than four pixels. But, sprites can be actually moved on all X coordinates. Since a given memory location cannot hold a value bigger than 255, and since the Commodore 64 viewable screen is 320 pixels wide, another register is required to fully set X position.
If X position is more than 255, then register V+16 must be used. This register can enable the extended X position for each sprite. Each bit corresponds to a different sprite, like on register V+21.
So, to set sprite 0 to X position 324, we must set bit 0 of register V+16 to 1, then we must set X position register V to the value 324-256 = 68.
In fact, absolute X positions from 0 to 255 are handled with extended X position disabled; the remaining absolute positions are from 256 on, and are handled with extended X position enabled. That’s why we have 324-256 in the previous example. To put it clear, when X extended position is enabled, on the X register the value 0 means 256 for the absolute position.
In a way, we may think that when we go past absolute position 255 we have some sort of wrap around to 0. But, register V+16 is needed to make a distinction from a 0 value in X register that actually means “0 X absolute position”, and from a 0 value in the X register that instead means “256 X absolute position” as a result of a wrap around from 255.
In order to provide a simple example on extended X positioning, the following program will move sprite 0 horizontally across the two side borders.
10 v=53248:pokev+21,1:pokev+16,0:poke2040,192:fort=12288to12350:poket,255:next 15 x=24:xr=24 20 pokev+39,1:pokev+1,150 25 pokev,x:x=x+1:xr=xr+1:ifx>255then pokev+16,1:x=0 28 if xr>320 then end 30 goto 25
Again, the program is written for clarity rather than efficiency. Raster synchronization has been omitted as well. The variable xr holds the real X position, which is the value that takes into account the full width of the screen.
The variable x holds the actual X position to be stored on X position register. When x is greater than 255, it cannot be poked on the X register. So, we must enable the extended X positioning by properly setting register V+16, then we must reset the value of x. This is performed by the assignment X = 0 in line 25.
When more sprites are moved, it is important to remember that any modification to the value of register V+16 must be done so that only the required sprites are affected. Otherwise, unwanted displacements may happen.
So, to enable extended X positioning for sprite 0 without interfering with the X position of other sprites, we may code:
poke v+16, peek(v+16) or 1
Register V+16 controls the way the X coordinate of a given sprite is interpreted. So, changing this register will make one or more sprites change their position, even without altering the value on X position registers. That’s why a bitwise control is mandatory when more sprites are in play.
The following program enables all eight sprites, then sets extended X positioning for sprites 7 and 6 only. Other sprites positions are left unchanged.
10 v=53248:pokev+21,255:fort=12288to12350:poket,255:next 15 pokev+16,0 20 fort=0to7:pokev+39+t,1:poke2040+t,192:next 25 x=24:y=150:gosub 100 30 pokev+sx,x 35 x=x+28:sx=sx+2:if sx>10 then 45 40 goto 30 45 pokev+16,peek(v+16)or192 50 pokev+12,0:pokev+14,28 99 end 100 fort=0to15step2:pokev+1+t,y:next 110 return
Although the instruction on line 45 is the proper way to set things up, a straight POKEV+16,192 would have worked in this case. But if we want to move sprite 6 alone to X position 192, we must use a bitwise approach (so using both PEEK and POKE instructions). Please take a look at the following picture.
Only bit 6 of register V+16 have been disabled. This way, sprite 7 has been properly retained its extended X positioning.
Sprites can be expanded in both X and Y direction. Sprite size is just doubled in either X or Y direction. A sprite can be also expanded in both directions at the same time.
An X expansion register (V+29) and an Y expansion register (V+23) are provided. Each bit of these registers controls a given sprite.
When a sprite is expanded, its resolution is halved. For instance, if a sprite is expanded on the X direction, its pixels will be doubled in size. On the other hand, if a sprite is expanded on the Y direction, its pixels will be doubled in height. And finally, if a sprite is expanded in both directions, its pixels will be four times bigger than conventional hi-res pixels. However, despite the resolution loss, expanded sprites can still be moved smoothly in steps of one hi-res pixel.
The following program creates a couple of chessboard-like sprites. One sprite is being expanded so that you can see the resolution loss that happens. Of course, you can also see how big sprites become. Again, you can paste the program on the VICE emulator. After you paste, remember to hit return.
5 print chr$(147) 10 v=53248:pokev+21,3:poke2040,192:poke2041,192 12 fort=12288to12350step6 15 poket,170:poket+1,170:poket+2,170 16 poket+3,85:poket+4,85:poket+5,85 18 next t 20 pokev+39,14:pokev+40,14:pokev,100:pokev+1,100:pokev+2,50:pokev+3,100 25 gosub 100 30 pokev+29,1:gosub 100:pokev+29,0:gosub100:pokev+23,1:gosub100 35 pokev+29,0:pokev+23,0:gosub100:pokev+29,1:pokev+23,1 99 end 100 fort=1to1600:next 110 return
The VIC-II chip offers automatic sprite collision detection capabilities. Although these features usually require machine language, a simple BASIC program explaining how collisions work will be offered.
Sprite to sprite collisions and sprite to background collisions can be detected on hardware. Register V+30 is used for sprite to sprite collisions, register V+31 is used for sprite to background collision. On each register, each bit corresponds to a different sprite. For instance, if sprite 0 collides with sprite 1, both bit 0 and bit 1 will be set to a 1. That means, register V+30 will hold the value 3 (assuming no other sprites are colliding). Whenever a collision register is read, it will be automatically reset.
If sprite 0 collides with a character on the screen, then bit 0 of register V+31 will be set to a 1.
The following program uses the above concepts. As BASIC is quite slow, a counter (CD) has been set up so that when sprite 0 collides with a character, some time is allowed before the sprite to character collision register is read again. That compensates for the slow collision detection performance offered by BASIC and avoids that the sprite gets stuck due to un unwanted collision detection.
When sprite 0 hits a character, the direction of its movement is reversed. Furthermore, sprite 1 is set back into position, so that a new collision with sprite 0 can happen.
Sprite to sprite collision is also demonstrated. When sprite 0 and sprite 1 collide, sprite 1 smoothly moves down.
10 print chr$(147):v=53248 12 printchr$(13);chr$(13);chr$(13):printspc(2)chr$(209)spc(22)chr$(209) 15 fort=12288to12350:poket,255:next:poke2040,192:poke2041,192 20 pokev+39,1:pokev+40,3:sc=v+31:ss=v+30 25 x=80:dx=1:pokev,x:pokev+2,125:pokev+1,80:pokev+3,80:c=0:pokev+21,3 30 x=x+dx:pokev,x 33 xx=peek(sc):xy=peek(ss) 34 ifcd=0thenifxx=1thendx=-dx:pokev+3,peek(v+3)-23:cd=20 35 ifcd<>0thencd=cd-1 38 ifxy=3thenpokev+3,peek(v+3)+1 46 goto30
This simple article has been written in order to have a quick review of sprites. I don’t have a great experience with sprites, so having a look at those simple concepts has been helpful to me. If you needed to dust off your C64 sprites knowledge, I hope this article has been of help. There are a lot of books and tutorials dealing with Commodore 64 sprites, should you require to go deeper into concepts. I think the book “Mapping the Commodore 64” is a good start.