I’ve been recently toying with the idea of writing a game using the CoCo 3 256 x 200 x 16 colour resolution and I got far enough along to require collision detection of the game sprites. I wrote my own code but it wasn’t working properly and after fighting with it I decided I’d look at ROBOTRON’s source code (which is freely available online) and see if I can use it’s collision detection code in my game. I converted the ROBOTRON code to work with my game but it wasn’t working either. I fought with it for awhile knowing that the code works perfect for ROBOTRON. I finally realized that my game was using bytes for the width of the sprites and both my original collision detection code and the ROBOTRON code is using pixels for the width. Now that I’ve got the ROBOTRON version documented line by line (the original source wasn’t commented very much) and in a format that can be used easily I thought I’d share it and explain the basics of how it works.
I’m super impressed with the ROBOTRON source code, the programmers did a wickedly clever and optimized version of collision detection for the 6809. I’ll explain a few of the things they did that really impressed me down below as I get more into the guts of the code.
First an overview of how the collision detection of two sprites works:
1 – Test to see if the bounding boxes of each sprite are overlapping on screen
2 – Figure out what section of each sprite is on top of the other sprite and test if the overlapping pixels of the sprite are on top of each other. If so then you have a collision.
If you are writing an arcade game for the CoCo you are probably using compiled sprites. Which means you need to also have a bitmap copy of your sprite to test for collisions.
For example, if you have the Galaga ship as a sprite that ends up on screen as this:
; Image Width: 18 Image Height: 16
; 000 ........0.........
; 001 ........0.........
; 002 ........0.........
; 003 .......000........
; 004 .......000........
; 005 ....1..000..1.....
; 006 ....1..000..1.....
; 007 ....0.00000.0.....
; 008 .1..0.00100.0..1..
; 009 .1...0011100...1..
; 010 .0..000101000..0..
; 011 .0.00000000000.0..
; 012 .000001000100000..
; 013 .000.1100011.000..
; 014 .00..11.0.11..00..
; 015 .0......0......0..
You wound need a bitmap of this sprite so the collision detection routine can use it to test with, like this:
FCB $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$0F,$FF,$00,$00,$00,$00
FCB $00,$00,$00,$0F,$FF,$00,$00,$00,$00
FCB $00,$00,$F0,$0F,$FF,$00,$F0,$00,$00
FCB $00,$00,$F0,$0F,$FF,$00,$F0,$00,$00
FCB $00,$00,$F0,$FF,$FF,$F0,$F0,$00,$00
FCB $0F,$00,$F0,$FF,$FF,$F0,$F0,$0F,$00
FCB $0F,$00,$0F,$FF,$FF,$FF,$00,$0F,$00
FCB $0F,$00,$FF,$FF,$FF,$FF,$F0,$0F,$00
FCB $0F,$0F,$FF,$FF,$FF,$FF,$FF,$0F,$00
FCB $0F,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00
FCB $0F,$FF,$0F,$FF,$FF,$FF,$0F,$FF,$00
FCB $0F,$F0,$0F,$F0,$F0,$FF,$00,$FF,$00
FCB $0F,$00,$00,$00,$F0,$00,$00,$0F,$00
Once you have your bitmap for Sprite1 and Sprite2 you’re ready to test them to see if they’ve collided.
Before you use the code below you need to setup the variables for Sprite1 and Sprite2. The comments in the code explain what you need to set them up as before calling the DoCollision routine. All the variables below should be in direct page memory.
* Variables for collision detection, these should all be in Direct Page memory
* Sprite 1 related variables
Sprite1X RMB 1 * X co-ordinate
Sprite1Y RMB 1 * Y co-ordinate
Sprite1Width RMB 1 * Sprite 1 width in bytes
Sprite1Height RMB 1 * Sprite 1 height
Sprite1PixelsWide RMB 2 * Sprite 1 width in Pixels, 2nd byte will be a copy of Sprite1Height
Sprite1Bitmap RMB 2 * Address where Sprite1's bitmap starts
* Sprite 2 related variables
Sprite2X_Co_ordinate RMB 1 * Sprite2 X co-ordinate on screen (top left corner of sprite)
Sprite2Y_Co_ordinate RMB 1 * Sprite2 Y co-ordinate on screen (top left corner of sprite)
Sprite2Width RMB 1 * Width of Sprite2 in bytes
Sprite2Hieght RMB 1 * Height of Sprite2 (number of rows)
Sprite2PixelsWide RMB 1 * Width of Sprite2 in pixels (bytes * 2)
Sprite2HieghtAgain RMB 1 * Height of Sprite2 (number of rows) will also be the same as Sprite2Hieght
Sprite2Bitmap RMB 2 * Address in memory where Sprite2 bitmap is located (to test for collision)
*COLLISION DETECTION ROUTINE PARAMETERS
* Do not change the order of these, some of them get loaded as a 16 bit value, so they need to be in the correct order
*
XCNT RMB 1 * SCAN COUNTS
YCNT RMB 1
OX1 RMB 1 * OFFSETS Object X Value
OY1 RMB 1 * Object Y Value
PX1 RMB 1 * Player X Value
PY1 RMB 1 * Player Y Value
PLW RMB 1 * Width TEMPS
OBW RMB 1
ULX RMB 1 * UPPER LEFT X COORD
ULY RMB 1 * Upper Left Y Coord
LRX RMB 1 * Lower Right X Coord
LRY RMB 1 * Lower Right Y Coord
CTEMP RMB 2 * INDEX TEMP
CTEMP2 RMB 2
XTEMP RMB 2
Once you’ve setup Sprite1 and Sprite2’s variables you can call the routine below called DoCollisionDetection. If there is a collision then the carry bit will be set. If it returns and the carry bit is clear then there was no collision. Here’s an example of setting up Sprite1 and Sprite2’s variables then calling the DoCollisionDetection routine and how to react to it when it returns:
* Setup Sprite1
LDA ShotX * Players Shot X location
LDB ShotY * Players Shot Y location
STD Sprite1X * Save Sprite1 X & Y
LDA #1 * Shot width in bytes
LDB #1 * Shot height in rows
STD Sprite1Width * Save Sprite1's width in bytes and height
LDA #1 * Shot width in pixels
STD Sprite1PixelsWide * Save Sprite1's width in pixels and height
LDD #ShotBitmap * Address in memory where the shot bitmap is
STD Sprite1Bitmap * Save the location where the bitmap is in memory for Sprite1
* Setup Sprite2
LDA EnemyX * Enemy Ship X location
LDB EnemyY * Enemy Ship Y location
STD Sprite2X_Co_ordinate * Save Sprite2 X & Y
LDA #10 * Enemy Ship width in bytes
LDB #20 * Enemy Ship height in rows
STD Sprite2Width * Save Sprite2's width in bytes and height
LSLA * Enemy Ship width in pixels
STD Sprite2PixelsWide * Save Sprite2's width in pixels and height
LDD #EnemyBitmap * Address in memory where the Enemy Ship bitmap is
STD Sprite2Bitmap * Save the location where the bitmap is in memory for Sprite2
* Go see if the sprites collided
JSR DoCollisionDetection * See if Sprite1 and Sprite2 have collided
BCC NoCollision * If the carry flag is clear then no collision is detected
* If we get here then the two sprites have collided
* deal with the collision
...
...
RTS
* If we get here then No collision has happenned
NoCollision:
...
...
RTS
Here is the code that does the magic:
* Enter with Sprite1 and Sprite2 variables already setup
DoCollisionDetection:
LDD Sprite1X * A = Sprite1 X Pixel, B = Sprite1 Row Y (screen location)
STD ULX * Save Sprite1 Upper Left X & Y Co-ordinate
* CALC LOWER RT
ADDD Sprite1PixelsWide * Sprite1 Width = Sprite1Width + Object width, Sprite1Height = Sprite1Height + Object Height
STD LRX * Save the Lower right corner of Sprite1 X & Y co-ordinate
* Test to see if the two sprites overlap
LDD Sprite2X_Co_ordinate * A = where Sprite2 X starts upper left corner, B = where Sprite2 Y starts (upper row)
CMPA LRX * If Sprite2 upper left X >= Lower right corner of Sprite1 X
BHS NoCollision@ * Then return with no collision
CMPB LRY * If Sprite2 upper row Y >= Lower right corner of Sprite1 Y
BHS NoCollision@ * Then return with no collision
ADDD Sprite2Width * D = D + sprite2's Width & Height, A = Sprite2's bottom right X co-ordinate, B = Sprite2's bottom right Y Co-oridnate
CMPA ULX * If sprite2's bottom right X <= sprite1's Upper Left X
BLS NoCollision@ * Then return with no collision
CMPB ULY * If sprite2's bottom right Y > sprite1's Upper Left Y
BHI CheckBoundingBoxes * If B is greater, then collision is possible, go check where the bounding boxes overlap
NoCollision@
CLRA * Clear the carry flag to indicate no collision
RTS * Return
*
*COMPARE FOR PICTURE INTERSECTION
CheckBoundingBoxes:
* D = D + sprite2's Width & Height, A = Sprite2's bottom right X co-ordinate, B = Sprite2's bottom right Y Co-oridnate
SUBD Sprite2PixelsWide * D = sprite2's Width & Height - sprite1's Width & Height = size of the over lapping box
* D = D - (Width and Height of Sprite2)
STD XTEMP * Save D where A = difference from sprite2's bottom right X co-ordinate and sprite2's width
* B = difference from sprite2's bottom right y co-ordinate and sprite2's height
LDD #$0000 * CLEAR OFFSETS * D = 0
STD OX1 * GET OBJECT * OX1 = Sprite2 = X,Y = 0
STD PX1 * PLAYER * PX1 = Sprite1 = X,Y = 0
LDD XTEMP * Restore D = sprite2's Width & Height - sprite2's Width & Height
* FIND UPPERMOST
SUBB ULY * FIND UPPERMOST * B = B - Sprite1 Upper Left Y Co-ordinate
BHI IC10 * PLAYER UPPERMOST * If it's higher then use Sprite1 is the uppermost
NEGB * Otherwise make B a positive number and use Sprite2 is the uppermost
STB OY1 * OBJECT UPPERMOST * Save it as Sprite2 is uppermost
BRA IC2 * Skip ahead
IC10 STB PY1 * Save B sprite1 Y is uppermost
*FIND LEFTMOST
IC2 SUBA ULX * UPPER LEFT X COORD * A = A - Sprite1 Upper Left X Co-ordinate
BHI IC20 * PLAYER LEFTMOST * If it's higher then Sprite1 it is the left most
NEGA * Otherwise make A positive
STA OX1 * OBJECT LEFTMOST * save the value as Sprite2 is the left most
BRA IC3 * Skip ahead
IC20 STA PX1 * Save A sprite1 X is the left most
*FIND LOWERMOST
*CALC OY2
IC3 LDD XTEMP * GET OBJX * D = sprite2's Width & Height - sprite1's Width & Height
ADDD Sprite2PixelsWide * A = A + Sprite2 width, B = B + Sprite2 Height
SUBB LRY * Lower Right Y Coord * B = B - Lower right corner of Sprite1 Y
BHI IC4 * OBJECT LOWEST * If B was > LRY then Object is the lowest, skip ahead
CLRB * PLAYER LOWEST * B=0, Sprite1 is the lowest
*FIND RIGHTMOST
*CALC OX2
IC4 SUBA LRX * Lower Right X Coord A = A - lower right corner of sprite1 X
BHI IC40 * OBJECT RIGHTMOST * A was > LRX then Sprite2 is rightmost, skip ahead
CLRA * PLAYER RIGHTMOST * A =0, Sprtie1 is the rightmost
*CALC X,Y COUNT
IC40 STD CTEMP * SAVE * Save OX2 & OY2 as D in CTEMP
* GET WIDTH,HEIGHT
LDD Sprite2PixelsWide * D = Sprite2 width in pixels & Height
SUBD OX1 * OX1,OY1 * A = A - OX1, B = B - OY1
SUBD CTEMP * OX2,OY2 * A = A - OX2, B = B - OY2
LSRA * Divide X count by 2 so we have # of bytes to check per row of overlapping sprites
DECA * We test the bitmaps using LDA B,U (if we need to test 2 bytes), this will make itso we test LDA 1,U and LDA 0,U
* Otherwise we will be testing LDA 2,U and LDA 1,U which will be wrong (also do a LDA B,Y)
* Original code had a DECB down below that is commented out, no need to do it each row, just do it here once
STD XCNT * XCNT,YCNT * Save the X count & Y count in XCNT as the columns and rows in the bitmaps to check
*INIT SCAN PARAMETERS
*Y=OBJECT ADDR
*U=PLAYER ADDR
LDA Sprite1Width * A = Sprite1's width in bytes
STA PLW * Save it as Sprite1's width, so we can move to the next row to check against (below)
LDB PY1 * B = PY1 Height = Height of sprite1
MUL * D = A * B = starting row to test the bitmap from
LDX Sprite1Bitmap * X = Sprite1 bitmap start location
LEAX D,X * INIT PLAYER INDEXSH * X now points at the row to start testing Sprite1's bitmap at in memory
LDB PX1 * B = PX1 = pointer to start checking sprite1's bitmp at
ABX * X = X + B (column), X is now ready to test the bitplane for Sprite1 (ABX uses unsigned values in B for big sprites)
TFR X,U * U = X, U is now ready to test the bitplane for Sprite1
LDA Sprite2Width * A = Sprite2's width in bytes
STA OBW * Save it as Sprite2's width, so we can move to the next row to check against (below)
LDB OY1 * B = OY1 = Height of sprite2
MUL * D = A * B = starting row to test the bitmap from
LDX Sprite2Bitmap * X = Sprite2 bitmap start location
LEAX D,X * INIT PLAYER INDEXSH * X now points at the row to start testing Sprite2's bitmap at in memory
LDB OX1 * B = OX1 = pointer to start checking sprite2's bitmap at
ABX * X = X + B (column), X is now ready to test the bitplane for Sprite2
IC5 LDB XCNT * B = count of bytes to test per row (X direction)
; DECB * B = B - 1, test each column (make it zero based), not needed as we now do this above before saving this value
IC50 LDA B,U * A = byte in Sprite1's bitmap to test
BEQ NoCollisionYet * If = 0 then this pixel is transparent no need to compare, skip checking Sprite2
LDA B,X * CHECK * A = byte in Sprite2
BEQ NoCollisionYet * If = 0 then no need to compare, skip checking
* Test nibbles for collisions
* If we get here we know both bytes have a value (we don't know which nibble though)
* Test the left nibble of each, A already has value at B,Y from above
ANDA #$F0
BEQ CheckOther * If the left nible is zero then we know the right nibble must have a value or we wouldn't be here, go check Sprite2's right nibble
* If we get here then Sprite2 has a value in the left nibble, check Sprite1
LDA B,U * Get Sprite1 left nibble value
ANDA #$F0
BNE CollisionDetected * If the left nible is Not zero then there is a collision
* If we get here then the left nibble of Sprite2 is used but Sprite1's left nibble is zero
CheckRight:
LDA B,X * Get Sprite2 right nibble value
ANDA #$0F
BEQ NoCollisionYet * If the right nible is zero then skip ahead, no collision
CheckOther:
* If we get here then Sprite2 has a value in the right nibble, check Sprite1's
LDA B,U * Get Sprite1's right nibble value
ANDA #$0F
BNE CollisionDetected * If the right nible is Not zero then there is a collision
* If we get here then no collision detected so far
NoCollisionYet:
DECB * decreemnt the position to check the column in the bitmap
BPL IC50 * NOT FINISHED COLUMN * keep looping until we've checked all columns (B is not -1 yet the loop)
LDD PLW * A=PLW=Sprite1's width, B=OBW=Sprite2's width
LEAU A,U * Move to the next row of the bitmap to test from for Sprite1
ABX * Move to the next row of the bitmap to test from for Sprite2
DEC YCNT * Decrement the number of rows to test
BNE IC5 * Loop again if we haven't gone through all the rows yet
CLRA * Clear the carry flag to indicate no collision
RTS * Return
CollisionDetected:
COMA * Set the carry flag to indicate a collision
RTS * Return
I haven’t done extensive testing of the code above, most of it is a conversion of the same code in ROBOTRON with only a few tweaks of my own to speed it up a little so it should be pretty solid. Plus I added code to detect collision at the nibble level.
As for the really cool things I like about the code above, is it’s way smaller than the code I wrote. I really like the way it figures out which sprite is the upper/left and which is the lower/right. This is done where it does the comparison and if the value is a negative then do a NEG instruction and use the other sprite with the NEG value which then makes it a positive and uses that value.
When you write code that does a compare command like CMPA the processor does a SUBA but doesn’t change the value of the A instruction. It just sets the condition code flags accordingly. There are a couple lines of code above where it does the SUBA or SUBB instruction then does a BHI where it actually uses the results of the SUBA/SUBB instruction instead of doing a CMPA/CMPB and then doing the actions afterwards. This is VERY COOL, in my opinion.
I hope this code is helpful for other 6809 programmers to use for their games. If you do use another screen size like 320 x 224 x 16 colours you could change the code above to work on bytes only and ignore the pixel values. You would have a width of 160 bytes instead of 320 pixels which would keep the values in a range of under 256 so the code would work the same.
See you in the next post,
Glen