Animation is an important capability of any home computer system. Activity on the screen can greatly add to the excitement and realism of any program. Certainly animation is crucial to the appeal of many computer games. More important, an animated image can convey information with more impact and clarity than a static image. It can draw attention to an item or event of importance. It can directly show a dynamic process rather than indirectly talk about it. Animation must accordingly be regarded as an important element of the graphics capabllities of any computer system.
The conventional way to effect animation with home computers is to move the image data through the screen RAM area. This requires a two-step process. First, the program must erase the old image by writing background values to the RAM containing the current image. Then the program must write the image data to the RAM corresponding to the new position of the image. By repeating this process over and over, the image will appear to move on the screen.
There are two problems with this technique. First, if the animation is being done in a graphics mode with large pixels, the motion will not be smooth; the image will jerk across the screen. With other computers the only solution is to use a graphics mode with smaller pixels (higher resolution).
The second problem is much worse. The screen is a two-dimensional image but the screen RAM is organized one-dimensionally. This means that an image which is contiguous on the screen will not be contiguous in the RAM. The discrepancy is illustrated in Figure 4-1.
Figure 4-1 Noncontiguous RAM Images. Image Bytes Scattered Through RAM.
The significance of this discrepancy does not become obvious until you try to write a program to move such an image. Look how the bytes that make up the image are scattered through the RAM. To erase them, your program must calculate their addresses. This calculation is not always easy to do. The assembly code just to access a single screen byte at screen location (XPOS,YPOS) would look like this (this code assumes 40 bytes per screen line):
LDA SCRNRM ; address of the beginning of screen RAM STA POINTR ; zero page pointer LDA SCRNRM+1 ; high order byte of address STA POINTR+1 ; high order pointer LDA #$00 STA TEMPA+1 ; temporary register LDA YPOS ; vertical position ASL A ; times 2 ROL TEMPA+1 ; shift carry into TEMPA+1 ASL A ; times 4 ROL TEMPA+1 ; shift carry again ASL A ; times 8 ROL TEMPA+1 ; shift again LDX TEMPA+1 ; save YPOS*8 STX TEMPB+1 ; into TEMPB STA TEMB ; low byte ASL A ; times 16 ROL TEMPA+1 ASL A ; times 32 ROL TEMPA+1 CLC ADC TEMPB ; add in YPOS*8 to get YPOS*40 STA TEMPB LDA TEMPA+1 ; now, do high order byte ADC TEMPA+1 STA TEMPB+1 LDA TEMPB ; TEMPB contains the offset from top of screen to pixel CLC ADC POINTR STA POINTR LDA TEMPB+1 ADC POINTR+1 STA POINTR+1 LDY XPOS LDA (POINTR),Y
Clearly, this code to access a screen location is too cumbersome. This is certainly not the most elegant or fastest code to solve the problem. Certainly a good programmer could take advantage of special circumstances to make the code more compact. The point of this is that accessing pixels on a screen takes a lot of computing. The above routine takes about 100 machine cycles to access a single byte on the screen. To move an image that occupies, say, 50 bytes, would require 100 accesses or about 10,000 machine cycles or roughly 10 milliseconds. This may not sound like much but if you want to achieve smooth motion, you have to move the object every 17 milliseconds. If there are other objects to move or any calculations to carry out there isn't much processor time left to devote to them. What this means is that this type of animation (called “playfield animation”) is too slow for many purposes. You can still get animation this way, but you are limited to few objects or small objects or slow motion or few calculations between motion. The trade-offs that a programmer must make in using such animation are too restrictive.
The ATARI Home Computer solution to this problem is player-missile graphics. In order to understand player-missile graphics, it is important to understand the essence of the problem of playfield animation: the screen image is two-dimensional while the RAM image is one-dimensional. The solution was to create a graphics object that is one-dimensional on the screen as well as one-dimensional in RAM. This object (called a player) appears in RAM as a table that is either 128 or 256 bytes long. The table is mapped directly to the screen. It appears as a vertical band stretching from the top of the screen to the bottom. Each byte in the table is mapped into either one or two horizontal scanlines, with the choice between the two made by the programmer. The screen image is a simple bitmap of the data in the table. If a bit is on, then the corresponding pixel in the vertical column is lit; if the bit is off, then the corresponding pixel is off. Thus, the player image is not strictly one-dimensional; it is actually eight bits wide.
Drawing a player image on the screen is quite simple. First you draw a picture of the desired image on graph paper. This image must be no more than eight pixels wide. You then translate the image into binary code, substituting ones for illuminated pixels and zeros for empty ones. Then you translate the resulting binary number into decimal or hexadecimal, depending on which is more convenient. Then you store zeros into the player RAM to clear the image. Next, store the image data into the player RAM, with the byte at the top of the player image going first, followed by the other image bytes in top to bottom sequence. The further down in RAM you place the data, the lower the image will appear on the screen.
Animating this image is very easy. Vertical motion is obtained by moving the image data through the player RAM. This is, in principle, the same method used in playfield animation, but there is a big difference in practice; the move routine for vertical motion is a one-dimensional move instead of a two-dimensional move. The program does not need to multiply by 40 and it often does not need to use indirection. It could be as simple as:
LDX #$01 LOOP LDA PLAYER,X STA PLAYER-1,X INX BNE LOOP
This routine takes about 4 milliseconds to move the entire player, about half as long as the playfield animation routine which actually moves only 50 bytes where this one moves 256 bytes. If high speed is necessary, the loop can be trimmed to move only the image bytes themselves rather than the whole player; then the loop would easily run in about 100-200 microseconds. The point here is that vertical motion with players is both simpler and faster than motion with playfield objects.
Horizontal motion is even easier than vertical motion. There is a register for the player called the horizontal position register. The value in this register sets the horizontal position of the player on the screen. All you do is store a number into this register and the player jumps to that horizontal position. To move the player horizontally simply change the number stored in the horizontal position register. That's all there is to it.
Horizontal and vertical motion are independent; you can combine them in any fashion you choose.
The scale for the horizontal position register is one color clock per unit. Thus, adding one to the horizontal position register will move the player one color clock to the right. There are only 228 color clocks in a single scan line; furthermore, some of these are not displayed because of overscan. The horizontal position register can hold 256 positions; some of these are off the left or right edge of the screen. Position 47 corresponds to the left edge of the standard playfield; position 208 corresponds to the right edge of the standard playfield. Thus, the visible region of the player is in horizontal positions 47 through 208. Remember, however, that this may vary from television to television due to differences in overscan. A conservative range of values is from 60 to 200. This coordinate range can sometimes be clumsy to use, but it does offer a nice feature: a simple way to remove a player from the screen is to set the player's horizontal position to zero. With a single load and store in assembly (or a single POKE in BASIC), the player will disappear.
The system described so far makes it possible to produce high-speed animation. There are a number of embellishments which greatly add to its overall utility. The first embellishment is that there are four independent players to use. These players all have their own sets of control registers and RAM area; thus their operation is completely independent. They are labeled P0 through P3. They can be used side by side to give up to 32 bits of horizontal resolution, or they can be used independently to give four movable objects.
Each player has its own color register; this color register is completely independent of the playfield color registers. The player color registers are called COLP(X) and are shadowed at PCOLR(X). This gives you the capability to put much more color onto the screen. However, each player has only one color; multicolored players are not possible without display list interrupts (display list interrupts are discussed in Section 5).
Each player has a controllable width; you can set it to have normal width, double width, or quadruple width with the SIZEP(X) registers. This is useful for making players take on different sizes. You also have the option of choosing the vertical resolution of the players. You can use single-line resolution, in which each byte in the player table occupies one horizontal scan line, or double-line resolution, in which each byte occupies two horizontal scanlines. With single-line resolution, each player bitmap table is 256 bytes long; with double-line resolution each table is 128 bytes long. This is the only case where player properties are not independent; the selection of vertical resolution applies to all players. Player vertical resolution is controlled by bit D4 of the DMACTL register. In single-line resolution, the first 32 bytes in the player table area lie above the standard playfield. The last 32 bytes lie below the standard playfield. In double-line resolution, 16 bytes lie above and 16 bytes lie below the standard playfield.
The next embellishment is the provision of missiles. These are 2-bit wide graphics objects associated with the players. There is one missile assigned to each player; it takes its color from the player's color register. Missile shape data comes from the missile bitmap table in RAM just in front of the player tables. All four missiles are packed into the same table (four missiles times 2 bits per missile gives 8 bits). Missiles can move independently of players; they have their own horizontal position registers. Missiles have their own size register, SIZEM, which can set the horizontal width just like the SIZEP(X) registers do for players. However, missiles cannot be set to different sizes; they are all set together. Missiles are useful as bullets or for skinny vertical lines on the screen.
If desired, the missiles can be grouped together into a fifth player, in which case they take the color of playfield color register 3. This is done by setting bit D4 of the priority control register (PRIOR). Note that missiles can still move independently when this option is in effect; their horizontal positions are set by their horizontal position registers. The fifth player enable bit only affects the color of the missiles.
You move a missile vertically the same way that you move a player: by moving the missile image data through the missile RAM area. This can be difficult to do because missiles are grouped into the same RAM table. To access a single missile, you must mask out the bits for the other missiles.
An important feature of player-missile graphics is that players and missiles are completely independent of the playfield. You can mix them with any graphics mode, text or map. This raises a problem: what happens if a player ends up on top of some playfield image? Which image has priority? You have the option to define the priorities used in displaying players. If you wish, all players can have priority over all playfield color registers. Or you can set all playfield color registers (except background) to have priority over all players. Or you can set player 0 and player 1 (henceforth referred to as P0 and P1) to have priority over all playfield color registers, with P2 and P3 having less priority than the playfield. Or you can set playfield color registers 0 and 1 (PF0 and PF1) to have priority over all players, which then have priority over PF2 and PF3. These priorities are selected with the priority control register (PRIOR) which is shadowed at GPRIOR. This capability allows a player to pass in front of one image and behind another, allowing three-dimensional effects.
The final embellishment is the provision for hardware collision detection. This is primarily of value for games. You can check if any graphics object (player or missile) has collided with anything else. Specifically, you can check for missile-player collisions, missile-playfield collisions, player-player collisions, and player-playfield collisions.
There are 54 possible collisions, and each one has a bit assigned to it that can be checked. If the bit is set, a coillision has occurred. These bits are mapped into 15 registers in CTIA (only the lower 4 bits are used and some are not meaningful). These are read-only registers; they cannot be cleared by writing zeros to them. The registers can be cleared for further collision detection by writing any value to register HITCLR. All collision registers are cleared by this command.
In hardware terms, collisions occur when a player image coincides with another image; thus, the collision bit will not be set until the part of the screen showing the collision is drawn. This means that collision detection might not occur until as much as 16 milliseconds have elapsed since the player was moved. The preferred solution is to execute player motion and collision detection during the vertical blank interrupt routine (see Section 8 for a discussion of vertical blank interrupts). In this case collision detection should be checked first, then collisions cleared, then players moved. Another solution is to wait at least 16 milliseconds after moving a player before checking for a collision involving that player.
There are a number of steps necessary to use player-missile graphics. First you must set aside a player-missile RAM area and tell the computer where it is. If you use single-line resolution, this RAM area will be 1280 bytes long; if you use double-line resolution it will be 640 bytes long. A good practice is to use the RAM area just in front of the display area at the top of RAM. The layout of the player-missile area is shown in Figure 4-2.
Figure 4-2 Player-Missile RAM Area Layout
The pointer to the beginning of the player-missile area is labeled PMBASE. Because of internal limitations of ANTIC, PMBASE must be on a 2K address boundary for single-line resolution, or a 1K address boundary for double-line resolution. If you elect not to use all of the players or none of the missiles, the areas of RAM set aside for the unused objects may be used for other purposes. Once you have decided where your player-missile RAM area will be, you inform ANTIC of this by storing the page number of PMBASE into the PMBASE register in ANTIC. Note that the address boundary restrictions on PMBASE preclude vertical motion of players by modifying PMBASE.
The next step is to clear the player and missile RAM by storing zeros into all locations in the player-missile RAM area. Then draw the players and missiles by storing image data into the appropriate locations in the player-missile RAM area.
Next, set the player parameters by setting the player color, horizontal position, and width registers to their initial values. If necessary, set the player/playfield priorities. Inform ANTIC of the vertical resolution you desire by setting bit D4 of register DMACTL (shadowed at SDMCTL) for single-line resolution, and clearing the bit for double-line resolution. Finally, enable the players by setting the PM DMA enable bit in DMACTL. Be careful not to disturb the other bits in DMACTL.
A sample BASIC program for setting up a player and moving it with the joystick is given below:
1 PMBASE=54279:REM Player-missile base pointer 2 RAMTOP=106:REM OS top of RAM pointer 3 SDMCTL=559:REM RAM shadow of DMACTL register 4 GRACTL=53277:REM CTIA graphics control register 5 HPOSP0=53248:REM Horizontal position of P0 6 PCOLR0=704:REM Shadow of player 0 color 10 GRAPHICS 0:SETCOLOR 2,0,0:REM Set background color to black 20 X=100:REM BASIC's player horizontal position 30 Y=48:REM BASIC's player vertical position 40 A=PEEK(RAMTOP)-8:REM Get RAM 2K below top of RAM 50 POKE PMBASE,A:REM Tell ANTIC where PM RAM is 60 MYPMBASE=256*A:REM Keep track of PM RAM address 70 POKE SDMCTL,46:REM Enable PM DMA with 2-line res 80 POKE GRACTL,3:REM Enable PM display 90 POKE HPOSP0,100:REM Declare horizontal position 100 FOR I=MYPMBASE+512 TO MYPMBASE+640:REM this loop clears player 110 POKE I,0 120 NEXT I 130 FOR I=MYPMBASE+512+Y TO MYPMBASE+518+Y 140 READ A:REM This loop draws the player 150 POKE I,A 160 NEXT I 170 DATA 8,17,35,255,32,16,8 180 POKE PCOLR0,88:REM Make the player pink 190 A=STICK(0):REM Read joystick 200 IF A=15 THEN GOTO 190:REM If inactive, try again 210 IF A=11 THEN X=X-1:POKE HPOSP0,X 220 IF A=7 THEN X=X+1:POKE HPOSP0,X 230 IF A<>13 THEN GOTO 280 240 FOR I=8 TO 0 STEP -1 250 POKE MYPMBASE+512+Y+I,PEEK(MYPMBASE+511+Y+I) 260 NEXT I 270 Y=Y+1 280 IF A<>14 THEN GOTO 190 290 FOR I=0 TO 8 300 POKE MYPMBASE+511+Y+I,PEEK(MYPMBASE+512+Y+I) 310 NEXT I 320 Y=Y-1 330 GOTO 190
Once players are displayed, they can be difficult to remove from the screen. This is because the procedure by which they are displayed involves several steps. First, ANTIC retrieves player-missile data from RAM (if such retrieval is enabled in DMACTL). Then ANTIC ships the player-missile data to CTIA (if such action is enabled in GRACTL). CTIA displays whatever is in its player and missile graphics registers (GRAFP0 through GRAFP3 and GRAFM). Many programmers attempt to turn off player-missile graphics by clearing the control bits in DMACTL and GRACTL. This only prevents ANTIC from sending new player-missile data to CTIA; the old data in the GRAF(X) registers will still be displayed. To completely clear the players the GRAF(X) registers must be cleared after the control bits in DMACTL and GRACTL have been cleared. A simpler solution is to leave the player up but set its horizontal position to zero. Of course, if this solution is used, ANTIC will continue to use DMA to retrieve player-missile data, wasting roughly 70,000 machine cycles per second.
Player-missile graphics allow a number of very special capabilities. They are obviously of great value in animation. They do have limitations: there are only four players and each is only 8 bits wide. If you need more bits of horizontal resolution you can always fall back on playfield animation. But for high-speed animation or quick and dirty animation, player-missile graphics work very well.
It is possible to bypass ANTIC and write player-missile data directly into the player-missile graphics registers (GRAFP(X)) in CTIA. This gives the programmer more control over player-missile graphics. It also increases his responsibilities concomitantly. The programmer must maintain a bitmap of player-missile data and move it into the graphics registers at the appropriate times. The 6502 must therefore be slaved to the screen drawing cycle. (See the discussion of kernels in Chapter 5.) This is a clumsy technique that offers minor improvements in return for major programming efforts. The programmer who bypasses the hardware power offered by ANTIC must make up for it with his own effort.
Players can also be used to produce apparent 3-dimensional motion. This is accomplished with the player width option. Each player is drawn with one of several bitmaps. One bitmap shows the player as 6 bits wide, and another shows the player in 8 bits. When the 6 bit player is drawn at normal resolution, it will be 6 color clocks wide. The next size step is achieved by going to double width with the 6 bit image; this will be 12 color clocks wide. The 8 bit image will be 16 color clocks wide. Similarly, going to quadruple width will produce images 24 and 32 color clocks wide. Thus, the image can grow in size from 6 color clocks to 32 color clocks wide. This technique is used very effectively in STAR RAIDERS™. The Zylons there are two players with 16 bits, so the size transitions are even smoother.
Player-missile graphics offer many capabilities in addition to animation. Players are an excellent way to increase the amount of color in a display. The four additional color registers they provide allow four more colors on each line of the display. Of course, the 8-bit resolution does limit the range of their application. There is a way around this that can sometimes be used. Take a player at quadruple width and put it onto the screen. Then set the priorities so that the player has lower priority than a playfield color. Next, reverse that playfield color with background, so that the apparent background color of the screen is really a playfield color. The player disappears behind this new false background. Now cut a hole in the false background by drawing true background on it. The player will show up in front of the true background color, but only in the area where true background has been drawn. In this way the player can have more than eight bits of horizontal resolution. A sample program for doing this:
CH4PRG2.BAS 1 RAMTOP=106:REM OS top of RAM pointer 2 PMBASE=54279:REM ANTIC player-missile RAM pointer 3 SDMCTL=559:REM Shadow of DMACTL 4 GRACTL=53277:REM CTIA graphics control register 5 HPOSP0=53248:REM Horizontal position register of P0 6 PCOLR0=704:REM Shadow of player 0 color register 7 SIZEP0=53256:REM Player width control register 8 GPRIOR=623:REM Priority control register 10 GRAPHICS 7 20 SETCOLOR 4,8,4 30 SETCOLOR 2,0,0 40 COLOR 3 50 FOR Y=0 TO 79:REM This loop fills the screen 60 PLOT 0,Y 70 DRAWTO 159,Y 80 NEXT Y 90 A=PEEK(RAMTOP)-20:REM Must back up further for GR. 7 100 POKE PMBASE,A 110 MYPMBASE=256*A 120 POKE SDMCTL,46 130 POKE GRACTL,3 140 POKE HPOSP0,100 150 FOR I=MYPMBASE+512 TO MYPMBASE+640 160 POKE I,255:REM Make player solid color 170 NEXT I 180 POKE PCOLR0,88 190 POKE SIZEP0,3:REM Set player to quadruple width 200 POKE GPRIOR,4:REM Set priority 210 COLOR 4 220 FOR Y=30 TO 40 230 PLOT Y+22,Y 240 DRAWTO Y+43,Y 250 NEXT Y
This program produces the following display:
Figure 4-3 Masking a Player for More Resolution
Another application of player-missile graphics is for special characters. There are many special types of characters that cross vertical boundaries in normal character sets. One way to deal with these is to create special character sets that address this problem. Another way is to use a player. Subscripts, integral signs, and other special symbols can be done this way. A sample program for doing this is:
CH4PRG3.BAS 1 RAMTOP=106:REM OS top of RAM pointer 2 PMBASE=54279:REM ANTIC player-missile RAM pointer 3 SDMCTL=559:REM Shadow of DMACTL 4 GRACTL=53277:REM CTIA's graphics control register 5 HPOSP0=53248:REM Horizontal position register of P0 6 PCOLR0=704:REM Shadow of player 0 color register 10 GRAPHICS 0:A=PEEK(RAMTOP)-16:REM Must back up for 1-line resolution 20 POKE PMBASE,A 30 MYPMBASE=256*A 40 POKE SDMCTL,62 50 POKE GRACTL,3 60 POKE HPOSP0,102 70 FOR I=MYPMBASE+1024 TO MYPMBASE+1280 80 POKE I,0 90 NEXT I 100 POKE PCOLR0,140 110 FOR I=0 TO 15 120 READ X 130 POKE MYPMBASE+1100+I,X 140 NEXT I 150 DATA 14,29,24,24,24,24,24,24 160 DATA 24,24,24,24,24,24,184,112 170 ?" ":REM Clear screen 180 POSITION 15,6 190 ?"xdx"
This program produces the following display:
Figure 4-4 Using a Player as a Special Character
A particularly useful application of players is for cursors. With their ability to smoothly move anywhere over the screen without disturbing its contents they are ideally suited for such applications. The cursor can change color as it moves over the screen to indicate what it has under it.
Player-missile graphics provide many capabilities. Their uses for action games as animated objects are obvious. They have many serious uses as well. They can add color and resolution to any display. They can present special characters. They can be used as cursors. Use them.