Saturday, August 31, 2019

Sprites are Player Missile Graphics

To plan out the classes that I would use for creating the player and missile sprites I put together the following table which shows the various registers used for the different player and missiles as well as how the different objects compare to each other. Table 1 is a handy chart to have for programming the 2600 and wish I would have created it when I first started playing around with the 2600!


The original plan was to have a base class that held the common functionality and have the player class and missile/ball classes override this class. This is probably the “proper” way to do this but the classes are so similar that having them combined into a single class seemed like the easiest solution. A missile or ball is essentially a sprite that is only 1 pixel so by simply tracking the number of pixels as size, all the different player missile graphics can be incorporated.

Sprites have a collision mask that will be used for determining which collision flags they are part of. This will be explained later when we implement collisions but for now it can be thought of as simply a magic number with each bit in the number representing a particular pair of objects that can collide with each other.

Scale is how many pixels to draw for each pixel in the sprite. The 2600 supports scales of 1, 2, 4, and 8 with player sprites limited to a maximum scale of 4. The number of copies is how many times to draw the sprite while the gap is the distance (in multiples of the size) between the copies. The 2600 supports distances of 2, 4 and 8.

The sprite is placed on the screen at the x location with the deltaX variable being how much to alter the x position when an HMOVE command is issued. Normally we do not want the sprite to move so the default is 0.

The drawing bits are the byte used to represent the image in the sprite at that horizontal scanline. It can be considered a bit of a kludge when using missiles and balls as only the one bit is used. Mirroring is used to indicate which order the pixels should be drawn. For balls and missiles it will mirror the single pixel but as the result of that is the original image there is not much use for size 1 sprites.

class PlayerMissileGraphic(var size:Int, var collisionMask:Int) {
var scale = 1
var copies = 1
var distanceBetweenCopies = 0   // close = 2, med = 4 far = 8
var x = 0
var deltaX = 0
var drawingBits = 0
var delayedDraw = false
var mirror = false

The heart of the sprite class is the isPixelDrawn method which simply determines if the current state of the sprite would result in a pixel being drawn at the indicated location. This is simply a matter of looping through the number of copies of the sprite and for each copy being drawn figure out where it will be drawn. We know the number of pixels that a copy will occupy as it is simply the size times the scale so if the column being drawn is within that range we figure out which pixel in the image is at that location and if that bit is 1 then we are drawing otherwise we are not. Calculating which bit in the drawing data to draw depends on whether we are mirroring the sprite and the scale of the pixels.

fun isPixelDrawn(col:Int):Boolean {
if (col < x) return false
if (delayedDraw) return false

var pixelAtCol = false
for (copyCount in 1..copies) {
val copyStart = x + (copyCount - 1) * size * scale * distanceBetweenCopies
val copyEnd = copyStart + size * scale
if ((col >= copyStart) and (col < copyEnd)) {
val bitPos = (col - copyStart) / scale
val bit = if (mirror) 1 shl bitPos else (1 shl (size-1)) ushr bitPos
if ((bit and drawingBits) > 0) pixelAtCol = true
}
}

return pixelAtCol
}


The HMOVE command is processed simply by adjusting the x variable by the movement delta with a bit of clamping code to make sure it is within the range of the display. My clamping is done by nesting a max function within a min function by having the highest column be the minimum to compare against and 0 and the target column being the max to pick from. As a result the max will clip away negative values making them 0 while the min will clip away off-right-edge values making them the right edge.

fun hmove() {
x = min(159, max(0, x + deltaX))
}


The TIA player sprites have 8 different modes as shown by the chart below. While this could be handled by the TIA register calls, there would be duplication of code so I wrote a simple utility method that will set the sprite scale, copies, and distance based on a player sprite mode. This method is just a simple when statement which sets the parameters appropriately based on the indicated mode. The different play modes that the TIA recognizes is in table 2.



fun setPlayerScaleCopy(scmode:Int) {
when (scmode) {
TIAPIARegs.PMG_NORMAL ->
{scale = 1; copies = 1; distanceBetweenCopies = 1}
TIAPIARegs.PMG_TWO_CLOSE  ->
{scale = 1; copies = 2; distanceBetweenCopies = 2}
TIAPIARegs.PMG_TWO_MEDIUM  ->
{scale = 1; copies = 2; distanceBetweenCopies = 4}
TIAPIARegs.PMG_THREE_CLOSE ->
{scale = 1; copies = 3; distanceBetweenCopies = 2}
TIAPIARegs.PMG_TWO_WIDE  ->
{scale = 1; copies = 2; distanceBetweenCopies = 8}
TIAPIARegs.PMG_DOUBLE_SIZE  ->
{scale = 2; copies = 1; distanceBetweenCopies = 1}
TIAPIARegs.PMG_THREE_MEDIUM  ->
{scale = 1; copies = 3; distanceBetweenCopies = 4}
TIAPIARegs.PMG_QUAD_PLAYER  ->
{scale = 4; copies = 1; distanceBetweenCopies = 1}
}
}

We now have a class that can handle the sprites in the TIA but we still have to handle the TIA registers related to drawing the sprites and perform the drawing of the sprites when the color clock reaches them. This will be what we do next.

Saturday, August 17, 2019

The 2600 Play-field

The play-field is a 40-bit image that is stretched across the 160 pixels that make up a scan-line. This means that each play-field bit occupies 4 pixels. The original purpose behind the play-field is to create the map or maze that the player occupies. It is often used for other things such as drawing scores, background images or title screens. Anything that can be represented using crude graphics is fine.

The play-field is managed using the CTRLPF, PF0, PF1 and PF2 registers. The CTRLPF register controls how the play-field will be displayed controlling the display order of the play-field, whether the bits should be repeated or mirrored, and if the play-field should be drawn using the play-field color or drawn using player 0 color on the left and player 1 color on the right. This register also handles scaling of the ball which will be covered later.

The PF0, PF1 and PF2 registers hold the 20 bits that are to be drawn. These bits will either be repeated or mirrored based on the settings in CTRLPF. As with color, you can change the play-field bits in the middle of drawing so that the right side of the screen is different from the left side of the screen. The order of the bits is weird which is likely a by-product of keeping the transistor count of the chip down. The image below shows the order of the pixels that make up the display.



Essentially the play-field drawing starts with the 4 bits in the PF0 register drawn in low to high order. This is followed by the eight bits from PF1 in high to low order. Finally the PF2 bits are drawn in low to high order.  This is repeated or mirrored for the right half of the screen. As many games used symmetrical maps this made it easy to set up the play-field bits and simply mirror them. Asymmetric maps are a bit trickier as you need to set up the PF0, PF1, and PF2 registers for the left half of the screen then wait until the beam is past PF0 to change PF0 to the right side PF0, wait until the beam is past PF1 to change to the right side PF1 and wait until the beam is past PF2 to change to the right side PF2. While you could theoretically do this while mirroring, asymmetrical images are easier to do with repeated mode unless you don’t need to change PF2.

The hard part about implementing the play-field is dealing with the different orientations of the data. My internal representation of the play-field data is simply a 20 bit integer with the bits being the play-field bits from left to right. When one of the PF registers is called, the appropriate bits of this play-field value are set. This requires that we flip the bits, but this is a simple process of using and to see if a bit is set or to build the reversed byte and shifts to get the bits aligned properly.

fun reversePFBits(value:Int):Int {
var reversed = 0
var testBit = 1
for (cntr in 0..7) {
reversed *= 2
reversed += if ((testBit and value) > 0) 1 else 0
testBit *= 2
}
return reversed
}

The calls to the playfield register simply take the value that is to be written to the register and, if necessary, reverse the bits. It then masks out the bits in the playfield that the register represents, shifts the bits to the appropriate part of the playfield then uses a logical or to put the new value in place. The playfield control register CTRLPF simply sets flags to indicate how the playfield should be rendered. The flags are mirror to indicate if mirroring should be taking place, scoreMode to determine if we are drawing using the playfield color or the player 0 color on the left and the player 1 color on the right. The priorityPlayfield flag determines if the playfield should be drawn first or last.

TIAPIARegs.PF0 -> {
var bits = reversePFBits(value)
playfieldBits = (playfieldBits and 0xFFFF) or (bits shl(16))
}
TIAPIARegs.PF1 -> {
var bits = value
playfieldBits = (playfieldBits and 0xF00FF) or (bits shl(8))
}
TIAPIARegs.PF2 -> {
var bits = reversePFBits(value)
playfieldBits = (playfieldBits and 0xFFF00) or bits
}
TIAPIARegs.CTRLPF -> {
mirror = (value and 1) == 1             // bit 0 handles mirror mode
scoreMode = (value and 2) == 2          // bit 1 handles score mode
priorityPlayfield = (value and 4) == 4  // bit 2 Places playfield above sprites
}

To draw the play-field we simply determine which play-field pixel should be processed. The left is simply the column being drawn divided by 4. The right is the same (less 80) if repeated and the inverse if mirrored. Once we know the bit to be checked we can determine if there is a pixel there and if so set the color based on the scoreMode. We then adjust our collision mask which will be explained in the collisions section later but is essentially a set of bits indicating which collisions have happened.


val pfCol = if (column < 80) column / 4 else
if (mirror) (159 - column) / 4 else (column - 80) / 4
var pfPixelMask = 0x80000 shr pfCol
val shouldDrawPlayfield =(playfieldBits and pfPixelMask) > 0
val pfColor = if (scoreMode) {if (column < 80) playerMissile0Color else
playerMissile1Color } else playfieldBallColor
if ( ! shouldDrawPlayfield) collisionMask = collisionMask and 31668


At the start of the drawing of the pixel, we check to see if the playfield is normal priority and if so draw it.

if ((shouldDrawPlayfield) and ( ! priorityPlayfield )) pixelColor = pfColor
// TODO sprite drawing code here 
if ((shouldDrawPlayfield) and (priorityPlayfield)) pixelColor = pfColor


Finally, at the end of the drawing of a pixel, we see if the playfield is priority and if so draw it. Between these two checks is the sprite drawing which is the next thing to be implemented.

Saturday, August 3, 2019

Drawing Pixels on 2600 TIA

In the 2600, the TIA is drawing the display in real time by sending signals to the television. There was no frame buffer in the TIA. With an emulator, our situation is slightly different. As I want the TIA emulator to work in several different platforms, a cross-platform way of generating the display is needed. My solution is to simply have a 160 “pixel” raster line that the TIA emulator writes to. Whenever a scan-line is finished, or part way through drawing the scan-line if you are doing some type of stepping through the program, the class managing the display will grab this information and use the TIAColors information to build a display appropriate to the platform that is being rendered.

The figure below shows the logic that the TIA uses to determine which of the four colors to place on a pixel. We start with the background color. This would be the background color at the time that the pixel was drawn. As the color registers can be changed in the middle of rendering a line this can be a distinct color at different points along the scan-line.



The order that things are drawn depends on flags in the CTRLPF register. Normally the play-field is drawn right after the background but if bit 1 of the CTRLPF is set then the play-field and ball will be drawn after the players and missiles. Player 1 and the missile associated with that player is drawn before player 0. So if CTRLPF bit 1 is set the drawing order is background, player and missile 1, player and missile 0, play-field and ball. Otherwise the drawing order is background, play-field and ball , player and missile 1, player and missile 0.

This drawing order determines what the final pixel color will be in cases where multiple display objects overlap a given pixel. The 2600 goes even further by tracking the overlaps and setting a series of collision flags that can be tested to see if there were any collision between display objects when the line was being drawn.

This effectively means that we have six things to potentially draw in any given pixel. Three of these --  the ball, missile 0, and missile 1 – are simply a line so they can be grouped into a common class. The two player sprites are not that much more complicated so while they could be a separate class from the ball and missile, making a more complicated player missile graphics class to handle all five of these display objects makes sense.

This leaves us with the play-field. This is my favorite part of the 2600 rasterizer as it is the closest thing you have to traditional bitmap graphics.