skip navigation

How the ATARI 2600 and COMBAT Handle Collision Detection

Description

In this episode we talk about collision detection, how it works, and the 15 registers we can use. We also explore how the game Combat handles collisions and bouncing missiles, and have a look at some example code to create a ball that bounces around the playfield. As always, sample code for the episode is available in our github. Learn to program games for the Atari 2600!

Released:
December 6, 2022

Original Link:
https://youtube.com/watch/fi5n4tPL1PI

Transcription

Your support is greatly appreciated! https://www.patreon.com/8blit

Welcome to 8-Blit, a channel covering all the tips, tricks, and everything in between for programming your own games for the Atari 2600 from 1977.

Every second of every day we’re interacting with the world around us. Walking down the street, sitting on a chair, hitting a baseball, firing defensive weapons at incoming ballistics from your base on the planet Zardon protecting your cities from the evil planet Krytol.

All these examples have at least one thing in common. Things need to interact with each other. It wouldn’t be much fun if you went out for a walk… and then fell through the earth, and the Zardontons wouldn’t be able to fend off the Kyrtolians if they couldn’t hit those incoming missiles… but I guess their weapons wouldn’t be much of a threat either.

Anyway, art imitates life. Therefore, our game objects need to mimic the ability to interact with each other. In this episode we talk about Collision Detection, the ATARI 2600’s ability to detect them, and as usual, its limitations. That’s all coming up on this episode of 8blit.

If you ever went to the corner store with a note from your mother to buy cigarettes, or scored some smokes from a vending machine, you just might like this channel.

We explore the technology and history of arguably one of the most iconic game consoles ever made. It’s a flashback to when things were simple, when everyone wore really tiny shorts with tall socks, pedaled around on banana seat bicycles, and your friends would come by to play Atari.

Click on the subscribe button to join the community, stay up to date with new videos, and get access to tutorials and a ton of code examples. If you’d like to help out with producing more videos capturing the intrepid spirit of Atari from the 70’s right through the 80’s, then please consider joining our other patrons, and help support quality educational videos. More information on how to become a patron in the video description.

In today’s three-dimensional games, there are many different methods to determine if one object has intersected with another. Generally, these would be implemented using invisible geometric shapes that surround the player and other objects. Fairly complicated calculations can determine if one of these shapes has intersected with another so the game engine can determine what action to perform. Are we limiting the players movement, collecting a loot box, or registering damage?

Bounding Boxes tend to encapsulate a play area to limit mobility. Hit boxes can be used to either surround an entire player’s model, or move along with the player’s limbs as they move for greater accuracy. Hurt Boxes can be defined to surround vulnerable areas of the player’s model that deal damage if another player’s hit box intersects. Given the complex mathematics involved and the impact on the processor, often a larger less accurate box may be used to surround a player model to detect a collision before calculating if there was an intersection with smaller more accurate hit boxes.

In two-dimensions it’s a lot less processor intensive as we would usually deal with sprites overlapping as our collision detection. This is still the basic concept of a bounding box, whereas the sprites dimensions are used along with its position in the game environment to calculate if another sprites bounding box overlaps.

If the player graphic fills the entire bounding box then any other sprites bounding box will register an accurate hit. However, if your player graphic is smaller than the box, then a hit within the box doesn’t necessarily mean that your player was hit. In these cases where accuracy is desired, we might use a ‘mask’. This can either be another version of the sprite defining a silhouette of the hit area, or using a particular color to mask out no-hit regions. The second would also be used to define the sprites’ transparent pixels where the pixel plotting routine used that color to indicate when to not draw those particular pixels.

On the ATARI 2600, we have none of these things. I mean, you could define a bounding box around your graphic objects. You could track the x/y positions of all your objects, and use their height and width in a calculation to determine if one intersects another. You could also adjust your movement routine to restrict movement between a min/max x or y value. However, we’re already heavily restricted with the processor speed and limited ram.

So what are our options here?

Well, this is where the TIA (or Television Interface Adaptor) helps us out again. It doesn’t process bounding boxes or sprite dimensions, as we’re always working within the context of a single scanline. However, while the TIA is drawing the graphics along the scanline, it can detect if any pixels of our active graphic objects are written to the screen at the same time. This will set a collision flag which we can observe to determine which objects have collided.

The ATARI 2600 has 5 graphics objects we can use in our game. Player Zero, Player One, Missile Zero, Missile One, and the Ball. These are all handled by the TIA which is responsible for drawing the object on each scanline and all five can be manipulated in some way by the TIA, such as altering the size, how many copies (and they’re spacing as they’re drawn to the screen), and in the case of the player graphics whether they are drawn normally or in reverse. On newer consoles these would all be referred to as sprites.

However, there are two additional graphics objects we can use. The background, and the Playfield. The background can be considered a non-interactive object within a frame. We use this to paint the screen by applying a color property to a scanline. In many games the background is a single color for the entire frame, while others use it in creative ways to achieve more advanced graphical environments like changing the color on each scanline to create a gradient to mimic a setting sun. Beyond that, the background doesn’t have any other capabilities.

The playfield is forty pixels wide, where each pixel is 4 color-clocks wide (or 4 graphic pixels wide). It’s often used for purely graphical purposes like the background, where you can change the color every scanline (or more often in some games), and you can turn on and off the pixels to create shapes and designs. However, it can also be used for collision detection with other objects like the players, missiles, and ball. This allows us to create a playfield that can restrict movement, or even take damage.

In total there are 6 objects that can interact with each other, and the TIA gives us a 15 bit collision register to detect all possible collisions between the 6 objects.

These are split up between 8 addresses where bits D7 and D6 on each address represent the type of collision.

Each address in this list begins with CX to identify it as a collision address. Here’s how the collision bit indicators are mapped out. I’ll leave out the CX on each line for simplicity.

M ZERO P: D7 for Missile Zero and Player One, and D6 for Missile Zero and Player Zero M ONE P: D7 for Missile One and Player Zero, and D6 for Missile One and Player One P ZERO FB: D7 for Player Zero and the Playfield, and D6 for Player Zero and the Ball P ONE FB: D7 for Player One and the Playfield, and D6 for Player One and the Ball M ZERO FB: D7 for Missile Zero and the Playfield, and D6 for Missile Zero and the Ball M ONB FB: D7 for Missile One and the Playfield, and D6 for Missile One and the Ball BLPF: This one only uses D7 for the Ball and the Playfield PPMM: D7 for Player One and Player Two, D6 for Missile One and Missile two. Each of the D7 and D6 bits at these 8 addresses are Latching bits, which means that once a collision occurs the bit remains set until you clear them. For that you can strobe the CXCLR register which will clear all the latches. Since the collision latches are set while the screen is drawing, it’s best to handle the collisions some time after VSYNC on the next frame when you’re ensured that all the possible collisions will have been captured. After you’re done, make sure to clear the register so you can start collision detection again.

While the collision detection register on the ATARI 2600 is easy to use, it doesn’t tell us anything about what caused a collision.

In games where we’re firing a weapon at an opponent, all that’s needed is to check if my missile hit their player, and vice versa. If we want to destroy a missile when it hits a wall, then we just check if the missile collided with the play field. If we want to limit movement of a player, then on each move we save the last position of the player, and if the player collides with the playfield then we simply return them to the last position before the collision occurred.

What if we need to know more about the collision, like where the collision occurred? What if we wanted to bounce a ball off the playfield? How would we know which side of a playfield pixel the collision occurred?

If we look at an arena where there are four walls, we have two that are horizontal and two that are vertical. If the ball is moving at an angle then it’s traveling both horizontally and vertically at the same time. There’s no way for us to calculate if the ball hit the wall horizontally or vertically, so which way should it bounce?

Luckily for us, this was solved in the game Combat from 1977 by Joe Decuir, in a rather ingenious way.

As we can see, the arena in combat not only contains walls surrounding the arena, but it also includes walls within the arena making our problem even more complex. Firing a missile in a game when bounce is turned on, reflects the missile off both the outer and inner walls.

Combat is somehow able to determine the direction of the missile, where it hits the wall, and the direction it bounces off. When the tank fires a missile and it collides with the wall, it first tries to bounce by reversing the heading vertically. In the next frame after the missile’s position has moved once in the new heading, it then checks to see if there’s a second collision in series. In the case of a vertical wall with no obstructions, there is no second collision so the missile has no problem bouncing off the wall.

However, if it tries this with a horizontal wall, after the first collision when it reverses the heading, the missile is still inside the wall. In this case there is a second collision. Now, instead of going deeper into the wall, we’ll turn our default vertical bounce into a horizontal bounce by reversing the missile heading by 180 degrees. After that it’s guaranteed that there will be a collision on the next frame because it will be back into its original position, so it has to wait one more frame to check for a collision to see if it’s outside the wall. Hopefully it is and the missile can continue on its way.

If it’s still registering a collision, say we’re hitting very close to a corner, and after vertically reversing the initial heading, and then reversing it horizontally… and it’s still stuck inside the wall. This time it will revert the heading back to what it was initially on the very first collision, and then reflect it by 180 degrees. This will make the missile bounce back the way it came. Thus, freeing it from the wall.

I have provided a link in the video description to a fully disassembled and documented copy of the complete source code for Combat. It’s full of valuable information and it’s really great to see just how the classic game works under the hood.

In this episode’s code examples, we can use the tricks we’ve learned from Combat to make an enclosed room with an endless bouncing ball. We’re making use of the bearings, and bouncing, and x/y movement used in Combat and presenting it in a way that will make it easier to understand and experiment.

Here we can see our code example in action. The ball is set to an initial position and bearing, and then moves freely throughout the arena, bouncing off the walls and platforms which were made using the playfield. Let’s have a quick look at the code and I’ll explain how this works.

In this implementation, it heavily depends on the data and code working together. Let’s first start by looking at the bearing_offsets table and how these values are used.

Here we have a table of 16 bytes. These bytes represent the bearing (or direction) our ball can move in 22.5 degree increments. The value of the bytes encode the horizontal and vertical movement for the 16 bearings. The first nibble, D7 to D4 is the HMOVE value (or Horizontal Movement) that will be assigned to the ball object, while the second nibble, D3 to D0 is used to calculate the change in the y position of the ball. Which would be the scanline the ball is drawn.

With each bounce, we’ll use an index on this table to change which bearing the ball is headed.

Let’s have a look at how we initialize our ball.

First we’ll arbitrarily choose a starting x and y position for our ball and then place the ball. We’ll then give it an initial bearing.

When we enter our kernel, one of the first things we’ll do is check to see if we have a collision. This is normally done during Vertical Blank because all the collision bits would have been set during the previous frame.

Since we’re using the ball object, we’ll check the CXBLPF address to determine if the ball has collided with the playfield. You may remember a bit earlier that this address is only used for collisions between the ball and the playfield, so any positive value would indicate a collision has occurred.

If there’s no collision then we would skip over collision handling and move the ball along the bearing to its next position. Here we load the current bearing, and then strip off the high nibble to maintain a 4 bit value for the bearing index. Then we load the offsets for that bearing for our table.

Since the D7 to D4 bits of our offset values contain the horizontal movement for our ball, and the the ball objects horizontal movement register only uses D7 to D4, we don’t have to prepare the value at all, we can assign the full byte and it will ignore the D3 to D0 bits that contain our y offsets.

Now that our horizontal motion is set, we need to isolate the vertical offset. We do that by striping off the high nibble of the offset value which leaves us with only D3 to D0. This will leave us with a possible value from 0 to 15. We need to convert this value into something we can use to adjust our y position, in a way that can not only increase the y position if the ball is going up, but to decrease it if the ball is going down. For this we subtract 8. This converts the value into a signed value, by shifting the range downward to go from negative 8 to plus 7. This is our y offset. Now we just need to add this to the previous y position to give us the new y position for the ball.

Now let’s head back up and see how we handle when a collision does occur.

First, let’s check if a collision is already ongoing. If this is the first collision, then we’ll load up what the current bearing is, and store it for later. We’ll use this original bearing as our last resort if we get stuck in a wall. If that happens we’ll reverse this bearing by 180 degrees to bounce the ball back the way it came.

Next we’ll continue processing this first bounce by rotating the bearing by 90 degrees vertical. We do this by doing an exclusive or of our bearing to inverse our number to negative, then increase it by one. Funny thing about the 6502 and 6507, it doesn’t include an instruction to increase a value stored in the A register, so we’ll store it in memory and increase it there. Next comes another interesting bit. Each 4th bearing in our list is pointing directly North, South, East or West. A ball heading directly at 90 degrees to a wall will bounce back the way it came. This would make it very boring in a playfield made up of flat surfaces. So if the ball does happen traveling on one of these bearings we’re going to offset it by 1, so the ball doesn’t get trapped somewhere.

All we need to do now for the first collision is to continue on and position the ball as we showed earlier. In the case that the first bearing adjustment didn’t work and the next frame registered another collision, then we’re going to check if this is the second collision in series, and then jump down and load the current bearing, which is the one that we already reflected vertically.

If this is the third collision then we’ll load the original bearing.

Now we’ll reverse the bearing by 180 degrees. If this was the second collision it will be the same as if we have reflected the original bearing horizontally, and if it’s the third collision then it will be the same as reflecting the original bearing by 180 degrees to send the ball back the way it came.

After we’ve done that, we’ll increase the collision count to keep track of home many times we’ve done this.

Now that we’ve processed the collision detection and after moving our ball, all we need to do is clear the collision registers by strobing the CXCLR register.

After all of this, when we run our code we can see our ball bouncing all around our arena. It’s interesting to watch the ball for a while. Without the presence of external influence, the ball will eventually settle into a pattern that it will continue indefinitely.

Feel free to try this out yourself. The source code for all of our episodes are available on our github which is linked in the videos description.

If you found the video interesting I’d appreciate it if you’d hit that like button and share this episode, it really helps us out.

If you haven’t already done so, please consider subscribing to the channel and hit the bell to make sure you’re notified when a new video comes out.

If you’d like to help support the development of the channel, please consider becoming a Patron. I’ll link to that in the description.

Well, that’s all for now, thanks for watching, and I’ll catch you later!

Back to top of page