10

Detecting Collisions

Suppose a player fires a missile at an alien attacking space craft. Wouldn't it be nice to have a simple way to tell when there is a collision between the missile and alien ship? Or suppose you're making a maze game. Would you like to be able to detect when a player touches a maze wall?

COLLISION DETECTION REGISTERS

You can. Collision detection is easy with PMG! The Atari engineers wisely provided several collision detection registers for this purpose. Here they are:

Shows:
Player 0 to player collisions
Player 1 to player collisions
Player 2 to player collisions
Player 3 to player collisions
Player 0 to playfield
Player 1 to playfield
Player 2 to playfield
Player 3 to playfield

ASSIGNING VARIABLE NAMES

To simplify your programming task, I recommend that you assign a variable name to each collision register you need to use. It's a lot easier to keep track of variable names than all those memory locations.* Atari recommends variable names such as these:

In the demonstration program in this chapter we will be using two collision registers: M0PF and P0PF.

What register do you think P0PF refers to?

ANSWER

1. 384 2. 128 3. a

Player 0 to playfield collision (53252)
If you have the program from the previous chapter loaded into memory, you might want to initialize P0PF and M0PF right now by adding these statements to line 10070:
P0PF=53252:
M0PF=53248

*Later, though, you may wish to go back to using constants (such as 53260, 53261, etc.). That's because Atari BASIC only allows you 128 variable names. Also, statements with constants actually execute slightly faster than those using a corresponding variable! (This is not true with other computers such as the PET and Apple, where variables execute 10 to 40 times faster than constants.) I'd like to thank B. B. Garrett for clarifying this in his informative article "Atari Times" in the May, 1983 issue of Compute!

READING COLLISION REGISTERS

You can read a collision register with a peek command. For example:

COLLISION=PEEK(P0PF)

After this statement executes, COLLISION will contain either 0,1,2, or 4. In our sample program you will find that:

If P0PF contains a zero, then there was no collision.

If P0PF contains a 1, then PLAYER 0 collided with that part of the playfield drawn with COLOR 1.

If P0PF contains a 2, then PLAYER 1 collided with that part of the playfield drawn with COLOR 2.

If P0PF contains a 4, then PLAYER 1 collided with that part of the playfield drawn with COLOR 3.

Ok, try this one. Suppose you draw a line on the screen with these statements:

GRAPHICS 7:SETCOLOR 2,3,4:COLOR 3:PLOT 0,0:DRAWTO 159,0

Furthermore, suppose you read a collision register like this:

COLLISION=PEEK(P0PF)

What value will be in COLLISION if player 0 is touching the line you drew?

ANSWER

4 (a 4 shows a collision with a playfield drawn with COLOR 3).

MULTIPLE COLLISIONS

Once a collision occurs, the collision register retains the number that was placed in it. If a second collision occurs, the next number is added to the number that already exists there.

Suppose player 0 collides with a playfield drawn with COLOR 1 and then collides with a playfield drawn with COLOR 3. What value will be the collision register? (Careful now, this one is a bit tricky.)

ANSWER

5 (the 1 from the first collision will be added to the 4 from the second collision).

CLEARING COLLISION REGISTERS

Since the collision registers retain the values put in them when a collision occurs, it's important to reset them. This is almost as easy as taking candy from a baby. All you do is poke a 1 into the HIT CLEAR register at location 53278.

I suggest you use a variable for the HIT CLEAR register. Let's call it HITCLR. At line 10070 insert this statement:

HITCLR=53278

Often we think of clearing something by poking zeros in it. But this is different; here we are turning on the hit clear switch. That's why we poke it with 1 rather than 0.

When we poke a 1 into HITCLR what do you think happens?

All collision registers are cleared.

Only selected registers are cleared.

ANSWER

You're right if you said "a. All collision registers are cleared."

USING COLLISION REGISTERS

Often programmers peek at the collision registers and then use a series of IF-statements to decide on what action to take. But there's a much better way. Remember, in Atari BASIC you can GOSUB a variable or even GOSUB a PEEKED value! We did this in our joystick routine, and we can also do it with collision registers.

Suppose we want to execute a subroutine that starts at line 41 if player 0 collides with a COLOR 1 playfield.* Write a statement to make that happen. (GOSUB the value in P0PF plus an offset.)

ANSWER

GOSUB PEEK(P0PF)+40

Now suppose player 0 hits a COLOR 3 playfield. At what line number must we have a subroutine to handle this possibility?

ANSWER

We need a subroutine at line 44. That's because P0PF will contain a 4 if player 0 hits a COLOR 3 playfield.

*In this book, a COLOR 1 playfield is simply a playfield drawn with COLOR 1. The term "playfield 1," as used in the Atari technical manual, is not synonymous with the term "COLOR 1 playfield."

DRAWING A PLAYFIELD

Now let's use some of these techniques in a PMG program. First, let's draw a playfield. We'll make it a maze so that in a later chapter we can expand the program into a full-fledged game.

We've already reserved lines 12000-13000 for drawing a playfield, so let's put our new playfield subroutine there. (Note that we need to delete the previous playfield, which was contained in lines 12000, 12010, 12020, and 12030.)

Here are the lines for the new playfield subroutine. I suggest you enter them now.

Also, let's revise lines 2010 and 2015 so that line 2010 becomes 2015 and 2015 becomes 2010. It seems that PMG works better when the playfield is drawn before the PMG setup is executed.

And at line 2005 change GRAPHICS 5 to GRAPHICS 7 since our new playfield requires that graphics mode.

REVISING THE MAIN LOOP

Now let's revise the main loop of our program so that it detects when our player touches the sides of the walls. First, change line 200 into line 201. Do this so that we can create some new collision detection routines at line 200.

Now at the beginning of line 200, let's simply print the contents of the collision register--just so we can see what they contain when various objects collide. This is easy to do, like so:

? "P0PF=";PEEK (P0PF),"M0PF=";PEEK(M0PF)

Next, also at line 200, let's call for the execution of the two collision detection subroutines, one for P0PF and one for M0PF. And let's arrange things so that if there is no collision, we immediately return from each subroutine. We'll let the P0PF subroutine start at line 40 and the M0PF subroutine start at line 50.

PLAYER-PLAYFIELD COLLISIONS

Simple right? Now all we need to do is decide what we want to happen when a collision occurs. For now, let's fix things so that our player cannot walk through the maze walls. Here's how we'll do it. At the beginning of line 201 we'll save the X0 and Y0 coordinates by inserting the statement X0A=X0 and Y0A=Y0:

201 Y0A=Y0:X0A=X0:GOSUB PEEK(JOYSTICK):GOSUB MOVELEGS

Then when our player hits a maze wall, we'll reset X0 and Y0 back to what they were before the collision. Got it?

Suppose our player hits a COLOR 1 playfield. Then P0PF will contain a "1," and control will pass to line 41 (as a result of GOSUB PEEK(P0PF)+40). Then at line 41 all we need to do is:

1. Set the Y0 and X0 coordinates to what they were before the collision.

2. Clear the collision registers by poking a 1 into HITCLR.

See if you can write the code for line 41:

ANSWER

41 X0=X0A:Y0=Y0A:POKE HITCLR,1:RETURN

We also need this same subroutine at line 42. That's because P0PF will contain a 2 if our player hits a COLOR 2 playfield.

If our player hits a COLOR 3 playfield, P0PF will contain a 4 (strange as this may seem).

So what line will be executed if our player hits a COLOR 3 playfield?

ANSWER

44 (Since 40+4=44.)

So what code do we need at line 44?

ANSWER

44 X0=X0A:Y0=Y0A:POKE HITCLR,1:RETURN

Let's look at the playfield detection statement more closely. It is: GOSUB PEEK(M0PF+40).

If the player does not hit anything. P0PF will contain a zero. So where will control pass when the playfield detection statement is executed?

ANSWER

Control will pass to line 40 (0+40=40).

What code would be appropriate for line 40? (Hint: we don't really need to do anything since no collision has occurred. All we need to do is get back to the main loop.)

ANSWER

All we need is a RETURN statement at line 40. This will return control to the main loop.

MISSILE COLLISIONS

The missile collision subroutines start at line 50 since the calling routine is GOSUB M0PF+50. If no missile-playfield collision has occurred, we won't need any specific action.

What will the code be for line 50?

ANSWER

50 RETURN

If a missile hits a wall, we will want to do these things:

Move the missile off the screen by poking zero into HPOSM0.

Turn off the missile sound.

Turn off the missile move indicator (FIRE0).

Clear the collision registers.

Since we will have to do this whenever a missile hits a wall, let's put it into a subroutine at line 190. See if you can write the code:

ANSWER

190 POKE HPOSM0,0:D1=0:FIRE0=0:POKE HITCLR,1:RETURN

Now if a missile hits playfield 1, to which line will control pass?

ANSWER

51 (M0PF will contain 1.1+50=51)

What additional line numbers will we need for playfields 2 and 3?

ANSWER

We'll need line 52 and line 54. (Remember, if a missile hits playfield 3 then the collision register will contain a 4.)

Line 190 contains the commands that we want executed if a missile hits a wall so at line 52 all we need is:

52 GOSUB 190:RETURN

Similarly, at lines 53 and 54 we'll use the same code:

53 GOSUB 190:RETURN
54 GOSUB 190:RETURN

TRY IT

Make all the changes I've discussed so far to MISSILE.SAV. Also, change line 2 to:

Save the program and then run it. Note: I suggest you push the RESET button each time before you run the program; this will ensure that the PMG image appears correctly when the program first starts. As you move the player against the various walls of the maze, notice the values that appear in P0PF.

Next try firing the missile in various directions. Notice that sometimes the missile will hit a wall. When this happens, the missile sound stops and the missile disappears. The player can now immediately fire another missile.

Sometimes the missile will go right "through a wall." That's because the missile is moving in increments of 6. Actually, the missile is "hopping over the walls"--it really never touches the wall--hence there is no collision. This could be fixed by making the walls thicker or the missile bigger. Or the missile coordinates could be adjusted by 1 instead of 6. Of course, then the missile would probably move too slowly for most purposes.

Yet another approach would be to use an assembly language routine to move the missile.* In the programs in this book we'll simply let the missile pass through walls. In a game situation, this effect is good because you never know when one of your missiles will be "super-charged" and capable of passing through a wall.

*Watch for my next book on advanced PMG techniques. In it I'll show you how to use machine language subroutines to speed up your PMG programs.

SLOW PLAYER MOVEMENT

Notice that the player moves rather slowly. That's mainly because we are constantly peeking at and printing the values contained in the collision registers. You can speed up the player's movement considerably by deleting ?P0PF=";PEEK(P0PF) and ?M0PF=";PEEK(M0PF) in line 200.

Of course part of the reduction in the player's speed results because the main loop is becoming more complicated. Remember, we are doing quite a bit in this little BASIC program. We have created:

Vertical, horizontal, and diagonal movement

Leg animation

Missile firing and moment in eight directions

Random missile-size selection

Explosive missile sound

Player and missile collision detection.

But with player-missile graphics, once we have come this far, it's easy to add even more "features."

ADDING MORE FEATURES

Let's try something a little different. Let's pretend that the COLOR 1 playfield has an electrical charge that will zap our player if he touches it. To produce the "zapping effect," let's produce a strange sound and flash several colors through the player when he touches playfield 1. Furthermore, let's set our player to a random new color and then put him back to his starting location. The code to do this is relatively simple and won't slow down the main action. That's because the code will not be in the main loop but in subroutines that will execute only when a collision occurs.

Here's the new code to "zap" the player when he touches the COLOR 1 playfield. Change line 41 to:

As you can see the subroutine at line 400 rapidly moves different values into player 0's color register. This causes him to "glow." In the same loop we also include a nice sound effect with the aid of a couple of SOUND statements. (I used SOUND statements, here, rather than poking audio control registers, because SOUND statements are easier to use. Remember, speed is not so important here since we are not in the main loop. When a player is zapped, it's natural for the action to stop. All attention is focused on the zapped player, anyway.)

There you have it. Collision detection complete with a fancy routine to zap a player and move him back to his starting location if he hits a specific kind of playfield.

In the next chapter we'll pull together everything you've learned so far and create "MAZEDUEL," a racing game in which two players compete for a dangerous but valuable "crystal."