I take the
"ground
up" approach when I
program a game: the
graphics first, then
the game logic.
up" approach when I
program a game: the
graphics first, then
the game logic.
A little
character
Perhaps one of the toughest parts of programming a
game is deciding where to start (you may, however, disagree). The best
thing to do is begin with the most dominant part of the game, the part
that everything else tends to rely on. For example, I would start with
the maze if I were doing Pac-Man,
the terrain if I were doing Scramble
and the centipede if I were doing Centipede. Once these elements are
in
place, the rest can be added piece by piece. It's sort of the
equivalent of building a house floor by floor, starting with the
basement.Of course, this is only my personal preference. You may prefer to start with the framework and then fill in the details (design the game logic first and then do the graphics). The reason I take the "ground up" approach is because in most games the logic relies on the graphics being in place, so that it can detect collisions and respond appropriately. Also, it's a lot easier to program when you can see the results on the screen.
Needless to say, we'll be programming our BASIC Invaders example from the ground up, which means we'll begin with the invaders themselves. As a result, this column will be dealing with character graphics. Please keep in mind that this may not be the case in your game. Your key element may use bit-mapped graphics or PMG. The important thing to take note of here, then, is not the fact that we're beginning with character graphics, but rather the techniques that we use to implement the character graphics.
Our first step is to take the invader shapes we came up with earlier and translate them into values that the computer can understand. As you probably already know, this is done by treating each shape as a series of bytes stacked on top of each other. Each byte consists of eight bits which, like the dots, can be turned on or off. So we treat each dot as a bit. Because the invaders are more than eight dots wide, we'll be using two characters for each invader.
So let's get started. Because this is a relatively straightforward process, we'll single-step through the first invader. The first invader is shown in Figure 1.
Now that we have our values, we're almost ready to give them to the computer. First of all, though, we have to reserve a place to put them, along with the rest of the character set. We do this with the following program lines:
3150
CB=PEEK(740)-4:P0KE
106,CB-4:CA=CB*256
3160 GRAPHICS 0:POKE 756
,CB
106,CB-4:CA=CB*256
3160 GRAPHICS 0:POKE 756
,CB
Now we have 1024 bytes reserved at the end of memory, and have told the computer that it will a find a character set there. So we'd better set one up quickly! To make things easier, I'll give you the complete program listing first, and then we'll go over it line by line:
3030
DIM MLANG$(90)
3050 DIM MOVMEM$(41):GOS
UB 29500:MOVMEM$=MLANG$
3150 CB=PEEK(740)-4:POKE
106,CB-4:CA=CB*256
3160 GRAPHICS 0:POKE 756
,CB
3210 X=USR(ADR(MOVMEM$),
57344,CA,1023)
3220 MEM=CA+512:FOR SEC=
0 TO 1:GOSUB 32500+10*SE
C:X=USR(ADR(MOVMEM$),ADR
(MLANG$),MEM,LEN(MLANG$)
-1)
3230 MEM=MEM+LEN(MLANG$)
:NEXT SEC
3240 X=USR(ADR(MOVMEM$),
CA+128,CA+640,335)
28999 END
3050 DIM MOVMEM$(41):GOS
UB 29500:MOVMEM$=MLANG$
3150 CB=PEEK(740)-4:POKE
106,CB-4:CA=CB*256
3160 GRAPHICS 0:POKE 756
,CB
3210 X=USR(ADR(MOVMEM$),
57344,CA,1023)
3220 MEM=CA+512:FOR SEC=
0 TO 1:GOSUB 32500+10*SE
C:X=USR(ADR(MOVMEM$),ADR
(MLANG$),MEM,LEN(MLANG$)
-1)
3230 MEM=MEM+LEN(MLANG$)
:NEXT SEC
3240 X=USR(ADR(MOVMEM$),
CA+128,CA+640,335)
28999 END
Don't forget that lines 29500 through 32510 come from a previous column. In case you're wondering about the rest of the program, here's the complete explanation:
3030; You may remember MLANG$ from the program lines we generated previously. It's used for temporary storage of the machine language routines and character set data.
3050; MOVMEM$ holds a machine language routine that we'll be using throughout BASIC Invaders to move things around in memory. You'll see how it's used later in this explanation.
3150; You saw this already, but I'll explain in case it wasn't clear. Location 740 points to the top (or end) of memory. Location 106 points to where the computer thinks the end of memory is. So, we set a variable called CB to point to where the beginning of our character set will be (PEEK(740)-4, which is four pages below the top of memory, with a page being 256 bytes). Then we reset location 106 to point 1024 bytes (256*4) below the character set.
Why? Because the area of memory right after that pointed to by location 106 isn't always safe. Anyway, it isn't really that important for you to understand this. Just keep in mind that this line will reserve memory for the character set. You may also want to know that if you're using a program like BASIC A+, which also changes location 106, then you should change CB=PEEK(740)-4 to CB=PEEK(106)-4.
3160; The screen is usually kept at the top of memory, and is right now in the space we've reserved for our character set. By using a GRAPHICS command, we move it down below the new top of memory. Also, we tell the computer where our new character set will be.
3210; This line moves the regular character set to our reserved space, and is an example of using MOVMEM. In general, the way to use MOVMEM is with the following command:
X=USR(ADR(M0VMEM$),FR0M,
TO,LENGTH-1)
TO,LENGTH-1)
FROM is the address of the first memory location you want to move from, TO is the address of the first memory location you want to move to and LENGTH is the number of bytes you want to move. So, in our example, we are moving the 1024 bytes starting at location 57344 (the address of the Atari character set) to the memory area starting at location CA (the address of our character set).
Before you
start complaining that your invader
characters don't look right, keep in mind that
you're looking at them in graphics mode zero.
characters don't look right, keep in mind that
you're looking at them in graphics mode zero.
3220-3230; Now we put our redefined characters into the new character set. The graphics characters being at CA+512. We've already put our character data into strings in lines 32500 and 32510, so we GOSUB to these lines, then move the data from MLANG$ to the character set. Notice that we once again use MOVMEM. You'll find that there are a lot of times that MOVMEM will come in handy, and not just the ones that we'll cover in this book.
3240; Here's MOVMEM again! Actually, this line isn't really necessary at this point, but I included it because it follows along with everything else here. All it does is move the numbers and uppercase characters into the lowercase part of the character set. Why? In graphics mode one, you have to choose between lowercase/graphics and uppercase/numbers. We want to be able to have uppercase, numbers and graphics at the same time (so we can include the score), so we simply move things around a little.
28999; That's it folks.
29500-32510; These are just the subroutines to set up MOVMEM and the redefined characters. See the previous column on how to set them up.
I know there are a few things that I haven't mentioned yet, and I'll get into them soon. First, however, why don't we make sure that this program really works. Run it, and then try typing CTRL-A, CTRL-B and so forth, all the way up to CTRL-N (preferably all right next to each other). This should give you all our invaders characters. Now before you start complaining that they don't look right, keep in mind that you're looking at them in graphics mode zero. We'll be using them in graphics mode one, and in a minute you'll see what they look like in that mode. But first, it's time to clear up a few things.
In the process of getting all of this up and working, I've conveniently neglected to fill in a few of the details. For example, you may have noticed that the first character we redefined was all zeros or a blank space. Why? Try going into graphics mode zero, and then POKE 756,226. See how the screen fills with hearts? What we just did was switch to lowercase/graphics. In lowercase/graphics, there is no space character. What the computer uses instead is the heart. So, to avoid this problem, we redefine the heart to be a space. Okay?
Well, that was easy to explain. Next up is a problem that many people run into when working with the character set: the character values. You already know about ATASCII values, right? Each character is assigned an ATASCII value between zero and 255. If you want to find out the value for a particular character, use the ASC command from BASIC (PRINT ASC("E"), for example).
Anyway, each character has a value, which tends to put the characters in a specific order. Now it would make sense that the characters would be stored in this order in the character set, right? Of course it would, but when was the last time a computer made sense? Instead, there is another order for the character set, called the internal order. There is an easy way to figure out the internal value from the ATASCII value. The following table shows how to do it:
TYPE OF CHARACTER | ATASCII | INTERNAL |
Graphics Uppercase/Numbers Lowercase |
0-31 32-95 96-127 |
Add 64 Subtract 32 Same |
(Anything with a value greater than 127 is just the inverse of the character with the same value minus 128.)
All this does is switch the graphics and uppercase/numbers as far as order is concerned. No big deal, but you have to remember to use the internal order when dealing directly with the character set.
One more detail that tends to trip a lot of people up. Suppose you want to change a character with an internal value of n, and the character set begins at CA. Do you start changing bytes at location CA +n? No, because each character takes up eight bytes in the character set. That means that the character starts at CA+n*8. A silly little detail like this has left a lot of people wondering what went wrong!
Okay, now it's time to put our invaders into action. What we're going to do is put them into a long string and then just print this string on the screen. Remember, though, that there are two versions of each invader. So our string will actually hold two versions of the invader screen. We'll alternately print each version, thus getting some animation out of the invaders. This will make more sense after you try it out, so make the following changes to the previous program:
3030
DIM MLANG$(90),INV$
(480),DAT$(16)
3160 GRAPHICS 17:POKE 75
6,CB+2
5370 INV$="":INV$(480)=
"":INV$(2)=INV$
5380 RESTORE 5410
5390 FOR LP=0 TO 4 STEP
2:READ DAT$:INV$(LP*40+1
,LP*40+16)=DAT$:INV$(LP*
40+281,LP*40+296)=DAT$
5400 READ DAT$:INV$(LP*4
0+41,LP*40+56)=DAT$:INV$
(LP*40+241,LP*40+256)=DA
T$:NEXT LP
5420 POSITION 0,0:PRINT
#6;INV$(1,240)
5430 POSITION 0,0:PRINT
#6;INV$(241,480)
5440 GOTO 5420
(480),DAT$(16)
3160 GRAPHICS 17:POKE 75
6,CB+2
5370 INV$="":INV$(480)=
"":INV$(2)=INV$
5380 RESTORE 5410
5390 FOR LP=0 TO 4 STEP
2:READ DAT$:INV$(LP*40+1
,LP*40+16)=DAT$:INV$(LP*
40+281,LP*40+296)=DAT$
5400 READ DAT$:INV$(LP*4
0+41,LP*40+56)=DAT$:INV$
(LP*40+241,LP*40+256)=DA
T$:NEXT LP
5420 POSITION 0,0:PRINT
#6;INV$(1,240)
5430 POSITION 0,0:PRINT
#6;INV$(241,480)
5440 GOTO 5420
You can go ahead and run the new program and watch the results. When you're done, here's the explanation of the changes:
3030; We've added two more string variables to this line. INV$ is going to hold the invaders screens, and we'll use DAT$ to help us set up INV$.
3160; We're using a full screen graphics mode one (GRAPHICS 1+16), and the graphics/lowercase section of our new character set.
5370; Okay, now we start setting up INV$. This line is a tricky way to set the entire string to hearts, which we've redefined to a space character. Incidentally, CTRL- will get you the heart character.
5380; In a long program where you'll be reading different data over and over again, it's a good idea to RESTORE the data first, just to make sure that you won't be reading the wrong stuff. That's why this line is here.
5390-5400; Now things start to get a little complicated, so let's take a good, close look at this loop. LP keeps track of the invader row that we're working on. We'll be doing two rows at a time, so we use STEP 2 (there are three types of invaders and two rows of each). The first thing we do is read a row of invaders into DAT$. Then DAT$ gets transferred to two places in INV$ (more about this in a minute). Finally, another row is read into DAT$ and then transferred to INV$, and the loop repeats.
The thing that really needs explaining is the process of transferring DAT$ to INV$. What exactly is going on? Let's look at the whole thing in English from start to finish. When matching the English explanation to the program lines, keep in mind that the first invader screen starts at INV$ (1,1) and the second at INV$ (240,240). You should also be aware that a line on the screen is twenty characters long, and we keep a blank line between each row of invaders.
Now that you're all geared up for the incredibly complicated explanation that you're sure is about to follow, relax. The first time through, the loop reads a row of invaders (version one of invader one) and puts it into the first row of the first screen and the second row of the second screen. Then it reads another row (version two of invader one) and puts it into the second row of the second screen and the first row of the second screen (by alternating versions like this, there will be more variety in the rows).
The next time through the loop, version one of invader two gets put into the third row of screen one and the fourth row of screen two. Then version two of invader two gets put into the fourth row of screen one and the third row of screen two. The third and last time through the loop, version one of invader three gets put into the fifth row of screen one and the sixth row of screen two. Version two of invader three then gets put into the sixth row of screen one and the fifth row of screen two. And that's all there is to it.
5410; Here's the data for the rows. How, you may be wondering, am I supposed to type this in? Either that, or you somehow managed to type it in already and are now extremely upset that I waited until now to tell you how. Sorry about that. Anyway, it's made up of eight CTRL -A/CTRL-Bs, eight CTRL-C/CTRL-Ds, eight CTRL-E/CTRL-Fs, eight CTRL-G/CTRL-Hs, eight CTRL-I/CTRLJs and eight CTRL-K/CTRL-Ls. If you type it in after running the original program, it will make more sense (since these graphics characters will look like the invaders).
5420; Now we're ready to get things going on the screen. We first put the cursor at the top left of the screen. Then we print the first 240 characters of INV$, which is the first invader screen.
5430; Now we do the same thing, except this time we print the second 240 characters of INV$ or the second invader screen.
5440; Finally, we make it into a loop so that the invaders will walk in place forever (or until you press the BREAK key).
So, what do you think so far? Not bad for BASIC? The problem is, we haven't really done anything yet. Keeping that in mind, what you see on the screen is actually extremely slow. By the time the rest of the program is in place, these moving invaders would be moving in extremely slow motion. But is there any way to speed things up?
Before I answer, think about the fact that what's slowing us down is the PRINT statement. What we need to do is find another way to get the invaders from INV$ to the screen. Perhaps I should restate that as "moving the invaders from the INV$ to the screen." Does that suggest a solution to you? If you thought of MOVMEM, give yourself ten points. Since we can find out the address of the screen using locations 88 and 89, there is absolutely no reason why we can't use MOVMEM instead of PRINT. How much of a speed difference will it make? Make the following changes and find out for yourself:
5180
MEM1=PEEK(88)+PEEK(
89)*256
5420 X=USR(ADR(MOVMEM$),
ADR(INV$),MEM1,239)
5430 X=USR(ADR(MOVMEM$),
ADR(INV$)+240,MEM1,239)
5440 GOTO 5420
89)*256
5420 X=USR(ADR(MOVMEM$),
ADR(INV$),MEM1,239)
5430 X=USR(ADR(MOVMEM$),
ADR(INV$)+240,MEM1,239)
5440 GOTO 5420
Wow, quite a difference, eh? Here's a brief explanation:
5180; MEMI is the address of screen memory.
5420; This is the equivalent of the old Line 5420.
5430; This is the equivalent of the old Line 5430.
5440; Do it again (and again and again and).
Once you get over the shock of the extra speed, notice the invader's color. It may not be obvious, but it is a different color than when we were PRINTing them. The reason for this is the fact that when we move characters directly to screen memory, we're dealing with the internal values rather than the ATASCII ones. In graphics mode zero, this means that we don't get the proper character. In graphics modes one and two, we either get the wrong character or a change in color. Change line 5410 to the following:
For some
reason, scrolling has become the
BASIC programmer's dream and nightmare.
Despite the great effects it creates, it's not easy
to get scrolling working from BASIC.
BASIC programmer's dream and nightmare.
Despite the great effects it creates, it's not easy
to get scrolling working from BASIC.
If you RUN the program with this change you'll notice that, despite the fact that we've changed some of the CTRL characters to letters, we still get the invader characters on the screen, only in different colors. To figure out what's going on, look up the internal values of the characters in Line 5410 and compare them to the internal values of the CTRL characters.
In graphics modes one and two, only the values between zero and 63 are used to determine the character. By adding 64, 128 or 192 to these values, you simply specify a different color for the character. You might like to play around with the characters in Line 5410 to see what I mean. When you're done, leave them as they are in the above line. It's nice for each type of invader to have a color of its own.
Before we go on to the next stage of our game program, a few words about your own games. Even though we're using different colored invaders in this game, each invader only has one color. What if you want to use more than one color in each character? Is it possible? Yes, and there are a number of different ways to go about it.
In graphics mode zero, there is something called "artifacting" that will allow you to have more than one color per character. In this mode, the dots in evennumbered columns have a different color than the dots in odd-numbered columns. Two dots side-by-side make a third color (white, usually). Along with the background color, this gives you a total of four colors instead of the usual two. The only problem is you have to be careful of where you place your dots. As an exercise, try redefining a couple of graphics mode zero characters so that one is all even-column dots, one is all odd and the other is a mix.
The other way to get multi-color characters is through the use of a special graphics mode called ANTIC mode four. In this mode, each dot can have one of four colors also, but you don't have to worry about odd and even. Also, the dots get their colors from the color registers, whereas the colors in artifacting come from a trick, and you can't change one of them without changing the rest.
To get four colors in ANTIC mode four, each dot is two bits wide instead of one. These two bits can hold a value between zero and three, which specifies the color register. So, when designing characters in this mode, you must keep in mind that each dot is two bits wide instead of one and has three ways of being "on" instead of one. Otherwise, the procedure for redefining characters is exactly the same. As far as getting to the mode in the first place, that has to do with Display Lists, which we'll be getting to in the future.
So much for that. Our next step in programming our game is to get the invaders moving back and forth across the screen. Unfortunately, here we run into a problem. The only way to move things using character graphics alone is to move them a whole character at a time. This means that characters will appear to jump across the screen instead of moving smoothly. That's not good. We want our invaders to move one tiny step at a time. So how do we solve this dilemma?
Actually, there is a way to do it with character graphics. If we design eight versions of each character, with each version being shifted to the right one dot, then we can get smooth movement by switching between these different versions. Well, this may be a good solution in some instances, but not in ours. Don't forget that we're using fourteen different characters for the invaders and the invader explosion. Eight versions of each would mean a total of 112 characters. Not only is that most of the character set, but it's also more than the 64 we're allowed in graphics mode one.
Besides, even if we could have that many, we would also have to have eight versions of INV$, which would take up almost 4K of memory alone. Nope, this technique is just not going to work for us here. Instead, we're going to use something that I'm sure you know about but haven't been able to use much before: fine scrolling.
Scrolling
For some reason, scrolling has become the BASIC
programmer's dream and nightmare at the same time. Despite the great
effect it creates in even the simplest programs, it seems that it's not
too easy to get scrolling working from BASIC. Well, by the end of this
discussion, fine scrolling will be just as easy as a simple POKE or two.Instead of spending time looking at exactly what fine scrolling is and how it's done, let's jump right into an example. After we've got something up and running, we'll come back and take a look at the details. For now, delete lines 5420, 5430 and 5440 from the program we just created, and then add the following lines:
90
GOTO 3010
1000 X=USR(ADR(MOVMEM$),
ADR(INV$)+SB,MEM1,239)
1080 SB=240*(SB=0)
1280 IF COARSE=4 OR COAR
SE=-2 THEN CHANGE=-CHANG
E:POKE 1791,129-PEEK(179
1)
1330 SCROLL=SCROLL+CHANG
E
1340 IF SCROLL>15 THEN S
CROLL=SCROLL-16:COARSE=C
OARSE+2:POKE 1790,2:GOTO
1360
1350 IF SCROLL<0 THEN SC
ROLL=SCROLL+16:COARSE=CO
ARSE-2:POKE 1790,2
1360 POKE 1788,SCROLL:PO
KE 1787,1
1380 IF PEEK(1790)<>0 TH
EN 1380
1390 GOTO 1000
3010 POKE 559,0
3040 DIM VBLOFF$(20):GOS
UB 29000:VBLOFF$=MLANG$
3160 FOR SEC=0 TO 1:GOSU
B 31000+10*SEC
3170 X=USR(ADR(MOVMEM$),
ADR(MLANG$),CA-256+90*SE
C,LEN(MLANG$)-1):NEXT SE
C
4000 GRAPHICS 17:POKE 55
9,0:POKE 756,CB+2
5010 DLIST=PEEK(560)+PEE
K(561)*256
5020 POKE DLIST+3,86
5030 FOR LINE=2 TO 12:PO
KE DLIST+4+LINE,22:NEXT
LINE
5270 SCROLL=0:CHANGE=1:S
B=0:COARSE=0
5200 POKE 559,34
5290 POKE 54276,0
5360 POKE 1789,0:POKE 17
90,0:POKE 1791,128
5460 GOSUB 31500:X=USR(A
DR(MLANG$),CA-256)
5490 GOTO 1000
1000 X=USR(ADR(MOVMEM$),
ADR(INV$)+SB,MEM1,239)
1080 SB=240*(SB=0)
1280 IF COARSE=4 OR COAR
SE=-2 THEN CHANGE=-CHANG
E:POKE 1791,129-PEEK(179
1)
1330 SCROLL=SCROLL+CHANG
E
1340 IF SCROLL>15 THEN S
CROLL=SCROLL-16:COARSE=C
OARSE+2:POKE 1790,2:GOTO
1360
1350 IF SCROLL<0 THEN SC
ROLL=SCROLL+16:COARSE=CO
ARSE-2:POKE 1790,2
1360 POKE 1788,SCROLL:PO
KE 1787,1
1380 IF PEEK(1790)<>0 TH
EN 1380
1390 GOTO 1000
3010 POKE 559,0
3040 DIM VBLOFF$(20):GOS
UB 29000:VBLOFF$=MLANG$
3160 FOR SEC=0 TO 1:GOSU
B 31000+10*SEC
3170 X=USR(ADR(MOVMEM$),
ADR(MLANG$),CA-256+90*SE
C,LEN(MLANG$)-1):NEXT SE
C
4000 GRAPHICS 17:POKE 55
9,0:POKE 756,CB+2
5010 DLIST=PEEK(560)+PEE
K(561)*256
5020 POKE DLIST+3,86
5030 FOR LINE=2 TO 12:PO
KE DLIST+4+LINE,22:NEXT
LINE
5270 SCROLL=0:CHANGE=1:S
B=0:COARSE=0
5200 POKE 559,34
5290 POKE 54276,0
5360 POKE 1789,0:POKE 17
90,0:POKE 1791,128
5460 GOSUB 31500:X=USR(A
DR(MLANG$),CA-256)
5490 GOTO 1000
Before I get into an explanation of what we just did, we need to take a look at exactly what scrolling is in the first place. "Easy stuff," you say, "it's just moving things around on the screen." Yes it is, but it's the way things are moved that counts. As you probably know already, you can move characters around the screen simply by printing them in different positions. Unfortunately, the kind of movement that results isn't exactly the smoothest thing in the world, and it's also very slow. There's another way to get the screen to move without actually moving the objects on the screen. This method involves something called the "display list," which you may have heard of, and is the heart of fine scrolling.
The Atari computers do something that no other home computer that I know of allows you to do: put the screen data anywhere in memory. You can put the whole screen in one place, or break it up into two or more pieces and put each piece in a totally different place. You can even put two or more of these pieces in the same place, thus making things appear in two or more places on the screen (think that one over).
All of this is because of the display list, which we'll cover in excruciating detail in the next column. For now, suffice it to say that you can specify a different screen memory location for each line on the screen. In a regular graphics mode screen, a location is specified for the first line, and the rest of the screen is assumed to come right after the date for that line.
Okay, so we've now specified where the screen memory can be found. Let's suppose that the number "1234567890" is on the screen, starting at the top left-hand corner. What would happen if we now added one to the screen memory address? Screen memory would then start at the location that the "2" is stored in, right? And what effect would that have on the screen? The "2" will now be in the top left-hand corner, and the whole screen would have appeared to shift to the left by one character (the "1" will have disappeared off the left-hand side). Similarly, if we had subtracted one from the address, the screen would have shifted to the right by one.
This relatively simple concept is the whole basis of scrolling. Each time you want to scroll by one character, you just add or subtract one to the screen memory addresses in the display list (if you don't want a particular line to scroll, you just leave the screen memory address for that line alone).
What if you want to scroll up or down instead of left or right? Let's suppose you were using graphics mode one, where there are 20 characters on a line. Scrolling up by one character would then be the same as scrolling left 20 characters, and scrolling down one the same as scrolling right 20. Can you see why? Adding or subtracting 20 from the screen memory addresses moves screen memory to the next or previous line respectively, since the lines are stored one after the other in memory. It's as simple as that.
Fine scrolling
Who cares about scrolling by one character anyway?
Wasn't the whole point of this column to learn how to scroll by less
than a character, a task called "fine scrolling"? Yes, and fine
scrolling is actually easier than scrolling by one character (called
"coarse scrolling"). The problem is, you can only fine scroll by up to
two characters (graphics mode one size). To do more than that, you have
to combine fine scrolling with coarse scrolling. Before we get into
that, however, let's look at how to fine scroll.There are actually only two relatively simple steps to fine scrolling. The first is to decide which lines you want to fine scroll and then make some changes to the display list so that the computer knows which ones too. The second step is nothing more than a simple POKE to tell the computer how much to scroll. That's it.
But like I said before, the most you can scroll a line is two characters horizontally and two characters vertically. To get continuous fine scrolling, the trick is to fine scroll by two characters (although some prefer only to scroll by one), then coarse scroll by two and reset the fine scrolling at the same time. This has no visible effect on the screen (since the coarse scrolling moves moves the screen two characters in one direction, and resetting the fine scrolling moves it two characters in the opposite direction), but you are no longer at your fine scrolling limit and can now go ahead and fine scroll two more characters. By repeating this process, you can fine scroll forever if you want to.
Are you suffering from information overload? Perhaps it's time to go ahead and explain the above program lines, since they give an example of everything we just talked about.
90; We're going to start by going off to the end of the program and getting everything all set up. The stuff that actually gets done during the game will come earlier on in the program listing, because Atari BASIC is set up to do the things that come early faster.
1000; This is the routine to move the invaders from INV$ to the screen. Apart from moving it to here, we've also changed it so that the variable SB will determine the screen version that will be printed. SB is equal to zero for the first screen (ADR(INV$)+0= INV$(1,1)) and, for now, 240 for the second screen (ADR(INV$)+240=INV$(241,241)).
1080; After we print a particular screen, we want to change SB so that the other screen will be printed next time around. This line simply switches SB between zero and 240. (SB=0) is equal to one if SB=0 and 0 if SB< >0.
When you fine
scroll horizontally, the computer
makes each scrolling line longer than 20
characters per line.
makes each scrolling line longer than 20
characters per line.
1280; COARSE is a variable that we use to keep track of how many characters we've coarse scrolled by so far. We are only worrying about moving the invaders from side to side at this point, and we want to make sure that they don't go off the edge of the screen. So, we change the direction that they're scrolling in when COARSE gets too high (they've reached the right-hand limit) or too low (they've reached the left-hand limit). CHANGE tells what direction they're going in and is equal to one for right and minus one for left. Location 1791 is used in the machinelanguage scroll routine (called SCROLL) and also keeps track of direction. It is equal to 128 for right and one for left. We'll be going into more detail on exactly how SCROLL works later.
1330; The variable SCROLL keeps track of the amount of fine scrolling that has been done. I mentioned before that there is a memory location that takes care of fine scrolling. Actually, there are two. HSCROL at location 54276 takes care of horizontal scrolling and VSCROL at location 54277 takes care of vertical scrolling. As it turns out, our machine-language SCROLL will take care of both of these locations, but regardless of this, you can only POKE them, you can't PEEK (well you can, but you won't get the same values you POKEd). That means that you have to keep track of their values in your own variables, which is what the variable SCROLL does.
1340; When we have fine scrolled 16 times, we will have gone past the fine scrolling limit and must do a coarse scroll. In this line, we reset the variable SCROLL (which we will later use to reset the fine scrolling) and update COARSE. Location 1790 tells our machine-language routine that we want to coarse scroll by two characters in the direction specified by location 1791 (see line 1280). Having taken care of all of these, we then skip ahead to line 1360.
1350; If we're scrolling in the other direction, then we want to do the exact opposite when we've fine scrolled down to zero. We set the variable SCROLL to 15, update COARSE in the opposite direction, and again set location 1790.
1360; Location 1789 tells the machinelanguage routine the value to store in HSCROL, and if the value in location 1788 is not zero, then the routine knows that something needs to be done.
1380; Now we wait until the routine is finished.
1390; And then we go back to line 1000 to do the whole thing all over again with the next invader screen.
3010; We're going to be making some changes to the display list, so we turn off the screen temporarily to avoid a mess.
3040; VBLOFF$ will hold a machinelanguage routine that is used to turn off our VBLANK routines. SCROLL is a VBLANK routine, so we may as well set up and introduce VBLOFF now.
3160-3170; Here we set up SCROLL itself. The way that SCROLL works, it has to be stored in memory, not in a string. But it has been designed so that it can go anywhere in memory, so we'll put it right below the character set, since we've already made sure that part of memory is reserved.
4000; Now we actually set up our initial graphics mode and tell the computer where the character set is.
5010; We find out where the display list is. 5020; And change the first line (the one that also tells where screen memory is) to a fine scrolling line.
5030; Now we change the next 11 lines as well.
5200; Our changes are complete, so we turn the screen back on.
5270; Here we initialize our variables (their uses are explained in the earlier part of the listing).
5290; This just makes sure that the scrolling register is initially zero.
5360; Now we initialize the machinelanguage routine SCROLL.
5460; This turns on SCROLL. The CA-256 in the USR command is to tell where SCROLL is located in memory.
5490; We're all done setting things up, so go and start the actual movement routine.
29000; This is VBLOFF.
31000; This is SCROLL.
31500; This is SCRLON, the routine to turn on SCROLL.
If you're curious about SCROLL, the machine-language routine that we're using here, you may wish to skip ahead and take a look at the complete explanation of it. I'm leaving it until the end because at this point it should be fairly straightforward as to how it is used in our program, and there are more important problems that I'm sure you've already run across that I feel should be explained first.
Assuming you've made the previous additions to the program and have gotten it running, you're probably wondering what's going on. After all, the invaders are no longer neatly lined up on the screen any more, are they? Don't worry, you didn't do anything wrong. The problem comes from the fact that we are now fine scrolling.
When you fine scroll horizontally, the computer makes each scrolling line longer than, in the case of graphics mode one and two, 20 characters per line. To be exact, it makes these lines 24 characters long (48 in graphics mode zero). Why?
Let's suppose you scroll a normal width line two characters to the right. What is the computer supposed to put on the left edge of that line? The same is true for scrolling to the left. The computer has to have two extra characters on either side of each scrolling line in order to have something to scroll onto the left or right side of the line. Thus the extra four characters. When we set up INV$, we thought there would only be 20 characters on a line, and that's why the invaders are now spread all across the screen.
Our solution? We change the program so that INV$ is set up with 24 characters per line instead of 20. We'll do this by adding two spaces at the beginning and end of each line:
1000
X=USR(ADR(MOVMEM$),
ADR(INV$)+SB,MEM1,283)
1080 SB=288*(SB=0)
3030 DIM MLANG$(90),INV$
(576),DAT$(16)
5370 INVS="":INV$(576)=
"":INV$(2)=INV$
5390 FOR LP=0 TO 4 STEP
2:READ DAT$:INV$(LP*48+3
,LP*48+18)=DAT$:INV$(LP*
48+339,LP*48+354)=DAT$
5400 READ DAT$:INV$(LP*4
8+51,LP*48+66)=DAT$:INV$
(LP*48+291,LP*48+306)=DA
T$:NEXT LP
ADR(INV$)+SB,MEM1,283)
1080 SB=288*(SB=0)
3030 DIM MLANG$(90),INV$
(576),DAT$(16)
5370 INVS="":INV$(576)=
"":INV$(2)=INV$
5390 FOR LP=0 TO 4 STEP
2:READ DAT$:INV$(LP*48+3
,LP*48+18)=DAT$:INV$(LP*
48+339,LP*48+354)=DAT$
5400 READ DAT$:INV$(LP*4
8+51,LP*48+66)=DAT$:INV$
(LP*48+291,LP*48+306)=DA
T$:NEXT LP
Here's the explanation of these changes:
1000-1080; We've added four characters to each of the 12 lines that make up each screen, so we have to add 48 (4*12) to the values in these lines.
3030-5370; An additional 48 characters per screen, times two screens, means an additional 96 characters altogether for INV$.
5390-5400; What have we done here? We're multiplying LP by 48 now (24*2), have shifted the beginning position of each row of invaders by two to give us the extra two spaces at the beginning of each line, and the second screen now begins an extra 48 characters into the string.
How do things look now, a little better? As you can see though, there's still a problem. As the invaders scroll to the right, a pair of characters that don't belong appear at the top left-hand corner of the screen. Before I tell you why and how to correct it, I'd like you to think about it. You should be able to come up with the answer yourself, based on everything we've talked about so far.
Give up, or did you figure it out? Either way, the problem comes from the fact that the computer is trying to scroll on information that doesn't exist. When you first set up a graphics mode, the computer sets aside an area of memory for use as screen memory. The memory right before this screen memory usually holds the display list. Anyway, when you coarse scroll far enough so that screen memory now begins before the point it originally began at, you start getting strange stuff appearing on the screen.
That's our problem now. How do we get rid of it? We can do one of two things. If we change things so that the first line on the screen doesn't scroll, and start printing our invaders on the second line, we'll be okay. This way, we would be backing up screen memory into the first line, which we know is full of spaces.
The other way is to change the initial screen memory address so that it points ahead in memory. That way we know the memory before it (which used to be screen memory) is also full of spaces. Which of these methods is best? In this case I tend to favor the second, because it's just a little easier to do and also because it will come in handy later in the program. Also, with the first method you don't get to use the first line for scrolling. As it turns out, we won't want to anyway, but it is something to keep in mind. So, without any further ado, here are the changes necessary to move the screen memory forward:
5030
L=PEEK(DLIST+4)+44:
POKE DLIST+5,PEEK(DLIST+
5)+(L>255):POKE DLIST+4,
L-256*(L>255)
5040 FOR LINE=2 TO 12:PO
KE DLIST+4+LINE,22:NEXT
LINE
5180 MEM1=PEEK(DLIST+4)+
PEEK(DLIST+5)*256
POKE DLIST+5,PEEK(DLIST+
5)+(L>255):POKE DLIST+4,
L-256*(L>255)
5040 FOR LINE=2 TO 12:PO
KE DLIST+4+LINE,22:NEXT
LINE
5180 MEM1=PEEK(DLIST+4)+
PEEK(DLIST+5)*256
And, of course, the explanation.
5030; Here we make the changes to the screen memory address, which is stored at the beginning of the display list. See next month's column to have this make more sense to you.
5040; This is just the old line 5030.
5180; We now find out where screen memory is from the display list, not the operating system (which is what locations 88 and 89 are for).
Ta-da! We're now all cleaned up and looking good. This is all we're going to do with the invaders for now. It will make life easier for us later when we're figuring out some of the logic. As a matter of fact, we won't program them to move down the screen until we're almost finished with the program. For now, it's important that the invaders just go back and forth forever so that we can keep them under control.
Even though we're done with the programming part of this column, there's still more do discuss. First of all, I should explain SCROLL, as promised. SCROLL is a VBLANK routine to do fine and coarse scrolling for you. VBLANK stands for Vertical BLANK, which happens 60 times a second and is the time during which the electron beam is on its way from the bottom of the screen back to the top (see the column on Video Magic).
During VBLANK, there are no changes being made to the television screen, so it is a good time to make changes to the display list and screen memory. In this case, we're making changes to the display list. If we did not make these changes during VBLANK, the screen would "jump" while you were making them. Anyway, this is all just to satisfy your possible curiosity; you don't have to worry about the details since I've already taken care of them for you. All that you have to deal with is how the SCROLL routine is used.
You've seen in the above program listings how to set SCROLL up and turn it on. Once you turn it on, it will just sit and wait for you to tell it what to do. And how do you tell it what to do? By setting two or more of five memory locations. Here are those locations and their meanings:
1787; This location tells SCROLL when you want to do something. You set it to one after you've set the next four locations (emphasis on the "after").
1788; This location is used to set the horizontal fine scroll register. Just store the value you want in the register here (and then set location 1787 to one).
1789; This is the same as 1788, except for the vertical fine scrolling register.
1790; This location is used to specify the number of bytes you want to coarse scroll by. Set it when you're ready to coarse scroll.
1791; Finally, this location is used to tell SCROLL what direction to coarse scroll in. Set it to one for left and up, and 128 for right 43 and down.
Every VBLANK, SCROLL will check location 1787 to see if it's set to one. If it isn't, then that's all SCROLL does. If it is, then it takes the values in locations 1788 and 1789 and stores them in the fine scroll registers. Then it checks to see if location 1790 is equal to zero, in which case it sets location 1787 back to zero and stops. If it isn't set to zero, then that means you want to coarse scroll, so it goes through the display list and updates all the fine scrolling screen memory addresses. Then it sets locations 1787 and 1790 back to zero and waits for your next command.
Let me answer a question that you may have: Why can't the fine scroll registers be changed directly? Why does SCROLL have to do it? Well, it doesn't. Try making this temporary change to our program:
1355
POKE 54276,SCROLL
Now you're changing the horizontal fine scroll register directly. Do you notice the occasional flicker on the screen? That's why we use SCROLL for this.
|
It's now time to take care of a few details about scrolling that BASIC invaders doesn't really get into. First of all, our demonstration game does not use vertical fine scrolling, and chances are that may not be the case with a game of your own. We've already seen that vertical scrolling is basically the same as horizontal, with the exception that we have to coarse scroll by a whole line instead of one character.
But there is also another difference between the two. You recall that we ran into the problem of needing a wider screen for our horizontal scrolling. The same type of problem exists with vertical scrolling, except it is only a problem at the bottom of the screen. The solution is also similar to that for horizontal scrolling, and it is to add an extra line at the bottom of the screen, one that does not scroll.
This line then tends to act as a buffer and gives the screen a place to get the extra information from. So when you're setting up your display list for a screen that is to be fine scrolled vertically, remember to add an extra line at the bottom of the screen, one that isn't set up for scrolling.
The last thing we're going to cover here is how to set up screens for games like Scramble and Eastern Front, where they are much larger than the display screen. For example, let's suppose that you want to design a game in graphics mode one where the entire game screen is 40 characters wide and 48 characters high (two display screens wide by two high). First of all, this means that you will need four times as much screen memory as a regular graphics mode one screen. How do you get this memory? One way is to just reserve it at the top of memory, just like you reserve space for a character set. There is, however, an easier way, at least in this case.
In Table 6 you'll find a chart that lists a whole bunch of information about the various graphics modes. One of these pieces of information is the amount of screen memory that the graphics modes use, and you'll see that graphics mode one uses 480 bytes (20*24). We need enough memory to store 48 lines of 40 characters each, or a total of 1920 bytes.
Looking at the chart again, we see that graphics mode six happens to use 1920 bytes for screen memory (usually you won't get an exact match, so you'd pick whatever mode came closest without going under). So if we set up a graphics mode six screen, we'll have the right number of bytes already set up for us. Of course we will have to change the display list but, as you'll see in next month's column, that's a piece of cake.
Once screen memory has been set up, the next step is set up the display list so that every line (except the last one, remember) is set to scroll vertically and horizontally, and also specifies a section of screen memory. Each of these screen memory addresses will be 48 bytes past the previous one, since that's how long our new lines are.
The final step before we actually begin to scroll is to set up the screen data in screen memory. This is just a matter of figuring out how you want the screen to look on paper (or design it a display screen at a time on the computer). From there, transfer the data into a string (remembering that the first 48 bytes of the string will be the first line of the screen), and then use MOVMEM to move the string data into the screen memory.
With all this taken care of, you're now ready to scroll around your new giant screen. This is the easy part, since SCROLL takes care of all the work for you. All you have to do is tell SCROLL when to scroll, and also keep track of how far you've scrolled, both vertically and horizontally, so you don't run off the edge of your screen. You can do this very simply by keeping two variables, say VCOARSE and HCOARSE. At any given time, these variables should show how many bytes you've scrolled down and how many you've scrolled right, respectively (you should update them every time you coarse scroll, just like we updated COARSE in our previous program).
In the case of our example, you don't want VCOARSE to get greater than 24, or HCOARSE to get greater than 16. Why not 48 and 40? When VCOARSE gets to 24, 24 lines will have already scrolled off the screen and there will be 24 lines on the screen, for our total of 48. Similarly, when HCOARSE gets to 16, 16 lines will have scrolled off the screen, and 24 will be on, for our total of 40. Well, that about does it for fine scrolling. Of course, I still haven't explained the display list. We'll cover that topic next month.