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:
Missile 0 to playfield collision
Missile 1 to playfield collision
Missile 2 to playfield collision
Missile 3 to playfield collision
Missile 0 to player collision
Missile 1 to player collision
Missile 2 to player collision
Missile 3 to player collision
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:
M0PF (missile 0 to playfield collision) M1PF (missile 1 to playfield collision)
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?
*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:
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:
- What value will be in COLLISION if player 0 is touching the line you drew?
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.)
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:
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.
- 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.)
- Now suppose player 0 hits a COLOR 3 playfield. At what line number must we have a subroutine to handle this possibility?
*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.
11999 REM DRAW PLAYFIELD 12000 SETCOLOR 2,3,4:COLOR 1 12010 PLOT 0,0:DRAWTO 159,0:DRAWTO 159,79:DRAWTO 0,79:DRAWTO 0,0 12015 COLOR 2 12020 PLOT 80,61:DRAWTO 80,79:PLOT 0,60:DRAWTO 30,60:DRAWTO 30,79 12025 COLOR 3 12030 PLOT 52,6O:DRAWTO 52,45:DRAWTO 110,45:DRAWTO 110,60:DRAWTO 137,60:DRAWTO 137,45:DRAWTO 110,45:PLOT 159,32 12040 DRAWTO 110,32:PLOT 80,45:DRAWTO 80,20:PLOT 130,16:DRAWTO 130,14: DRAWTO 127,14:DRAWTO 127,16:DRAWTO 130,16 12050 PLOT 43,0:DRAWTO 43,30:PLOT 52,45:DRAWTO 22,45:DRAWTO 22,13:PLOT 103,0:DRAWTO 103,17:POKE 559,34:RETURN
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.
Here's the call to the M0PF subroutine:GOSUB PEEK(M0PF)+40:
- Now you write the call to the P0PF subroutine:
Altogether then, line 200 will look like this:
200? "P0PF=";PEEK(P0PF),"M0PF=";PEEK(M0PF):GOSUB PEEK(M0PF)+40:GOSUB PEEK(P0PF)+50
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:
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?
- So what code do we need at line 44?
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?
- 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.)
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?
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:
- Now if a missile hits playfield 1, to which line will control pass?
- What additional line numbers will we need for playfields 2 and 3?
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
Make all the changes I've discussed so far to MISSILE.SAV. Also, change line 2 to:
All the changes are summarized in the listing that follows:
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:
41 X0=175:Y0=80:GOSUB 400:POKE 704,PEEK(RANDOM):
PLAYER0$=BUFFER$:POKE HPOSP0,X0:POKE HITCLR,1:RETURN
And add lines 400 and 410:
400 FOR I=0 to 100 STEP 2:POKE 704,I:SOUND 0,I,10,15:SOUND 1,I+50,10,15:NEXT I
Also change line 499 to line 390 so that line 390 reads "GOTO 200."
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."
Return to Table of Contents | Previous Chapter | Next Chapter