Italian Sprite Multiplexing Tutorial

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 56

https://intoinside.github.

io/archive/

Sprite multiplexing - part 1

The first post (the serious one with content), is about sprite multiplexing on the Commodore 64. It is
a technique for displaying more sprites than the hardware can show at the same time. In this first
part, I will make a quick pass at explaining the concept of sprites; in the next post I will move on to
multiplexing.

The C64 produces its output on video through the MOS 6569/8565/8566, better known as the VIC-II.
This is a component, an evolution of the VIC-I used on the Vic-20, that can handle, among other
things, the display of sprites.

Sprites are graphic elements measuring 24x21 pixels. They can be displayed in HiRes mode (1 color +
the transparency color) or Multicolor mode (3 colors + the transparency color). There are limitations
in using the 3 colors: two of them are common to all sprites, while one is specific to the individual
sprite. Also, in this mode, the pixels are twice the size.

The VIC-II can handle up to 8 sprites simultaneously on the screen, and their behavior is governed by
a series of memory registers that allow you to:

move each sprite on the screen (addresses $d000-$d00f and address $d010 to move beyond the x
coordinate = 255)

display or hide each sprite ($d015)

double the height or width ($d017 and $d01d)

declare sprites HiRes or Multicolor ($d01c) ...and then set colors, detect collisions, and many other
properties.

In this example I will make use of a sprite created by hand with SpritePad
As you can see from the image, it is a HiRes sprite with yellow main color (7) and blue transparent
color (6).

The following Assembly listing (for use with KickAssembler) is used to set all 8 sprites with the same
image and color, on line 150.

In the first block I set the color, in the second I indicate the reference in memory where to find the
sprite to be drawn, then I set the X and Y coordinate of the sprites, finally I color the screen black and
enable all the sprites. At the bottom of the listing is the definition of the sprite mapped from memory
location $0940.

:BasicUpstart2($0810)

* = $0810

Init:

// Color of sprites

lda #YELLOW

sta $d027
sta $d028

sta $d029

sta $d02a

sta $d02b

sta $d02c

sta $d02d

sta $d02e

// Sprite pointer

lda #$25

sta $07f8

sta $07f9

sta $07fa

sta $07fb

sta $07fc

sta $07fd

sta $07fe

sta $07ff

// I set the X coordinate of all sprites

lda #31

sta $d000

lda #62

sta $d002

lda #93

sta $d004

lda #124

sta $d006

lda #155

sta $d008

lda #186
sta $d00a

lda #217

sta $d00c

lda #248

sta $d00e

// I set the Y coordinate of all sprites

lda #150

sta $d001

sta $d003

sta $d005

sta $d007

sta $d009

sta $d00B

sta $d00D

sta $d00F

// Color border and background of black

lda #0

sta $d020

sta $d021

// I enable all sprites

lda #%11111111

sta $d015

rts

* = $0940

Sprites:
.byte $00,$00,$00,$00,$00,$00,$03,$f8,$00,$07,$f8,$00,$0f,$f8,$00,$1f

.byte $f8,$00,$1f,$07,$f0,$3e,$07,$e0,$3c,$07,$c0,$3c,$07,$80,$3c,$00

.byte $00,$3c,$07,$80,$3c,$07,$c0,$3e,$07,$e0,$1f,$07,$f0,$1f,$f8,$00

.byte $0f,$f8,$00,$07,$f8,$00,$03,$f8,$00,$00,$00,$00,$00,$00,$00,$07

Running this program produces the following output.

Okay, that's the starting point. In the next post, we will do the interesting stuff!

Want to make a comment or discuss the contents of this article? I'll be waiting for you on Gitter!

The most interesting discussions will be added here.


Sprite multiplexing - part 2

Okay, well, having understood the concept of sprites and reiterated that the C64 cannot show more
than 8 at once, let's try to understand how more can be added. To do this, we need to introduce the
tool behind the technique. It is called interrupt raster.

One of the special features of the Vic-II is the ability to generate a "signal" to the processor that can
interrupt (hence the word interrupt) program execution at the moment the image is physically
generated on the screen. It is a bit like the Vic-II saying to the processor, "Hey, finish the instruction
you are executing right now, then give me some time." The processor, diligently, completes the
instruction in progress then transfers control to a known memory location that contains a script.

Upon completion of this script, the processor resumes execution where it left off. There are actually
other steps and activities performed to make this process possible but, simplifying, the flow is this.

The screen grid on which the image is drawn is called a raster, the horizontal lines are called
scanlines, and the components of the grid are called pixels. Through certain sequences of commands,
it is possible to tell the Vic-II to generate an interrupt when it is about to draw a given scanline.

This is all well and good, but what is the basic idea behind the multiplexing mechanism? It is to take
advantage of the 8 available sprites, deluding those who are looking at the image into thinking that
they have more by simply moving them around while drawing the screenline.

To understand this, if I want to draw 8 sprites on line 150 and as many on line 200, thus getting 16
sprites visible at the same time, the following steps should be followed:

Set an interrupt on line 149

Move the sprites to line 150

Set an interrupt to line 199

Move the sprites to line 200

Start over again

Therefore, going into more detail:

the program is interrupted when it is starting the drawing of line 149

the routine executed during the interrupt moves all sprites to line 150

a new interrupt is set at line 199


normal program execution resumes in the meantime the sprites have been drawn at line 150

the program is interrupted when it is starting the drawing of line 199

the routine executed during the interrupt (different from the previous one) moves all the sprites to
line 200

an interrupt is set again at line 149 meanwhile the sprites have been drawn at line 200 (along with
those in line 150)

Q: Why is the interrupt set to line 149 and the sprites are placed on line 150?

A: The placement of sprites on a given area must occur before that area is drawn. If the interrupt
were set after line 150, the screen area prior to the scanline has already been drawn and a
repositioning of the sprites to that area would have no effect. It is also not possible to set the
interrupt on scanline 150 because there must be time to finish the move operations and you risk
having a partial drawing of the sprites.

How to set an interrupt

The activities to indicate at which line the Vic-II should throw an interrupt are:

lda #149

sta $d012

lda #<Irq

sta $0314

lda #>Irq

sta $0315

The first two instructions load the value 149 (the scanline of the interrupt we want) into the
accumulator and, from there, into the address $d012 (this is the address of the Vic-II designated for
this purpose). The next 4 instructions load the address of the routine to be executed when the
interrupt is thrown. It is a 16-bit address to be loaded into two 8-bit locations, so in $0314 there will
be the low (least significant) part of the address of the routine and in $0315 there will be the high
(most significant) part. In our example, the routine is labeled Irq.

Full Listing

Below is the complete listing followed by a bit of explanation.

:BasicUpstart2($0810)
* = $0810

Init:

// SEt Interrupt bit, prevents Cpu from responding to interrupts.

// Prevents, while we are defining things, the program from being

// interrupted sei

// Sprite pointer

lda #$28

sta $07f8

sta $07f9

sta $07fa

sta $07fb

sta $07fc

sta $07fd

sta $07fe

sta $07ff

// I set the X coordinate of all sprites

lda #31

sta $d000

lda #62

sta $d002

lda #93

sta $d004

lda #124

sta $d006

lda #155

sta $d008

lda #186

sta $d00a

lda #217
sta $d00c

lda #248

sta $d00e

// Color border and background of black

lda #0

sta $d020

sta $d021

// Turns off interrupts that may come from CIA-1

lda #%01111111

sta $dc0d

// Reset bit 7 of the Vic-II raster register.

and $d011

sta $d011

// Confirmation for interrupts generated by CIA-1 and CIA-2

lda $dc0d

lda $dd0d

// I set the first interrupt to line 149

lda #149

sta $d012

// I set the routine at address Irq

lda #<Irq

sta $0314

lda #>Irq

sta $0315
// Enables Vic-II to launch interrupts

lda #%00000001

sta $d01a

// I enable all sprites

lda #%11111111

sta $d015

// Allows the CPU to respond to interrupts that arrive

cli

rts

Irq:

// Moving sprites on line 150

lda #150

sta $d001

sta $d003

sta $d005

sta $d007

sta $d009

sta $d00B

sta $d00D

sta $d00F

// Color all sprites of green

lda #GREEN

sta $d027

sta $d028

sta $d029

sta $d02a

sta $d02b
sta $d02c

sta $d02d

sta $d02e

// I set the next interrupt to line 199

lda #199

sta $d012

// I set the routine at address Irq2

lda #<Irq2

sta $0314

lda #>Irq2

sta $0315

// We confirm to Vic-II the execution of the routine

asl $d019

// Start a KERNAL routine to restore the state prior to the

// throw the interrupt, handle the cursor blink...n-thousand other things

// and resume normal program execution

jmp $ea81

Irq2:

lda #200

sta $d001

sta $d003

sta $d005

sta $d007

sta $d009

sta $d00B

sta $d00D
sta $d00F

lda #RED

sta $d027

sta $d028

sta $d029

sta $d02a

sta $d02b

sta $d02c

sta $d02d

sta $d02e

lda #149

sta $d012

lda #<Irq

sta $0314

lda #>Irq

sta $0315

asl $d019

// Start a KERNAL routine to restore the state prior to the

// interrupt launch, similar to the previous one but lighter

jmp $ea31

* = $0a00

Sprites:

.byte $00,$00,$00,$00,$00,$00,$03,$f8,$00,$07,$f8,$00,$0f,$f8,$00,$1f

.byte $f8,$00,$1f,$07,$f0,$3e,$07,$e0,$3c,$07,$c0,$3c,$07,$80,$3c,$00
.byte $00,$3c,$07,$80,$3c,$07,$c0,$3e,$07,$e0,$1f,$07,$f0,$1f,$f8,$00

.byte $0f,$f8,$00,$07,$f8,$00,$03,$f8,$00,$00,$00,$00,$00,$00,$00,$07

Compared to the previous listing, it can be seen that in the Init part, the parts that set the color of
the sprites and the positioning on the line have been removed. The color was removed because it is
set dynamically on the two interrupt routines, which we will see in detail later. Vertical positioning is
no longer present on the Init because it has become redundant as each sprite is repositioned on two
different rows each time the screen is refreshed.

Immediately after the border/background color setting there are some outline instructions to
properly trigger the system when interrupts are thrown. Let's take them as they are; they are not
relevant to the technique.

We then come to the Irq routine. From the way the program is set up, we know that it will be
launched on scanline 149. At this point we can move the sprites to scanline 150, set the color to
green, and prepare the next interrupt. End.

Right after that is the Irq2 routine. It will start at scanline 199 so, as with the previous one, we move
the sprites to line 200 and update the color.

The result of the listing is as follows.


Q: but can you do anything else in interrupt routines as well?

A: well... yes... we will see some examples in the next installment

Q: should you set the scanline equal to one pixel less than the sprites?

A: no, as long as it is prior to the position of the sprites. Sometimes it is necessary to use a margin
greater than 1 because if the routine is complex it may take longer to execute...while meanwhile the
drawing is proceeding expeditiously.

If you want clarification on any point in this post, write to me on Gitter!

More interesting discussions will be added here.


Sprite multiplexing - part 3

More examples

Doubling size in hardware

Poking around in the Vic-II registers we find $d017 and $d01d which allow us to duplicate,
respectively, the height and width of sprites. The effect is that of a "stretched" sprite similarly to
what happens when we enlarge an image in a graphics program.

If we add these three instructions in the Irq routine after setting the color

lda #%00000000

sta $d017

sta $d01d

And these three in the Irq2 routine

lda #%11111111

sta $d017

sta $d01d

we will get this result

The first bit in Irq disables hardware doubling of height and width for all sprites, while the bit in Irq2
activates the feature.

Each bit is tied to a sprite: bit #0 (the rightmost bit) commands sprite #0, the last bit (the leftmost bit)
commands sprite #7. Clearly you can enable the functionality for single sprite, for height only or for
width only.

Single sprite color change


In the previous post we saw that we can set different colors to the sprites in the two scanlines, but
no one prohibits us from setting different colors for each individual sprite.

If, for example, in the Irq routine we insert the code

lda #WHITE

sta $d027

lda #RED

sta $d028

lda #CYAN

sta $d029

lda #PURPLE

sta $d02a

lda #GREEN

sta $d02b

lda #BLUE

sta $d02c

lda #YELLOW

sta $d02d

lda #ORANGE

sta $d02e

and in the Irq2 routine we insert the code

lda #BROWN

sta $d027

lda #LIGHT_RED

sta $d028

lda #DARK_GRAY

sta $d029

lda #GREY

sta $d02a

lda #LIGHT_GREEN

sta $d02b
lda #LIGHT_BLUE

sta $d02c

lda #LIGHT_GRAY

sta $d02d

lda #WHITE

sta $d02e

(in both cases replacing the part of the code that assigns the color) we get this effect

Changing sprites

So far we have always worked with the same sprite, the definition of which is at the bottom of the
source file. It is also possible to change the sprite between two scanlines, and we see this right away
with an example. First, at the bottom of the file, we provide the definition of a second sprite, right
after the definition of the first one:

* = $0a00

Sprites:

.byte $00,$00,$00,$00,$00,$00,$03,$f8,$00,$07,$f8,$00,$0f,$f8,$00,$1f

.byte $f8,$00,$1f,$07,$f0,$3e,$07,$e0,$3c,$07,$c0,$3c,$07,$80,$3c,$00

.byte $00,$3c,$07,$80,$3c,$07,$c0,$3e,$07,$e0,$1f,$07,$f0,$1f,$f8,$00

.byte $0f,$f8,$00,$07,$f8,$00,$03,$f8,$00,$00,$00,$00,$00,$00,$00,$07

Baloon:

.byte $00,$7f,$00,$01,$ff,$c0,$03,$ff,$e0,$03,$e7,$e0,$07,$d9,$f0,$07
.byte $df,$f0,$02,$d9,$f0,$03,$e7,$e0,$03,$ff,$e0,$03,$ff,$e0,$02,$ff

.byte $a0,$01,$7f,$40,$01,$3e,$40,$00,$9c,$80,$00,$9c,$80,$00,$49,$00

.byte $00,$49,$00,$00,$3e,$00,$00,$3e,$00,$00,$3e,$00,$00,$1c,$00

Recall, as written in post 1, the sprite can be generated with SpritePad, exported to Raw, inserted as
a sequence of bytes (as in this example) or included via .import directive (see the Kick Assembler
guide reference here).

Then, suppose we want to show this new sprite in the second scanline then in Irq2, we change the
line from

lda #$28

in

lda #$29

while also maintaining the color definition from the previous point we will get this output:

If you sharpen your eyesight a bit, you will notice that on the dome of the last two hot air balloons
are two stripes of different colors and, coincidentally, the same color as the sprite above. Why?

Well it is a problem related to the last Q/A in the previous post namely: on the second scanline, the
Vic-II started drawing the hot air balloon before the color change command for sprite #6 and #7 was
completed. The Vic's redrawing was faster than the execution of instructions on the processor, so a
portion was drawn with the old color.

How to get around this problem? The solution is to give the processor more time to complete its
work. So, either move the sprites further down or throw the interrupt a few lines in advance. If we
try to throw the interrupts on 145 and 195 (which is plenty of advance), the problem is gone.
It is not easy to have good control, so you need to do a lot of testing to see if you can get a result in
line with expectations.

Conclusions

From the few examples, you can see that a number of things can be done with the raster interrupt to
manipulate sprites and get around physical limitations. The tradeoff is that what the hardware
cannot provide must be handled "manually" by the program. These are the basics for understanding
how the flow works and, roughly, what the potential is. To you the invitation to take these examples
and experiment.

If you want clarification on any point in this post, write to me on Gitter!

The most interesting discussions will be added here.


Sprite multiplexing – arrange

Foreword

I have done some research and analyzed various solutions. What I am proposing is an interpretation
of a possible solution to handle more than 8 sprites on screen, it is not THE solution but just one of
many. Take your cue, elaborate or ignore what you will see from now on. This is my point of view,
nothing more. That said, I invite you to send suggestions or counter suggestions.

I have taken some code on GitHub (link shortly) and analyzed it a bit trying to make it easier to
understand, I hope I have succeeded.

Basic idea

As laid out in the conclusions of the previous posts, if we want to go beyond the canonical 8
hardware sprites, we have to trick the user by taking advantage of the graphical capabilities of the
system.

This is accomplished by using the raster interrupt near the Y coordinate at which we want to draw
the sprites. In fact, the same sprite is continuously moved from one position to another during the
drawing of each image. The mechanism becomes complex because our program must take charge of
keeping track of all the sprites to be displayed (which from now on we will call "virtual" sprites), since
it cannot rely on the registers intended to track the 8 in hardware.

It is therefore a matter of doing collaborative work between the main program and the interrupt
(henceforth irq) raster:

the main has to deal with program logic and positioning (but also color, size, shape, etc...in short, any
aspect of any sprite in the program). It must basically specify how things are to work.

the raster interrupt must deal with visualization, specifically the mapping of the virtual sprites to the
8 hardware sprites.

The main will do the init of all the variables used, the sprites, and prepare the launch of the irq raster.
It will also, for teaching purposes, move the sprites randomly throughout the program.

There will be two routines related to the irq rasters:

one will take care of sorting the sprites by Y-coordinate (we will explain why later in the post)

another will take care of displaying the sprites and will start on multiple scanlines based on the
sprites to be displayed.
Well, with the sufficient degree of messiness generated by these lines, let us begin to say something
about the main program.

Accessory functions

Meanwhile, let us define the subroutine that prepares the launch of the irq raster; many of the things
have already been seen in previous installments. We note only the launch scanline set to
IrqSortingLine (a label equal to $fc => 252):

InitRaster: {

sei

lda #<IrqSorting

sta $0314

lda #>IrqSorting

sta $0315

lda #$7f

sta $dc0d

lda #$01

sta $d01a

lda #27

sta $d011

lda #IrqSortingLine

sta $d012

lda $dc0d

cli

rts

We also define a macro and a subroutine for generating random numbers:

.macro GetRandomNumberInRange(minNumber, maxNumber) {

lda #minNumber

sta GetRandom.GeneratorMin

lda #maxNumber

sta GetRandom.GeneratorMax
jsr GetRandom

GetRandom: {

Loop:

lda $d012

eor $dc04

sbc $dc05

cmp GeneratorMax

bcs Loop

cmp GeneratorMin

bcc Loop

rts

GeneratorMin: .byte $00

GeneratorMax: .byte $00

The GetRandom subroutine generates a random number (reading the current scanline) and makes
sure it is between two limit values. The macro allows you to set the two limit values before calling
the subroutine.

Below is another macro to set the initial state of the virtual sprites:

.macro InitSpritesData() {

ldy #1

dex

initloop:

GetRandomNumberInRange(25, 255)

sta sprx,x

jsr GetRandom

sta spry,x

lda #$3f

sta sprf,x
tya

sta sprc,x

iny

cpy #16

bne nextcolorok

ldy #1

nextcolorok:

dex

bpl initloop

you set the initial x and y positions of the virtual sprites by inserting them into the sprx and spry
vectors. Then you set the structure of the sprites by retrieving it from the memory area where it is
defined ($3f) and inserting it into sprf Finally you set the color of each sprite in sprc. The color must
be other than black otherwise it will blend in with the background.

Main program

And here is finally the main in all its glory:

Start: {

jsr InitSprites

jsr InitRaster

lda #00

sta $d020

sta $d021

ldx #MAXSPR

stx numsprites

InitSpritesData()

MainLoop:

inc SpriteUpdateFlag

MoveLoop:
txa

lsr

lsr

lsr

adc sprx,x

sta sprx,x

lda Direction

eor #$ff

bpl rewind

inc spry,x

jmp EndLoop

rewind:

dec spry,x

EndLoop:

sta Direction

dex

bpl MoveLoop

jmp MainLoop

Direction: .byte 0

As can be seen, the entire part preceding MainLoop is devoted to initializing the program. The label
MAXSPR indicates the maximum number of sprites to be generated. Once the init part is finished, the
MainLoop of the program begins and is repeated ad infinitum.

The first thing that is done at the beginning of a new loop is to make sure that the sprites are sorted
correctly in the vector. This is not the place where this is done but in the IrqSorting. Therefore, at the
main, all that is left to do is to request a sorting (by setting the SpriteUpdateFlag "guard" variable to
1) and wait for this to be done in the appropriate irq (which will reset the guard when the job is
finished).

Assuming we have gotten the go-ahead to proceed, now the main is in charge of moving the sprites,
and it does:

for x, adding a value equal to x / 8

transfer x to a, executing three times lsr (logical shift to the right, which is equivalent to dividing by 2)

for y, adding or removing 1 to the previous y coordinate The process is repeated for all sprites after
which we return to MainLoop and start over.

In a serious, noninstructional program such as this, positioning may be determined by a more refined
function, or it may depend on what is pressed on the keyboard or how the joystick is used. Also there
would be all the logic of operation. There is obviously none of that here, the important thing is to
understand the breakdown of the code and which parts have the different tasks.

Well, now we come to the two most important as well as complicated parts. The first of the two is
the Irq raster sprite sorting subroutine and it starts on scanline 252, well beyond visible space.

Theoretical reasoning about sorting and visualization

So why does it become necessary to reorder sprites? First, let's say that by "sprite sorting" we mean
reading sprites from the arrays that contain their positions (spry, sprx, etc.) and creating as many
arrays by sorting by y position. The subroutine will insert the sprites in order from highest to lowest.
This sorting is to simplify the work of the subroutine that will draw the sprites on the screen. The
drawing subroutine has very little time to work (about 63 clock cycles per scanline - if you want to
understand why this number is so high, I'll leave you the link to this post on Lemon64
https://www.lemon64.com/forum/viewtopic.php?
t=71390&sid=a0e7eca10fd18beae6e00a8e63cb8152 ) and therefore must have everything ready to
display.

In its top-to-bottom scrolling, the drawing subroutine only needs to draw the sprites that are on the
current scanline. If there were no sorting step, at each scanline it would have to check which sprite,
of all those present, should be drawn on the current scanline. It takes time for this verification, time
that is not there, so sorting is necessary.

The sorting job then is done when the scanline is beyond the visible screen, thus starting as
mentioned before on line 252. Again, there is not all the time in the world but surely we can take
things relatively slowly so that we are ready for the next rendering.

Of course, sorting will handle not only the y position (in the sortspry array) but also the x position
(sortsprx), keep track of the color (sortsprc) and the pointer to the image to be shown (sortsprf). In
the irq code there is also the scanline setting at which the irq raster should be launched, which will
collect the sorted arrays and arrange the display.
Sorting

IrqSorting: {

dec $d019

lda #RED

sta $d020

lda #$ff // Spostamento di tutti gli sprite

sta $d001 // in un'area invisibile per evitare

sta $d003 // strani/brutti effetti visivi

sta $d005

sta $d007

sta $d009

sta $d00b

sta $d00d

sta $d00f

lda SpriteUpdateFlag // Sprite da ordinare?

beq irq1_nonewsprites

lda #$00

sta SpriteUpdateFlag

lda numsprites // Prendiamo il numero di sprite

sta SortedSprites // Se zero non c'è bisogno di

bne irq1_beginsort // ordinare

irq1_nonewsprites:

ldx SortedSprites

cpx #$09

bcc irq1_notmorethan8

ldx #$08

irq1_notmorethan8:

lda d015tbl,x // Abilita gli sprite da mostrare

sta $d015
beq irq1_nospritesatall

lda #$00 // Preparazione dell'irq di display

sta sprirqcounter

lda #<IrqDisplay

sta $0314

lda #>IrqDisplay

sta $0315

jmp IrqDisplay.irq2_direct // Se non c'è niente da ordinare vado

// subito alla routine di display

irq1_nospritesatall:

lda #BLACK

sta $d020

jmp $ea81

irq1_beginsort:

ldx #MAXSPR

dex

cpx SortedSprites

bcc irq1_cleardone

lda #$ff // Imposta gli sprite inutilizzati con

irq1_clearloop:

sta spry,x // la coordinata Y $ff

dex // finiranno così in fondo all'array

cpx SortedSprites // ordinato

bcs irq1_clearloop

irq1_cleardone:

ldx #$00

irq1_sortloop:

ldy sortorder+1,x // Codice di ordinamento, algoritmo

lda spry,y // preso da Dragon Breed -)


ldy sortorder,x

cmp spry,y

bcs irq1_sortskip

stx irq1_sortreload+1

irq1_sortswap:

lda sortorder+1,x

sta sortorder,x

sty sortorder+1,x

cpx #$00

beq irq1_sortreload

dex

ldy sortorder+1,x

lda spry,y

ldy sortorder,x

cmp spry,y

bcc irq1_sortswap

irq1_sortreload:

ldx #$00

irq1_sortskip:

inx

cpx #MAXSPR-1

bcc irq1_sortloop

ldx SortedSprites

lda #$ff // $ff è l'indicatore di fine per

sta sortspry,x // la routine

ldx #$00

irq1_sortloop3:

ldy sortorder,x // Giro finale

lda spry,y // Copia delle variabili nell'array

sta sortspry,x // ordinato

lda sprx,y
sta sortsprx,x

lda sprf,y

sta sortsprf,x

lda sprc,y

sta sortsprc,x

inx

cpx SortedSprites

bcc irq1_sortloop3

jmp irq1_nonewsprites

I have to be honest, the algorithm is complex and I have not spent much time on it to understand and
dissect it. If, like me, you prefer to stay out of it and take it as dogma, just know that at the end of
processing, we will find the sort arrays (i.e., all those beginning with sort*) ready to be used in the
display phase.

In developing a game, I would tend not to make this subroutine and the subsequent display
subroutine the subject of modification. I would rather focus on the logic and rules that govern
everything.

Display

IrqDisplay: {

dec $d019

lda #GREEN

sta $d020

irq2_direct:

ldy sprirqcounter // Reads the number of the next sprite

lda sortspry,y // Reads the y-coordinate

clc

adc #$10 // Calculates 16 lines from the y

bcc irq2_notover // that will be the end of this irq

lda #$ff // The deadline must be by $ff

irq2_notover:

sta tempvariable

irq2_spriteloop:
lda sortspry,y

cmp tempvariable // Irq terminated?

bcs irq2_endspr

ldx physicalsprtbl2,y

sta $d001,x

lda sortsprx,y

asl

sta $d000,x

bcc irq2_lowmsb

lda $d010

ora ortbl,x

sta $d010

jmp irq2_msbok

irq2_lowmsb:

lda $d010

and andtbl,x

sta $d010

irq2_msbok:

ldx physicalsprtbl1,y

lda sortsprf,y

sta $07f8,x

lda sortsprc,y

sta $d027,x

iny

bne irq2_spriteloop

irq2_endspr:

cmp #$ff // Reached the deadline?

beq irq2_lastspr

sty sprirqcounter

sec // The last coordinate - $10 is the start

sbc #$10 // of the new irq


cmp $d012 // If we are late, let's go right away

bcc irq2_direct // to the next irq

sta $d012

lda #BLACK

sta $d020

jmp $ea81

irq2_lastspr:

lda #<IrqSorting // Managed the last sprite

sta $0314 // A new sort is being prepared

lda #>IrqSorting

sta $0315

lda #IrqSortingLine

sta $d012

lda #BLACK

sta $d020

jmp $ea81

This is the listing of the subroutine that performs the sprite display. There is a lot of stuff so let's
analyze it slowly.

The first instruction performs an Irq launch confirmation, then follows a border color change to
green. It serves to indicate on the screen, for debugging purposes, when the irq has started. The
same mechanism was present in the sorting subroutine.

The general operation is to place, at each scanline, all the sprites that are nearby. Once the group of
nearby sprites has been drawn, the algorithm considers whether to launch an irq on a new scanline
further down or to launch a sorting irq (in case all sprites have already been drawn).

The first block takes the sprite that is to be drawn (whose index is sprirqcounter) and retrieves its y-
coordinate. To this coordinate it adds the value 16 and the result will be the line at which the irq will
end. Immediately afterwards we make sure that the ending coordinate of the irq is no more than
255.
If the sprite to be drawn is not beyond the end coordinate of the irq, we proceed with the drawing by
retrieving coordinates, colors and shape from the arrays. This is done in the part from irq2_spriteloop
to irq2_endspr. In this block:

the x register "points" to the physical sprites (thus allowing access to the coordinates, color, and
shape)

the y register contains the index of the virtual sprite to be drawn

Below (starting with irq2_endspr) the indexes are updated and the next tasks to be done are checked
(terminate the subroutine, set the next irq etc...)

If the routine has completed the last sprite, it sets the start of a new sorting.

And finally the complete listing

The complete program can be found here.


//==============================================================================
//C64 Sprite multiplexing example
//
//All credit to Lasse Oorni (loorni@student.oulu.fi) for the original code
//
//This routine can run over 32 sprites (change value of 'MAXSPR', and increase
//the number of entries in the 'Sprite' tables to match).
//
//First IRQ 'Sorts' the sprites at the bottom of the screen, the second IRQ
//displays them.
//
//Why sorted top-bottom order of sprites is necessary for multiplexing,
//because raster interrupts are used to "rewrite" the sprite registers
//in the middle of the screen and raster interrupts follow the
//top->bottom movement of the TV/monitor electron gun as it draws each
//frame.
//
//Formatted to run in the CBM prgStudio IDE -> MARVIN HARDY 2018
//
//==============================================================================

// SYS Call to $1000:

*=$0801

.byte $0E, $08, $0A, $00, $9E, $20, $28, $34, $30, $39, $36, $29, $00,
$00, $00

*= $0fc0
//The Sprite DATA (Enter any single colour sprite code here, to apply to ALL
generated sprites):
.byte 0,0,0
.byte 124,241,1
.byte 253,251,1
.byte 193,131,3
.byte 193,243,99
.byte 193,251,246
.byte 193,155,246
.byte 253,248,100
.byte 124,240,100
.byte 0,0,0
.byte 12,241,224
.byte 13,251,240
.byte 12,27,48
.byte 12,251,240
.byte 13,249,224
.byte 13,131,48
.byte 13,251,240
.byte 13,249,224
.byte 0,0,0
.byte 0,0,0
.byte 0,0,0

*=$1000 //Program start address, first define some


constants:

.label IrqSortingLine = $fc //This is the place on screen where the


sorting
//IRQ happens
.label IRQ2Line = $2a //This is where sprite displaying begins...

.label MAXSPR = 16 //Number of sprites

.label numsprites = $02 //Number of sprites that the main program


wants
//to pass to the sprite sorter
.label SpriteUpdateFlag = $03 //Main program must write a nonzero value
here
//when it wants new sprites to be displayed
.label SortedSprites = $04 //Number of sorted sprites for the raster
//interrupt
.label tempvariable = $05 //Just a temp variable used by the raster
//interrupt
.label sprirqcounter = $06 //Sprite counter used by the interrupt

.label sortorder = $10 //Order-table for sorting. Needs as


many .bytes
.label sortorderlast = $2f //as there are sprites.

.macro InitSpritesData() {
ldy #1
dex
initloop:
GetRandomNumberInRange(25, 255)
sta sprx,x
jsr GetRandom
sta spry,x
lda #$3f
sta sprf,x
tya
sta sprc,x
iny
cpy #16
bne nextcolorok
ldy #1
nextcolorok:
dex
bpl initloop
}

//Main program
Start: {
jsr InitSprites
jsr InitRaster
lda #00
sta $d020
sta $d021
ldx #MAXSPR
stx numsprites

InitSpritesData()

MainLoop:
inc SpriteUpdateFlag

WaitLoop:
lda SpriteUpdateFlag
bne WaitLoop
ldx #MAXSPR-1
MoveLoop:
txa
lsr
lsr
lsr
adc sprx,x
sta sprx,x

lda Direction
eor #$ff
bpl rewind
inc spry,x
jmp EndLoop

rewind:
dec spry,x

EndLoop:
sta Direction
dex
bpl MoveLoop

jmp MainLoop

Direction: .byte 0
}

// Routine to init the raster interrupt system


InitRaster: {
sei
lda #<IrqSorting
sta $0314
lda #>IrqSorting
sta $0315
lda #$7f //CIA interrupt off
sta $dc0d
lda #$01 //Raster interrupt on
sta $d01a
lda #27 //High bit of interrupt position = 0
sta $d011
lda #IrqSortingLine //Line where next IRQ happens
sta $d012
lda $dc0d //Acknowledge IRQ (to be sure)
cli
rts
}

// Routine to init the sprite multiplexing system


InitSprites: {
lda #$00
sta SortedSprites
sta SpriteUpdateFlag
ldx #MAXSPR-1
is_orderlist:
txa
sta sortorder,x
dex
bpl is_orderlist
rts
}

//Raster interrupt 1. This is where sorting happens.


IrqSorting: {
dec $d019 //Acknowledge raster interrupt
lda #RED
sta $d020
lda #$ff //Move all sprites
sta $d001 //to the bottom to prevent
sta $d003 //weird effects when sprite
sta $d005 //moves lower than what it
sta $d007 //previously was
sta $d009
sta $d00b
sta $d00d
sta $d00f

lda SpriteUpdateFlag //New sprites to be sorted?


beq irq1_nonewsprites
lda #$00
sta SpriteUpdateFlag
lda numsprites //Take number of sprites given
//by the main program
sta SortedSprites //If itïs zero, donït need to
bne irq1_beginsort //sort

irq1_nonewsprites:
ldx SortedSprites
cpx #$09
bcc irq1_notmorethan8
ldx #$08
irq1_notmorethan8:
lda d015tbl,x //Now put the right value to
sta $d015 //$d015, based on number of
beq irq1_nospritesatall //sprites
//Now init the sprite-counter
lda #$00 //for the actual sprite display
sta sprirqcounter //routine
lda #<IrqDisplay //Set up the sprite display IRQ
sta $0314
lda #>IrqDisplay
sta $0315
jmp IrqDisplay.irq2_direct //Go directly// we might be late
irq1_nospritesatall:
lda #BLACK
sta $d020

jmp $ea81 //Continue IRQs

irq1_beginsort:
ldx #MAXSPR
dex
cpx SortedSprites
bcc irq1_cleardone
lda #$ff //Mark unused sprites with the
irq1_clearloop:
sta spry,x //lowest Y-coordinate ($ff)//
dex //these will "fall" to the
cpx SortedSprites //bottom of the sorted table
bcs irq1_clearloop
irq1_cleardone:
ldx #$00
irq1_sortloop:
ldy sortorder+1,x //Sorting code. Algorithm
lda spry,y //ripped from Dragon Breed -)
ldy sortorder,x
cmp spry,y
bcs irq1_sortskip
stx irq1_sortreload+1
irq1_sortswap:
lda sortorder+1,x
sta sortorder,x
sty sortorder+1,x
cpx #$00
beq irq1_sortreload
dex
ldy sortorder+1,x
lda spry,y
ldy sortorder,x
cmp spry,y
bcc irq1_sortswap
irq1_sortreload:
ldx #$00
irq1_sortskip:
inx
cpx #MAXSPR-1
bcc irq1_sortloop
ldx SortedSprites
lda #$ff //$ff is the endmark for the
sta sortspry,x //sprite interrupt routine
ldx #$00
irq1_sortloop3:
ldy sortorder,x //Final loop
lda spry,y //Now copy sprite variables to
sta sortspry,x //the sorted table
lda sprx,y
sta sortsprx,x
lda sprf,y
sta sortsprf,x
lda sprc,y
sta sortsprc,x
inx
cpx SortedSprites
bcc irq1_sortloop3
jmp irq1_nonewsprites
}

//Raster interrupt 2. This is where sprite displaying happens

IrqDisplay: {
dec $d019 //Acknowledge raster interrupt
lda #GREEN
sta $d020
irq2_direct:
ldy sprirqcounter //Take next sorted sprite number
lda sortspry,y //Take Y-coord of first new sprite
clc
adc #$10 //16 lines down from there is
bcc irq2_notover //the endpoint for this IRQ
lda #$ff //Endpoint canït be more than $ff
irq2_notover:
sta tempvariable
irq2_spriteloop:
lda sortspry,y
cmp tempvariable //End of this IRQ?
bcs irq2_endspr
ldx physicalsprtbl2,y //Physical sprite number x 2
sta $d001,x //for X & Y coordinate
lda sortsprx,y
asl
sta $d000,x
bcc irq2_lowmsb
lda $d010
ora ortbl,x
sta $d010
jmp irq2_msbok
irq2_lowmsb:
lda $d010
and andtbl,x
sta $d010
irq2_msbok:
ldx physicalsprtbl1,y //Physical sprite number x 1
lda sortsprf,y
sta $07f8,x //for color & frame
lda sortsprc,y
sta $d027,x
iny
bne irq2_spriteloop
irq2_endspr:
cmp #$ff //Was it the endmark?
beq irq2_lastspr
sty sprirqcounter
sec //That coordinate - $10 is the
sbc #$10 //position for next interrupt
cmp $d012 //Already late from that?
bcc irq2_direct //Then go directly to next IRQ
sta $d012
lda #BLACK
sta $d020
jmp $ea81

irq2_lastspr:
lda #<IrqSorting //Was the last sprite,
sta $0314 //go back to IrqSorting
lda #>IrqSorting //(sorting interrupt)
sta $0315
lda #IrqSortingLine
sta $d012
lda #BLACK
sta $d020
jmp $ea81 //Continue IRQs
}

.macro GetRandomNumberInRange(minNumber, maxNumber) {


lda #minNumber
sta GetRandom.GeneratorMin
lda #maxNumber
sta GetRandom.GeneratorMax
jsr GetRandom
}

GetRandom: {
Loop:
lda $d012
eor $dc04
sbc $dc05
cmp GeneratorMax
bcs Loop
cmp GeneratorMin
bcc Loop
rts

GeneratorMin: .byte $00


GeneratorMax: .byte $00
}

// SPRITE TABLES:

// Unsorted sprite table


// X position
sprx: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
// Y position
spry: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
// Color
sprc: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
// Frame
sprf: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

sortsprx: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0


//Sorted sprite table
sortspry: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
//Must be one .byte extra for the
sortsprc: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
sortsprf: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

// CONSTANTS

d015tbl:
.byte %00000000 //Table of sprites that are "on"
.byte %00000001 //for $d015
.byte %00000011
.byte %00000111
.byte %00001111
.byte %00011111
.byte %00111111
.byte %01111111
.byte %11111111

physicalsprtbl1:
.byte 0,1,2,3,4,5,6,7 //Indexes to frame & color
.byte 0,1,2,3,4,5,6,7 //registers
.byte 0,1,2,3,4,5,6,7
.byte 0,1,2,3,4,5,6,7
.byte 0,1,2,3,4,5,6,7
.byte 0,1,2,3,4,5,6,7
.byte 0,1,2,3,4,5,6,7
.byte 0,1,2,3,4,5,6,7

physicalsprtbl2:
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14
.byte 0,2,4,6,8,10,12,14

andtbl: .byte 255-1

ortbl: .byte 1
.byte 255-2
.byte 2
.byte 255-4
.byte 4
.byte 255-8
.byte 8
.byte 255-16
.byte 16
.byte 255-32
.byte 32
.byte 255-64
.byte 64
.byte 255-128
.byte 128

Useful links
I leave you with some interesting links:

https://kodiak64.com/blog/toggleplexing-sprites-c64

https://codebase64.org/doku.php?id=base:sprite_multiplexing

https://forums.tigsource.com/index.php?topic=69477.20

If you want clarification on any point in this post, write to me on Gitter!

The most interesting discussions will be added here.


Character detection under a sprite

Here we are with a new post devoted to sprites, much simpler than the previous ones, but dealing
with a situation that I have often dealt with: how to read the character that is under a sprite?

Introduction

Let's start by showing a schematic of the C64 screen, in which I placed a sprite at the top-leftmost
point so that it is fully visible.

The coordinates of this sprite are (24, 50). The whole sprite is visible, in the four corners I
intentionally put a bright dot to indicate the vertex.

The purpose of this post is to calculate the position of the underlying character (in this case, it is the
character at position 0,0) and detect which character it is.

Logical operation

The way to get this information is to relate the position of the sprite to the character area. The
formula is trivial and for the two coordinates is:
Character column = (X - 24) / 8

Character row = (Y - 50) / 8

The two 8 values in the formulas are the dimensions in pixels of the individual character.

For clarity, the formula allows you to find the character below the top left point of the sprite.

Suppose we want to calculate which character is on the grid from the x,y coordinates. We can
imagine, for example, that these x,y belong to a sprite that has made a collision with a character on
the screen.

The collision can be detected by several techniques, one of which is to monitor the state of the Vic-II
register reachable at address $d01f (yes, as I said, there are other more efficient, more elegant, more
more more...).

Well, from this address, you can detect which sprite generated the collision, for simplicity let's
assume it is sprite #0.

Code

Okay come on, now one piece at a time and with some support structure, let's go find the underlying
character.
Consider the situation above, we have sprite #0 being defined with these instructions:

lda #$28

sta $07f8

lda #68

sta $d000

lda #80

sta $d001

lda #1

sta $d015

Previously seen code: setting sprite shape, positioning on coordinates (68, 80) and triggering. It is
minimal; I have included the bare minimum for the example.

With this starting condition, we proceed to calculate the character. Let's evaluate the Y coordinate:

lda $d001

sec

sbc #50

lsr

lsr

lsr

The first instruction reads the Y coordinate of the sprite and inserts the value into the A register.
Immediately after that, I perform the subtraction of 50 (equal to the top edge). Following this are
three lsr instructions. Each lsr instruction performs a logical bit shift to the right of the contents of
the A register.
As can be seen, shifting the bits to the right generates a binary value that is half of the previous one.
It is equivalent, therefore, to integer division by 2, with loss of remainder. Replicating the division
three times results in division by 8.

The initial intent begins to show; the Y coordinate of the character is 3 (just as we expected). But let's
proceed further with the calculation of X.

lda $d000

sec

sbc #24

lsr

lsr

lsr

Similar to the above, first the X coordinate of the sprite is retrieved and inserted into the A register.
Then the ground is prepared for the subtraction of 24 (size in pixels of the right edge). Again division
by 8 is performed, obtained by three consecutive lsr instructions.
In this case the result is 5, which corresponds to the X position of the character we are looking for.

Putting the two code snippets together, we get that, given a sprite at position (68, 80), we can detect
the coordinates of the screen character, which are (5, 3).

To detect the character present at these coordinates, we insert these snippets into a more complex
block.

lda $d001

sec

sbc #50

lsr

lsr

lsr

tay

lda ScreenMemTableL, y

sta ScreenPositionAddress

lda ScreenMemTableH, y

sta ScreenPositionAddress + 1

lda $d000

sec
sbc #24

lsr

lsr

lsr

clc

adc ScreenPositionAddress

sta ScreenPositionAddress

lda ScreenPositionAddress + 1

adc #0

sta ScreenPositionAddress + 1

To detect the character present at a given coordinate, we have to try to locate the memory address
in the ScreenRam and, from there, perform a read. The address is a 16-bit value so we will use a
variable so defined to handle it:

ScreenPositionAddress: .word $0000

The first block of code, as already seen is in charge of finding the Y coordinate of the character on the
screen. After transferring the value from register A to Y, you use it as an index to access two support
tables that, against a Y coordinate, return the hi-byte (ScreenMemTableH) and lo-byte
(ScreenMemTableL) of the coordinate in the screen ram relative to the row of interest.

Next, with the code already seen, the X coordinate of the character is calculated. The value, found in
the A register, is added to the variable ScreenPositionAddress (the first adc does the summing, the
second adc, adds any carryover to the hi-byte).

At the end of these instructions, inside ScreenPositionAddress we have the memory location in the
screen ram of the character we are interested in.

Final Listing

For better understanding, below I have written a listing that positions a sprite and moves it one pixel
at a time along the horizontal axis (with a rudimentary wait loop to make the movement smooth and
visible). With each move, a subroutine is called that detects the character underneath the sprite and
writes it to another location on the screen.

To perform the read from the screen ram, self-mod coding is required. This involves modifying the
program code at runtime. It is a means of having something similar to the pointer in C (forgive me
purists for the arrogant simplification).

:BasicUpstart2($0810)
* = $0810

Init: {

lda #$28

sta $07f8

lda #30

sta $d000

lda #80

sta $d001

lda #1

sta $d015

MainLoop: // I perform a wait and render

ldy #50 // the movement of the sprite

WaitLoop2: // smoother ldx #150

WaitLoop:

dex

bne WaitLoop

dey

bne WaitLoop2

inc $d000 // I move sprite #0 one pixel to the right

jsr GetCharacter // We call the subroutine to read

// the character underlying the sprite

jmp MainLoop // I repeat the process over and over again

rts

GetCharacter: {
lda $d001 // I get the Y coordinate

sec

sbc #50

lsr

lsr

lsr

tay // I calculate the Y coordinate on the screenram

lda ScreenMemTableL, y

sta ScreenPositionAddress

lda ScreenMemTableH, y

sta ScreenPositionAddress + 1

lda $d000 // I get the X coordinate

sec

sbc #24

lsr

lsr

lsr

clc // I calculate the X,Y coordinate on the screenram

adc ScreenPositionAddress

sta ScreenPositionAddress

lda ScreenPositionAddress + 1

adc #0

sta ScreenPositionAddress + 1

// I do a self-mod to read the location

// of memory contained in the variable

// ScreenPositionAddress

lda ScreenPositionAddress

sta CheckChar + 1
lda ScreenPositionAddress + 1

sta CheckChar + 2

CheckChar:

lda $beef // $beef is just a placeholder, it will be overwritten

// from the self-mod code above

sta $05ef // I write on the screen the character I just read

rts

ScreenPositionAddress: .word $0000

* = $0a00

Sprites:

.byte $81,$ff,$01,$07,$ff,$00,$0f,$ff,$00,$1f,$ff,$00,$3f,$ff,$00,$7f

.byte $e0,$ff,$7f,$80,$fe,$7f,$00,$fc,$fe,$00,$f8,$fe,$00,$f0,$fe,$00

.byte $00,$fe,$00,$f0,$fe,$00,$f8,$ff,$00,$fc,$7f,$80,$fe,$7f,$e0,$ff

.byte $3f,$ff,$00,$1f,$ff,$00,$0f,$ff,$00,$07,$ff,$00,$81,$ff,$01,$05

// I define helper tables to get addresses in the screenram

* = * "ScreenMemTableL"

ScreenMemTableL:

.for (var i = 0; i<25; i++) .byte <$0400 + (i * $28)

* = * "ScreenMemTableH"

ScreenMemTableH:

.for (var i = 0; i<25; i++) .byte >$0400 + (i * $28)

If you want clarification on any point in this post, write to me on Gitter!

The most interesting discussions will be added here.


Sprite collision

Welcome to this new post. The sprite thread continues and this time we talk about collisions.

Introduction

The C64, on the subject of sprite collisions, provides two registers on the Vic-II: $d01e and $d01f. For
both registers, each bit maps to a sprite (#0 to #7) and is set to 1 if the respective sprite participates
in a collision.

The first register maps to collisions between sprites, the second to collisions between sprites and the
background. Of the two, in this post, we are (at least partially) interested in the first of the two.

If we exploit the first register, we are talking about hardware collision. Looking at the register, we
realize that so much collision information is fused together making it impossible for us, in many
situations, to figure out which sprites were involved.

If, when reading the register, the value %10000011 is returned, we can tell that sprite #0 and sprite
#1 are in collision. Up to this point, it is easy.

If, however, after reading we receive the value %00000111, we detect that sprites #2, #1 and #0 are
involved in a collision. The above read occurs for a set of sprites as in the figure.

However, the same reading can also occur for a sprite configuration such as the following.
Therefore, it can be inferred that, given the same read, it is not possible to determine with certainty
between which sprites the collision occurred: if the bit of a sprite is 1 it means that sprite had a
collision, but, when there are more than two sprites involved, there is no way to know which sprite
the collision occurred with.

This is a problem if the logic of our game/program requires this information.

Logical operation

One can look for a solution by building things in house, choosing a completely software or hybrid
route.

I had this problem in my ForestSaver game, where there are many sprites simultaneously visible and I
need to know between whom collisions occur.

I therefore chose to implement a subroutine to make a software collision detector. My solution could
also be hybrid if it relied on the Vic-II register to optimize execution time.

Basically, the basic idea of my solution is to check whether a point on a sprite (e.g., the top left
vertex) is within the area of another sprite. If it is then the collision is established otherwise...not.

In this post, I reproduce the subroutine used in the game to check whether sprite #0 (the ranger) had
a collision with one of the other sprites of the "little men" in the game.

The solution consists of a first part where you set the sprites to be checked and the subroutine that
does all the work.
Implementation

The example we will see will check whether sprite #1 is in collision with the ranger (sprite #0).

Let's tackle the first part right away, which is setting up the data to perform the verification.

It is quite simple and consists of assigning to two variables (OtherX and OtherY) the coordinates of
the sprite to be checked (in our case, sprite #1). The OtherX variable is a word (16 bits) because the X
coordinate can go beyond 255 pixels.

To know if a sprite is beyond this limit, we make use of the $d010 register, which sets the bits to 1 if
the corresponding sprite is beyond 255 pixels. So, since we are talking about sprite #1, simply use the
%000010 mask and put it in AND with the reading of register $d010.

If the result is non-zero then we are beyond 255 pixels, so the top of OtherX must be set to 1.

This setting is done with the first few lines of the following snippet.

lda $d010 // Read the registry

and #%00000010 // applies the mask to know the status of the

beq !+ // sprite #1, if the result (stored in A) is 0 it skips

lda #$1 // to the label ! otherwise set in A the value 1

!:

sta SpriteCollision.OtherX + 1

lda $d002

sta SpriteCollision.OtherX

lda $d003

sta SpriteCollision.OtherY

jsr SpriteCollision

The next lines set the lower part of OtherX with the X coordinate from register $d002 and the Y
coordinate from register $d003 in OtherY.

The last instruction calls the subroutine to perform the verification, the code for which is as follows.
SpriteCollision: {

// I determine the edges of the ranger sprite

lda $d010

and #%00000001

sta RangerX1 + 1

sta RangerX2 + 1

lda $d000

sta RangerX1

sta RangerX2

add16value($0018, RangerX2)

lda $d001

sta RangerY1

clc

adc #21

sta RangerY2

// The collision occurs if the coordinates of the other sprite are

// inside the ranger's container.

// This occurs if

// RangerX1 < OtherX < RangerX2

// RangerY1 < OtherY < RangerY2

// If OtherX < RangerX1 then jump out (no collision)

bmi16(OtherX, RangerX1)

bmi NoCollisionDetected

// If RangerX2 < OtherX then jump out (no collision)

bmi16(RangerX2, OtherX)

bmi NoCollisionDetected
// If OtherY < RangerY1 then jump out (no collision)

lda OtherY

cmp RangerY1

bmi NoCollisionDetected

// If RangerY2 < OtherY then jump out (no collision)

lda RangerY2

cmp OtherY

bmi NoCollisionDetected

CollisionDetected:

lda #$01

jmp Done

NoCollisionDetected:

lda #$00

Done:

rts

// Ranger's container

RangerX1: .word $0000

RangerX2: .word $0000

RangerY1: .byte $00

RangerY2: .byte $00

// Coordinates of the other sprite

OtherX: .word $0000

OtherY: .byte $00

}
When the subroutine returns, there will be 1 in register A if the collision was detected, otherwise 0.

There is no need to be frightened by some instructions in the listing. The meaning is as follows:

add16value(x, y): adds the value x to the variable y and stores the result (similar to adc but operates
on 16-bit values)

bmi16(x, y) performs comparisons between two 16-bit variables and sets flags for the execution of a
bmi instruction.

Notes

The collision is detected based, as mentioned at the beginning, on the top left vertex of sprite #1. If
we wanted to use the center of the sprite just add 12 to OtherX and 10 to OtherY (be careful to
handle the top of OtherX correctly should the addition exceed 255).

Sprite #0 can be freed by making the subroutine more generic, just enter its coordinates as
parameters.

The initial parameter setting can be transfrom a macro, thus simplifying the code.

Hardware detection checks whether sprites overlap in their colored parts (collision does not occur if
transparent areas overlap). This subroutine is not as refined.

Sprite multiplexing

Entering the topic of multiplexing, the use of a software detector becomes very important if not
essential.

Since in this subroutine, verification is done by coordinate comparison, it can be completely freed
from the Vic-II registers. If we consider the example from one of the previous posts, where the 8
hardware sprites map to the "virtual" sprites, simply replace the instructions that read the sprite
position registers with the structure that stores the positions of the virtual sprites and you are done.

Conclusions

I have talked about a solution closely related to a problem I have had in the past, and you can see
that it is very adherent to the needs.
I have given some ideas to generalize it and adapt it to various contexts, then of course it depends on
the logic of the game/program.

If you want clarification on any point in this post, write to me on Gitter!

The most interesting discussions will be added here.

You might also like