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 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.
IMAGE | Corresponding Bytes in RAM |
---|---|
00 00 00 |
00 00 00 00 99 00 00 BD 00 00 FF 00 00 BD 00 00 99 00 00 00 00
Image Bytes Scattered Through RAM
Figure 4-1 Noncontiguous RAM Images
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 address. This calculation is not always easy to do. The assembly code just to access a single byte at screen location (XPOS, YPOS) would look like this (this code assumes 40 bytes per screen line):
LDA SCRNRM Address of 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 TEMPB low byte
ASL A times 16
ROL TEMPA+1
ASL A times 32
ROL TEMPA+1
CLC
ADC TEMPB add YPOS*8 to get YPOS*40
STA TEMPB
LDA TEMPA+1 now do high order byte
ADC TEMPB+1
STA TEMPB+1
LDA TEMPB TEMPB contains the offset from the 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 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.
PLAYER-MISSILE FUNDAMENTALS
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 scan lines, with the choice between
the two made by the programmer. The screen image is a simple
bit-map 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. The 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 you place data, the lower the
image will appear on the screen.
VERTICAL MOTION
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
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 singe 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 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 singe POKE in BASIC), the player will disappear.
OTHER PLAYER-MISSILE FEATURES
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 individual players to use.
These players all have their own sets of control registers and
RAM area; thus their operation is completely independent. They
are labelled 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 scan lines. With single-line
resolution, each player bit-map 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.
MISSILES
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 bit-map table in RAM just in front of the player's
table. 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.
PLAYFIELD AND PLAYFIELD PRIORITIES
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) the 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.
HARDWARE COLLISION DETECTION
The final embellishment is the provision for hardware collision
detection. This is primarily of value for games. You can check
if any graphic 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 collision 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, a collision occurs 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 labelled 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=0: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.
APPLICATIONS OF PLAYER-MISSILE GRAPHICS
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 eight 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 bit map 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 bit maps. One bit map 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 he 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:
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
SPECIAL CHARACTERS
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:
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=MYPMBASE+512+Y to MYPMBASE+518+Y
120 READ X
130 POKE MYPMBASE+1100+I,X
140 NEXT X
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.