A.N.A.L.O.G. ISSUE 10 / MARCH 1983 / PAGE 73
Just before issue #9 of A.N.A.L.O.G. went to press, I was handed a letter from Long Mai of Salt Lake City, Utah (see the Reader Comment section of this issue). Like many BASIC programmers, he was frustrated by BASIC’s snail-like pace, and asked for a machine-language subroutine that could be used with BASIC to move player-missile graphics around on the screen.
Unfortunately, it was too late to write such an article for issue #9, but here it is, along with fully documented assembly-language code.
Player-missile graphics are one of the most powerful graphic features of the ATARI 400/800 computer systems. Unlike traditional graphics, players and missiles can be moved around on the screen without disturbing the existing display. In order to use players and missiles, one must first reserve a portion of memory. Once this is done, the user can begin designing and displaying the players and missiles.
The problems begin when the user wants to move a player or missile around on the screen. Horizontal movement is done easily. A POKE to the appropriate horizontal position memory location will move the desired player to any horizontal location on the screen. If the user wants to move a player or missile vertically, he or she must copy the P/M bit image to another location in memory. BASIC is too slow to do this, but it can call a machine-language subroutine to do the “dirty work.”
The BASIC program listed here will allow the user to move any player around on the screen. It calls the P/M movement subroutine, shown in the Assembly language listing.
As listed, the program will move a shape around on the screen at random. The shape of the player is stored as a series of bytes in the string “P0$”. By altering the data in line 420, you can change the shape that appears on the screen. There are currently 7 bytes in line 420, but this can be changed by altering lines 130, 290 and 420.
Lines 110-180 set up the subroutine and turn on the P/M graphics. Lines 220-310 are for demonstration purposes only. You can put your program code in this section.
Line 110 – Loads the string PMMOV$ with the P/M movement subroutine.
Line 130 – Places the data that defines the graphics shape into the string P0$.
Line 140 – Tell the system where the P/M memory starts.
Line 150 – Saves the P/M base address.
Line 160 – Saves the graphics data string address.
Line 170 – Turns on P/M direct memory access.
Line 180 – Sets the color of player to light blue.
Line 220 – Initialize the X and Y coordinates of the player.
Line 230-280 – Alter the X and Y coordinates to move the player.
Line 290 – This USR call moves the player to the desired X and Y location. This statement has 7 parameters inside the USR parentheses:
A=USR(MOVE,0,PMB,PMD,X,Y,7)
“MOVE” is set up in line 110. Do not change this variable. It is the address of the P/M mover subroutine.
“0” means that we want to move player zero. This value can range from 0 to 3, moving players through 3.
“PMB” is the P/M base address. Do not change this value.
“PMD” is the address of the string that holds the player image data. This should be set to the address of the string you are using to hold your player shape data. If your player shape data is in a string called “PL$,” you could replace “PMD” with “ADR(PL$)”.
An “X” and “Y” are the horizontal and vertical coordinates when you want to place your player. X ranges from 0-255, and Y ranges from 0-127. “7” in this case indicates that our player is 7 bytes long (see line 130 and the player image data in line 420). If your player is 10 bytes long, place a “10” here.
This program should help out anyone who wants to use player-missile graphics from BASIC. If there are any questions, write me care of A.N.A.L.O.G. (please include a pre-addressed, stamped envelope). If there is a specific application you would like to see appear in a future issue, just ask. You never know — there may be hundreds of other readers who would like the same information.
10 REM ***************************** 20 REM * P/M MOVER SUBROUTINE DEMO * 30 REM * * 40 REM * BY TOM HUDSON * 50 REM * * 60 REM * A.N.A.L.O.G. COMPUTING * 70 REM ***************************** 80 REM 90 REM *********** SETUP *********** 100 REM 110 DIM PMMOV$(100),P0$(30):MOVE=ADR(PMMOV$):FOR X=1 TO 100:READ N:PMMOV$(X) =CHR$(N):NEXT X:REM *READ ML DATA* 120 REM *** NOW READ SHAPE DATA *** 130 FOR X=1 TO 7:READ N:P0$(X)=CHR$(N) :NEXT X 140 PMBASE=INT((PEEK(145)+3)/4)*4:POKE 54279,PMBASE:REM *** SET UP P/M AREA *** 150 PMB=PMBASE*256 160 PMD=ADR(P0$):REM *** P/M DATA ADDR ESS *** 170 POKE 559,46:POKE 53277,3:REM *** P /M DMA *** 180 POKE 704,136:REM *** PLAYER 0 COLO R *** 190 REM 200 REM **** YOUR PROGRAM HERE! **** 210 REM 220 X=128:Y=64 230 XI=1-INT(RND(0)*3):YI=1-INT(RND(0) *3) 240 X=X+XI:Y=Y+YI 250 IF X<50 THEN X=50:GOTO 270 260 IF X>190 THEN X=190 270 IF Y<20 THEN Y=20:GOTO 290 280 IF Y>110 THEN Y=110 290 A=USR(MOVE,0,PMB,PMD,X,Y,7) 300 IF RND(0)>0.95 THEN 230 310 GOTO 240 320 REM 330 REM *** PM MOVER DATA *** 340 REM 350 DATA 216,104,104,104,133,213,104,2 4,105,2,133,206,104,133,205,104,133,20 4,104,133,203,104,104,133,208 360 DATA 104,104,133,209,104,104,24,10 1,209,133,207,166,213,240,16,165,205,2 4,105,128,133,205,165,206,105 370 DATA 0,133,206,202,208,240,160,0,1 62,0,196,209,144,19,196,207,176,15,132 ,212,138,168,177,203,164 380 DATA 212,145,205,232,169,0,240,4,1 69,0,145,205,200,192,128,208,224,166,2 13,165,208,157,0,208,96 390 REM 400 REM *** PLAYER IMAGE DATA *** 410 REM 420 DATA 255,129,129,231,129,129,255
10 DATA 532,930,996,64,0,483,544,265,989,74,765,328,743,901,536,8870 160 DATA 729,778,445,101,552,79,854,96 8,479,928,983,921,954,424,374,9561 310 DATA 704,84,588,90,639,732,435,188 ,185,191,83,56,3887
; ;PLAYER-MISSILE MOVER SUBROUTINE ; ;BY TOM HUDSON ;A.N.A.L.O.G. COMPUTING #10 ; ; ;PAGE ZERO USAGE ; PMSTR = $CB ;P/M BASIC STRING PLADR = $CD ;PLAYER ADDRESS PMEND = $CF ;PLAYER IMAGE END XPOS = $D0 ;X POSITION YPOS = $D1 ;Y POSITION HOLD = $D4 ;HOLD AREA PLNUM = $D5 ;PLAYER # TO MOVE ; ;OPERATING SYSTEM EQUATES ; HPOSP0 = $D000 ; ;PROGRAM STARTS HERE! ; ORG $6000 ;ANY ADDRESS START CLD ;CLEAR DECIMAL MODE PLA ;DISCARD PLA ;DISCARD # HI PLA ;PULL PLAYER # LO STA PLNUM ;AND SAVE IT! PLA ;PULL P/M BASE HI CLC ;ADD OFFSET TO GET ADC #2 ;PLAYER MEMORY ADDR STA PLADR+1 ;AND SAVE! PLA ;PULL P/M BASE LO STA PLADR ;AND SAVE! PLA ;PULL STRING HI STA PMSTR+1 ;AND SAVE! PLA ;PULL STRING LO STA PMSTR ;AND SAVE! PLA ;DISCARD X HI PLA ;PULL X LO STA XPOS ;AND SAVE IT! PLA ;DISCARD Y HI PLA ;PULL Y LO STA YPOS ;AND SAVE IT! PLA ;DISCARD LENGTH HI PLA ;PULL LENGTH LO CLC ;ADD Y POSITION ADC YPOS ;TO GET END STA PMEND ;AND SAVE IT! LDX PLNUM ;GET PLAYER# BEQ ENDCAL ;NO INDEX NEEDED! PLCALC LDA PLADR ;ADD 128 TO CLC ;PLAYER ADC #128 ;ADDRESS STA PLADR ;TO LDA PLADR+1 ;POINT TO ADC #0 ;NEXT STA PLADR+1 ;PLAYER. DEX ;ANOTHER ADJUSTMENT? BNE PLCALC ;YES! ENDCAL LDY #0 ;ZERO P/M COUNT LDX #0 ;ZERO STRING COUNT COPYLP CPY YPOS ;COPYING DATA YET? BCC ZERO ;NO! CPY PMEND ;FINISHED COPYING? BCS ZERO ;YES! STY HOLD ;SAVE Y REG TXA ;MOVE X REG... TAY ;TO Y REGISTER LDA (PMSTR),Y ;GET P/M BYTE LDY HOLD ;GET P/M OFFSET STA (PLADR),Y ;CHANGE PLAYER! INX ;NEXT STRING BYTE. LDA #0 ;FORCE BRANCH BEQ NEXT ;TO NEXT BYTE! ZERO LDA #0 ;ZERO OUT... STA (PLADR),Y ;PLAYER BYTE! NEXT INY ;NEXT P/M BYTE CPY #128 ;DONE W/COPY? BNE COPYLP ;NOT DONE YET! LDX PLNUM ;GET PLAYER # LDA XPOS ;NOW JUST SET STA HPOSP0,X ;X LOCATION! RTS ;FINIS! .END
A.N.A.L.O.G. ISSUE 11 / MAY 1983 / PAGE 29
Last issue, I presented a machine-language subroutine which enabled BASIC programmers to move players around on the screen quickly. Shortly after the issue was sent to readers, I received a letter from Jeff Stefanski (see the Reader Comment section of this issue). Jeff asked for a modification to allow the subroutine to move missiles as well.
Rather than modify the existing player movement subroutine, I decided to write a new, stand-alone missile movement subroutine. It can be used by itself if only missiles are desired, or can be used in conjunction with the player mover from A.N.A.L.O.G. #10 if both players and missiles are needed.
Following this article are two listings. The first is a BASIC program which demonstrates the use of the missile movement subroutine. The second listing is the fully documented assembler source code for the subroutine.
Hidden deep inside each ATARI computer is a mysterious graphics ability known as player-missile graphics. These graphics work with any graphics mode, and can be moved around on the screen without disturbing any other graphics.
Why are these graphics called players and missiles? PLAYERS are eight pixels (picture elements) wide and therefore can be used to create fairly detailed images, such as spaceships, cars, or other animated figures representing the player. MISSILES, on the other hand, are only TWO pixels wide. They were designed to be used as simple projectiles, because of their limited resolution.
Enter Listing 1 into your computer. Before running it, be sure to SAVE it, as a mistake in typing the assembly-language code could “lock-up” your computer, making it necessary to re-enter the program. When RUN, this program will place the numbers 1-4 on the screen using the four missiles and move them around randomly. Let’s walk through the program and see what each line does.
Line 200 – This line loads the machine-language missile movement subroutine into a string called “MISMOV$.” This subroutine will be called whenever we want to move a missile on the screen.
Line 240 – This line sets up four string variables, M0$ through M3$. These strings will hold missile shape data. These strings are currently set up as 6 bytes long, which limits the missile graphics images to 6 pixels in height. You can change this length to up to 128 bytes, making the missile image 128 pixels high.
Lines 250-280 – These lines READ the DATA in lines 690-750 into the missile shape strings set up in line 240. Once again, note that each line reads 6 bytes into the appropriate string. To make missile images of different height, change the 6 to the desired value.
Lines 290-320 – These lines set up the player-missile area in memory and activate them. Line 320 sets the player-missile priority to 1. This causes the players and missiles to appear “in front” of other graphics. These lines should not be changed.
Lines 330-360 – Since there is no SETCOLOR command for player-missile graphics, we must POKE the appropriate color values into the P/M color registers. To get the color number, use the formula:
COLOR POKE VALUE = (COLOR NUMBER *16) + BRIGHTNESS
Line 370 – Sets the background color to black.
Line 410 – Dimensions two arrays, X and Y. These arrays will be used to hold the X and Y coordinates of each missile.
Line 420 – This line initializes all the missiles’ X coordinates to 128 and the Y coordinates to 64. These coordinates will place the missiles at the center of the screen.
Line 430 – This line starts a FOR-NEXT loop which will process each missile, from missile to missile 3.
Lines 440-490 – These lines randomly change the X and Y coordinates of the missiles, which will make them wander around on the screen.
Line 500 – Depending on the missile number (I) this line transfers control to the appropriate USR statement in order to move the desired missile.
Lines 510-540 – These lines send the X and Y coordinate information to the missile movement subroutine. Line 510 moves missile 0, 520 moves missile 1, etc. Let’s look at line 510:
A=USR(MISL,0,PMB,ADR(M0$),X(0),Y(0),6):GOTO 550
This statement has 7 parameters inside the USR parentheses.
“MISL” is set up in line 200. Do not change this variable. It is the address of the missile mover subroutine.
“0” means we want to move missile zero. Note that line 520 has a “1” here, since it moves missile 1. This value can range from 0 to 3, and will move the appropriate missile.
“PMB” is the player-missile base address, which was calculated in line 300. Do not change this variable.
“ADR(M0$)” tells the missile mover where to get the missile image data. In this case, we want the subroutine to use the information in the string variable M0$. Try changing this to “ADR(M1$)” and RUN the program. You will see two “2’s” moving on the screen. This is because the string Ml$ contains the data for the number 2, and it is now used in both missile and missile 1.
X(0) is the X coordinate where we want to place missile 0. You can place any number or variable here, ranging from 0-255.
Y(0) is the Y coordinate where we want to place missile 0. You can place any number or variable here, ranging from 0-127.
“6” tells the subroutine how many bytes are used for the player image. In this case our missiles are 6 bytes long (see lines 240, 250, and 690). If you want a different number of bytes (resulting in a different missile height), change these lines accordingly.
Line 550 – This line completes the FOR-NEXT loop set up in line 430.
Line 560 – This line passes control to line 430, causing the program to loop forever, moving the missiles randomly until you press the BREAK key.
Lines 600-640 – These lines contain the DATA for the missile mover routine. Do not change these values.
Line 690 – DATA for M0$, the number “1.”
Line 710 – DATA for Ml$, the number “2.”
Line 730 – DATA for M2$, the number “3.”
Line 750 – DATA for M3$, the number “4.”
Now, that we have walked through the program and studied what each line does, let’s design our own missile image. Since we’re limited to 2 pixels in width, the image will have to be very simple. Of course, it can be any height up to 128 pixels.
The numbers that the demonstration program moved around on the screen are very simple. Figure 1 shows how the number 2 was turned into DATA in line 710.
2+1 =3 1 =1 2+1 =3 2 =2 2 =2 2+1 =3Figure 1
The shape we will make is shown in Figure 2.
1 =1 1 =1 2+1 =3 =0 2+1 =3 =0 2+1 =3 2 =2 2 =2Figure 2
Now let’s put our shape into demonstration program. We’ll use missile number to show the image, so replace line 690 with the following:
690 DATA 1,1,3,0,3,0,3,2,2This missile image is 9 bytes long, so it will be necessary to change lines 240, 250 and 510 as follows:
240 DIM M0$(9),M1$(6),M2$(6),M3$(6) 250 FOR I=1 TO 9:READ N:M0$(I)=CHR$(N):NEXT I:REM *** MISSILE 0 *** 510 A=USR(MISL,0,PMB,ADR(M0$),X(0),Y(0),9):GOTO 550
After the program is changed, RUN it. You will see the numbers 2,3, and 4 on the screen, along with the shape we just defined. It’s that simple!
Using players and missiles in BASIC can be very fast and easy when a machine-language subroutine is used to perform time-consuming operations. This demonstration program may be used as a framework for more complex programs. Simply replace lines 410-560 with your own program code, and you’re all set to begin exploring the wonders of player-missile graphics!
If you have any questions or suggestions about this article write me care of A.N.A.L.O.G. Be sure to include a pre-addressed, stamped envelope if you would like a reply.
100 REM ***************************** 110 REM * MISSILE SUBROUTINE DEMO * 120 REM * * 130 REM * BY TOM HUDSON * 140 REM * * 150 REM * A.N.A.L.O.G. COMPUTING * 160 REM ***************************** 170 REM 180 REM *********** SETUP *********** 190 REM 200 DIM MISMOV$(114):MISL=ADR(MISMOV$):FOR X=1 TO 114:READ N:MISMOV$(X)=CHR$ (N):NEXT X:REM *READ ML DATA* 210 REM 220 REM *** LOAD MISSILE IMAGES *** 230 REM 240 DIM M0$(6),M1$(6),M2$(6),M3$(6) 250 FOR I=1 TO 6:READ N:M0$(I)=CHR$(N) :NEXT I:REM *** MISSILE 0 *** 260 FOR I=1 TO 6:READ N:M1$(I)=CHR$(N) :NEXT I:REM *** MISSILE 1 *** 270 FOR I=1 TO 6:READ N:M2$(I)=CHR$(N) :NEXT I:REM *** MISSILE 2 *** 280 FOR I=1 TO 6:READ N:M3$(I)=CHR$(N) :NEXT I:REM *** MISSILE 3 *** 290 PMBASE=INT((PEEK(145)+3)/4)*4:POKE 54279,PMBASE:REM *** SET UP P/M AREA *** 300 PMB=PMBASE*256 310 POKE 559,46:POKE 53277,3:REM *** P /M DMA *** 320 POKE 623,1:REM *** P/M PRIORITY ** * 330 POKE 704,134:REM *** P/M 0 COLOR * ** 340 POKE 705,136:REM *** P/M 1 COLOR * ** 350 POKE 706,138:REM *** P/M 2 COLOR * ** 360 POKE 707,142:REM *** P/M 3 COLOR * ** 370 SETCOLOR 2,0,0:REM *** BACKGROUND IS BLACK *** 380 REM 390 REM **** YOUR PROGRAM HERE! **** 400 REM 410 DIM X(3),Y(3) 420 FOR I=0 TO 3:X(I)=128:Y(I)=64:NEXT I 430 FOR I=0 TO 3 440 XI=2-INT(RND(0)*5):YI=2-INT(RND(0) *5) 450 X(I)=X(I)+XI:Y(I)=Y(I)+YI 460 IF X(I)<50 THEN X(I)=50:GOTO 480 470 IF X(I)>190 THEN X(I)=190 480 IF Y(I)<20 THEN Y(I)=20:GOTO 500 490 IF Y(I)>110 THEN Y(I)=110 500 ON I+1 GOTO 510,520,530,540 510 A=USR(MISL,0,PMB,ADR(M0$),X(0),Y(0 ),6):GOTO 550 520 A=USR(MISL,1,PMB,ADR(M1$),X(1),Y(1 ),6):GOTO 550 530 A=USR(MISL,2,PMB,ADR(M2$),X(2),Y(2 ),6):GOTO 550 540 A=USR(MISL,3,PMB,ADR(M3$),X(3),Y(3 ),6) 550 NEXT I 560 GOTO 430 570 REM 580 REM *** MISSILE MOVER DATA *** 590 REM 600 DATA 216,104,104,104,133,213,104,1 33,206,104,24,105,128,133,205,165,206, 105,1,133,206,104,133,204,104 610 DATA 133,203,104,104,133,208,104,1 04,133,209,104,104,24,101,209,133,207, 160,0,162,0,134,212,169,252 620 DATA 166,213,240,7,10,10,9,3,202,2 08,249,166,212,49,205,145,205,196,209, 144,30,196,207,176,26 630 DATA 132,212,138,168,177,203,164,2 13,240,5,10,10,136,208,251,164,212,17, 205,145,205,232,169,0,240 640 DATA 0,200,192,128,208,196,166,213 ,165,208,157,4,208,96 650 REM 660 REM *** MISSILE IMAGE DATA *** 670 REM 680 REM "1" 690 DATA 1,1,1,1,1,1 700 REM "2" 710 DATA 3,1,3,2,2,3 720 REM "3" 730 DATA 3,1,3,1,1,3 740 REM "4" 750 DATA 1,3,3,1,1,1
100 DATA 778,122,244,236,250,743,796,95,82,101,420,79,336,85,653,5020 250 DATA 303,310,317,324,918,525,764,9 56,925,934,943,937,145,102,581,8984 400 DATA 80,27,287,293,985,719,583,574 ,556,545,765,333,343,353,205,6648 550 DATA 751,724,103,504,109,674,637,1 46,251,237,99.405,105,342,282,5369 700 DATA 322,279,330,282,338,280,1831
; -------------------------- ; MISSILE MOVER SUBROUTINE ; -------------------------- ; -------------------------- ; BY TOM HUDSON ; A.N.A.L.O.G. COMPUTING #11 ; -------------------------- ; -------------------------- ; PAGE ZERO USAGE ; -------------------------- PMSTR = $CB ;P/M BASIC STRING MADR = $CD ;MISSILE ADDRESS PMEND = $CF ;MISSILE IMAGE END XPOS = $D0 ;X POSITION YPOS = $D1 ;Y POSITION HOLD = $D4 ;HOLD AREA MNUM = $D5 ;MISSILE # TO MOVE ; ------------------------ ; OPERATING SYSTEM EQUATES ; ------------------------ HPOSM0 = $D004 ; ------------------------ ; SUBROUTINE STARTS HERE! ; ------------------------ ORG $6000 ;ANY ADDRESS START CLD ;CLEAR DECIMAL MODE PLA ;DISCARD PLA ;DISCARD # HI PLA ;PULL MISSILE # LO STA MNUM ;AND SAVE IT! PLA ;PULL P/M BASE HI STA MADR+1 ;AND SAVE! PLA ;PULL P/M BASE LO CLC ;OFFSET INTO ADC #128 ;MISSILE AREA STA MADR ;AND SAVE! LDA MADR+1 ;OFFSET MISSILE ADC #1 ;ADDR HI STA MADR+1 PLA ;PULL STRING HI STA PMSTR+1 ;AND SAVE! PLA ;PULL STRING LO STA PMSTR ;AND SAVE! PLA ;DISCARD X HI PLA ;PULL X LO STA XPOS ;AND SAVE IT! PLA ;DISCARD Y HI PLA ;PULL Y LO STA YPOS ;AND SAVE IT! PLA ;DISCARD LENGTH HI PLA ;PULL LENGTH LO CLC ;ADD Y POSITION ADC YPOS ;TO GET END STA PMEND ;AND SAVE IT! LDY #0 ;ZERO P/M COUNT LDX #0 ;ZERO STRING COUNT COPYLP STX HOLD ;SAVE X REG LDA #$FC ;SET HIGH 6 BITS LDX MNUM ;GET MISSILE # BEQ ZEROIT ;IF 0, DON'T SHIFT ZERSHF ASL A ;SHIFT LEFT... ASL A ;TWO BITS ORA #3 ;SET LOWER 2 BITS DEX ;DONE SHIFTING? BNE ZERSHF ;NO! ZEROIT LDX HOLD ;RESTORE X REG AND (MADR),Y ;ZERO OUT... STA (MADR),Y ;MISSILE BYTE! CPY YPOS ;COPYING DATA YET? BCC NEXT ;NO! CPY PMEND ;FINISHED COPYING? BCS NEXT ;YES! STY HOLD ;SAVE Y REG TXA ;MOVE X REG... TAY ;TO Y REGISTER LDA (PMSTR),Y ;GET P/M BYTE LDY MNUM ;SHIFT BIT IMAGE BEQ ENDBS ;IF NOT MISSILE 0 BYTSHF ASL A ;SHIFT LEFT... ASL A ;2 BITS DEY ;MORE TO SHIFT? BNE BYTSHF ;YES! ENDBS LDY HOLD ;GET P/M OFFSET ORA (MADR),Y ;'OR' BITS STA (MADR),Y ;CHANGE MISSILE! INX ;NEXT STRING BYTE. LDA #0 ;FORCE BRANCH BEQ NEXT ;TO NEXT BYTE! NEXT INY ;NEXT P/M BYTE CPY #128 ;DONE W/COPY? BNE COPYLP ;NOT DONE YET! LDX MNUM ;GET MISSILE # LDA XPOS ;NOW JUST SET STA HPOSM0,X ;X LOCATION! RTS ;FINIS! .END