Programming sprites on the Commodore 64, a simple tutorial using BASIC V2

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:

POKE V+21,1

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.


Expanding sprites

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


Sprite collisions

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.


10 Replies to “Programming sprites on the Commodore 64, a simple tutorial using BASIC V2”

  1. Thanks for this. I’ve wanted to learn this my entire life, but never got around to it. With the help of this tutorial, I made my first C64 game, 30 years later!

  2. Great tutorial.

    A minor correction: “So, to set sprite 0 to X position 300, we must set bit 0 of register V+16 to 1, the we must set X position register V to the value 324-255 = 69.” In this sentence, 300 should be 324 and 255 should be 256 (which has the effect that 69 should be 68 of course).

    The value of the ninth bit in the X position is 2^8 = 256.

    1. Hi! Thanks for appreciating my tutorial 🙂 Thanks ever so much for this important contribute! Now I fixed my post.

      I should have put 324, I put 300 by mistake, now fixed it – many thanks!
      As for 256 you are right too, but the reason is another in my humble opinion. Since I didn’t have this thing clear, I did some considerations – I’m putting them here as a help for others who may have my same doubts.
      For instance, if you want to put the sprite in position 260 and you are currently in position 255, you activate the extended position and then:
      x position 0 is 256
      x position 1 is 257
      x position 2 is 258
      x position 3 is 259
      x position 4 is 260
      So yes, to put sprite in position 260 you have to store a value of 260-256 into the X register. And, in the case of the tutorial, you have to put 324-256= 68.
      In fact, if you put sprite 0 in position 255, and then you code:
      POKE V+16,1: POKEV,0
      you’ll see that the sprite moves by one pixel, so when the extended position is activated, x = 0 is absolute position 256 on screen. So, to get the value to put in the x register while extended position is active, we need to compute Absolute position – 256.
      The only thing I have to point out is that the reason is not that 2^8 equals 256 (for bit 8). In fact, X ABSOLUTE position is 256 when bit 8 is turned OFF. In binary numbers, 256 is 100000000. In low byte – high byte order: 00000000, 00000001. We may consider the high byte as the value of the extended position. But that is not the case, as if we put a 0 in the X value when extended position is active, we already get an absolute value of X of 256. So, it’s just that the VIC-II chip has been designed like this. My mistake in fact derived from the bad assumption that the extended position value is actually the high byte of the 9 bit number required for absolute X-position.

  3. Dear,
    Thank a lot for your masterful post!
    I have a question for you: what can be an effective way to understand which character on the screen collided with a sprite in BASIC? In a manual I found this formula:
    C = INT (1024 + PEEK (SPRITE POS X) / 8 + PEEK (SPRITE POS Y) / 8)
    IFPEEK (V + 16) = SPRITE’SBITOVER255 THEN C = C + 32
    but the number C which represents the position of the character is not good. Do you have any advice for me? Heartfelt thanks in advance!
    See you!

  4. Hi there! Thank you for your kind words! Sadly I am not on C64 programming lately, but if in the future I’ll be back on coding I’ll try to give you some help. Bye!

Leave a Reply

Your email address will not be published. Required fields are marked *

Insert math as
Additional settings
Formula color
Text color
Type math using LaTeX
Nothing to preview