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 m