Saturday, September 28, 2019

2600 Collisions

One of the nicer things about the 2600 is the hardware collision detection. Having written software sprite-based engines where collision detection had to be done with software this is a nice feature. As the TIA must know what to draw in the pixel, and there are only 6 things that can collide, the hardware simply needs to do a series of and operations with the information it has and store that information in the appropriate register. As each of the 6 thing that can collide can’t collide with themselves and are reversible (missile1 hitting player0 is the same as player0 hitting missile1) that means that there are only 15 possible collisions which I outline in the table below.



By assigning each collision with a bit we can put all the possible TIA collisions into a 15 bit word or split it into 2 bytes. This is the way a modern system would do this if the API designer didn’t add a hundred layers of abstraction to the system. For some reason, the TIA designers decided to have the collision bits divided into 8 registers as shown in table \ref{tbl:collisionregs}. This lets you use the BIT instruction which sets the Negative flag if bit seven is set so BPL/BMI can be used to check this bit as well as setting the overflow flag if bit 6 is set allowing for BVC/BVS branching.



Once a collision register has been set it stays set until the CXCLR is written to. As you generally want the collision for the whole frame this is not a big issue but something that needs to be kept in mind if you want to figure out where on the screen the collision occurred.

The TIA emulator handles collisions by tracking the flags as a single integer with bits set up as per the first table. This is a global collisonState variable which is cleared by CXCLR. Whenever a pixel is being written, we create a mask that will be logically ORed to the collision state. It assumes that all collisions are going to happen.

var collisionMask = 32767

As we draw each sprite, we check to see if the element is drawn. If it is, the mask is unchanged, but if it is not then no collision with that component could have happened so we remove those bits using the collision mask as an indication of which bits should be ANDed out.

pixelColor = if (sprites[0].isPixelDrawn(column)) playfieldBallColor else {
collisionMask = collisionMask and sprites[0].collisionMask
pixelColor}
pixelColor = if (sprites[1].isPixelDrawn(column)) playerMissile1Color else {
collisionMask = collisionMask and sprites[1].collisionMask
pixelColor}
pixelColor = if (sprites[2].isPixelDrawn(column)) playerMissile1Color else {
collisionMask = collisionMask and sprites[2].collisionMask
pixelColor}
pixelColor = if (sprites[3].isPixelDrawn(column)) playerMissile0Color else {
collisionMask = collisionMask and sprites[3].collisionMask
pixelColor}
pixelColor = if (sprites[4].isPixelDrawn(column)) playerMissile0Color else {
collisionMask = collisionMask and sprites[4].collisionMask
pixelColor}

collisonState = collisonState or collisionMask

With that done, we now have all the information we need to read the collision registers so we implement the readRegister method to return the requested pair of collision bits as outlined in our second table.

private fun buildCollisionByte(bit7mask:Int, bit6mask:Int):Int {
val bit7 = if ((collisonState and bit7mask) == bit7mask) 128 else 0
val bit6 = if ((collisonState and bit6mask) == bit6mask) 64 else 0
return bit7 or bit6


fun readRegister(address:Int):Int {
return when(address) {
TIAPIARegs.CXBLPF -> buildCollisionByte(TIAPIARegs.ICOL_PFBL,0)
TIAPIARegs.CXM0FB -> buildCollisionByte(TIAPIARegs.ICOL_PFM0, TIAPIARegs.ICOL_BLM0)
TIAPIARegs.CXM1FB -> buildCollisionByte(TIAPIARegs.ICOL_PFM1, TIAPIARegs.ICOL_BLM1)
TIAPIARegs.CXM0P -> buildCollisionByte(TIAPIARegs.ICOL_P0M0, TIAPIARegs.ICOL_M0P1)
TIAPIARegs.CXM1P -> buildCollisionByte(TIAPIARegs.ICOL_P0M1, TIAPIARegs.ICOL_P1M1)
TIAPIARegs.CXP0FB -> buildCollisionByte(TIAPIARegs.ICOL_PFP0, TIAPIARegs.ICOL_BLP0)
TIAPIARegs.CXP1FB -> buildCollisionByte(TIAPIARegs.ICOL_PFP1, TIAPIARegs.ICOL_BLP1)
TIAPIARegs.CXPPMM -> buildCollisionByte(TIAPIARegs.ICOL_P0P1, TIAPIARegs.ICOL_M0M1)

// Unknown or unimplemented registers print warning
else -> { println("TIA register $address not implemented!"); 0}
}
}

This means that we now have collisions. Next fortnight things are changing as I make up my mind about the future of this blog.

No comments:

Post a Comment